diff options
203 files changed, 13675 insertions, 0 deletions
diff --git a/.arch-ids/ChangeLog.id b/.arch-ids/ChangeLog.id new file mode 100644 index 0000000..73830c6 --- /dev/null +++ b/.arch-ids/ChangeLog.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.0 diff --git a/.arch-ids/LICENSE.id b/.arch-ids/LICENSE.id new file mode 100644 index 0000000..3c14d1d --- /dev/null +++ b/.arch-ids/LICENSE.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.1 diff --git a/.arch-ids/MANIFEST.in.id b/.arch-ids/MANIFEST.in.id new file mode 100644 index 0000000..47e296e --- /dev/null +++ b/.arch-ids/MANIFEST.in.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.2 diff --git a/.arch-ids/Makefile.id b/.arch-ids/Makefile.id new file mode 100644 index 0000000..f7851c3 --- /dev/null +++ b/.arch-ids/Makefile.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.3 diff --git a/.arch-ids/README.id b/.arch-ids/README.id new file mode 100644 index 0000000..2e9ed54 --- /dev/null +++ b/.arch-ids/README.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.4 diff --git a/.arch-ids/TODO.id b/.arch-ids/TODO.id new file mode 100644 index 0000000..0db596c --- /dev/null +++ b/.arch-ids/TODO.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.5 diff --git a/.arch-ids/setup.py.id b/.arch-ids/setup.py.id new file mode 100644 index 0000000..086f3bf --- /dev/null +++ b/.arch-ids/setup.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.8 diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..57b1b15 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,529 @@ +2004-03-19 Bob Halley <halley@dnspython.org> + + * Added support for new DNSSEC types RRSIG, NSEC, and DNSKEY. + +2004-01-16 Bob Halley <halley@dnspython.org> + + * dns/query.py (_connect): Windows returns EWOULDBLOCK instead + of EINPROGRESS when trying to connect a nonblocking socket. + +2003-11-13 Bob Halley <halley@dnspython.org> + + * dns/rdtypes/ANY/LOC.py (LOC.to_wire): We encoded and decoded LOC + incorrectly, since we were interpreting the values of altitiude, + size, hprec, and vprec in meters instead of centimeters. + + * dns/rdtypes/IN/WKS.py (WKS.from_wire): The WKS protocol value is + encoded with just one octet, not two! + +2003-11-09 Bob Halley <halley@dnspython.org> + + * dns/resolver.py (Cache.maybe_clean): The cleaner deleted items + from the dictionary while iterating it, causing a RuntimeError + to be raised. Thanks to Mark R. Levinson for the bug report, + regression test, and fix. + +2003-11-07 Bob Halley <halley@dnspython.org> + + * (Version 1.2.0 released) + +2003-11-03 Bob Halley <halley@dnspython.org> + + * dns/zone.py (_MasterReader.read): The saved_state now includes + the default TTL. + +2003-11-01 Bob Halley <halley@dnspython.org> + + * dns/tokenizer.py (Tokenizer.get): The tokenizer didn't + handle escaped delimiters. + +2003-10-27 Bob Halley <halley@dnspython.org> + + * dns/resolver.py (Resolver.read_resolv_conf): If no nameservers + are configured in /etc/resolv.conf, the default nameserver + list should be ['127.0.0.1']. + +2003-09-08 Bob Halley <halley@dnspython.org> + + * dns/resolver.py (Resolver._config_win32_fromkey): We didn't + catch WindowsError, which can happen if a key is not defined + in the registry. + +2003-09-06 Bob Halley <halley@dnspython.org> + + * (Version 1.2.0b1 released) + +2003-09-05 Bob Halley <halley@dnspython.org> + + * dns/query.py: Timeout support has been overhauled to provide + timeouts under Python 2.2 as well as 2.3, and to provide more + accurate expiration. + +2003-08-30 Bob Halley <halley@dnspython.org> + + * dns/zone.py: dns.exception.SyntaxError is raised for unknown + master file directives. + +2003-08-28 Bob Halley <halley@dnspython.org> + + * dns/zone.py: $INCLUDE processing is now enabled/disabled using + the allow_include parameter. The default is to process $INCLUDE + for from_file(), and to disallow $INCLUDE for from_text(). The + master reader now calls zone.check_origin_node() by default after + the zone has been read. find_rdataset() called get_node() instead + of find_node(), which result in an incorrect exception. The + relativization state of a zone is now remembered and applied + consistently when looking up names. from_xfr() now supports + relativization like the _MasterReader. + +2003-08-22 Bob Halley <halley@dnspython.org> + + * dns/zone.py: The _MasterReader now understands $INCLUDE. + +2003-08-12 Bob Halley <halley@dnspython.org> + + * dns/zone.py: The _MasterReader now specifies the file and line + number when a syntax error occurs. The BIND 8 TTL format is now + understood when loading a zone, though it will never be emitted. + The from_file() function didn't pass the zone_factory parameter + to from_text(). + +2003-08-10 Bob Halley <halley@dnspython.org> + + * (Version 1.1.0 released) + +2003-08-07 Bob Halley <halley@dnspython.org> + + * dns/update.py (Update._add): A typo meant that _add would + fail if the thing being added was an Rdata object (as + opposed to an Rdataset or the textual form of an Rdata). + +2003-08-05 Bob Halley <halley@dnspython.org> + + * dns/set.py: the simple Set class has been moved to its + own module, and augmented to support more set operations. + +2003-08-04 Bob Halley <halley@dnspython.org> + + * Node and all rdata types have been "slotted". This speeds + things up a little and reduces memory usage noticeably. + +2003-08-02 Bob Halley <halley@dnspython.org> + + * (Version 1.1.0c1 released) + +2003-08-02 Bob Halley <halley@dnspython.org> + + * dns/rdataset.py: SimpleSets now support more set options. + + * dns/message.py: Added the get_rrset() method. from_file() now + allows Unicode filenames and turns on universal newline support if + it opens the file itself. + + * dns/node.py: Added the delete_rdataset() and replace_rdataset() + methods. + + * dns/zone.py: Added the delete_node(), delete_rdataset(), and + replace_rdataset() methods. from_file() now allows Unicode + filenames and turns on universal newline support if it opens the + file itself. Added a to_file() method. + +2003-08-01 Bob Halley <halley@dnspython.org> + + * dns/opcode.py: Opcode from/to text converters now understand + numeric opcodes. The to_text() method will return a numeric opcode + string if it doesn't know a text name for the opcode. + + * dns/message.py: Added set_rcode(). Fixed code where ednsflags + wasn't treated as a long. + + * dns/rcode.py: ednsflags wasn't treated as a long. Rcode from/to + text converters now understand numeric rcodes. The to_text() + method will return a numeric rcode string if it doesn't know + a text name for the rcode. + + * examples/reverse.py: Added a new example program that builds a + reverse (address-to-name) mapping table from the name-to-address + mapping specified by A RRs in zone files. + + * dns/node.py: Added get_rdataset() method. + + * dns/zone.py: Added get_rdataset() and get_rrset() methods. Added + iterate_rdatas(). + +2003-07-31 Bob Halley <halley@dnspython.org> + + * dns/zone.py: Added the iterate_rdatasets() method which returns + a generator which yields (name, rdataset) tuples for all the + rdatasets in the zone matching the specified rdatatype. + +2003-07-30 Bob Halley <halley@dnspython.org> + + * (Version 1.1.0b2 released) + +2003-07-30 Bob Halley <halley@dnspython.org> + + * dns/zone.py: Added find_rrset() and find_rdataset() convenience + methods. They let you retrieve rdata with the specified name + and type in one call. + + * dns/node.py: Nodes no longer have names; owner names are + associated with nodes in the Zone object's nodes dictionary. + + * dns/zone.py: Zone objects now implement more of the standard + mapping interface. __iter__ has been changed to iterate the keys + rather than values to match the standard mapping interface's + behavior. + +2003-07-20 Bob Halley <halley@dnspython.org> + + * dns/ipv6.py (inet_ntoa): Handle embedded IPv4 addresses. + +2003-07-19 Bob Halley <halley@dnspython.org> + + * (Version 1.1.0b1 released) + +2003-07-18 Bob Halley <halley@dnspython.org> + + * dns/tsig.py: The TSIG validation of TCP streams where not + every message is signed now works correctly. + + * dns/zone.py: Zones can now be compared for equality and + inequality. If the other object in the comparison is also + a zone, then "the right thing" happens; i.e. the zones are + equal iff.: they have the same rdclass, origin, and nodes. + +2003-07-17 Bob Halley <halley@dnspython.org> + + * dns/message.py (Message.use_tsig): The method now allows for + greater control over the various fields in the generated signature + (e.g. fudge). + (_WireReader._get_section): UnknownTSIGKey is now raised if an + unknown key is encountered, or if a signed message has no keyring. + +2003-07-16 Bob Halley <halley@dnspython.org> + + * dns/tokenizer.py (Tokenizer._get_char): get_char and unget_char + have been renamed to _get_char and _unget_char since they are not + useful to clients of the tokenizer. + +2003-07-15 Bob Halley <halley@dnspython.org> + + * dns/zone.py (_MasterReader._rr_line): owner names were being + unconditionally relativized; it makes much more sense for them + to be relativized according to the relativization setting of + the reader. + +2003-07-12 Bob Halley <halley@dnspython.org> + + * dns/resolver.py (Resolver.read_resolv_conf): The resolv.conf + parser did not allow blank / whitespace-only lines, nor did it + allow comments. Both are now supported. + +2003-07-11 Bob Halley <halley@dnspython.org> + + * dns/name.py (Name.to_digestable): to_digestable() now + requires an origin to be specified if the name is relative. + It will raise NeedAbsoluteNameOrOrigin if the name is + relative and there is either no origin or the origin is + itself relative. + (Name.split): returned the wrong answer if depth was 0 or depth + was the length of the name. split() now does bounds checking + on depth, and raises ValueError if depth < 0 or depth > the length + of the name. + +2003-07-10 Bob Halley <halley@dnspython.org> + + * dns/ipv6.py (inet_ntoa): The routine now minimizes its output + strings. E.g. the IPv6 address + "0000:0000:0000:0000:0000:0000:0000:0001" is minimized to "::1". + We do not, however, make any effort to display embedded IPv4 + addresses in the dot-quad notation. + +2003-07-09 Bob Halley <halley@dnspython.org> + + * dns/inet.py: We now supply our own AF_INET and AF_INET6 + constants since AF_INET6 may not always be available. If the + socket module has AF_INET6, we will use it. If not, we will + use our own value for the constant. + + * dns/query.py: the functions now take an optional af argument + specifying the address family to use when creating the socket. + + * dns/rdatatype.py (is_metatype): a typo caused the function + return true only for type OPT. + + * dns/message.py: message section list elements are now RRsets + instead of Nodes. This API change makes processing messages + easier for many applications. + +2003-07-07 Bob Halley <halley@dnspython.org> + + * dns/rrset.py: added. An RRset is a named rdataset. + + * dns/rdataset.py (Rdataset.__eq__): rdatasets may now be compared + for equality and inequality with other objects. Rdataset instance + variables are now slotted. + + * dns/message.py: The wire format and text format readers are now + classes. Variables related to reader state have been moved out + of the message class. + +2003-07-06 Bob Halley <halley@dnspython.org> + + * dns/name.py (from_text): '@' was not interpreted as the empty + name. + + * dns/zone.py: the master file reader derelativized names in rdata + relative to the zone's origin, not relative to the current origin. + The reader now deals with relativization in two steps. The rdata + is read and derelativized using the current origin. The rdata's + relativity is then chosen using the zone origin and the relativize + boolean. Here's an example. + + $ORIGIN foo.example. + $TTL 300 + bar MX 0 blaz + + If the zone origin is example., and relativization is on, then + This fragment will become: + + bar.foo.example. 300 IN MX 0 blaz.foo.example. + + after the first step (derelativization to current origin), and + + bar.foo 300 IN MX 0 blaz.foo + + after the second step (relativiation to zone origin). + + * dns/namedict.py: added. + + * dns/zone.py: The master file reader has been made into its + own class. Reader-related instance variables have been moved + form the zone class into the reader class. + + * dns/zone.py: Add node_factory class attribute. An application + can now subclass Zone and Node and have a zone whose nodes are of + the subclassed Node type. The from_text(), from_file(), and + from_xfr() algorithms now take an optional zone_factory argument. + This allows the algorithms to be used to create zones whose class + is a subclass of Zone. + + +2003-07-04 Bob Halley <halley@dnspython.org> + + * dns/renderer.py: added new wire format rendering module and + converted message.py to use it. Applications which want + fine-grained control over the conversion to wire format may call + the renderer directy, instead of having it called on their behalf + by the message code. + +2003-07-02 Bob Halley <halley@dnspython.org> + + * dns/name.py (_validate_labels): The NameTooLong test was + incorrect. + + * dns/message.py (Message.to_wire): dns.exception.TooBig is + now raised if the wire encoding exceeds the specified + maximum size. + +2003-07-01 Bob Halley <halley@dnspython.org> + + * dns/message.py: EDNS encoding was broken. from_text() + didn't parse rcodes, flags, or eflags correctly. Comparing + messages with other types of objects didn't work. + +2003-06-30 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0 released) + +2003-06-30 Bob Halley <halley@dnspython.org> + + * dns/rdata.py: Rdatas now implement rich comparisons instead of + __cmp__. + + * dns/name.py: Names now implement rich comparisons instead of + __cmp__. + + * dns/inet.py (inet_ntop): Always use our code, since the code + in the socket module doesn't support AF_INET6 conversions if + IPv6 sockets are not available on the system. + + * dns/resolver.py (Answer.__init__): A dangling CNAME chain was + not raising NoAnswer. + + * Added a simple resolver Cache class. + + * Added an expiration attribute to answer instances. + +2003-06-24 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0b3 released) + +2003-06-24 Bob Halley <halley@dnspython.org> + + * Renamed module "DNS" to "dns" to avoid conflicting with + PyDNS. + +2003-06-23 Bob Halley <halley@dnspython.org> + + * The from_text() relativization controls now work the same way as + the to_text() controls. + + * DNS/rdata.py: The parsing of generic rdata was broken. + +2003-06-21 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0b2 released) + +2003-06-21 Bob Halley <halley@dnspython.org> + + * The Python 2.2 socket.inet_aton() doesn't seem to like + '255.255.255.255'. We work around this. + + * Fixed bugs in rdata to_wire() and from_wire() routines of a few + types. These bugs were discovered by running the tests/zone.py + Torture1 test. + + * Added implementation of type APL. + +2003-06-20 Bob Halley <halley@dnspython.org> + + * DNS/rdtypes/IN/AAAA.py: Use our own versions of inet_ntop and + inet_pton if the socket module doesn't provide them for us. + + * The resolver now does a better job handling exceptions. In + particular, it no longer eats all exceptions; rather it handles + those exceptions it understands, and leaves the rest uncaught. + + * Exceptions have been pulled into their own module. Almost all + exceptions raised by the code are now subclasses of + DNS.exception.DNSException. All form errors are subclasses of + DNS.exception.FormError (which is itself a subclass of + DNS.exception.DNSException). + +2003-06-19 Bob Halley <halley@dnspython.org> + + * Added implementations of types DS, NXT, SIG, and WKS. + + * __cmp__ for type A and AAAA could produce incorrect results. + +2003-06-18 Bob Halley <halley@dnspython.org> + + * Started test suites for zone.py and tokenizer.py. + + * Added implementation of type KEY. + + * DNS/rdata.py(_base64ify): \n could be emitted erroneously. + + * DNS/rdtypes/ANY/SOA.py (SOA.from_text): The SOA RNAME field could + be set to the value of MNAME in common cases. + + * DNS/rdtypes/ANY/X25.py: __init__ was broken. + + * DNS/zone.py (from_text): $TTL handling erroneously caused the + next line to be eaten. + + * DNS/tokenizer.py (Tokenizer.get): parsing was broken for empty + quoted strings. Quoted strings didn't handle \ddd escapes. Such + escapes are appear not to comply with RFC 1035, but BIND allows + them and they seem useful, so we allow them too. + + * DNS/rdtypes/ANY/ISDN.py (ISDN.from_text): parsing was + broken for ISDN RRs without subaddresses. + + * DNS/zone.py (from_file): from_file() didn't work because + some required parameters were not passed to from_text(). + +2003-06-17 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0b1 released) + +2003-06-17 Bob Halley <halley@dnspython.org> + + * Added implementation of type PX. + +2003-06-16 Bob Halley <halley@dnspython.org> + + * Added implementation of types CERT, GPOS, LOC, NSAP, NSAP-PTR. + + * DNS/rdatatype.py (_by_value): A cut-and-paste error had broken + NSAP and NSAP-PTR. + +2003-06-12 Bob Halley <halley@dnspython.org> + + * Created a tests directory and started adding tests. + + * Added "and its documentation" to the permission grant in the + license. + +2003-06-12 Bob Halley <halley@dnspython.org> + + * DNS/name.py (Name.is_wild): is_wild() erroneously raised IndexError + if the name was empty. + +2003-06-10 Bob Halley <halley@dnspython.org> + + * Added implementations of types AFSDB, X25, and ISDN. + + * The documentation associated with the various rdata types has been + improved. In particular, instance variables are now described. + +2003-06-09 Bob Halley <halley@dnspython.org> + + * Added implementations of types HINFO, RP, and RT. + + * DNS/message.py (make_query): Document that make_query() sets + flags to DNS.flags.RD, and chooses a random query id. + +2003-06-05 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0a2 released) + +2003-06-05 Bob Halley <halley@dnspython.org> + + * DNS/node.py: removed __getitem__ and __setitem__, since + they are not used by the codebase and were not useful in + general either. + + * DNS/message.py (from_file): from_file() now allows a + filename to be specified instead of a file object. + + * DNS/rdataset.py: The is_compatible() method of the + DNS.rdataset.Rdataset class was deleted. + +2003-06-04 Bob Halley <halley@dnspython.org> + + * DNS/name.py (class Name): Names are now immutable. + + * DNS/name.py: the is_comparable() method has been removed, since + names are always comparable. + + * DNS/resolver.py (Resolver.query): A query could run for up + to the lifetime + the timeout. This has been corrected and the + query will now only run up to the lifetime. + +2003-06-03 Bob Halley <halley@dnspython.org> + + * DNS/resolver.py: removed the 'new' function since it is not the + style of the library to have such a function. Call + DNS.resolver.Resolver() to make a new resolver. + +2003-06-03 Bob Halley <halley@dnspython.org> + + * DNS/resolver.py (Resolver._config_win32_fromkey): The DhcpServer + list is space separated, not comma separated. + +2003-06-03 Bob Halley <halley@dnspython.org> + + * DNS/update.py: Added an update module to make generating updates + easier. + +2003-06-03 Bob Halley <halley@dnspython.org> + + * Commas were missing in some of the __all__ entries in various + __init__.py files. + +2003-05-30 Bob Halley <halley@dnspython.org> + + * (Version 1.0.0a1 released) @@ -0,0 +1,14 @@ +Copyright (C) 2001-2003 Nominum, Inc. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose with or without fee is hereby granted, +provided that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..d58fb8b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE ChangeLog TODO +recursive-include examples *.txt *.py +recursive-include tests *.txt *.py Makefile *.good example diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a1d02e6 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile,v 1.16 2004/03/19 00:17:27 halley Exp $ + +PYTHON=python + +all: + ${PYTHON} ./setup.py build + +install: + ${PYTHON} ./setup.py install + +clean: + ${PYTHON} ./setup.py clean --all + find . -name '*.pyc' -exec rm {} \; + find . -name '*.pyo' -exec rm {} \; + rm -f TAGS + +distclean: clean docclean + rm -rf build dist + rm -f MANIFEST + +doc: + epydoc -n dnspython -u http://www.dnspython.org \ + dns/*.py dns/rdtypes/*.py dns/rdtypes/ANY/*.py \ + dns/rdtypes/IN/*.py + +dockits: doc + mv html dnspython-html + tar czf html.tar.gz dnspython-html + zip -r html.zip dnspython-html + mv dnspython-html html + +docclean: + rm -rf html.tar.gz html.zip html + +kits: + ${PYTHON} ./setup.py sdist --formats=gztar,zip + ${PYTHON} ./setup.py bdist_rpm + ${PYTHON} ./setup.py bdist_wininst + +tags: + find . -name '*.py' -print | etags - @@ -0,0 +1,73 @@ +dnspython + +INTRODUCTION + +dnspython is a DNS toolkit for Python. It supports almost all record +types. It can be used for queries, zone transfers, and dynamic +updates. It supports TSIG authenticated messages and EDNS0. + +dnspython provides both high and low level access to DNS. The high +level classes perform queries for data of a given name, type, and +class, and return an answer set. The low level classes allow direct +manipulation of DNS zones, messages, names, and records. + +To see a few of the ways dnspython can be used, look in the examples/ +directory. + +dnspython originated at Nominum where it was developed to facilitate +the testing of DNS software. Nominum has generously allowed it to be +open sourced under a BSD-style license, and helps support its future +development by continuing to employ the author :). + + +ABOUT THIS RELEASE + +This is dnspython 1.3.0. + +New since 1.2.0: + + Added support for new DNSSEC types RRSIG, NSEC, and DNSKEY. + +This release fixes all known bugs. + +See the ChangeLog file for more detailed information on changes since +the prior release. + + +REQUIREMENTS + +Python 2.2 or later. + + +INSTALLATION + +To build and install dnspython, type + + python setup.py install + + +HOME PAGE + +For the latest in releases, documentation, and information, visit the +dnspython home page at + + http://www.dnspython.org/ + + + +DOCUMENTATION + +Documentation is sparse at the moment. Use pydoc, or read the HTML +documentation at the dnspython home page, or download the HTML +documentation. + + +BUG REPORTS + +Bug reports may be sent to bugs@dnspython.org + + +MAILING LISTS + +A number of mailing lists are available. Visit the dnspython home +page to subscribe or unsubscribe. @@ -0,0 +1,17 @@ +Tutorial documentation + +More examples + +It would be nice to have a tokenizer that used regular expressions +because it would be faster. + +Teach the resolver about DNAME (right now it relies on the server adding +synthesized CNAMEs) + +Add TKEY support. + +TSIG works, but needs cleaning up -- probably better encapsulation of +TSIG state to make things much simpler and easier to use. + +Pickling support. + diff --git a/dns/.arch-ids/=id b/dns/.arch-ids/=id new file mode 100644 index 0000000..7000627 --- /dev/null +++ b/dns/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.6 diff --git a/dns/.arch-ids/__init__.py.id b/dns/.arch-ids/__init__.py.id new file mode 100644 index 0000000..b7e9c57 --- /dev/null +++ b/dns/.arch-ids/__init__.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.0 diff --git a/dns/.arch-ids/dnssec.py.id b/dns/.arch-ids/dnssec.py.id new file mode 100644 index 0000000..d860188 --- /dev/null +++ b/dns/.arch-ids/dnssec.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.1 diff --git a/dns/.arch-ids/exception.py.id b/dns/.arch-ids/exception.py.id new file mode 100644 index 0000000..b8cf527 --- /dev/null +++ b/dns/.arch-ids/exception.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.2 diff --git a/dns/.arch-ids/flags.py.id b/dns/.arch-ids/flags.py.id new file mode 100644 index 0000000..e7cfa7f --- /dev/null +++ b/dns/.arch-ids/flags.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.3 diff --git a/dns/.arch-ids/inet.py.id b/dns/.arch-ids/inet.py.id new file mode 100644 index 0000000..ecfc9cb --- /dev/null +++ b/dns/.arch-ids/inet.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.4 diff --git a/dns/.arch-ids/ipv4.py.id b/dns/.arch-ids/ipv4.py.id new file mode 100644 index 0000000..b2668e4 --- /dev/null +++ b/dns/.arch-ids/ipv4.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.5 diff --git a/dns/.arch-ids/ipv6.py.id b/dns/.arch-ids/ipv6.py.id new file mode 100644 index 0000000..8b481cc --- /dev/null +++ b/dns/.arch-ids/ipv6.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.6 diff --git a/dns/.arch-ids/message.py.id b/dns/.arch-ids/message.py.id new file mode 100644 index 0000000..c70aeb5 --- /dev/null +++ b/dns/.arch-ids/message.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.7 diff --git a/dns/.arch-ids/name.py.id b/dns/.arch-ids/name.py.id new file mode 100644 index 0000000..3d4ae70 --- /dev/null +++ b/dns/.arch-ids/name.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.8 diff --git a/dns/.arch-ids/namedict.py.id b/dns/.arch-ids/namedict.py.id new file mode 100644 index 0000000..ccd02cf --- /dev/null +++ b/dns/.arch-ids/namedict.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.9 diff --git a/dns/.arch-ids/node.py.id b/dns/.arch-ids/node.py.id new file mode 100644 index 0000000..b3ac040 --- /dev/null +++ b/dns/.arch-ids/node.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.10 diff --git a/dns/.arch-ids/opcode.py.id b/dns/.arch-ids/opcode.py.id new file mode 100644 index 0000000..46275e8 --- /dev/null +++ b/dns/.arch-ids/opcode.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.11 diff --git a/dns/.arch-ids/query.py.id b/dns/.arch-ids/query.py.id new file mode 100644 index 0000000..541fed1 --- /dev/null +++ b/dns/.arch-ids/query.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.12 diff --git a/dns/.arch-ids/rcode.py.id b/dns/.arch-ids/rcode.py.id new file mode 100644 index 0000000..578b147 --- /dev/null +++ b/dns/.arch-ids/rcode.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.13 diff --git a/dns/.arch-ids/rdata.py.id b/dns/.arch-ids/rdata.py.id new file mode 100644 index 0000000..ff5a7c2 --- /dev/null +++ b/dns/.arch-ids/rdata.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.14 diff --git a/dns/.arch-ids/rdataclass.py.id b/dns/.arch-ids/rdataclass.py.id new file mode 100644 index 0000000..88d1b13 --- /dev/null +++ b/dns/.arch-ids/rdataclass.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.15 diff --git a/dns/.arch-ids/rdataset.py.id b/dns/.arch-ids/rdataset.py.id new file mode 100644 index 0000000..f3d3651 --- /dev/null +++ b/dns/.arch-ids/rdataset.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.16 diff --git a/dns/.arch-ids/rdatatype.py.id b/dns/.arch-ids/rdatatype.py.id new file mode 100644 index 0000000..40c7b6d --- /dev/null +++ b/dns/.arch-ids/rdatatype.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.17 diff --git a/dns/.arch-ids/renderer.py.id b/dns/.arch-ids/renderer.py.id new file mode 100644 index 0000000..de4b8ea --- /dev/null +++ b/dns/.arch-ids/renderer.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.18 diff --git a/dns/.arch-ids/resolver.py.id b/dns/.arch-ids/resolver.py.id new file mode 100644 index 0000000..592ea30 --- /dev/null +++ b/dns/.arch-ids/resolver.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.19 diff --git a/dns/.arch-ids/rrset.py.id b/dns/.arch-ids/rrset.py.id new file mode 100644 index 0000000..8110b75 --- /dev/null +++ b/dns/.arch-ids/rrset.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.20 diff --git a/dns/.arch-ids/set.py.id b/dns/.arch-ids/set.py.id new file mode 100644 index 0000000..25ccb25 --- /dev/null +++ b/dns/.arch-ids/set.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.21 diff --git a/dns/.arch-ids/tokenizer.py.id b/dns/.arch-ids/tokenizer.py.id new file mode 100644 index 0000000..b9c678d --- /dev/null +++ b/dns/.arch-ids/tokenizer.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.22 diff --git a/dns/.arch-ids/tsig.py.id b/dns/.arch-ids/tsig.py.id new file mode 100644 index 0000000..9c62049 --- /dev/null +++ b/dns/.arch-ids/tsig.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.23 diff --git a/dns/.arch-ids/tsigkeyring.py.id b/dns/.arch-ids/tsigkeyring.py.id new file mode 100644 index 0000000..450f231 --- /dev/null +++ b/dns/.arch-ids/tsigkeyring.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.24 diff --git a/dns/.arch-ids/ttl.py.id b/dns/.arch-ids/ttl.py.id new file mode 100644 index 0000000..cce7cef --- /dev/null +++ b/dns/.arch-ids/ttl.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.25 diff --git a/dns/.arch-ids/update.py.id b/dns/.arch-ids/update.py.id new file mode 100644 index 0000000..dfbbf6e --- /dev/null +++ b/dns/.arch-ids/update.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.26 diff --git a/dns/.arch-ids/version.py.id b/dns/.arch-ids/version.py.id new file mode 100644 index 0000000..b9f2143 --- /dev/null +++ b/dns/.arch-ids/version.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.27 diff --git a/dns/.arch-ids/zone.py.id b/dns/.arch-ids/zone.py.id new file mode 100644 index 0000000..9a47e15 --- /dev/null +++ b/dns/.arch-ids/zone.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.28 diff --git a/dns/__init__.py b/dns/__init__.py new file mode 100644 index 0000000..00b9c5d --- /dev/null +++ b/dns/__init__.py @@ -0,0 +1,50 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: __init__.py,v 1.17 2004/03/19 00:17:27 halley Exp $ + +"""dnspython DNS toolkit""" + +__all__ = [ + 'dnssec', + 'exception', + 'flags', + 'inet', + 'ipv4', + 'ipv6', + 'message', + 'name', + 'namedict', + 'node', + 'opcode', + 'query', + 'rcode', + 'rdata', + 'rdataclass', + 'rdataset', + 'rdatatype', + 'renderer', + 'resolver', + 'rrset', + 'set', + 'tokenizer', + 'tsig', + 'tsigkeyring', + 'ttl', + 'rdtypes', + 'update', + 'version', + 'zone', +] diff --git a/dns/dnssec.py b/dns/dnssec.py new file mode 100644 index 0000000..5406f45 --- /dev/null +++ b/dns/dnssec.py @@ -0,0 +1,64 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: dnssec.py,v 1.5 2004/03/19 00:17:27 halley Exp $ + +"""Common DNSSEC-related functions and constants.""" + +RSAMD5 = 1 +DH = 2 +DSA = 3 +ECC = 4 +INDIRECT = 252 +PRIVATEDNS = 253 +PRIVATEOID = 254 + +_algorithm_by_text = { + 'RSAMD5' : RSAMD5, + 'DH' : DH, + 'DSA' : DSA, + 'ECC' : ECC, + 'INDIRECT' : INDIRECT, + 'PRIVATEDNS' : PRIVATEDNS, + 'PRIVATEOID' : PRIVATEOID, + } + +# We construct the inverse mapping programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mapping not to be true inverse. + +_algorithm_by_value = dict([(y, x) for x, y in _algorithm_by_text.iteritems()]) + +class UnknownAlgorithm(Exception): + """Raised if an algorithm is unknown.""" + pass + +def algorithm_from_text(text): + """Convert text into a DNSSEC algorithm value + @rtype: int""" + + value = _algorithm_by_text.get(text.upper()) + if value is None: + value = int(text) + return value + +def algorithm_to_text(value): + """Convert a DNSSEC algorithm value to text + @rtype: string""" + + text = _algorithm_by_value.get(value) + if text is None: + text = str(value) + return text diff --git a/dns/exception.py b/dns/exception.py new file mode 100644 index 0000000..ffd136a --- /dev/null +++ b/dns/exception.py @@ -0,0 +1,42 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: exception.py,v 1.5 2004/03/19 00:17:27 halley Exp $ + +"""Common DNS Exceptions.""" + +class DNSException(Exception): + """Abstract base class shared by all dnspython exceptions.""" + pass + +class FormError(DNSException): + """DNS message is malformed.""" + pass + +class SyntaxError(DNSException): + """Text input is malformed.""" + pass + +class UnexpectedEnd(SyntaxError): + """Raised if text input ends unexpectedly.""" + pass + +class TooBig(DNSException): + """The message is too big.""" + pass + +class Timeout(DNSException): + """The operation timed out.""" + pass diff --git a/dns/flags.py b/dns/flags.py new file mode 100644 index 0000000..5c26f43 --- /dev/null +++ b/dns/flags.py @@ -0,0 +1,108 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: flags.py,v 1.9 2004/03/19 00:17:27 halley Exp $ + +"""DNS Message Flags.""" + +# Standard DNS flags + +QR = 0x8000 +AA = 0x0400 +TC = 0x0200 +RD = 0x0100 +RA = 0x0080 +AD = 0x0020 +CD = 0x0010 + +# EDNS flags + +DO = 0x8000 + +_by_text = { + 'QR' : QR, + 'AA' : AA, + 'TC' : TC, + 'RD' : RD, + 'RA' : RA, + 'AD' : AD, + 'CD' : CD +} + +_edns_by_text = { + 'DO' : DO +} + + +# We construct the inverse mappings programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mappings not to be true inverses. + +_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) + +_edns_by_value = dict([(y, x) for x, y in _edns_by_text.iteritems()]) + +def _order_flags(table): + order = list(table.iteritems()) + order.sort() + order.reverse() + return order + +_flags_order = _order_flags(_by_value) + +_edns_flags_order = _order_flags(_edns_by_value) + +def _from_text(text, table): + flags = 0 + tokens = text.split() + for t in tokens: + flags = flags | table[t.upper()] + return flags + +def _to_text(flags, table, order): + text_flags = [] + for k, v in order: + if flags & k != 0: + text_flags.append(v) + return ' '.join(text_flags) + +def from_text(text): + """Convert a space-separated list of flag text values into a flags + value. + @rtype: int""" + + return _from_text(text, _by_text) + +def to_text(flags): + """Convert a flags value into a space-separated list of flag text + values. + @rtype: string""" + + return _to_text(flags, _by_value, _flags_order) + + +def edns_from_text(text): + """Convert a space-separated list of EDNS flag text values into a EDNS + flags value. + @rtype: int""" + + return _from_text(text, _edns_by_text) + +def edns_to_text(flags): + """Convert an EDNS flags value into a space-separated list of EDNS flag + text values. + @rtype: string""" + + return _to_text(flags, _edns_by_value, _edns_flags_order) diff --git a/dns/inet.py b/dns/inet.py new file mode 100644 index 0000000..1cf246f --- /dev/null +++ b/dns/inet.py @@ -0,0 +1,74 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: inet.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +"""Generic Internet address helper functions.""" + +import socket + +import dns.ipv4 +import dns.ipv6 + + +# We assume that AF_INET is always defined. + +AF_INET = socket.AF_INET + +# AF_INET6 might not be defined in the socket module, but we need it. +# We'll try to use the socket module's value, and if it doesn't work, +# we'll use our own value. + +try: + AF_INET6 = socket.AF_INET6 +except AttributeError: + AF_INET6 = 9999 + +def inet_pton(family, text): + """Convert the textual form of a network address into its binary form. + + @param family: the address family + @type family: int + @param text: the textual address + @type text: string + @raises NotImplementedError: the address family specified is not + implemented. + @rtype: string + """ + + if family == AF_INET: + return dns.ipv4.inet_aton(text) + elif family == AF_INET6: + return dns.ipv6.inet_aton(text) + else: + raise NotImplementedError + +def inet_ntop(family, address): + """Convert the binary form of a network address into its textual form. + + @param family: the address family + @type family: int + @param address: the binary address + @type address: string + @raises NotImplementedError: the address family specified is not + implemented. + @rtype: string + """ + if family == AF_INET: + return dns.ipv4.inet_ntoa(address) + elif family == AF_INET6: + return dns.ipv6.inet_ntoa(address) + else: + raise NotImplementedError diff --git a/dns/ipv4.py b/dns/ipv4.py new file mode 100644 index 0000000..b22d7e5 --- /dev/null +++ b/dns/ipv4.py @@ -0,0 +1,38 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: ipv4.py,v 1.3 2004/03/19 00:17:27 halley Exp $ + +"""IPv4 helper functions.""" + +import socket +import sys + +if sys.hexversion < 0x02030000 or sys.platform == 'win32': + # + # Some versions of Python 2.2 have an inet_aton which rejects + # the valid IP address '255.255.255.255'. It appears this + # problem is still present on the Win32 platform even in 2.3. + # We'll work around the problem. + # + def inet_aton(text): + if text == '255.255.255.255': + return '\xff' * 4 + else: + return socket.inet_aton(text) +else: + inet_aton = socket.inet_aton + +inet_ntoa = socket.inet_ntoa diff --git a/dns/ipv6.py b/dns/ipv6.py new file mode 100644 index 0000000..afb69cc --- /dev/null +++ b/dns/ipv6.py @@ -0,0 +1,161 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: ipv6.py,v 1.10 2004/03/19 00:17:27 halley Exp $ + +"""IPv6 helper functions.""" + +import re + +import dns.exception +import dns.ipv4 + +_leading_zero = re.compile(r'0+([0-9a-f]+)') + +def inet_ntoa(address): + """Convert a network format IPv6 address into text. + + @param address: the binary address + @type address: string + @rtype: string + @raises ValueError: the address isn't 16 bytes long + """ + + if len(address) != 16: + raise ValueError, "IPv6 addresses are 16 bytes long" + hex = address.encode('hex_codec') + chunks = [] + i = 0 + l = len(hex) + while i < l: + chunk = hex[i : i + 4] + # strip leading zeros. we do this with an re instead of + # with lstrip() because lstrip() didn't support chars until + # python 2.2.2 + m = _leading_zero.match(chunk) + if not m is None: + chunk = m.group(1) + chunks.append(chunk) + i += 4 + # + # Compress the longest subsequence of 0-value chunks to :: + # + best_start = 0 + best_len = 0 + last_was_zero = False + for i in xrange(8): + if chunks[i] != '0': + if last_was_zero: + end = i + current_len = end - start + if current_len > best_len: + best_start = start + best_len = current_len + last_was_zero = False + elif not last_was_zero: + start = i + last_was_zero = True + if last_was_zero: + end = 8 + current_len = end - start + if current_len > best_len: + best_start = start + best_len = current_len + if best_len > 0: + if best_start == 0 and \ + (best_len == 6 or + best_len == 5 and chunks[5] == 'ffff'): + # We have an embedded IPv4 address + if best_len == 6: + prefix = '::' + else: + prefix = '::ffff:' + hex = prefix + dns.ipv4.inet_ntoa(address[12:]) + else: + hex = ':'.join(chunks[:best_start]) + '::' + \ + ':'.join(chunks[best_start + best_len:]) + else: + hex = ':'.join(chunks) + return hex + +_v4_ending = re.compile(r'(.*):(\d+)\.(\d+)\.(\d+)\.(\d+)$') +_colon_colon_start = re.compile(r'::.*') +_colon_colon_end = re.compile(r'.*::$') + +def inet_aton(text): + """Convert a text format IPv6 address into network format. + + @param text: the textual address + @type text: string + @rtype: string + @raises dns.exception.SyntaxError: the text was not properly formatted + """ + + # + # Our aim here is not something fast; we just want something that works. + # + + if text == '::': + text = '0::' + # + # Get rid of the icky dot-quad syntax if we have it. + # + m = _v4_ending.match(text) + if not m is None: + text = "%s:%04x:%04x" % (m.group(1), + int(m.group(2)) * 256 + int(m.group(3)), + int(m.group(4)) * 256 + int(m.group(5))) + # + # Try to turn '::<whatever>' into ':<whatever>'; if no match try to + # turn '<whatever>::' into '<whatever>:' + # + m = _colon_colon_start.match(text) + if not m is None: + text = text[1:] + else: + m = _colon_colon_end.match(text) + if not m is None: + text = text[:-1] + # + # Now canonicalize into 8 chunks of 4 hex digits each + # + chunks = text.split(':') + l = len(chunks) + if l > 8: + raise dns.exception.SyntaxError + seen_empty = False + canonical = [] + for c in chunks: + if c == '': + if seen_empty: + raise dns.exception.SyntaxError + seen_empty = True + for i in xrange(0, 8 - l + 1): + canonical.append('0000') + else: + lc = len(c) + if lc > 4: + raise dns.exception.SyntaxError + if lc != 4: + c = ('0' * (4 - lc)) + c + canonical.append(c) + if l < 8 and not seen_empty: + raise dns.exception.SyntaxError + text = ''.join(canonical) + + # + # Finally we can go to binary. + # + return text.decode('hex_codec') diff --git a/dns/message.py b/dns/message.py new file mode 100644 index 0000000..cd746ee --- /dev/null +++ b/dns/message.py @@ -0,0 +1,915 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: message.py,v 1.35 2004/03/19 00:17:27 halley Exp $ + +"""DNS Messages""" + +import cStringIO +import random +import string +import struct +import sys +import time + +import dns.exception +import dns.flags +import dns.name +import dns.opcode +import dns.rcode +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rrset +import dns.renderer +import dns.tsig + +class ShortHeader(dns.exception.FormError): + """Raised if the DNS packet passed to from_wire() is too short.""" + pass + +class TrailingJunk(dns.exception.FormError): + """Raised if the DNS packet passed to from_wire() has extra junk + at the end of it.""" + pass + +class UnknownHeaderField(dns.exception.DNSException): + """Raised if a header field name is not recognized when converting from + text into a message.""" + pass + +class BadEDNS(dns.exception.FormError): + """Raised if an OPT record occurs somewhere other than the start of + the additional data section.""" + pass + +class BadTSIG(dns.exception.FormError): + """Raised if a TSIG record occurs somewhere other than the end of + the additional data section.""" + pass + +class UnknownTSIGKey(dns.exception.DNSException): + """Raised if we got a TSIG but don't know the key.""" + pass + +class Message(object): + """A DNS message. + + @ivar id: The query id; the default is a randomly chosen id. + @type id: int + @ivar flags: The DNS flags of the message. @see: RFC 1035 for an + explanation of these flags. + @type flags: int + @ivar question: The question section. + @type question: list of dns.rrset.RRset objects + @ivar answer: The answer section. + @type answer: list of dns.rrset.RRset objects + @ivar authority: The authority section. + @type authority: list of dns.rrset.RRset objects + @ivar additional: The additional data section. + @type additional: list of dns.rrset.RRset objects + @ivar edns: The EDNS level to use. The default is -1, no Edns. + @type edns: int + @ivar ednsflags: The EDNS flags + @type ednsflags: long + @ivar payload: The EDNS payload size. The default is 0. + @type payload: int + @ivar keyring: The TSIG keyring to use. The default is None. + @type keyring: dict + @ivar keyname: The TSIG keyname to use. The default is None. + @type keyname: dns.name.Name object + @ivar request_mac: The TSIG MAC of the request message associated with + this message; used when validating TSIG signatures. @see: RFC 2845 for + more information on TSIG fields. + @type request_mac: string + @ivar fudge: TSIG time fudge; default is 300 seconds. + @type fudge: int + @ivar original_id: TSIG original id; defaults to the message's id + @type original_id: int + @ivar tsig_error: TSIG error code; default is 0. + @type tsig_error: int + @ivar other_data: TSIG other data. + @type other_data: string + @ivar mac: The TSIG MAC for this message. + @type mac: string + @ivar xfr: Is the message being used to contain the results of a DNS + zone transfer? The default is False. + @type xfr: bool + @ivar origin: The origin of the zone in messages which are used for + zone transfers or for DNS dynamic updates. The default is None. + @type origin: dns.name.Name object + @ivar tsig_ctx: The TSIG signature context associated with this + message. The default is None. + @type tsig_ctx: hmac.HMAC object + @ivar had_tsig: Did the message decoded from wire format have a TSIG + signature? + @type had_tsig: bool + @ivar multi: Is this message part of a multi-message sequence? The + default is false. This variable is used when validating TSIG signatures + on messages which are part of a zone transfer. + @type multi: bool + @ivar first: Is this message standalone, or the first of a multi + message sequence? This variable is used when validating TSIG signatures + on messages which are part of a zone transfer. + @type first: bool + """ + + def __init__(self, id=None): + if id is None: + self.id = random.randint(0, 65535) + else: + self.id = id + self.flags = 0 + self.question = [] + self.answer = [] + self.authority = [] + self.additional = [] + self.edns = -1 + self.ednsflags = 0 + self.payload = 0 + self.keyring = None + self.keyname = None + self.request_mac = '' + self.other_data = '' + self.tsig_error = 0 + self.fudge = 300 + self.original_id = self.id + self.mac = '' + self.xfr = False + self.origin = None + self.tsig_ctx = None + self.had_tsig = False + self.multi = False + self.first = True + + def __repr__(self): + return '<DNS message, ID ' + `self.id` + '>' + + def __str__(self): + return self.to_text() + + def to_text(self, origin=None, relativize=True, **kw): + """Convert the message to text. + + The I{origin}, I{relativize}, and any other keyword + arguments are passed to the rrset to_wire() method. + + @rtype: string + """ + + s = cStringIO.StringIO() + print >> s, 'id %d' % self.id + print >> s, 'opcode %s' % \ + dns.opcode.to_text(dns.opcode.from_flags(self.flags)) + rc = dns.rcode.from_flags(self.flags, self.ednsflags) + print >> s, 'rcode %s' % dns.rcode.to_text(rc) + print >> s, 'flags %s' % dns.flags.to_text(self.flags) + if self.edns >= 0: + print >> s, 'edns %s' % self.edns + if self.ednsflags != 0: + print >> s, 'eflags %s' % \ + dns.flags.edns_to_text(self.ednsflags) + print >> s, 'payload', self.payload + is_update = dns.opcode.is_update(self.flags) + if is_update: + print >> s, ';ZONE' + else: + print >> s, ';QUESTION' + for rrset in self.question: + print >> s, rrset.to_text(origin, relativize, **kw) + if is_update: + print >> s, ';PREREQ' + else: + print >> s, ';ANSWER' + for rrset in self.answer: + print >> s, rrset.to_text(origin, relativize, **kw) + if is_update: + print >> s, ';UPDATE' + else: + print >> s, ';AUTHORITY' + for rrset in self.authority: + print >> s, rrset.to_text(origin, relativize, **kw) + print >> s, ';ADDITIONAL' + for rrset in self.additional: + print >> s, rrset.to_text(origin, relativize, **kw) + # + # We strip off the final \n so the caller can print the result without + # doing weird things to get around eccentricities in Python print + # formatting + # + return s.getvalue()[:-1] + + def __eq__(self, other): + """Two messages are equal if they have the same content in the + header, question, answer, and authority sections. + @rtype: bool""" + if not isinstance(other, Message): + return False + if self.id != other.id: + return False + if self.flags != other.flags: + return False + for n in self.question: + if n not in other.question: + return False + for n in other.question: + if n not in self.question: + return False + for n in self.answer: + if n not in other.answer: + return False + for n in other.answer: + if n not in self.answer: + return False + for n in self.authority: + if n not in other.authority: + return False + for n in other.authority: + if n not in self.authority: + return False + return True + + def __ne__(self, other): + """Are two messages not equal? + @rtype: bool""" + return not self.__eq__(other) + + def is_response(self, other): + """Is other a response to self? + @rtype: bool""" + if other.flags & dns.flags.QR == 0 or \ + self.id != other.id or \ + dns.opcode.from_flags(self.flags) != \ + dns.opcode.from_flags(other.flags): + return False + if dns.rcode.from_flags(other.flags, other.ednsflags) != \ + dns.rcode.NOERROR: + return True + if dns.opcode.is_update(self.flags): + return True + for n in self.question: + if n not in other.question: + return False + for n in other.question: + if n not in self.question: + return False + return True + + def find_rrset(self, section, name, rdclass, rdtype, + covers=dns.rdatatype.NONE, deleting=None, create=False, + force_unique=False): + """Find the RRset with the given attributes in the specified section. + + @param section: the section of the message to look in, e.g. + self.answer. + @type section: list of dns.rrset.RRset objects + @param name: the name of the RRset + @type name: dns.name.Name object + @param rdclass: the class of the RRset + @type rdclass: int + @param rdtype: the type of the RRset + @type rdtype: int + @param covers: the covers value of the RRset + @type covers: int + @param deleting: the deleting value of the RRset + @type deleting: int + @param create: If True, create the RRset if it is not found. + The created RRset is appended to I{section}. + @type create: bool + @param force_unique: If True and create is also True, create a + new RRset regardless of whether a matching RRset exists already. + @type force_unique: bool + @raises KeyError: the RRset was not found and create was False + @rtype: dns.rrset.RRset object""" + + if not force_unique: + for rrset in section: + if rrset.match(name, rdclass, rdtype, covers, deleting): + return rrset + if not create: + raise KeyError + rrset = dns.rrset.RRset(name, rdclass, rdtype, covers, deleting) + section.append(rrset) + return rrset + + def get_rrset(self, section, name, rdclass, rdtype, + covers=dns.rdatatype.NONE, deleting=None, create=False, + force_unique=False): + """Get the RRset with the given attributes in the specified section. + + If the RRset is not found, None is returned. + + @param section: the section of the message to look in, e.g. + self.answer. + @type section: list of dns.rrset.RRset objects + @param name: the name of the RRset + @type name: dns.name.Name object + @param rdclass: the class of the RRset + @type rdclass: int + @param rdtype: the type of the RRset + @type rdtype: int + @param covers: the covers value of the RRset + @type covers: int + @param deleting: the deleting value of the RRset + @type deleting: int + @param create: If True, create the RRset if it is not found. + The created RRset is appended to I{section}. + @type create: bool + @param force_unique: If True and create is also True, create a + new RRset regardless of whether a matching RRset exists already. + @type force_unique: bool + @rtype: dns.rrset.RRset object or None""" + + try: + rrset = self.find_rrset(section, name, rdclass, rdtype, covers, + deleting, create, force_unique) + except KeyError: + rrset = None + return rrset + + def to_wire(self, origin=None, max_size=65535, **kw): + """Return a string containing the message in DNS compressed wire + format. + + Additional keyword arguments are passed to the rrset to_wire() + method. + + @param origin: The origin to be appended to any relative names. + @type origin: dns.name.Name object + @param max_size: The maximum size of the wire format output. + @type max_size: int + @raises dns.exception.TooBig: max_size was exceeded + @rtype: string + """ + + r = dns.renderer.Renderer(self.id, self.flags, max_size, origin) + for rrset in self.question: + r.add_question(rrset.name, rrset.rdtype, rrset.rdclass) + for rrset in self.answer: + r.add_rrset(dns.renderer.ANSWER, rrset, **kw) + for rrset in self.authority: + r.add_rrset(dns.renderer.AUTHORITY, rrset, **kw) + if self.edns >= 0: + r.add_edns(self.edns, self.ednsflags, self.payload) + for rrset in self.additional: + r.add_rrset(dns.renderer.ADDITIONAL, rrset, **kw) + r.write_header() + if not self.keyname is None: + r.add_tsig(self.keyname, self.keyring[self.keyname], + self.fudge, self.original_id, self.tsig_error, + self.other_data, self.request_mac) + self.mac = r.mac + return r.get_wire() + + def use_tsig(self, keyring, keyname=None, fudge=300, original_id=None, + tsig_error=0, other_data=''): + """When sending, a TSIG signature using the specified keyring + and keyname should be added. + + @param keyring: The TSIG keyring to use; defaults to None. + @type keyring: dict + @param keyname: The name of the TSIG key to use; defaults to None. + The key must be defined in the keyring. If a keyring is specified + but a keyname is not, then the key used will be the first key in the + keyring. Note that the order of keys in a dictionary is not defined, + so applications should supply a keyname when a keyring is used, unless + they know the keyring contains only one key. + @type keyname: dns.name.Name or string + @param fudge: TSIG time fudge; default is 300 seconds. + @type fudge: int + @param original_id: TSIG original id; defaults to the message's id + @type original_id: int + @param tsig_error: TSIG error code; default is 0. + @type tsig_error: int + @param other_data: TSIG other data. + @type other_data: string + """ + + self.keyring = keyring + if keyname is None: + self.keyname = self.keyring.keys()[0] + else: + if isinstance(keyname, str): + keyname = dns.name.from_text(keyname) + self.keyname = keyname + self.fudge = fudge + if original_id is None: + self.original_id = self.id + else: + self.original_id = original_id + self.tsig_error = tsig_error + self.other_data = other_data + + def use_edns(self, edns, ednsflags, payload): + """Configure EDNS behavior. + @param edns: The EDNS level to use. Specifying None or -1 means + 'do not use EDNS'. + @type edns: int or None + @param ednsflags: EDNS flag values. + @type ednsflags: int + @param payload: The EDNS sender's payload field, which is the maximum + size of UDP datagram the sender can handle. + @type payload: int + @see: RFC 2671 + """ + if edns is None: + edns = -1 + self.edns = edns + self.ednsflags = ednsflags + self.payload = payload + + def rcode(self): + """Return the rcode. + @rtype: int + """ + return dns.rcode.from_flags(self.flags, self.ednsflags) + + def set_rcode(self, rcode): + """Set the rcode. + @param rcode: the rcode + @type rcode: int + """ + (value, evalue) = dns.rcode.to_flags(rcode) + self.flags &= 0xFFF0 + self.flags |= value + self.ednsflags &= 0xFF000000L + self.ednsflags |= evalue + if self.ednsflags != 0 and self.edns < 0: + self.edns = 0 + +class _WireReader(object): + """Wire format reader. + + @ivar wire: the wire-format message. + @type wire: string + @ivar message: The message object being built + @type message: dns.message.Message object + @ivar current: When building a message object from wire format, this + variable contains the offset from the beginning of wire of the next octet + to be read. + @type current: int + @ivar updating: Is the message a dynamic update? + @type updating: bool + @ivar zone_rdclass: The class of the zone in messages which are + DNS dynamic updates. + @type zone_rdclass: int + """ + + def __init__(self, wire, message, question_only=False): + self.wire = wire + self.message = message + self.current = 0 + self.updating = False + self.zone_rdclass = dns.rdataclass.IN + self.question_only = question_only + + def _get_question(self, qcount): + """Read the next I{qcount} records from the wire data and add them to + the question section. + @param qcount: the number of questions in the message + @type qcount: int""" + + if self.updating and qcount > 1: + raise dns.exception.FormError + + for i in xrange(0, qcount): + (qname, used) = dns.name.from_wire(self.wire, self.current) + if not self.message.origin is None: + qname = qname.relativize(self.message.origin) + self.current = self.current + used + (rdtype, rdclass) = \ + struct.unpack('!HH', + self.wire[self.current:self.current + 4]) + self.current = self.current + 4 + self.message.find_rrset(self.message.question, qname, + rdclass, rdtype, create=True, + force_unique=True) + if self.updating: + self.zone_rdclass = rdclass + + def _get_section(self, section, count): + """Read the next I{count} records from the wire data and add them to + the specified section. + @param section: the section of the message to which to add records + @type section: list of dns.rrset.RRset objects + @param count: the number of records to read + @type count: int""" + + if self.updating: + force_unique = True + else: + force_unique = False + seen_opt = False + for i in xrange(0, count): + rr_start = self.current + (name, used) = dns.name.from_wire(self.wire, self.current) + if not self.message.origin is None: + name = name.relativize(self.message.origin) + self.current = self.current + used + (rdtype, rdclass, ttl, rdlen) = \ + struct.unpack('!HHIH', + self.wire[self.current:self.current + 10]) + self.current = self.current + 10 + if rdtype == dns.rdatatype.OPT: + if not section is self.message.additional or seen_opt: + raise BadEDNS + self.message.payload = rdclass + self.message.ednsflags = ttl + self.message.edns = (ttl & 0xff0000) >> 16 + seen_opt = True + elif rdtype == dns.rdatatype.TSIG: + if not (section is self.message.additional and + i == (count - 1)): + raise BadTSIG + if self.message.keyring is None: + raise UnknownTSIGKey, 'got signed message without keyring' + secret = self.message.keyring.get(name) + if secret is None: + raise UnknownTSIGKey, "key '%s' unknown" % name + self.message.tsig_ctx = \ + dns.tsig.validate(self.wire, + name, + secret, + int(time.time()), + self.message.request_mac, + rr_start, + self.current, + rdlen, + self.message.tsig_ctx, + self.message.multi, + self.message.first) + self.message.had_tsig = True + else: + if ttl < 0: + ttl = 0 + if self.updating and \ + (rdclass == dns.rdataclass.ANY or + rdclass == dns.rdataclass.NONE): + deleting = rdclass + rdclass = self.zone_rdclass + else: + deleting = None + if deleting == dns.rdataclass.ANY: + covers = dns.rdatatype.NONE + rd = None + else: + rd = dns.rdata.from_wire(rdclass, rdtype, self.wire, + self.current, rdlen, + self.message.origin) + covers = rd.covers() + if self.message.xfr and rdtype == dns.rdatatype.SOA: + force_unique = True + rrset = self.message.find_rrset(section, name, + rdclass, rdtype, covers, + deleting, True, force_unique) + if not rd is None: + rrset.add(rd, ttl) + self.current = self.current + rdlen + + def read(self): + """Read a wire format DNS message and build a dns.message.Message + object.""" + + l = len(self.wire) + if l < 12: + raise ShortHeader + (self.message.id, self.message.flags, qcount, ancount, + aucount, adcount) = struct.unpack('!HHHHHH', self.wire[:12]) + self.current = 12 + if dns.opcode.is_update(self.message.flags): + self.updating = True + self._get_question(qcount) + if self.question_only: + return + self._get_section(self.message.answer, ancount) + self._get_section(self.message.authority, aucount) + self._get_section(self.message.additional, adcount) + if self.current != l: + raise TrailingJunk + if self.message.multi and self.message.tsig_ctx and \ + not self.message.had_tsig: + self.message.tsig_ctx.update(self.wire) + + +def from_wire(wire, keyring=None, request_mac='', xfr=False, origin=None, + tsig_ctx = None, multi = False, first = True, + question_only = False): + """Convert a DNS wire format message into a message + object. + + @param keyring: The keyring to use if the message is signed. + @type keyring: dict + @param request_mac: If the message is a response to a TSIG-signed request, + I{request_mac} should be set to the MAC of that request. + @type request_mac: string + @param xfr: Is this message part of a zone transfer? + @type xfr: bool + @param origin: If the message is part of a zone transfer, I{origin} + should be the origin name of the zone. + @type origin: dns.name.Name object + @param tsig_ctx: The ongoing TSIG context, used when validating zone + transfers. + @type tsig_ctx: hmac.HMAC object + @param multi: Is this message part of a multiple message sequence? + @type multi: bool + @param first: Is this message standalone, or the first of a multi + message sequence? + @type first: bool + @param question_only: Read only up to the end of the question section? + @type question_only: bool + @raises ShortHeader: The message is less than 12 octets long. + @raises TrailingJunk: There were octets in the message past the end + of the proper DNS message. + @raises BadEDNS: An OPT record was in the wrong section, or occurred more + than once. + @raises BadTSIG: A TSIG record was not the last record of the additional + data section. + @rtype: dns.message.Message object""" + + m = Message(id=0) + m.keyring = keyring + m.request_mac = request_mac + m.xfr = xfr + m.origin = origin + m.tsig_ctx = tsig_ctx + m.multi = multi + m.first = first + + reader = _WireReader(wire, m, question_only) + reader.read() + + return m + + +class _TextReader(object): + """Text format reader. + + @ivar tok: the tokenizer + @type tok: dns.tokenizer.Tokenizer object + @ivar message: The message object being built + @type message: dns.message.Message object + @ivar updating: Is the message a dynamic update? + @type updating: bool + @ivar zone_rdclass: The class of the zone in messages which are + DNS dynamic updates. + @type zone_rdclass: int + @ivar last_name: The most recently read name when building a message object + from text format. + @type last_name: dns.name.Name object + """ + + def __init__(self, text, message): + self.message = message + self.tok = dns.tokenizer.Tokenizer(text) + self.last_name = None + self.zone_rdclass = dns.rdataclass.IN + self.updating = False + + def _header_line(self, section): + """Process one line from the text format header section.""" + + (ttype, what) = self.tok.get() + if what == 'id': + self.message.id = self.tok.get_int() + elif what == 'flags': + while True: + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + self.tok.unget(token) + break + self.message.flags = self.message.flags | \ + dns.flags.from_text(token[1]) + if dns.opcode.is_update(self.message.flags): + self.updating = True + elif what == 'edns': + self.message.edns = self.tok.get_int() + self.message.ednsflags = self.message.ednsflags | \ + (self.message.edns << 16) + elif what == 'eflags': + if self.message.edns < 0: + self.message.edns = 0 + while True: + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + self.tok.unget(token) + break + self.message.ednsflags = self.message.ednsflags | \ + dns.flags.edns_from_text(token[1]) + elif what == 'payload': + self.message.payload = self.tok.get_int() + if self.message.edns < 0: + self.message.edns = 0 + elif what == 'opcode': + text = self.tok.get_string() + self.message.flags = self.message.flags | \ + dns.opcode.to_flags(dns.opcode.from_text(text)) + elif what == 'rcode': + text = self.tok.get_string() + self.message.set_rcode(dns.rcode.from_text(text)) + else: + raise UnknownHeaderField + self.tok.get_eol() + + def _question_line(self, section): + """Process one line from the text format question section.""" + + token = self.tok.get(want_leading = True) + if token[0] != dns.tokenizer.WHITESPACE: + self.last_name = dns.name.from_text(token[1], None) + name = self.last_name + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + # Class + try: + rdclass = dns.rdataclass.from_text(token[1]) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except: + rdclass = dns.rdataclass.IN + # Type + rdtype = dns.rdatatype.from_text(token[1]) + self.message.find_rrset(self.message.question, name, + rdclass, rdtype, create=True, + force_unique=True) + if self.updating: + self.zone_rdclass = rdclass + self.tok.get_eol() + + def _rr_line(self, section): + """Process one line from the text format answer, authority, or + additional data sections. + """ + + deleting = None + # Name + token = self.tok.get(want_leading = True) + if token[0] != dns.tokenizer.WHITESPACE: + self.last_name = dns.name.from_text(token[1], None) + name = self.last_name + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + # TTL + try: + ttl = int(token[1], 0) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except: + ttl = 0 + # Class + try: + rdclass = dns.rdataclass.from_text(token[1]) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + if rdclass == dns.rdataclass.ANY or rdclass == dns.rdataclass.NONE: + deleting = rdclass + rdclass = self.zone_rdclass + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except: + rdclass = dns.rdataclass.IN + # Type + rdtype = dns.rdatatype.from_text(token[1]) + token = self.tok.get() + if token[0] != dns.tokenizer.EOL and token[0] != dns.tokenizer.EOF: + self.tok.unget(token) + rd = dns.rdata.from_text(rdclass, rdtype, self.tok, None) + covers = rd.covers() + else: + rd = None + covers = dns.rdatatype.NONE + rrset = self.message.find_rrset(section, name, + rdclass, rdtype, covers, + deleting, True, self.updating) + if not rd is None: + rrset.add(rd, ttl) + + def read(self): + """Read a text format DNS message and build a dns.message.Message + object.""" + + line_method = self._header_line + section = None + while 1: + token = self.tok.get(True, True) + if token[0] == dns.tokenizer.EOL or token[0] == dns.tokenizer.EOF: + break + if token[0] == dns.tokenizer.COMMENT: + u = token[1].upper() + if u == 'HEADER': + line_method = self._header_line + elif u == 'QUESTION' or u == 'ZONE': + line_method = self._question_line + section = self.message.question + elif u == 'ANSWER' or u == 'PREREQ': + line_method = self._rr_line + section = self.message.answer + elif u == 'AUTHORITY' or u == 'UPDATE': + line_method = self._rr_line + section = self.message.authority + elif u == 'ADDITIONAL': + line_method = self._rr_line + section = self.message.additional + self.tok.get_eol() + continue + self.tok.unget(token) + line_method(section) + + +def from_text(text): + """Convert the text format message into a message object. + + @param text: The text format message. + @type text: string + @raises UnknownHeaderField: + @raises dns.exception.SyntaxError: + @rtype: dns.message.Message object""" + + # 'text' can also be a file, but we don't publish that fact + # since it's an implementation detail. The official file + # interface is from_file(). + + m = Message() + + reader = _TextReader(text, m) + reader.read() + + return m + +def from_file(f): + """Read the next text format message from the specified file. + + @param f: file or string. If I{f} is a string, it is treated + as the name of a file to open. + @raises UnknownHeaderField: + @raises dns.exception.SyntaxError: + @rtype: dns.message.Message object""" + + if sys.hexversion >= 0x02030000: + # allow Unicode filenames; turn on universal newline support + str_type = basestring + opts = 'rU' + else: + str_type = str + opts = 'r' + if isinstance(f, str_type): + f = file(f, opts) + want_close = True + else: + want_close = False + + try: + m = from_text(f) + finally: + if want_close: + f.close() + return m + +def make_query(qname, rdtype, rdclass = dns.rdataclass.IN): + """Make a query message. + + The query name, type, and class may all be specified either + as objects of the appropriate type, or as strings. + + The query will have a randomly choosen query id, and its DNS flags + will be set to dns.flags.RD. + + @param qname: The query name. + @type qname: dns.name.Name object or string + @param rdtype: The desired rdata type. + @type rdtype: int + @param rdclass: The desired rdata class; the default is class IN. + @type rdclass: int + @rtype: dns.message.Message object""" + + if isinstance(qname, str): + qname = dns.name.from_text(qname) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(rdclass, str): + rdclass = dns.rdataclass.from_text(rdclass) + m = Message() + m.flags |= dns.flags.RD + m.find_rrset(m.question, qname, rdclass, rdtype, create=True, + force_unique=True) + return m diff --git a/dns/name.py b/dns/name.py new file mode 100644 index 0000000..615e4c6 --- /dev/null +++ b/dns/name.py @@ -0,0 +1,587 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: name.py,v 1.39 2004/03/19 00:17:27 halley Exp $ + +"""DNS Names. + +@var root: The DNS root name. +@type root: dns.name.Name object +@var empty: The empty DNS name. +@type empty: dns.name.Name object +""" + +import string +import struct +import sys +import types + +import dns.exception + +NAMERELN_NONE = 0 +NAMERELN_SUPERDOMAIN = 1 +NAMERELN_SUBDOMAIN = 2 +NAMERELN_EQUAL = 3 +NAMERELN_COMMONANCESTOR = 4 + +class EmptyLabel(dns.exception.SyntaxError): + """Raised if a label is empty.""" + pass + +class BadEscape(dns.exception.SyntaxError): + """Raised if an escaped code in a text format name is invalid.""" + pass + +class BadPointer(dns.exception.FormError): + """Raised if a compression pointer points forward instead of backward.""" + pass + +class BadLabelType(dns.exception.FormError): + """Raised if the label type of a wire format name is unknown.""" + pass + +class NeedAbsoluteNameOrOrigin(dns.exception.DNSException): + """Raised if an attempt is made to convert a non-absolute name to + wire when there is also a non-absolute (or missing) origin.""" + pass + +class NameTooLong(dns.exception.FormError): + """Raised if a name is > 255 octets long.""" + pass + +class LabelTooLong(dns.exception.SyntaxError): + """Raised if a label is > 63 octets long.""" + pass + +class AbsoluteConcatenation(dns.exception.DNSException): + """Raised if an attempt is made to append anything other than the + empty name to an absolute name.""" + pass + +_escaped = { + '"' : True, + '(' : True, + ')' : True, + '.' : True, + ';' : True, + '\\' : True, + '@' : True, + '$' : True + } + +def _escapify(label): + """Escape the characters in label which need it. + @returns: the escaped string + @rtype: string""" + text = '' + for c in label: + if c in _escaped: + text += '\\' + c + elif ord(c) > 0x20 and ord(c) < 0x7F: + text += c + else: + text += '\\%03d' % ord(c) + return text + +def _validate_labels(labels): + """Check for empty labels in the middle of a label sequence, + labels that are too long, and for too many labels. + @raises NameTooLong: the name as a whole is too long + @raises LabelTooLong: an individual label is too long + @raises EmptyLabel: a label is empty (i.e. the root label) and appears + in a position other than the end of the label sequence""" + + l = len(labels) + total = 0 + i = -1 + j = 0 + for label in labels: + ll = len(label) + total += ll + 1 + if ll > 63: + raise LabelTooLong + if i < 0 and label == '': + i = j + j += 1 + if total > 255: + raise NameTooLong + if i >= 0 and i != l - 1: + raise EmptyLabel + +class Name(object): + """A DNS name. + + The dns.name.Name class represents a DNS name as a tuple of labels. + Instances of the class are immutable. + + @ivar labels: The tuple of labels in the name. Each label is a string of + up to 63 octets.""" + + __slots__ = ['labels'] + + def __init__(self, labels): + """Initialize a domain name from a list of labels. + @param labels: the labels + @type labels: any iterable whose values are strings + """ + + super(Name, self).__setattr__('labels', tuple(labels)) + _validate_labels(self.labels) + + def __setattr__(self, name, value): + raise TypeError, "object doesn't support attribute assignment" + + def is_absolute(self): + """Is the most significant label of this name the root label? + @rtype: bool + """ + + return len(self.labels) > 0 and self.labels[-1] == '' + + def is_wild(self): + """Is this name wild? (I.e. Is the least significant label '*'?) + @rtype: bool + """ + + return len(self.labels) > 0 and self.labels[0] == '*' + + def __hash__(self): + """Return a case-insensitive hash of the name. + @rtype: int + """ + + h = 0L + for label in self.labels: + for c in label: + h += ( h << 3 ) + ord(c.lower()) + return int(h % sys.maxint) + + def fullcompare(self, other): + """Compare two names, returning a 3-tuple (relation, order, nlabels). + + I{relation} describes the relation ship beween the names, + and is one of: dns.name.NAMERELN_NONE, + dns.name.NAMERELN_SUPERDOMAIN, dns.name.NAMERELN_SUBDOMAIN, + dns.name.NAMERELN_EQUAL, or dns.name.NAMERELN_COMMONANCESTOR + + I{order} is < 0 if self < other, > 0 if self > other, and == + 0 if self == other. A relative name is always less than an + absolute name. If both names have the same relativity, then + the DNSSEC order relation is used to order them. + + I{nlabels} is the number of significant labels that the two names + have in common. + """ + + sabs = self.is_absolute() + oabs = other.is_absolute() + if sabs != oabs: + if sabs: + return (NAMERELN_NONE, 1, 0) + else: + return (NAMERELN_NONE, -1, 0) + l1 = len(self.labels) + l2 = len(other.labels) + ldiff = l1 - l2 + if ldiff < 0: + l = l1 + else: + l = l2 + + order = 0 + nlabels = 0 + namereln = NAMERELN_NONE + while l > 0: + l -= 1 + l1 -= 1 + l2 -= 1 + label1 = self.labels[l1].lower() + label2 = other.labels[l2].lower() + if label1 < label2: + order = -1 + if nlabels > 0: + namereln = NAMERELN_COMMONANCESTOR + return (namereln, order, nlabels) + elif label1 > label2: + order = 1 + if nlabels > 0: + namereln = NAMERELN_COMMONANCESTOR + return (namereln, order, nlabels) + nlabels += 1 + order = ldiff + if ldiff < 0: + namereln = NAMERELN_SUPERDOMAIN + elif ldiff > 0: + namereln = NAMERELN_SUBDOMAIN + else: + namereln = NAMERELN_EQUAL + return (namereln, order, nlabels) + + def is_subdomain(self, other): + """Is self a subdomain of other? + + The notion of subdomain includes equality. + @rtype: bool + """ + + (nr, o, nl) = self.fullcompare(other) + if nr == NAMERELN_SUBDOMAIN or nr == NAMERELN_EQUAL: + return True + return False + + def is_superdomain(self, other): + """Is self a superdomain of other? + + The notion of subdomain includes equality. + @rtype: bool + """ + + (nr, o, nl) = self.fullcompare(other) + if nr == NAMERELN_SUPERDOMAIN or nr == NAMERELN_EQUAL: + return True + return False + + def canonicalize(self): + """Return a name which is equal to the current name, but is in + DNSSEC canonical form. + @rtype: dns.name.Name object + """ + + return Name([x.lower() for x in self.labels]) + + def __eq__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] == 0 + else: + return False + + def __ne__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] != 0 + else: + return True + + def __lt__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] < 0 + else: + return NotImplemented + + def __le__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] <= 0 + else: + return NotImplemented + + def __ge__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] >= 0 + else: + return NotImplemented + + def __gt__(self, other): + if isinstance(other, Name): + return self.fullcompare(other)[1] > 0 + else: + return NotImplemented + + def __repr__(self): + return '<DNS name ' + self.__str__() + '>' + + def __str__(self): + return self.to_text(False) + + def to_text(self, omit_final_dot = False): + """Convert name to text format. + @param omit_final_dot: If True, don't emit the final dot (denoting the + root label) for absolute names. The default is False. + @rtype: string + """ + + if len(self.labels) == 0: + return '@' + if len(self.labels) == 1 and self.labels[0] == '': + return '.' + if omit_final_dot and self.is_absolute(): + l = self.labels[:-1] + else: + l = self.labels + s = string.join(map(_escapify, l), '.') + return s + + def to_digestable(self, origin=None): + """Convert name to a format suitable for digesting in hashes. + + The name is canonicalized and converted to uncompressed wire format. + + @param origin: If the name is relative and origin is not None, then + origin will be appended to it. + @type origin: dns.name.Name object + @raises NeedAbsoluteNameOrOrigin: All names in wire format are + absolute. If self is a relative name, then an origin must be supplied; + if it is missing, then this exception is raised + @rtype: string + """ + + if not self.is_absolute(): + if origin is None or not origin.is_absolute(): + raise NeedAbsoluteNameOrOrigin + labels = list(self.labels) + labels.extend(list(origin.labels)) + else: + labels = self.labels + dlabels = ["%s%s" % (chr(len(x)), x.lower()) for x in labels] + return ''.join(dlabels) + + def to_wire(self, file, compress = None, origin = None): + """Convert name to wire format, possibly compressing it. + + @param file: the file where the compressed name is emitted (typically + a cStringIO file) + @type file: file + @param compress: The compression table. If None (the default) names + will not be compressed. + @type compress: dict + @param origin: If the name is relative and origin is not None, then + origin will be appended to it. + @type origin: dns.name.Name object + @raises NeedAbsoluteNameOrOrigin: All names in wire format are + absolute. If self is a relative name, then an origin must be supplied; + if it is missing, then this exception is raised + """ + + if not self.is_absolute(): + if origin is None or not origin.is_absolute(): + raise NeedAbsoluteNameOrOrigin + labels = list(self.labels) + labels.extend(list(origin.labels)) + else: + labels = self.labels + i = 0 + for label in labels: + n = Name(labels[i:]) + i += 1 + if not compress is None: + pos = compress.get(n) + else: + pos = None + if not pos is None: + value = 0xc000 + pos + s = struct.pack('!H', value) + file.write(s) + return + else: + if not compress is None and len(n) > 1: + pos = file.tell() + if pos < 0xc000: + compress[n] = pos + l = len(label) + file.write(chr(l)) + if l > 0: + file.write(label) + + def __len__(self): + """The length of the name (in labels). + @rtype: int + """ + + return len(self.labels) + + def __getitem__(self, index): + return self.labels[index] + + def __getslice__(self, start, stop): + return self.labels[start:stop] + + def __add__(self, other): + return self.concatenate(other) + + def __sub__(self, other): + return self.relativize(other) + + def split(self, depth): + """Split a name into a prefix and suffix at depth. + + @param depth: the number of labels in the suffix + @type depth: int + @raises ValueError: the depth was not >= 0 and <= the length of the + name. + @returns: the tuple (prefix, suffix) + @rtype: tuple + """ + + l = len(self.labels) + if depth == 0: + return (self, dns.name.empty) + elif depth == l: + return (dns.name.empty, self) + elif depth < 0 or depth > l: + raise ValueError, \ + 'depth must be >= 0 and <= the length of the name' + return (Name(self[: -depth]), Name(self[-depth :])) + + def concatenate(self, other): + """Return a new name which is the concatenation of self and other. + @rtype: dns.name.Name object + @raises AbsoluteConcatenation: self is absolute and other is + not the empty name + """ + + if self.is_absolute() and len(other) > 0: + raise AbsoluteConcatenation + labels = list(self.labels) + labels.extend(list(other.labels)) + return Name(labels) + + def relativize(self, origin): + """If self is a subdomain of origin, return a new name which is self + relative to origin. Otherwise return self. + @rtype: dns.name.Name object + """ + + if not origin is None and self.is_subdomain(origin): + return Name(self[: -len(origin)]) + else: + return self + + def derelativize(self, origin): + """If self is a relative name, return a new name which is the + concatenation of self and origin. Otherwise return self. + @rtype: dns.name.Name object + """ + + if not self.is_absolute(): + return self.concatenate(origin) + else: + return self + + def choose_relativity(self, origin=None, relativize=True): + """Return a name with the relativity desired by the caller. If + origin is None, then self is returned. Otherwise, if + relativize is true the name is relativized, and if relativize is + false the name is derelativized. + @rtype: dns.name.Name object + """ + + if origin: + if relativize: + return self.relativize(origin) + else: + return self.derelativize(origin) + else: + return self + +root = Name(['']) +empty = Name([]) + +def from_text(text, origin = root): + """Convert text into a Name object. + @rtype: dns.name.Name object + """ + + if not isinstance(text, str): + raise ValueError, "input to from_text() must be a byte string" + if not (origin is None or isinstance(origin, Name)): + raise ValueError, "origin must be a Name or None" + labels = [] + label = '' + escaping = False + if text == '@': + text = '' + if text: + if text == '.': + return Name(['']) + for c in text: + if escaping: + if edigits == 0: + if c.isdigit(): + total = int(c) + edigits += 1 + else: + label += c + escaping = False + else: + if not c.isdigit(): + raise BadEscape + total *= 10 + total += int(c) + edigits += 1 + if edigits == 3: + escaping = False + label += chr(total) + elif c == '.': + if len(label) == 0: + raise EmptyLabel + labels.append(label) + label = '' + elif c == '\\': + escaping = True + edigits = 0 + total = 0 + else: + label += c + if len(label) > 0: + labels.append(label) + else: + labels.append('') + if (len(labels) == 0 or labels[-1] != '') and not origin is None: + labels.extend(list(origin.labels)) + return Name(labels) + +def from_wire(message, current): + """Convert possibly compressed wire format into a Name. + @param message: the entire DNS message + @type message: string + @param current: the offset of the beginning of the name from the start + of the message + @type current: int + @raises dns.name.BadPointer: a compression pointer did not point backwards + in the message + @raises dns.name.BadLabelType: an invalid label type was encountered. + @returns: a tuple consisting of the name that was read and the number + of bytes of the wire format message which were consumed reading it + @rtype: (dns.name.Name object, int) tuple + """ + + if not isinstance(message, str): + raise ValueError, "input to from_wire() must be a byte string" + labels = [] + biggest_pointer = current + hops = 0 + count = ord(message[current]) + current += 1 + cused = 1 + while count != 0: + if count < 64: + labels.append(message[current : current + count]) + current += count + if hops == 0: + cused += count + elif count >= 192: + current = (count & 0x3f) * 256 + ord(message[current]) + if hops == 0: + cused += 1 + if current >= biggest_pointer: + raise BadPointer + biggest_pointer = current + hops += 1 + else: + raise BadLabelType + count = ord(message[current]) + current += 1 + if hops == 0: + cused += 1 + labels.append('') + return (Name(labels), cused) diff --git a/dns/namedict.py b/dns/namedict.py new file mode 100644 index 0000000..aa9d4d9 --- /dev/null +++ b/dns/namedict.py @@ -0,0 +1,61 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: namedict.py,v 1.5 2004/03/19 00:17:27 halley Exp $ + +"""DNS name dictionary""" + +import dns.name + +class NameDict(dict): + + """A dictionary whose keys are dns.name.Name objects. + @ivar max_depth: the maximum depth of the keys that have ever been + added to the dictionary. + @type max_depth: int + """ + + def __init__(self, *args, **kwargs): + super(NameDict, self).__init__(*args, **kwargs) + self.max_depth = 0 + + def __setitem__(self, key, value): + if not isinstance(key, dns.name.Name): + raise ValueError, 'NameDict key must be a name' + depth = len(key) + if depth > self.max_depth: + self.max_depth = depth + super(NameDict, self).__setitem__(key, value) + + def get_deepest_match(self, name): + """Find the deepest match to I{name} in the dictionary. + + The deepest match is the longest name in the dictionary which is + a superdomain of I{name}. + + @param name: the name + @type name: dns.name.Name object + @rtype: (key, value) tuple + """ + + depth = len(name) + if depth > self.max_depth: + depth = self.max_depth + for i in xrange(-depth, 0): + n = dns.name.Name(name[i:]) + if self.has_key(n): + return (n, self[n]) + v = self[dns.name.empty] + return (dns.name.empty, v) diff --git a/dns/node.py b/dns/node.py new file mode 100644 index 0000000..dd594b9 --- /dev/null +++ b/dns/node.py @@ -0,0 +1,174 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: node.py,v 1.19 2004/03/19 00:17:27 halley Exp $ + +"""DNS nodes. A node is a set of rdatasets.""" + +import StringIO + +import dns.rdataset +import dns.rdatatype +import dns.renderer + +class Node(object): + """A DNS node. + + A node is a set of rdatasets + + @ivar rdatasets: the node's rdatasets + @type rdatasets: list of dns.rdataset.Rdataset objects""" + + __slots__ = ['rdatasets'] + + def __init__(self): + """Initialize a DNS node. + """ + + self.rdatasets = []; + + def to_text(self, name, **kw): + """Convert a node to text format. + + Each rdataset at the node is printed. Any keyword arguments + to this method are passed on to the rdataset's to_text() method. + @param name: the owner name of the rdatasets + @type name: dns.name.Name object + @rtype: string + """ + + s = StringIO.StringIO() + for rds in self.rdatasets: + print >> s, rds.to_text(name, **kw) + return s.getvalue()[:-1] + + def __repr__(self): + return '<DNS node ' + str(id(self)) + '>' + + def __eq__(self, other): + """Two nodes are equal if they have the same rdatasets. + + @rtype: bool + """ + # + # This is inefficient. Good thing we don't need to do it much. + # + for rd in self.rdatasets: + if rd not in other.rdatasets: + return False + for rd in other.rdatasets: + if rd not in self.rdatasets: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __len__(self): + return len(self.rdatasets) + + def __iter__(self): + return iter(self.rdatasets) + + def find_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE, + create=False): + """Find an rdataset matching the specified properties in the + current node. + + @param rdclass: The class of the rdataset + @type rdclass: int + @param rdtype: The type of the rdataset + @type rdtype: int + @param covers: The covered type. Usually this value is + dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or + dns.rdatatype.RRSIG, then the covers value will be the rdata + type the SIG/RRSIG covers. The library treats the SIG and RRSIG + types as if they were a family of + types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much + easier to work with than if RRSIGs covering different rdata + types were aggregated into a single RRSIG rdataset. + @type covers: int + @param create: If True, create the rdataset if it is not found. + @type create: bool + @raises KeyError: An rdataset of the desired type and class does + not exist and I{create} is not True. + @rtype: dns.rdataset.Rdataset object + """ + + for rds in self.rdatasets: + if rds.match(rdclass, rdtype, covers): + return rds + if not create: + raise KeyError + rds = dns.rdataset.Rdataset(rdclass, rdtype) + self.rdatasets.append(rds) + return rds + + def get_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE, + create=False): + """Get an rdataset matching the specified properties in the + current node. + + None is returned if an rdataset of the specified type and + class does not exist and I{create} is not True. + + @param rdclass: The class of the rdataset + @type rdclass: int + @param rdtype: The type of the rdataset + @type rdtype: int + @param covers: The covered type. + @type covers: int + @param create: If True, create the rdataset if it is not found. + @type create: bool + @rtype: dns.rdataset.Rdataset object or None + """ + + try: + rds = self.find_rdataset(rdclass, rdtype, covers, create) + except KeyError: + rds = None + return rds + + def delete_rdataset(self, rdclass, rdtype, covers=dns.rdatatype.NONE): + """Delete the rdataset matching the specified properties in the + current node. + + If a matching rdataset does not exist, it is not an error. + + @param rdclass: The class of the rdataset + @type rdclass: int + @param rdtype: The type of the rdataset + @type rdtype: int + @param covers: The covered type. + @type covers: int + """ + + rds = self.get_rdataset(rdclass, rdtype, covers) + if not rds is None: + self.rdatasets.remove(rds) + + def replace_rdataset(self, replacement): + """Replace an rdataset. + + It is not an error if there is no rdataset matching I{replacement}. + + Ownership of the I{replacement} object is transferred to the node; + in other words, this method does not store a copy of I{replacement} + at the node, it stores I{replacement} itself. + """ + + self.delete_rdataset(replacement.rdclass, replacement.rdtype, + replacement.covers) + self.rdatasets.append(replacement) diff --git a/dns/opcode.py b/dns/opcode.py new file mode 100644 index 0000000..b2b38ba --- /dev/null +++ b/dns/opcode.py @@ -0,0 +1,106 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: opcode.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +"""DNS Opcodes.""" + +import dns.exception + +QUERY = 0 +IQUERY = 1 +STATUS = 2 +NOTIFY = 4 +UPDATE = 5 + +_by_text = { + 'QUERY' : QUERY, + 'IQUERY' : IQUERY, + 'STATUS' : STATUS, + 'NOTIFY' : NOTIFY, + 'UPDATE' : UPDATE +} + +# We construct the inverse mapping programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mapping not to be true inverse. + +_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) + + +class UnknownOpcode(dns.exception.DNSException): + """Raised if an opcode is unknown.""" + pass + +def from_text(text): + """Convert text into an opcode. + + @param text: the textual opcode + @type text: string + @raises UnknownOpcode: the opcode is unknown + @rtype: int + """ + + if text.isdigit(): + value = int(text) + if value >= 0 and value <= 15: + return value + value = _by_text.get(text.upper()) + if value is None: + raise UnknownOpcode + return value + +def from_flags(flags): + """Extract an opcode from DNS message flags. + + @param flags: int + @rtype: int + """ + + return (flags & 0x7800) >> 11 + +def to_flags(value): + """Convert an opcode to a value suitable for ORing into DNS message + flags. + @rtype: int + """ + + return (value << 11) & 0x7800 + +def to_text(value): + """Convert an opcode to text. + + @param value: the opcdoe + @type value: int + @raises UnknownOpcode: the opcode is unknown + @rtype: string + """ + + text = _by_value.get(value) + if text is None: + text = str(value) + return text + +def is_update(flags): + """True if the opcode in flags is UPDATE. + + @param flags: DNS flags + @type flags: int + @rtype: bool + """ + + if (from_flags(flags) == UPDATE): + return True + return False diff --git a/dns/query.py b/dns/query.py new file mode 100644 index 0000000..e09b94d --- /dev/null +++ b/dns/query.py @@ -0,0 +1,261 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: query.py,v 1.18 2004/03/19 00:17:27 halley Exp $ + +"""Talk to a DNS server.""" + +from __future__ import generators + +import errno +import select +import socket +import struct +import sys +import time + +import dns.exception +import dns.name +import dns.message +import dns.rdataclass +import dns.rdatatype + +class UnexpectedSource(dns.exception.DNSException): + """Raised if a query response comes from an unexpected address or port.""" + pass + +class BadResponse(dns.exception.FormError): + """Raised if a query response does not respond to the question asked.""" + pass + +def _compute_expiration(timeout): + if timeout is None: + return None + else: + return time.time() + timeout + +def _wait_for(ir, iw, ix, expiration): + if expiration is None: + timeout = None + else: + timeout = expiration - time.time() + if timeout <= 0.0: + raise dns.exception.Timeout + if timeout is None: + (r, w, x) = select.select(ir, iw, ix) + else: + (r, w, x) = select.select(ir, iw, ix, timeout) + if len(r) == 0 and len(w) == 0 and len(x) == 0: + raise dns.exception.Timeout + +def _wait_for_readable(s, expiration): + _wait_for([s], [], [s], expiration) + +def _wait_for_writable(s, expiration): + _wait_for([], [s], [s], expiration) + +def udp(q, where, timeout=None, port=53, af=socket.AF_INET): + """Return the response obtained after sending a query via UDP. + + @param q: the query + @type q: dns.message.Message + @param timeout: The number of seconds to wait before the query times out. + If None, the default, wait forever. + @type timeout: float + @param port: The port to which to send the message. The default is 53. + @type port: int + @param af: the address family to use. The default is socket.AF_INET. + @type af: int + @rtype: dns.message.Message object""" + + wire = q.to_wire() + s = socket.socket(af, socket.SOCK_DGRAM, 0) + try: + expiration = _compute_expiration(timeout) + s.setblocking(0) + _wait_for_writable(s, expiration) + s.sendto(wire, (where, port)) + _wait_for_readable(s, expiration) + (wire, from_address) = s.recvfrom(65535) + finally: + s.close() + if from_address != (where, port): + raise UnexpectedSource + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + if not q.is_response(r): + raise BadResponse + return r + +def _net_read(sock, count, expiration): + """Read the specified number of bytes from sock. Keep trying until we + either get the desired amount, or we hit EOF. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + s = '' + while count > 0: + _wait_for_readable(sock, expiration) + n = sock.recv(count) + if n == '': + raise EOFError + count = count - len(n) + s = s + n + return s + +def _net_write(sock, data, expiration): + """Write the specified data to the socket. + A Timeout exception will be raised if the operation is not completed + by the expiration time. + """ + current = 0 + l = len(data) + while current < l: + _wait_for_writable(sock, expiration) + current += sock.send(data[current:]) + +def _connect(s, address): + try: + s.connect(address) + except socket.error: + (ty, v) = sys.exc_info()[:2] + if v[0] != errno.EINPROGRESS and v[0] != errno.EWOULDBLOCK: + raise ty, v + +def tcp(q, where, timeout=None, port=53, af=socket.AF_INET): + """Return the response obtained after sending a query via TCP. + + @param q: the query + @type q: dns.message.Message object + @param timeout: The number of seconds to wait before the query times out. + If None, the default, wait forever. + @type timeout: float + @param port: The port to which to send the message. The default is 53. + @type port: int + @param af: the address family to use. The default is socket.AF_INET. + @type af: int + @rtype: dns.message.Message object""" + + wire = q.to_wire() + s = socket.socket(af, socket.SOCK_STREAM, 0) + try: + expiration = _compute_expiration(timeout) + s.setblocking(0) + _connect(s, (where, port)) + + l = len(wire) + + # copying the wire into tcpmsg is inefficient, but lets us + # avoid writev() or doing a short write that would get pushed + # onto the net + tcpmsg = struct.pack("!H", l) + wire + _net_write(s, tcpmsg, expiration) + ldata = _net_read(s, 2, expiration) + (l,) = struct.unpack("!H", ldata) + wire = _net_read(s, l, expiration) + finally: + s.close() + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac) + if not q.is_response(r): + raise BadResponse + return r + +def xfr(where, zone, rdtype=dns.rdatatype.AXFR, rdclass=dns.rdataclass.IN, + timeout=None, port=53, keyring=None, keyname=None, relativize=True, + af=socket.AF_INET, lifetime=None): + """Return a generator for the responses to a zone transfer. + + @param zone: The name of the zone to transfer + @type zone: dns.name.Name object or string + @param rdtype: The type of zone transfer. The default is + dns.rdatatype.AXFR. + @type rdtype: int or string + @param rdclass: The class of the zone transfer. The default is + dns.rdatatype.IN. + @type rdclass: int or string + @param timeout: The number of seconds to wait for each response message. + If None, the default, wait forever. + @type timeout: float + @param port: The port to which to send the message. The default is 53. + @type port: int + @param keyring: The TSIG keyring to use + @type keyring: dict + @param keyname: The name of the TSIG key to use + @type keyname: dns.name.Name object or string + @param relativize: If True, all names in the zone will be relativized to + the zone origin. + @type relativize: bool + @param af: the address family to use. The default is socket.AF_INET. + @type af: int + @param lifetime: The total number of seconds to spend doing the transfer. + If None, the default, then there is no limit on the time the transfer may + take. + @type lifetime: float + @rtype: generator of dns.message.Message objects.""" + + if isinstance(zone, str): + zone = dns.name.from_text(zone) + q = dns.message.make_query(zone, rdtype, rdclass) + if not keyring is None: + q.use_tsig(keyring, keyname) + wire = q.to_wire() + s = socket.socket(af, socket.SOCK_STREAM, 0) + expiration = _compute_expiration(lifetime) + _connect(s, (where, port)) + l = len(wire) + tcpmsg = struct.pack("!H", l) + wire + _net_write(s, tcpmsg, expiration) + done = False + seen_soa = False + if relativize: + origin = zone + oname = dns.name.empty + else: + origin = None + oname = zone + tsig_ctx = None + first = True + while not done: + mexpiration = _compute_expiration(timeout) + if mexpiration is None or mexpiration > expiration: + mexpiration = expiration + ldata = _net_read(s, 2, mexpiration) + (l,) = struct.unpack("!H", ldata) + wire = _net_read(s, l, mexpiration) + r = dns.message.from_wire(wire, keyring=q.keyring, request_mac=q.mac, + xfr=True, origin=origin, tsig_ctx=tsig_ctx, + multi=True, first=first) + tsig_ctx = r.tsig_ctx + first = False + if not seen_soa: + if not r.answer or r.answer[0].name != oname: + raise dns.exception.FormError + rrset = r.answer[0] + if rrset.rdtype != dns.rdatatype.SOA: + raise dns.exception.FormError + seen_soa = True + if len(r.answer) > 1 and r.answer[-1].name == oname: + rrset = r.answer[-1] + if rrset.rdtype == dns.rdatatype.SOA: + if q.keyring and not r.had_tsig: + raise dns.exception.FormError, "missing TSIG" + done = True + elif r.answer and r.answer[-1].name == oname: + rrset = r.answer[-1] + if rrset.rdtype == dns.rdatatype.SOA: + if q.keyring and not r.had_tsig: + raise dns.exception.FormError, "missing TSIG" + done = True + yield r + s.close() diff --git a/dns/rcode.py b/dns/rcode.py new file mode 100644 index 0000000..62c1cad --- /dev/null +++ b/dns/rcode.py @@ -0,0 +1,121 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rcode.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +"""DNS Result Codes.""" + +import dns.exception + +NOERROR = 0 +FORMERR = 1 +SERVFAIL = 2 +NXDOMAIN = 3 +NOTIMP = 4 +REFUSED = 5 +YXDOMAIN = 6 +YXRRSET = 7 +NXRRSET = 8 +NOTAUTH = 9 +NOTZONE = 10 +BADVERS = 16 + +_by_text = { + 'NOERROR' : NOERROR, + 'FORMERR' : FORMERR, + 'SERVFAIL' : SERVFAIL, + 'NXDOMAIN' : NXDOMAIN, + 'NOTIMP' : NOTIMP, + 'REFUSED' : REFUSED, + 'YXDOMAIN' : YXDOMAIN, + 'YXRRSET' : YXRRSET, + 'NXRRSET' : NXRRSET, + 'NOTAUTH' : NOTAUTH, + 'NOTZONE' : NOTZONE, + 'BADVERS' : BADVERS +} + +# We construct the inverse mapping programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mapping not to be a true inverse. + +_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) + + +class UnknownRcode(dns.exception.DNSException): + """Raised if an rcode is unknown.""" + pass + +def from_text(text): + """Convert text into an rcode. + + @param text: the texual rcode + @type text: string + @raises UnknownRcode: the rcode is unknown + @rtype: int + """ + + if text.isdigit(): + v = int(text) + if v >= 0 and v <= 4095: + return v + v = _by_text.get(text.upper()) + if v is None: + raise UnknownRcode + return v + +def from_flags(flags, ednsflags): + """Return the rcode value encoded by flags and ednsflags. + + @param flags: the DNS flags + @type flags: int + @param ednsflags: the EDNS flags + @type ednsflags: int + @raises ValueError: rcode is < 0 or > 4095 + @rtype: int + """ + + value = (flags & 0x000f) | ((ednsflags >> 20) & 0xff0) + if value < 0 or value > 4095: + raise ValueError, 'rcode must be >= 0 and <= 4095' + return value + +def to_flags(value): + """Return a (flags, ednsflags) tuple which encodes the rcode. + + @param value: the rcode + @type value: int + @raises ValueError: rcode is < 0 or > 4095 + @rtype: (int, int) tuple + """ + + if value < 0 or value > 4095: + raise ValueError, 'rcode must be >= 0 and <= 4095' + v = value & 0xf + ev = long(value & 0xff0) << 20 + return (v, ev) + +def to_text(value): + """Convert rcode into text. + + @param value: the rcode + @type value: int + @rtype: string + """ + + text = _by_value.get(value) + if text is None: + text = str(value) + return text diff --git a/dns/rdata.py b/dns/rdata.py new file mode 100644 index 0000000..eebcdbc --- /dev/null +++ b/dns/rdata.py @@ -0,0 +1,430 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rdata.py,v 1.26 2004/03/19 00:17:27 halley Exp $ + +"""DNS rdata. + +@var _rdata_modules: A dictionary mapping a (rdclass, rdtype) tuple to +the module which implements that type. +@type _rdata_modules: dict +@var _module_prefix: The prefix to use when forming modules names. The +default is 'dns.rdtypes'. Changing this value will break the library. +@type _module_prefix: string +@var _hex_chunk: At most this many octets that will be represented in each +chunk of hexstring that _hexify() produces before whitespace occurs. +@type _hex_chunk: int""" + +import codecs +import cStringIO + +import dns.exception +import dns.rdataclass +import dns.rdatatype +import dns.tokenizer + +_hex_chunksize = 32 + +def _hexify(data, chunksize=None): + """Convert a binary string into its hex encoding, broken up into chunks + of I{chunksize} characters separated by a space. + + @param data: the binary string + @type data: string + @param chunksize: the chunk size. Default is L{dns.rdata._hex_chunksize} + @rtype: string + """ + + if chunksize is None: + chunksize = _hex_chunksize + hex = data.encode('hex_codec') + l = len(hex) + if l > chunksize: + chunks = [] + i = 0 + while i < l: + chunks.append(hex[i : i + chunksize]) + i += chunksize + hex = ' '.join(chunks) + return hex + +_base64_chunksize = 32 + +def _base64ify(data, chunksize=None): + """Convert a binary string into its base64 encoding, broken up into chunks + of I{chunksize} characters separated by a space. + + @param data: the binary string + @type data: string + @param chunksize: the chunk size. Default is + L{dns.rdata._base64_chunksize} + @rtype: string + """ + + if chunksize is None: + chunksize = _base64_chunksize + b64 = data.encode('base64_codec') + b64 = b64.replace('\n', '') + l = len(b64) + if l > chunksize: + chunks = [] + i = 0 + while i < l: + chunks.append(b64[i : i + chunksize]) + i += chunksize + b64 = ' '.join(chunks) + return b64 + +__escaped = { + '"' : True, + '\\' : True, + } + +def _escapify(qstring): + """Escape the characters in a quoted string which need it. + + @param qstring: the string + @type qstring: string + @returns: the escaped string + @rtype: string + """ + + text = '' + for c in qstring: + if c in __escaped: + text += '\\' + c + elif ord(c) >= 0x20 and ord(c) < 0x7F: + text += c + else: + text += '\\%03d' % ord(c) + return text + +def _truncate_bitmap(what): + """Determine the index of greatest byte that isn't all zeros, and + return the bitmap that contains all the bytes less than that index. + + @param what: a string of octets representing a bitmap. + @type what: string + @rtype: string + """ + + for i in xrange(len(what) - 1, -1, -1): + if what[i] != '\x00': + break + return ''.join(what[0 : i + 1]) + +class Rdata(object): + """Base class for all DNS rdata types. + """ + + __slots__ = ['rdclass', 'rdtype'] + + def __init__(self, rdclass, rdtype): + """Initialize an rdata. + @param rdclass: The rdata class + @type rdclass: int + @param rdtype: The rdata type + @type rdtype: int + """ + + self.rdclass = rdclass + self.rdtype = rdtype + + def covers(self): + """DNS SIG/RRSIG rdatas apply to a specific type; this type is + returned by the covers() function. If the rdata type is not + SIG or RRSIG, dns.rdatatype.NONE is returned. This is useful when + creating rdatasets, allowing the rdataset to contain only RRSIGs + of a particular type, e.g. RRSIG(NS). + @rtype: int + """ + + return dns.rdatatype.NONE + + def extended_rdatatype(self): + """Return a 32-bit type value, the least significant 16 bits of + which are the ordinary DNS type, and the upper 16 bits of which are + the "covered" type, if any. + @rtype: int + """ + + return self.covers() << 16 | self.rdtype + + def to_text(self, origin=None, relativize=True, **kw): + """Convert an rdata to text format. + @rtype: string + """ + raise NotImplementedError + + def to_wire(self, file, compress = None, origin = None): + """Convert an rdata to wire format. + @rtype: string + """ + + raise NotImplementedError + + def __repr__(self): + covers = self.covers() + if covers == dns.rdatatype.NONE: + ctext = '' + else: + ctext = '(' + dns.rdatatype.to_text(covers) + ')' + return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ + dns.rdatatype.to_text(self.rdtype) + ctext + ' rdata: ' + \ + str(self) + '>' + + def __str__(self): + return self.to_text() + + def _cmp(self, other): + """Compare an rdata with another rdata of the same rdtype and + rdclass. Return < 0 if self < other in the DNSSEC ordering, + 0 if self == other, and > 0 if self > other. + """ + + raise NotImplementedError + + def __eq__(self, other): + if not isinstance(other, Rdata): + return False + if self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return False + return self._cmp(other) == 0 + + def __ne__(self, other): + if not isinstance(other, Rdata): + return True + if self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return True + return self._cmp(other) != 0 + + def __lt__(self, other): + if not isinstance(other, Rdata) or \ + self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return NotImplemented + return self._cmp(other) < 0 + + def __le__(self, other): + if not isinstance(other, Rdata) or \ + self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return NotImplemented + return self._cmp(other) <= 0 + + def __ge__(self, other): + if not isinstance(other, Rdata) or \ + self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return NotImplemented + return self._cmp(other) >= 0 + + def __gt__(self, other): + if not isinstance(other, Rdata) or \ + self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype: + return NotImplemented + return self._cmp(other) > 0 + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + """Build an rdata object from text format. + + @param rdclass: The rdata class + @type rdclass: int + @param rdtype: The rdata type + @type rdtype: int + @param tok: The tokenizer + @type tok: dns.tokenizer.Tokenizer + @param origin: The origin to use for relative names + @type origin: dns.name.Name + @param relativize: should names be relativized? + @type origin: bool + @rtype: dns.rdata.Rdata instance + """ + + raise NotImplementedError + + from_text = classmethod(from_text) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + """Build an rdata object from wire format + + @param rdclass: The rdata class + @type rdclass: int + @param rdtype: The rdata type + @type rdtype: int + @param wire: The wire-format message + @type wire: string + @param current: The offet in wire of the beginning of the rdata. + @type current: int + @param rdlen: The length of the wire-format rdata + @type rdlen: int + @param origin: The origin to use for relative names + @type origin: dns.name.Name + @rtype: dns.rdata.Rdata instance + """ + + raise NotImplementedError + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + """Convert any domain names in the rdata to the specified + relativization. + """ + + pass + + +class GenericRdata(Rdata): + """Generate Rdata Class + + This class is used for rdata types for which we have no better + implementation. It implements the DNS "unknown RRs" scheme. + """ + + __slots__ = ['data'] + + def __init__(self, rdclass, rdtype, data): + super(GenericRdata, self).__init__(rdclass, rdtype) + self.data = data + + def to_text(self, origin=None, relativize=True, **kw): + return r'\# %d ' % len(self.data) + _hexify(self.data) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + if tok.get_string() != r'\#': + raise dns.exception.SyntaxError, \ + r'generic rdata does not start with \#' + length = tok.get_int() + chunks = [] + while 1: + (ttype, value) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + chunks.append(value) + hex = ''.join(chunks) + data = hex.decode('hex_codec') + if len(data) != length: + raise dns.exception.SyntaxError, \ + 'generic rdata hex data has wrong length' + return cls(rdclass, rdtype, data) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + file.write(self.data) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + return cls(rdclass, rdtype, wire[current : current + rdlen]) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + return cmp(self.data, other.data) + +_rdata_modules = {} +_module_prefix = 'dns.rdtypes' + +def get_rdata_class(rdclass, rdtype): + + def import_module(name): + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + mod = _rdata_modules.get((rdclass, rdtype)) + rdclass_text = dns.rdataclass.to_text(rdclass) + rdtype_text = dns.rdatatype.to_text(rdtype) + rdtype_text = rdtype_text.replace('-', '_') + if not mod: + mod = _rdata_modules.get((dns.rdatatype.ANY, rdtype)) + if not mod: + try: + mod = import_module('.'.join([_module_prefix, + rdclass_text, rdtype_text])) + _rdata_modules[(rdclass, rdtype)] = mod + except ImportError: + try: + mod = import_module('.'.join([_module_prefix, + 'ANY', rdtype_text])) + _rdata_modules[(dns.rdataclass.ANY, rdtype)] = mod + except ImportError: + mod = None + if mod: + cls = getattr(mod, rdtype_text) + else: + cls = GenericRdata + return cls + +def from_text(rdclass, rdtype, tok, origin = None, relativize = True): + """Build an rdata object from text format. + + This function attempts to dynamically load a class which + implements the specified rdata class and type. If there is no + class-and-type-specific implementation, the GenericRdata class + is used. + + Once a class is chosen, its from_text() class method is called + with the parameters to this function. + + @param rdclass: The rdata class + @type rdclass: int + @param rdtype: The rdata type + @type rdtype: int + @param tok: The tokenizer + @type tok: dns.tokenizer.Tokenizer + @param origin: The origin to use for relative names + @type origin: dns.name.Name + @param relativize: Should names be relativized? + @type relativize: bool + @rtype: dns.rdata.Rdata instance""" + + if isinstance(tok, str): + tok = dns.tokenizer.Tokenizer(tok) + cls = get_rdata_class(rdclass, rdtype) + return cls.from_text(rdclass, rdtype, tok, origin, relativize) + +def from_wire(rdclass, rdtype, wire, current, rdlen, origin = None): + """Build an rdata object from wire format + + This function attempts to dynamically load a class which + implements the specified rdata class and type. If there is no + class-and-type-specific implementation, the GenericRdata class + is used. + + Once a class is chosen, its from_wire() class method is called + with the parameters to this function. + + @param rdclass: The rdata class + @type rdclass: int + @param rdtype: The rdata type + @type rdtype: int + @param wire: The wire-format message + @type wire: string + @param current: The offet in wire of the beginning of the rdata. + @type current: int + @param rdlen: The length of the wire-format rdata + @type rdlen: int + @param origin: The origin to use for relative names + @type origin: dns.name.Name + @rtype: dns.rdata.Rdata instance""" + + cls = get_rdata_class(rdclass, rdtype) + return cls.from_wire(rdclass, rdtype, wire, current, rdlen, origin) diff --git a/dns/rdataclass.py b/dns/rdataclass.py new file mode 100644 index 0000000..e4e65c4 --- /dev/null +++ b/dns/rdataclass.py @@ -0,0 +1,116 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rdataclass.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +"""DNS Rdata Classes. + +@var _by_text: The rdata class textual name to value mapping +@type _by_text: dict +@var _by_value: The rdata class value to textual name mapping +@type _by_value: dict +@var _metaclasses: If an rdataclass is a metaclass, there will be a mapping +whose key is the rdatatype value and whose value is True in this dictionary. +@type _metaclasses: dict""" + +import re + +import dns.exception + +RESERVED0 = 0 +IN = 1 +CH = 3 +HS = 4 +NONE = 254 +ANY = 255 + +_by_text = { + 'RESERVED0' : RESERVED0, + 'IN' : IN, + 'CH' : CH, + 'HS' : HS, + 'NONE' : NONE, + 'ANY' : ANY + } + +# We construct the inverse mapping programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mapping not to be true inverse. + +_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) + +# Now that we've built the inverse map, we can add class aliases to +# the _by_text mapping. + +_by_text.update({ + 'INTERNET' : IN, + 'CHAOS' : CH, + 'HESIOD' : HS + }) + +_metaclasses = { + NONE : True, + ANY : True + } + +_unknown_class_pattern = re.compile('CLASS([0-9]+)$', re.I); + +class UnknownRdataclass(dns.exception.DNSException): + """Raised when a class is unknown.""" + pass + +def from_text(text): + """Convert text into a DNS rdata class value. + @param text: the text + @type text: string + @rtype: int + @raises dns.rdataclass.UnknownRdataClass: the class is unknown + @raises ValueError: the rdata class value is not >= 0 and <= 65535 + """ + + value = _by_text.get(text.upper()) + if value is None: + match = _unknown_class_pattern.match(text) + if match == None: + raise UnknownRdataclass + value = int(match.group(1)) + if value < 0 or value > 65535: + raise ValueError, "class must be between >= 0 and <= 65535" + return value + +def to_text(value): + """Convert a DNS rdata class to text. + @param value: the rdata class value + @type value: int + @rtype: string + @raises ValueError: the rdata class value is not >= 0 and <= 65535 + """ + + if value < 0 or value > 65535: + raise ValueError, "class must be between >= 0 and <= 65535" + text = _by_value.get(value) + if text is None: + text = 'CLASS' + `value` + return text + +def is_metaclass(rdclass): + """True if the class is a metaclass. + @param rdclass: the rdata class + @type rdclass: int + @rtype: bool""" + + if _metaclasses.has_key(rdclass): + return True + return False diff --git a/dns/rdataset.py b/dns/rdataset.py new file mode 100644 index 0000000..e1a7d13 --- /dev/null +++ b/dns/rdataset.py @@ -0,0 +1,332 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rdataset.py,v 1.23 2004/03/19 00:17:27 halley Exp $ + +"""DNS rdatasets (an rdataset is a set of rdatas of a given type and class)""" + +import random +import StringIO +import struct +import sys + +import dns.exception +import dns.rdatatype +import dns.rdataclass +import dns.rdata +import dns.set + +# define SimpleSet here for backwards compatibility +SimpleSet = dns.set.Set + +class DifferingCovers(dns.exception.DNSException): + """Raised if an attempt is made to add a SIG/RRSIG whose covered type + is not the same as that of the other rdatas in the rdataset.""" + pass + +class IncompatibleTypes(dns.exception.DNSException): + """Raised if an attempt is made to add rdata of an incompatible type.""" + pass + +class Rdataset(dns.set.Set): + """A DNS rdataset. + + @ivar rdclass: The class of the rdataset + @type rdclass: int + @ivar rdtype: The type of the rdataset + @type rdtype: int + @ivar covers: The covered type. Usually this value is + dns.rdatatype.NONE, but if the rdtype is dns.rdatatype.SIG or + dns.rdatatype.RRSIG, then the covers value will be the rdata + type the SIG/RRSIG covers. The library treats the SIG and RRSIG + types as if they were a family of + types, e.g. RRSIG(A), RRSIG(NS), RRSIG(SOA). This makes RRSIGs much + easier to work with than if RRSIGs covering different rdata + types were aggregated into a single RRSIG rdataset. + @type covers: int + @ivar ttl: The DNS TTL (Time To Live) value + @type ttl: int + """ + + __slots__ = ['rdclass', 'rdtype', 'covers', 'ttl'] + + def __init__(self, rdclass, rdtype, covers=dns.rdatatype.NONE): + """Create a new rdataset of the specified class and type. + + @see: the description of the class instance variables for the + meaning of I{rdclass} and I{rdtype}""" + + super(Rdataset, self).__init__() + self.rdclass = rdclass + self.rdtype = rdtype + self.covers = covers + self.ttl = 0 + + def _clone(self): + obj = super(Rdataset, self)._clone() + obj.rdclass = self.rdclass + obj.rdtype = self.rdtype + obj.covers = self.covers + obj.ttl = self.ttl + return obj + + def update_ttl(self, ttl): + """Set the TTL of the rdataset to be the lesser of the set's current + TTL or the specified TTL. If the set contains no rdatas, set the TTL + to the specified TTL. + @param ttl: The TTL + @type ttl: int""" + + if len(self) == 0: + self.ttl = ttl + elif ttl < self.ttl: + self.ttl = ttl + + def add(self, rd, ttl=None): + """Add the specified rdata to the rdataset. + + If the optional I{ttl} parameter is supplied, then + self.update_ttl(ttl) will be called prior to adding the rdata. + + @param rd: The rdata + @type rd: dns.rdata.Rdata object + @param ttl: The TTL + @type ttl: int""" + + # + # If we're adding a signature, do some special handling to + # check that the signature covers the same type as the + # other rdatas in this rdataset. If this is the first rdata + # in the set, initialize the covers field. + # + if self.rdclass != rd.rdclass or self.rdtype != rd.rdtype: + raise IncompatibleTypes + if not ttl is None: + self.update_ttl(ttl) + if self.rdtype == dns.rdatatype.RRSIG or \ + self.rdtype == dns.rdatatype.SIG: + covers = rd.covers() + if len(self) == 0 and self.covers == dns.rdatatype.NONE: + self.covers = covers + elif self.covers != covers: + raise DifferingCovers + if dns.rdatatype.is_singleton(rd.rdtype) and len(self) > 0: + self.clear() + super(Rdataset, self).add(rd) + + def union_update(self, other): + self.update_ttl(other.ttl) + super(Rdataset, self).union_update(other) + + def intersection_update(self, other): + self.update_ttl(other.ttl) + super(Rdataset, self).intersection_update(other) + + def update(self, other): + """Add all rdatas in other to self. + + @param other: The rdataset from which to update + @type other: dns.rdataset.Rdataset object""" + + self.update_ttl(other.ttl) + super(Rdataset, self).update(other) + + def __repr__(self): + if self.covers == 0: + ctext = '' + else: + ctext = '(' + dns.rdatatype.to_text(self.covers) + ')' + return '<DNS ' + dns.rdataclass.to_text(self.rdclass) + ' ' + \ + dns.rdatatype.to_text(self.rdtype) + ctext + ' rdataset>' + + def __str__(self): + return self.to_text() + + def __eq__(self, other): + """Two rdatasets are equal if they have the same class, type, and + covers, and contain the same rdata. + @rtype: bool""" + + if not isinstance(other, Rdataset): + return False + if self.rdclass != other.rdclass or \ + self.rdtype != other.rdtype or \ + self.covers != other.covers: + return False + return super(Rdataset, self).__eq__(other) + + def __ne__(self, other): + return not self.__eq__(other) + + def to_text(self, name=None, origin=None, relativize=True, + override_rdclass=None, **kw): + """Convert the rdataset into DNS master file format. + + @see: L{dns.name.Name.choose_relativity} for more information + on how I{origin} and I{relativize} determine the way names + are emitted. + + Any additional keyword arguments are passed on to the rdata + to_text() method. + + @param name: If name is not None, emit a RRs with I{name} as + the owner name. + @type name: dns.name.Name object + @param origin: The origin for relative names, or None. + @type origin: dns.name.Name object + @param relativize: True if names should names be relativized + @type relativize: bool""" + if not name is None: + name = name.choose_relativity(origin, relativize) + ntext = str(name) + pad = ' ' + else: + ntext = '' + pad = '' + s = StringIO.StringIO() + if not override_rdclass is None: + rdclass = override_rdclass + else: + rdclass = self.rdclass + if len(self) == 0: + # + # Empty rdatasets are used for the question section, and in + # some dynamic updates, so we don't need to print out the TTL + # (which is meaningless anyway). + # + print >> s, '%s%s%s %s' % (ntext, pad, + dns.rdataclass.to_text(rdclass), + dns.rdatatype.to_text(self.rdtype)) + else: + for rd in self: + print >> s, '%s%s%d %s %s %s' % \ + (ntext, pad, self.ttl, dns.rdataclass.to_text(rdclass), + dns.rdatatype.to_text(self.rdtype), + rd.to_text(origin=origin, relativize=relativize, **kw)) + # + # We strip off the final \n for the caller's convenience in printing + # + return s.getvalue()[:-1] + + def to_wire(self, name, file, compress=None, origin=None, + override_rdclass=None, want_shuffle=True): + """Convert the rdataset to wire format. + + @param name: The owner name of the RRset that will be emitted + @type name: dns.name.Name object + @param file: The file to which the wire format data will be appended + @type file: file + @param compress: The compression table to use; the default is None. + @type compress: dict + @param origin: The origin to be appended to any relative names when + they are emitted. The default is None. + @returns: the number of records emitted + @rtype: int + """ + + if not override_rdclass is None: + rdclass = override_rdclass + want_shuffle = False + else: + rdclass = self.rdclass + file.seek(0, 2) + if len(self) == 0: + name.to_wire(file, compress, origin) + stuff = struct.pack("!HHIH", self.rdtype, rdclass, 0, 0) + file.write(stuff) + return 1 + else: + if want_shuffle: + l = list(self) + random.shuffle(l) + else: + l = self + for rd in l: + name.to_wire(file, compress, origin) + stuff = struct.pack("!HHIH", self.rdtype, rdclass, + self.ttl, 0) + file.write(stuff) + start = file.tell() + rd.to_wire(file, compress, origin) + end = file.tell() + assert end - start < 65536 + file.seek(start - 2) + stuff = struct.pack("!H", end - start) + file.write(stuff) + file.seek(0, 2) + return len(self) + + def match(self, rdclass, rdtype, covers): + """Returns True if this rdataset matches the specified class, type, + and covers""" + if self.rdclass == rdclass and \ + self.rdtype == rdtype and \ + self.covers == covers: + return True + return False + +def from_text_list(rdclass, rdtype, ttl, text_rdatas): + """Create an rdataset with the specified class, type, and TTL, and with + the specified list of rdatas in text format. + + @rtype: dns.rdataset.Rdataset object + """ + + if isinstance(rdclass, str): + rdclass = dns.rdataclass.from_text(rdclass) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + r = Rdataset(rdclass, rdtype) + r.update_ttl(ttl) + for t in text_rdatas: + rd = dns.rdata.from_text(r.rdclass, r.rdtype, t) + r.add(rd) + return r + +def from_text(rdclass, rdtype, ttl, *text_rdatas): + """Create an rdataset with the specified class, type, and TTL, and with + the specified rdatas in text format. + + @rtype: dns.rdataset.Rdataset object + """ + + return from_text_list(rdclass, rdtype, ttl, text_rdatas) + +def from_rdata_list(ttl, rdatas): + """Create an rdataset with the specified TTL, and with + the specified list of rdata objects. + + @rtype: dns.rdataset.Rdataset object + """ + + if len(rdatas) == 0: + raise ValueError, "rdata list must not be empty" + r = None + for rd in rdatas: + if r is None: + r = Rdataset(rd.rdclass, rd.rdtype) + r.update_ttl(ttl) + first_time = False + r.add(rd) + return r + +def from_rdata(ttl, *rdatas): + """Create an rdataset with the specified TTL, and with + the specified rdata objects. + + @rtype: dns.rdataset.Rdataset object + """ + + return from_rdata_list(ttl, rdatas) diff --git a/dns/rdatatype.py b/dns/rdatatype.py new file mode 100644 index 0000000..20f0303 --- /dev/null +++ b/dns/rdatatype.py @@ -0,0 +1,215 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rdatatype.py,v 1.15 2004/03/19 00:17:27 halley Exp $ + +"""DNS Rdata Types. + +@var _by_text: The rdata type textual name to value mapping +@type _by_text: dict +@var _by_value: The rdata type value to textual name mapping +@type _by_value: dict +@var _metatypes: If an rdatatype is a metatype, there will be a mapping +whose key is the rdatatype value and whose value is True in this dictionary. +@type _metatypes: dict +@var _singletons: If an rdatatype is a singleton, there will be a mapping +whose key is the rdatatype value and whose value is True in this dictionary. +@type _singletons: dict""" + +import re + +import dns.exception + +NONE = 0 +A = 1 +NS = 2 +MD = 3 +MF = 4 +CNAME = 5 +SOA = 6 +MB = 7 +MG = 8 +MR = 9 +NULL = 10 +WKS = 11 +PTR = 12 +HINFO = 13 +MINFO = 14 +MX = 15 +TXT = 16 +RP = 17 +AFSDB = 18 +X25 = 19 +ISDN = 20 +RT = 21 +NSAP = 22 +NSAP_PTR = 23 +SIG = 24 +KEY = 25 +PX = 26 +GPOS = 27 +AAAA = 28 +LOC = 29 +NXT = 30 +SRV = 33 +NAPTR = 35 +KX = 36 +CERT = 37 +A6 = 38 +DNAME = 39 +OPT = 41 +APL = 42 +DS = 43 +RRSIG = 46 +NSEC = 47 +DNSKEY = 48 +UNSPEC = 103 +TKEY = 249 +TSIG = 250 +IXFR = 251 +AXFR = 252 +MAILB = 253 +MAILA = 254 +ANY = 255 + +_by_text = { + 'NONE' : NONE, + 'A' : A, + 'NS' : NS, + 'MD' : MD, + 'MF' : MF, + 'CNAME' : CNAME, + 'SOA' : SOA, + 'MB' : MB, + 'MG' : MG, + 'MR' : MR, + 'NULL' : NULL, + 'WKS' : WKS, + 'PTR' : PTR, + 'HINFO' : HINFO, + 'MINFO' : MINFO, + 'MX' : MX, + 'TXT' : TXT, + 'RP' : RP, + 'AFSDB' : AFSDB, + 'X25' : X25, + 'ISDN' : ISDN, + 'RT' : RT, + 'NSAP' : NSAP, + 'NSAP-PTR' : NSAP_PTR, + 'SIG' : SIG, + 'KEY' : KEY, + 'PX' : PX, + 'GPOS' : GPOS, + 'AAAA' : AAAA, + 'LOC' : LOC, + 'NXT' : NXT, + 'SRV' : SRV, + 'NAPTR' : NAPTR, + 'KX' : KX, + 'CERT' : CERT, + 'A6' : A6, + 'DNAME' : DNAME, + 'OPT' : OPT, + 'APL' : APL, + 'DS' : DS, + 'RRSIG' : RRSIG, + 'NSEC' : NSEC, + 'DNSKEY' : DNSKEY, + 'UNSPEC' : UNSPEC, + 'TKEY' : TKEY, + 'TSIG' : TSIG, + 'IXFR' : IXFR, + 'AXFR' : AXFR, + 'MAILB' : MAILB, + 'MAILA' : MAILA, + 'ANY' : ANY + } + +# We construct the inverse mapping programmatically to ensure that we +# cannot make any mistakes (e.g. omissions, cut-and-paste errors) that +# would cause the mapping not to be true inverse. + +_by_value = dict([(y, x) for x, y in _by_text.iteritems()]) + + +_metatypes = { + OPT : True + } + +_singletons = { + SOA : True, + NXT : True, + DNAME : True, + # CNAME is technically a singleton, but we allow multiple CNAMEs. + } + +_unknown_type_pattern = re.compile('TYPE([0-9]+)$', re.I); + +class UnknownRdatatype(dns.exception.DNSException): + """Raised if a type is unknown.""" + pass + +def from_text(text): + """Convert text into a DNS rdata type value. + @param text: the text + @type text: string + @raises dns.rdatatype.UnknownRdatatype: the type is unknown + @raises ValueError: the rdata type value is not >= 0 and <= 65535 + @rtype: int""" + + value = _by_text.get(text.upper()) + if value is None: + match = _unknown_type_pattern.match(text) + if match == None: + raise UnknownRdatatype + value = int(match.group(1)) + if value < 0 or value > 65535: + raise ValueError, "type must be between >= 0 and <= 65535" + return value + +def to_text(value): + """Convert a DNS rdata type to text. + @param value: the rdata type value + @type value: int + @raises ValueError: the rdata type value is not >= 0 and <= 65535 + @rtype: string""" + + if value < 0 or value > 65535: + raise ValueError, "type must be between >= 0 and <= 65535" + text = _by_value.get(value) + if text is None: + text = 'TYPE' + `value` + return text + +def is_metatype(rdtype): + """True if the type is a metatype. + @param rdtype: the type + @type rdtype: int + @rtype: bool""" + + if rdtype >= TKEY and rdtype <= ANY or _metatypes.has_key(rdtype): + return True + return False + +def is_singleton(rdtype): + """True if the type is a singleton. + @param rdtype: the type + @type rdtype: int + @rtype: bool""" + + if _singletons.has_key(rdtype): + return True + return False diff --git a/dns/rdtypes/.arch-ids/=id b/dns/rdtypes/.arch-ids/=id new file mode 100644 index 0000000..421f140 --- /dev/null +++ b/dns/rdtypes/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:26 2004 4754.29 diff --git a/dns/rdtypes/.arch-ids/__init__.py.id b/dns/rdtypes/.arch-ids/__init__.py.id new file mode 100644 index 0000000..2ded88f --- /dev/null +++ b/dns/rdtypes/.arch-ids/__init__.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:44 2004 4756.2 diff --git a/dns/rdtypes/.arch-ids/keybase.py.id b/dns/rdtypes/.arch-ids/keybase.py.id new file mode 100644 index 0000000..4b1704a --- /dev/null +++ b/dns/rdtypes/.arch-ids/keybase.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:44 2004 4756.3 diff --git a/dns/rdtypes/.arch-ids/mxbase.py.id b/dns/rdtypes/.arch-ids/mxbase.py.id new file mode 100644 index 0000000..a84ee43 --- /dev/null +++ b/dns/rdtypes/.arch-ids/mxbase.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:44 2004 4756.4 diff --git a/dns/rdtypes/.arch-ids/nsbase.py.id b/dns/rdtypes/.arch-ids/nsbase.py.id new file mode 100644 index 0000000..79a4dc3 --- /dev/null +++ b/dns/rdtypes/.arch-ids/nsbase.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:44 2004 4756.5 diff --git a/dns/rdtypes/.arch-ids/sigbase.py.id b/dns/rdtypes/.arch-ids/sigbase.py.id new file mode 100644 index 0000000..8984cb7 --- /dev/null +++ b/dns/rdtypes/.arch-ids/sigbase.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:44 2004 4756.6 diff --git a/dns/rdtypes/ANY/.arch-ids/=id b/dns/rdtypes/ANY/.arch-ids/=id new file mode 100644 index 0000000..3f3873c --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:43 2004 4756.0 diff --git a/dns/rdtypes/ANY/.arch-ids/AFSDB.py.id b/dns/rdtypes/ANY/.arch-ids/AFSDB.py.id new file mode 100644 index 0000000..a8c4388 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/AFSDB.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.0 diff --git a/dns/rdtypes/ANY/.arch-ids/CERT.py.id b/dns/rdtypes/ANY/.arch-ids/CERT.py.id new file mode 100644 index 0000000..4eedf7b --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/CERT.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.1 diff --git a/dns/rdtypes/ANY/.arch-ids/CNAME.py.id b/dns/rdtypes/ANY/.arch-ids/CNAME.py.id new file mode 100644 index 0000000..c05c9bc --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/CNAME.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.2 diff --git a/dns/rdtypes/ANY/.arch-ids/DNAME.py.id b/dns/rdtypes/ANY/.arch-ids/DNAME.py.id new file mode 100644 index 0000000..f259e01 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/DNAME.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.3 diff --git a/dns/rdtypes/ANY/.arch-ids/DNSKEY.py.id b/dns/rdtypes/ANY/.arch-ids/DNSKEY.py.id new file mode 100644 index 0000000..6f94ea2 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/DNSKEY.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.4 diff --git a/dns/rdtypes/ANY/.arch-ids/DS.py.id b/dns/rdtypes/ANY/.arch-ids/DS.py.id new file mode 100644 index 0000000..bce4d9a --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/DS.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.5 diff --git a/dns/rdtypes/ANY/.arch-ids/GPOS.py.id b/dns/rdtypes/ANY/.arch-ids/GPOS.py.id new file mode 100644 index 0000000..89a1148 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/GPOS.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.6 diff --git a/dns/rdtypes/ANY/.arch-ids/HINFO.py.id b/dns/rdtypes/ANY/.arch-ids/HINFO.py.id new file mode 100644 index 0000000..c437649 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/HINFO.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.7 diff --git a/dns/rdtypes/ANY/.arch-ids/ISDN.py.id b/dns/rdtypes/ANY/.arch-ids/ISDN.py.id new file mode 100644 index 0000000..1f3e5f3 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/ISDN.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.8 diff --git a/dns/rdtypes/ANY/.arch-ids/KEY.py.id b/dns/rdtypes/ANY/.arch-ids/KEY.py.id new file mode 100644 index 0000000..2e7d46e --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/KEY.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.9 diff --git a/dns/rdtypes/ANY/.arch-ids/LOC.py.id b/dns/rdtypes/ANY/.arch-ids/LOC.py.id new file mode 100644 index 0000000..13be404 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/LOC.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.10 diff --git a/dns/rdtypes/ANY/.arch-ids/MX.py.id b/dns/rdtypes/ANY/.arch-ids/MX.py.id new file mode 100644 index 0000000..1e62e2f --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/MX.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.11 diff --git a/dns/rdtypes/ANY/.arch-ids/NS.py.id b/dns/rdtypes/ANY/.arch-ids/NS.py.id new file mode 100644 index 0000000..bfc4d6a --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/NS.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.12 diff --git a/dns/rdtypes/ANY/.arch-ids/NSEC.py.id b/dns/rdtypes/ANY/.arch-ids/NSEC.py.id new file mode 100644 index 0000000..28664eb --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/NSEC.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.13 diff --git a/dns/rdtypes/ANY/.arch-ids/NXT.py.id b/dns/rdtypes/ANY/.arch-ids/NXT.py.id new file mode 100644 index 0000000..7cb4bb6 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/NXT.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.14 diff --git a/dns/rdtypes/ANY/.arch-ids/PTR.py.id b/dns/rdtypes/ANY/.arch-ids/PTR.py.id new file mode 100644 index 0000000..eace75c --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/PTR.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.15 diff --git a/dns/rdtypes/ANY/.arch-ids/RP.py.id b/dns/rdtypes/ANY/.arch-ids/RP.py.id new file mode 100644 index 0000000..a0bc21d --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/RP.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.16 diff --git a/dns/rdtypes/ANY/.arch-ids/RRSIG.py.id b/dns/rdtypes/ANY/.arch-ids/RRSIG.py.id new file mode 100644 index 0000000..c60e988 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/RRSIG.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.17 diff --git a/dns/rdtypes/ANY/.arch-ids/RT.py.id b/dns/rdtypes/ANY/.arch-ids/RT.py.id new file mode 100644 index 0000000..04c2dd0 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/RT.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.18 diff --git a/dns/rdtypes/ANY/.arch-ids/SIG.py.id b/dns/rdtypes/ANY/.arch-ids/SIG.py.id new file mode 100644 index 0000000..b064ab0 --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/SIG.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.19 diff --git a/dns/rdtypes/ANY/.arch-ids/SOA.py.id b/dns/rdtypes/ANY/.arch-ids/SOA.py.id new file mode 100644 index 0000000..b27f4be --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/SOA.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.20 diff --git a/dns/rdtypes/ANY/.arch-ids/TXT.py.id b/dns/rdtypes/ANY/.arch-ids/TXT.py.id new file mode 100644 index 0000000..e7020be --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/TXT.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.21 diff --git a/dns/rdtypes/ANY/.arch-ids/X25.py.id b/dns/rdtypes/ANY/.arch-ids/X25.py.id new file mode 100644 index 0000000..5eab8ca --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/X25.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.22 diff --git a/dns/rdtypes/ANY/.arch-ids/__init__.py.id b/dns/rdtypes/ANY/.arch-ids/__init__.py.id new file mode 100644 index 0000000..6088c1a --- /dev/null +++ b/dns/rdtypes/ANY/.arch-ids/__init__.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:56 2004 4758.23 diff --git a/dns/rdtypes/ANY/AFSDB.py b/dns/rdtypes/ANY/AFSDB.py new file mode 100644 index 0000000..aa873b5 --- /dev/null +++ b/dns/rdtypes/ANY/AFSDB.py @@ -0,0 +1,53 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: AFSDB.py,v 1.6 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.mxbase + +class AFSDB(dns.rdtypes.mxbase.UncompressedMX): + """AFSDB record + + @ivar subtype: the subtype value + @type subtype: int + @ivar hostname: the hostname name + @type hostname: dns.name.Name object""" + + # Use the property mechanism to make "subtype" an alias for the + # "preference" attribute, and "hostname" an alias for the "exchange" + # attribute. + # + # This lets us inherit the UncompressedMX implementation but lets + # the caller use appropriate attribute names for the rdata type. + # + # We probably lose some performance vs. a cut-and-paste + # implementation, but this way we don't copy code, and that's + # good. + + def get_subtype(self): + return self.preference + + def set_subtype(self, subtype): + self.preference = subtype + + subtype = property(get_subtype, set_subtype) + + def get_hostname(self): + return self.exchange + + def set_hostname(self, hostname): + self.exchange = hostname + + hostname = property(get_hostname, set_hostname) diff --git a/dns/rdtypes/ANY/CERT.py b/dns/rdtypes/ANY/CERT.py new file mode 100644 index 0000000..9ef744d --- /dev/null +++ b/dns/rdtypes/ANY/CERT.py @@ -0,0 +1,133 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: CERT.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import struct + +import dns.exception +import dns.dnssec +import dns.rdata +import dns.tokenizer + +_ctype_by_value = { + 1 : 'PKIX', + 2 : 'SPKI', + 3 : 'PGP', + 253 : 'URI', + 254 : 'OID', + } + +_ctype_by_name = { + 'PKIX' : 1, + 'SPKI' : 2, + 'PGP' : 3, + 'URI' : 253, + 'OID' : 254, + } + +def _ctype_from_text(what): + v = _ctype_by_name.get(what) + if not v is None: + return v + return int(what) + +def _ctype_to_text(what): + v = _ctype_by_value.get(what) + if not v is None: + return v + return str(what) + +class CERT(dns.rdata.Rdata): + """CERT record + + @ivar certificate_type: certificate type + @type certificate_type: int + @ivar key_tag: key tag + @type key_tag: int + @ivar algorithm: algorithm + @type algorithm: int + @ivar certificate: the certificate or CRL + @type certificate: string + @see: RFC 2538""" + + __slots__ = ['certificate_type', 'key_tag', 'algorithm', 'certificate'] + + def __init__(self, rdclass, rdtype, certificate_type, key_tag, algorithm, + certificate): + super(CERT, self).__init__(rdclass, rdtype) + self.certificate_type = certificate_type + self.key_tag = key_tag + self.algorithm = algorithm + self.certificate = certificate + + def to_text(self, origin=None, relativize=True, **kw): + certificate_type = _ctype_to_text(self.certificate_type) + return "%s %d %s %s" % (certificate_type, self.key_tag, + dns.dnssec.algorithm_to_text(self.algorithm), + dns.rdata._base64ify(self.certificate)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + certificate_type = _ctype_from_text(tok.get_string()) + key_tag = tok.get_uint16() + algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) + if algorithm < 0 or algorithm > 255: + raise dns.exception.SyntaxError, "bad algorithm type" + chunks = [] + while 1: + t = tok.get() + if t[0] == dns.tokenizer.EOL or t[0] == dns.tokenizer.EOF: + break + if t[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + chunks.append(t[1]) + b64 = ''.join(chunks) + certificate = b64.decode('base64_codec') + return cls(rdclass, rdtype, certificate_type, key_tag, + algorithm, certificate) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + prefix = struct.pack("!HHB", self.certificate_type, self.key_tag, + self.algorithm) + file.write(prefix) + file.write(self.certificate) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + prefix = wire[current : current + 5] + current += 5 + rdlen -= 5 + if rdlen < 0: + raise dns.exception.FormError + (certificate_type, key_tag, algorithm) = struct.unpack("!HHB", prefix) + certificate = wire[current : current + rdlen] + return cls(rdclass, rdtype, certificate_type, key_tag, algorithm, + certificate) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + f = cStringIO.StringIO() + self.to_wire(f) + wire1 = f.getvalue() + f.seek(0) + f.truncate() + other.to_wire(f) + wire2 = f.getvalue() + f.close() + + return cmp(wire1, wire2) diff --git a/dns/rdtypes/ANY/CNAME.py b/dns/rdtypes/ANY/CNAME.py new file mode 100644 index 0000000..922e4ef --- /dev/null +++ b/dns/rdtypes/ANY/CNAME.py @@ -0,0 +1,26 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: CNAME.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.nsbase + +class CNAME(dns.rdtypes.nsbase.NSBase): + """CNAME record + + Note: although CNAME is officially a singleton type, dnspython allows + non-singleton CNAME rdatasets because such sets have been commonly + used by BIND and other nameservers for load balancing.""" + pass diff --git a/dns/rdtypes/ANY/DNAME.py b/dns/rdtypes/ANY/DNAME.py new file mode 100644 index 0000000..6521186 --- /dev/null +++ b/dns/rdtypes/ANY/DNAME.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: DNAME.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.nsbase + +class DNAME(dns.rdtypes.nsbase.UncompressedNS): + """DNAME record""" + pass diff --git a/dns/rdtypes/ANY/DNSKEY.py b/dns/rdtypes/ANY/DNSKEY.py new file mode 100644 index 0000000..fe0789c --- /dev/null +++ b/dns/rdtypes/ANY/DNSKEY.py @@ -0,0 +1,22 @@ +# Copyright (C) 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: DNSKEY.py,v 1.1 2004/03/19 00:06:37 halley Exp $ + +import dns.rdtypes.keybase + +class DNSKEY(dns.rdtypes.keybase.KEYBase): + """DNSKEY record""" + pass diff --git a/dns/rdtypes/ANY/DS.py b/dns/rdtypes/ANY/DS.py new file mode 100644 index 0000000..8fabbb5 --- /dev/null +++ b/dns/rdtypes/ANY/DS.py @@ -0,0 +1,87 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: DS.py,v 1.6 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.rdata +import dns.rdatatype + +class DS(dns.rdata.Rdata): + """DS record + + @ivar key_tag: the key tag + @type key_tag: int + @ivar algorithm: the algorithm + @type algorithm: int + @ivar digest_type: the digest type + @type digest_type: int + @ivar digest: the digest + @type digest: int + @see: draft-ietf-dnsext-delegation-signer-14.txt""" + + __slots__ = ['key_tag', 'algorithm', 'digest_type', 'digest'] + + def __init__(self, rdclass, rdtype, key_tag, algorithm, digest_type, + digest): + super(DS, self).__init__(rdclass, rdtype) + self.key_tag = key_tag + self.algorithm = algorithm + self.digest_type = digest_type + self.digest = digest + + def to_text(self, origin=None, relativize=True, **kw): + return '%d %d %d %s' % (self.key_tag, self.algorithm, + self.digest_type, + dns.rdata._hexify(self.digest, + chunksize=128)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + key_tag = tok.get_uint16() + algorithm = tok.get_uint8() + digest_type = tok.get_uint8() + digest = tok.get_string() + digest = digest.decode('hex_codec') + tok.get_eol() + return cls(rdclass, rdtype, key_tag, algorithm, digest_type, + digest) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + header = struct.pack("!HBB", self.key_tag, self.algorithm, + self.digest_type) + file.write(header) + file.write(self.digest) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + header = struct.unpack("!HBB", wire[current : current + 4]) + current += 4 + rdlen -= 4 + digest = wire[current : current + rdlen] + return cls(rdclass, rdtype, header[0], header[1], header[2], digest) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + hs = struct.pack("!HBB", self.key_tag, self.algorithm, + self.digest_type) + ho = struct.pack("!HBB", other.key_tag, other.algorithm, + other.digest_type) + v = cmp(hs, ho) + if v == 0: + v = cmp(self.digest, other.digest) + return v diff --git a/dns/rdtypes/ANY/GPOS.py b/dns/rdtypes/ANY/GPOS.py new file mode 100644 index 0000000..74bb313 --- /dev/null +++ b/dns/rdtypes/ANY/GPOS.py @@ -0,0 +1,158 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: GPOS.py,v 1.9 2004/03/19 00:17:27 halley Exp $ + +import dns.exception +import dns.rdata +import dns.tokenizer + +def _validate_float_string(what): + if what[0] == '-' or what[0] == '+': + what = what[1:] + if what.isdigit(): + return + (left, right) = what.split('.') + if left == '' and right == '': + raise dns.exception.FormError + if not left == '' and not left.isdigit(): + raise dns.exception.FormError + if not right == '' and not right.isdigit(): + raise dns.exception.FormError + +class GPOS(dns.rdata.Rdata): + """GPOS record + + @ivar latitude: latitude + @type latitude: string + @ivar longitude: longitude + @type longitude: string + @ivar altitude: altitude + @type altitude: string + @see: RFC 1712""" + + __slots__ = ['latitude', 'longitude', 'altitude'] + + def __init__(self, rdclass, rdtype, latitude, longitude, altitude): + super(GPOS, self).__init__(rdclass, rdtype) + if isinstance(latitude, float) or \ + isinstance(latitude, int) or \ + isinstance(latitude, long): + latitude = str(latitude) + if isinstance(longitude, float) or \ + isinstance(longitude, int) or \ + isinstance(longitude, long): + longitude = str(longitude) + if isinstance(altitude, float) or \ + isinstance(altitude, int) or \ + isinstance(altitude, long): + altitude = str(altitude) + _validate_float_string(latitude) + _validate_float_string(longitude) + _validate_float_string(altitude) + self.latitude = latitude + self.longitude = longitude + self.altitude = altitude + + def to_text(self, origin=None, relativize=True, **kw): + return '%s %s %s' % (self.latitude, self.longitude, self.altitude) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + latitude = tok.get_string() + longitude = tok.get_string() + altitude = tok.get_string() + tok.get_eol() + return cls(rdclass, rdtype, latitude, longitude, altitude) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + l = len(self.latitude) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.latitude) + l = len(self.longitude) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.longitude) + l = len(self.altitude) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.altitude) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen: + raise dns.exception.FormError + latitude = wire[current : current + l] + current += l + rdlen -= l + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen: + raise dns.exception.FormError + longitude = wire[current : current + l] + current += l + rdlen -= l + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l != rdlen: + raise dns.exception.FormError + altitude = wire[current : current + l] + return cls(rdclass, rdtype, latitude, longitude, altitude) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + v = cmp(self.latitude, other.latitude) + if v == 0: + v = cmp(self.longitude, other.longitude) + if v == 0: + v = cmp(self.altitude, other.altitude) + return v + + def _get_float_latitude(self): + return float(self.latitude) + + def _set_float_latitude(self, value): + self.latitude = str(value) + + float_latitude = property(_get_float_latitude, _set_float_latitude, + doc="latitude as a floating point value") + + def _get_float_longitude(self): + return float(self.longitude) + + def _set_float_longitude(self, value): + self.longitude = str(value) + + float_longitude = property(_get_float_longitude, _set_float_longitude, + doc="longitude as a floating point value") + + def _get_float_altitude(self): + return float(self.altitude) + + def _set_float_altitude(self, value): + self.altitude = str(value) + + float_altitude = property(_get_float_altitude, _set_float_altitude, + doc="altitude as a floating point value") diff --git a/dns/rdtypes/ANY/HINFO.py b/dns/rdtypes/ANY/HINFO.py new file mode 100644 index 0000000..5a90509 --- /dev/null +++ b/dns/rdtypes/ANY/HINFO.py @@ -0,0 +1,85 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: HINFO.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +import dns.exception +import dns.rdata +import dns.tokenizer + +class HINFO(dns.rdata.Rdata): + """HINFO record + + @ivar cpu: the CPU type + @type cpu: string + @ivar os: the OS type + @type os: string + @see: RFC 1035""" + + __slots__ = ['cpu', 'os'] + + def __init__(self, rdclass, rdtype, cpu, os): + super(HINFO, self).__init__(rdclass, rdtype) + self.cpu = cpu + self.os = os + + def to_text(self, origin=None, relativize=True, **kw): + return '"%s" "%s"' % (dns.rdata._escapify(self.cpu), + dns.rdata._escapify(self.os)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + cpu = tok.get_string() + os = tok.get_string() + tok.get_eol() + return cls(rdclass, rdtype, cpu, os) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + l = len(self.cpu) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.cpu) + l = len(self.os) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.os) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen: + raise dns.exception.FormError + cpu = wire[current : current + l] + current += l + rdlen -= l + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l != rdlen: + raise dns.exception.FormError + os = wire[current : current + l] + return cls(rdclass, rdtype, cpu, os) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + v = cmp(self.cpu, other.cpu) + if v == 0: + v = cmp(self.os, other.os) + return v diff --git a/dns/rdtypes/ANY/ISDN.py b/dns/rdtypes/ANY/ISDN.py new file mode 100644 index 0000000..c8903e0 --- /dev/null +++ b/dns/rdtypes/ANY/ISDN.py @@ -0,0 +1,98 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: ISDN.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +import dns.exception +import dns.rdata +import dns.tokenizer + +class ISDN(dns.rdata.Rdata): + """ISDN record + + @ivar address: the ISDN address + @type address: string + @ivar subaddress: the ISDN subaddress (or '' if not present) + @type subaddress: string + @see: RFC 1183""" + + __slots__ = ['address', 'subaddress'] + + def __init__(self, rdclass, rdtype, address, subaddress): + super(ISDN, self).__init__(rdclass, rdtype) + self.address = address + self.subaddress = subaddress + + def to_text(self, origin=None, relativize=True, **kw): + if self.subaddress: + return '"%s" "%s"' % (dns.rdata._escapify(self.address), + dns.rdata._escapify(self.subaddress)) + else: + return '"%s"' % dns.rdata._escapify(self.address) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + address = tok.get_string() + t = tok.get() + if t[0] != dns.tokenizer.EOL and t[0] != dns.tokenizer.EOF: + tok.unget(t) + subaddress = tok.get_string() + else: + tok.unget(t) + subaddress = '' + tok.get_eol() + return cls(rdclass, rdtype, address, subaddress) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + l = len(self.address) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.address) + l = len(self.subaddress) + if l > 0: + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.subaddress) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen: + raise dns.exception.FormError + address = wire[current : current + l] + current += l + rdlen -= l + if rdlen > 0: + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l != rdlen: + raise dns.exception.FormError + subaddress = wire[current : current + l] + else: + subaddress = '' + return cls(rdclass, rdtype, address, subaddress) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + v = cmp(self.address, other.address) + if v == 0: + v = cmp(self.subaddress, other.subaddress) + return v diff --git a/dns/rdtypes/ANY/KEY.py b/dns/rdtypes/ANY/KEY.py new file mode 100644 index 0000000..4cf8e94 --- /dev/null +++ b/dns/rdtypes/ANY/KEY.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: KEY.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.keybase + +class KEY(dns.rdtypes.keybase.KEYBase): + """KEY record""" + pass diff --git a/dns/rdtypes/ANY/LOC.py b/dns/rdtypes/ANY/LOC.py new file mode 100644 index 0000000..f30bb95 --- /dev/null +++ b/dns/rdtypes/ANY/LOC.py @@ -0,0 +1,312 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: LOC.py,v 1.13 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import math +import struct + +import dns.exception +import dns.rdata + +_pows = (1L, 10L, 100L, 1000L, 10000L, 100000L, 1000000L, 10000000L, + 100000000L, 1000000000L, 10000000000L) + +def _exponent_of(what, desc): + exp = None + for i in xrange(len(_pows)): + if what // _pows[i] == 0L: + exp = i - 1 + break + if exp is None or exp < 0: + raise dns.exception.SyntaxError, "%s value out of bounds" % desc + return exp + +def _float_to_tuple(what): + if what < 0: + sign = -1 + what *= -1 + else: + sign = 1 + what = long(round(what * 3600000)) + degrees = int(what // 3600000) + what -= degrees * 3600000 + minutes = int(what // 60000) + what -= minutes * 60000 + seconds = int(what // 1000) + what -= int(seconds * 1000) + what = int(what) + return (degrees * sign, minutes, seconds, what) + +def _tuple_to_float(what): + if what[0] < 0: + sign = -1 + value = float(what[0]) * -1 + else: + sign = 1 + value = float(what[0]) + value += float(what[1]) / 60.0 + value += float(what[2]) / 3600.0 + value += float(what[3]) / 3600000.0 + return value + +def _encode_size(what, desc): + what = long(what); + exponent = _exponent_of(what, desc) & 0xF + base = what // pow(10, exponent) & 0xF + return base * 16 + exponent + +def _decode_size(what, desc): + exponent = what & 0x0F + if exponent > 9: + raise dns.exception.SyntaxError, "bad %s exponent" % desc + base = (what & 0xF0) >> 4 + if base > 9: + raise dns.exception.SyntaxError, "bad %s base" % desc + return long(base) * pow(10, exponent) + +class LOC(dns.rdata.Rdata): + """LOC record + + @ivar latitude: latitude + @type latitude: (int, int, int, int) tuple specifying the degrees, minutes, + seconds, and milliseconds of the coordinate. + @ivar longitude: longitude + @type longitude: (int, int, int, int) tuple specifying the degrees, + minutes, seconds, and milliseconds of the coordinate. + @ivar altitude: altitude + @type altitude: float + @ivar size: size of the sphere + @type size: float + @ivar horizontal_precision: horizontal precision + @type horizontal_precision: float + @ivar vertical_precision: vertical precision + @type vertical_precision: float + @see: RFC 1876""" + + __slots__ = ['latitude', 'longitude', 'altitude', 'size', + 'horizontal_precision', 'vertical_precision'] + + def __init__(self, rdclass, rdtype, latitude, longitude, altitude, + size=1.0, hprec=10000.0, vprec=10.0): + """Initialize a LOC record instance. + + The parameters I{latitude} and I{longitude} may be either a 4-tuple + of integers specifying (degrees, minutes, seconds, milliseconds), + or they may be floating point values specifying the number of + degrees. The other parameters are floats.""" + + super(LOC, self).__init__(rdclass, rdtype) + if isinstance(latitude, int) or isinstance(latitude, long): + latitude = float(latitude) + if isinstance(latitude, float): + latitude = _float_to_tuple(latitude) + self.latitude = latitude + if isinstance(longitude, int) or isinstance(longitude, long): + longitude = float(longitude) + if isinstance(longitude, float): + longitude = _float_to_tuple(longitude) + self.longitude = longitude + self.altitude = float(altitude) + self.size = float(size) + self.horizontal_precision = float(hprec) + self.vertical_precision = float(vprec) + + def to_text(self, origin=None, relativize=True, **kw): + if self.latitude[0] > 0: + lat_hemisphere = 'N' + lat_degrees = self.latitude[0] + else: + lat_hemisphere = 'S' + lat_degrees = -1 * self.latitude[0] + if self.longitude[0] > 0: + long_hemisphere = 'E' + long_degrees = self.longitude[0] + else: + long_hemisphere = 'W' + long_degrees = -1 * self.longitude[0] + text = "%d %d %d.%03d %s %d %d %d.%03d %s %0.2fm" % ( + lat_degrees, self.latitude[1], self.latitude[2], self.latitude[3], + lat_hemisphere, long_degrees, self.longitude[1], self.longitude[2], + self.longitude[3], long_hemisphere, self.altitude / 100.0 + ) + + if self.size != 1.0 or self.horizontal_precision != 10000.0 or \ + self.vertical_precision != 10.0: + text += " %0.2fm %0.2fm %0.2fm" % ( + self.size / 100.0, self.horizontal_precision / 100.0, + self.vertical_precision / 100.0 + ) + return text + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + latitude = [0, 0, 0, 0] + longitude = [0, 0, 0, 0] + size = 1.0 + hprec = 10000.0 + vprec = 10.0 + + latitude[0] = tok.get_int() + t = tok.get_string() + if t.isdigit(): + latitude[1] = int(t) + t = tok.get_string() + if '.' in t: + (seconds, milliseconds) = t.split('.') + latitude[2] = int(seconds) + latitude[3] = int(milliseconds) + t = tok.get_string() + elif t.isdigit(): + latitude[2] = int(t) + t = tok.get_string() + if t == 'S': + latitude[0] *= -1 + elif t != 'N': + raise dns.exception.SyntaxError, 'bad latitude hemisphere value' + + longitude[0] = tok.get_int() + t = tok.get_string() + if t.isdigit(): + longitude[1] = int(t) + t = tok.get_string() + if '.' in t: + (seconds, milliseconds) = t.split('.') + longitude[2] = int(seconds) + longitude[3] = int(milliseconds) + t = tok.get_string() + elif t.isdigit(): + longitude[2] = int(t) + t = tok.get_string() + if t == 'W': + longitude[0] *= -1 + elif t != 'E': + raise dns.exception.SyntaxError, 'bad longitude hemisphere value' + + t = tok.get_string() + if t[-1] == 'm': + t = t[0 : -1] + altitude = float(t) * 100.0 # m -> cm + + (ttype, value) = tok.get() + if ttype != dns.tokenizer.EOL and ttype != dns.tokenizer.EOF: + if value[-1] == 'm': + value = value[0 : -1] + size = float(value) * 100.0 # m -> cm + (ttype, value) = tok.get() + if ttype != dns.tokenizer.EOL and ttype != dns.tokenizer.EOF: + if value[-1] == 'm': + value = value[0 : -1] + hprec = float(value) * 100.0 # m -> cm + (ttype, value) = tok.get() + if ttype != dns.tokenizer.EOL and ttype != dns.tokenizer.EOF: + if value[-1] == 'm': + value = value[0 : -1] + vprec = float(value) * 100.0 # m -> cm + (ttype, value) = tok.get() + if ttype != dns.tokenizer.EOL and \ + ttype != dns.tokenizer.EOF: + raise dns.exception.SyntaxError, \ + "expected EOL or EOF" + + return cls(rdclass, rdtype, latitude, longitude, altitude, + size, hprec, vprec) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + if self.latitude[0] < 0: + sign = -1 + degrees = long(-1 * self.latitude[0]) + else: + sign = 1 + degrees = long(self.latitude[0]) + milliseconds = (degrees * 3600000 + + self.latitude[1] * 60000 + + self.latitude[2] * 1000 + + self.latitude[3]) * sign + latitude = 0x80000000L + milliseconds + if self.longitude[0] < 0: + sign = -1 + degrees = long(-1 * self.longitude[0]) + else: + sign = 1 + degrees = long(self.longitude[0]) + milliseconds = (degrees * 3600000 + + self.longitude[1] * 60000 + + self.longitude[2] * 1000 + + self.longitude[3]) * sign + longitude = 0x80000000L + milliseconds + altitude = long(self.altitude) + 10000000L + size = _encode_size(self.size, "size") + hprec = _encode_size(self.horizontal_precision, "horizontal precision") + vprec = _encode_size(self.vertical_precision, "vertical precision") + wire = struct.pack("!BBBBIII", 0, size, hprec, vprec, latitude, + longitude, altitude) + file.write(wire) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (version, size, hprec, vprec, latitude, longitude, altitude) = \ + struct.unpack("!BBBBIII", wire[current : current + rdlen]) + if latitude > 0x80000000L: + latitude = float(latitude - 0x80000000L) / 3600000 + else: + latitude = -1 * float(0x80000000L - latitude) / 3600000 + if latitude < -90.0 or latitude > 90.0: + raise dns.exception.FormError, "bad latitude" + if longitude > 0x80000000L: + longitude = float(longitude - 0x80000000L) / 3600000 + else: + longitude = -1 * float(0x80000000L - longitude) / 3600000 + if longitude < -180.0 or longitude > 180.0: + raise dns.exception.FormError, "bad longitude" + altitude = float(altitude) - 10000000.0 + size = _decode_size(size, "size") + hprec = _decode_size(hprec, "horizontal precision") + vprec = _decode_size(vprec, "vertical precision") + return cls(rdclass, rdtype, latitude, longitude, altitude, + size, hprec, vprec) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + f = cStringIO.StringIO() + self.to_wire(f) + wire1 = f.getvalue() + f.seek(0) + f.truncate() + other.to_wire(f) + wire2 = f.getvalue() + f.close() + + return cmp(wire1, wire2) + + def _get_float_latitude(self): + return _tuple_to_float(self.latitude) + + def _set_float_latitude(self, value): + self.latitude = _float_to_tuple(value) + + float_latitude = property(_get_float_latitude, _set_float_latitude, + doc="latitude as a floating point value") + + def _get_float_longitude(self): + return _tuple_to_float(self.longitude) + + def _set_float_longitude(self, value): + self.longitude = _float_to_tuple(value) + + float_longitude = property(_get_float_longitude, _set_float_longitude, + doc="longitude as a floating point value") diff --git a/dns/rdtypes/ANY/MX.py b/dns/rdtypes/ANY/MX.py new file mode 100644 index 0000000..7be54b9 --- /dev/null +++ b/dns/rdtypes/ANY/MX.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: MX.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.mxbase + +class MX(dns.rdtypes.mxbase.MXBase): + """MX record""" + pass diff --git a/dns/rdtypes/ANY/NS.py b/dns/rdtypes/ANY/NS.py new file mode 100644 index 0000000..1b44b2f --- /dev/null +++ b/dns/rdtypes/ANY/NS.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NS.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.nsbase + +class NS(dns.rdtypes.nsbase.NSBase): + """NS record""" + pass diff --git a/dns/rdtypes/ANY/NSEC.py b/dns/rdtypes/ANY/NSEC.py new file mode 100644 index 0000000..60b911d --- /dev/null +++ b/dns/rdtypes/ANY/NSEC.py @@ -0,0 +1,146 @@ +# Copyright (C) 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NSEC.py,v 1.1 2004/03/19 00:06:37 halley Exp $ + +import cStringIO +import struct + +import dns.exception +import dns.rdata +import dns.rdatatype +import dns.name + +class NSEC(dns.rdata.Rdata): + """NSEC record + + @ivar next: the next name + @type next: dns.name.Name object + @ivar windows: the windowed bitmap list + @type windows: list of (window number, string) tuples""" + + __slots__ = ['next', 'windows'] + + def __init__(self, rdclass, rdtype, next, windows): + super(NSEC, self).__init__(rdclass, rdtype) + self.next = next + self.windows = windows + + def to_text(self, origin=None, relativize=True, **kw): + next = self.next.choose_relativity(origin, relativize) + for (window, bitmap) in self.windows: + bits = [] + for i in xrange(0, len(bitmap)): + byte = ord(bitmap[i]) + for j in xrange(0, 8): + if byte & (0x80 >> j): + bits.append(dns.rdatatype.to_text(window * 256 + \ + i * 8 + j)) + text = ' '.join(bits) + return '%s %s' % (next, text) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + next = tok.get_name() + next = next.choose_relativity(origin, relativize) + rdtypes = [] + while 1: + (ttype, value) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + if value.isdigit(): + nrdtype = int(value) + else: + nrdtype = dns.rdatatype.from_text(value) + if nrdtype == 0: + raise dns.exception.SyntaxError, "NSEC with bit 0" + if nrdtype > 65535: + raise dns.exception.SyntaxError, "NSEC with bit > 65535" + rdtypes.append(nrdtype) + rdtypes.sort() + window = 0 + octets = 0 + prior_rdtype = 0 + bitmap = ['\0'] * 32 + windows = [] + for nrdtype in rdtypes: + if nrdtype == prior_rdtype: + continue + prior_rdtype = nrdtype + new_window = nrdtype // 256 + if new_window != window: + windows.append((window, ''.join(bitmap[0:octets]))) + bitmap = ['\0'] * 32 + window = new_window + offset = nrdtype % 256 + byte = offset / 8 + bit = offset % 8 + octets = byte + 1 + bitmap[byte] = chr(ord(bitmap[byte]) | (0x80 >> bit)) + windows.append((window, ''.join(bitmap[0:octets]))) + return cls(rdclass, rdtype, next, windows) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + self.next.to_wire(file, None, origin) + for (window, bitmap) in self.windows: + file.write(chr(window)) + file.write(chr(len(bitmap))) + file.write(bitmap) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (next, cused) = dns.name.from_wire(wire[: current + rdlen], current) + current += cused + rdlen -= cused + windows = [] + while rdlen > 0: + if rdlen < 3: + raise dns.exception.FormError, "NSEC too short" + window = ord(wire[current]) + octets = ord(wire[current + 1]) + if octets == 0 or octets > 32: + raise dns.exception.FormError, "bad NSEC octets" + current += 2 + rdlen -= 2 + if rdlen < octets: + raise dns.exception.FormError, "bad NSEC bitmap length" + bitmap = wire[current : current + octets] + current += octets + rdlen -= octets + windows.append((window, bitmap)) + if not origin is None: + next = next.relativize(origin) + return cls(rdclass, rdtype, next, windows) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.next = self.next.choose_relativity(origin, relativize) + + def _cmp(self, other): + v = cmp(self.next, other.next) + if v == 0: + b1 = cStringIO.StringIO() + for (window, bitmap) in self.windows: + b1.write(chr(window)) + b1.write(chr(len(bitmap))) + b1.write(bitmap) + b2 = cStringIO.StringIO() + for (window, bitmap) in other.windows: + b2.write(chr(window)) + b2.write(chr(len(bitmap))) + b2.write(bitmap) + v = cmp(b1.getvalue(), b2.getvalue()) + return v diff --git a/dns/rdtypes/ANY/NXT.py b/dns/rdtypes/ANY/NXT.py new file mode 100644 index 0000000..b290653 --- /dev/null +++ b/dns/rdtypes/ANY/NXT.py @@ -0,0 +1,100 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NXT.py,v 1.10 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.rdata +import dns.rdatatype +import dns.name + +class NXT(dns.rdata.Rdata): + """NXT record + + @ivar next: the next name + @type next: dns.name.Name object + @ivar bitmap: the type bitmap + @type bitmap: string + @see: RFC 2535""" + + __slots__ = ['next', 'bitmap'] + + def __init__(self, rdclass, rdtype, next, bitmap): + super(NXT, self).__init__(rdclass, rdtype) + self.next = next + self.bitmap = bitmap + + def to_text(self, origin=None, relativize=True, **kw): + next = self.next.choose_relativity(origin, relativize) + bits = [] + for i in xrange(0, len(self.bitmap)): + byte = ord(self.bitmap[i]) + for j in xrange(0, 8): + if byte & (0x80 >> j): + bits.append(dns.rdatatype.to_text(i * 8 + j)) + text = ' '.join(bits) + return '%s %s' % (next, text) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + next = tok.get_name() + next = next.choose_relativity(origin, relativize) + bitmap = ['\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00', + '\x00', '\x00', '\x00', '\x00' ] + while 1: + (ttype, value) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + if value.isdigit(): + nrdtype = int(value) + else: + nrdtype = dns.rdatatype.from_text(value) + if nrdtype == 0: + raise dns.exception.SyntaxError, "NXT with bit 0" + if nrdtype > 127: + raise dns.exception.SyntaxError, "NXT with bit > 127" + i = nrdtype // 8 + bitmap[i] = chr(ord(bitmap[i]) | (0x80 >> (nrdtype % 8))) + bitmap = dns.rdata._truncate_bitmap(bitmap) + return cls(rdclass, rdtype, next, bitmap) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + self.next.to_wire(file, None, origin) + file.write(self.bitmap) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (next, cused) = dns.name.from_wire(wire[: current + rdlen], current) + current += cused + rdlen -= cused + bitmap = wire[current : current + rdlen] + if not origin is None: + next = next.relativize(origin) + return cls(rdclass, rdtype, next, bitmap) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.next = self.next.choose_relativity(origin, relativize) + + def _cmp(self, other): + v = cmp(self.next, other.next) + if v == 0: + v = cmp(self.bitmap, other.bitmap) + return v diff --git a/dns/rdtypes/ANY/PTR.py b/dns/rdtypes/ANY/PTR.py new file mode 100644 index 0000000..f5c765a --- /dev/null +++ b/dns/rdtypes/ANY/PTR.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: PTR.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.nsbase + +class PTR(dns.rdtypes.nsbase.NSBase): + """PTR record""" + pass diff --git a/dns/rdtypes/ANY/RP.py b/dns/rdtypes/ANY/RP.py new file mode 100644 index 0000000..eaf8e1b --- /dev/null +++ b/dns/rdtypes/ANY/RP.py @@ -0,0 +1,86 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: RP.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class RP(dns.rdata.Rdata): + """RP record + + @ivar mbox: The responsible person's mailbox + @type mbox: dns.name.Name object + @ivar txt: The owner name of a node with TXT records, or the root name + if no TXT records are associated with this RP. + @type txt: dns.name.Name object + @see: RFC 1183""" + + __slots__ = ['mbox', 'txt'] + + def __init__(self, rdclass, rdtype, mbox, txt): + super(RP, self).__init__(rdclass, rdtype) + self.mbox = mbox + self.txt = txt + + def to_text(self, origin=None, relativize=True, **kw): + mbox = self.mbox.choose_relativity(origin, relativize) + txt = self.txt.choose_relativity(origin, relativize) + return "%s %s" % (str(mbox), str(txt)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + mbox = tok.get_name() + txt = tok.get_name() + mbox = mbox.choose_relativity(origin, relativize) + txt = txt.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, mbox, txt) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + self.mbox.to_wire(file, None, origin) + self.txt.to_wire(file, None, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (mbox, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + current += cused + rdlen -= cused + if rdlen <= 0: + raise dns.exception.FormError + (txt, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + mbox = mbox.relativize(origin) + txt = txt.relativize(origin) + return cls(rdclass, rdtype, mbox, txt) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.mbox = self.mbox.choose_relativity(origin, relativize) + self.txt = self.txt.choose_relativity(origin, relativize) + + def _cmp(self, other): + v = cmp(self.mbox, other.mbox) + if v == 0: + v = cmp(self.txt, other.txt) + return v diff --git a/dns/rdtypes/ANY/RRSIG.py b/dns/rdtypes/ANY/RRSIG.py new file mode 100644 index 0000000..92f76b1 --- /dev/null +++ b/dns/rdtypes/ANY/RRSIG.py @@ -0,0 +1,22 @@ +# Copyright (C) 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: RRSIG.py,v 1.1 2004/03/19 00:06:37 halley Exp $ + +import dns.rdtypes.sigbase + +class RRSIG(dns.rdtypes.sigbase.SIGBase): + """RRSIG record""" + pass diff --git a/dns/rdtypes/ANY/RT.py b/dns/rdtypes/ANY/RT.py new file mode 100644 index 0000000..04c29de --- /dev/null +++ b/dns/rdtypes/ANY/RT.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: RT.py,v 1.6 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.mxbase + +class RT(dns.rdtypes.mxbase.UncompressedMX): + """RT record""" + pass diff --git a/dns/rdtypes/ANY/SIG.py b/dns/rdtypes/ANY/SIG.py new file mode 100644 index 0000000..fad144b --- /dev/null +++ b/dns/rdtypes/ANY/SIG.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: SIG.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.sigbase + +class SIG(dns.rdtypes.sigbase.SIGBase): + """SIG record""" + pass diff --git a/dns/rdtypes/ANY/SOA.py b/dns/rdtypes/ANY/SOA.py new file mode 100644 index 0000000..7f157fd --- /dev/null +++ b/dns/rdtypes/ANY/SOA.py @@ -0,0 +1,123 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: SOA.py,v 1.17 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class SOA(dns.rdata.Rdata): + """SOA record + + @ivar mname: the SOA MNAME (master name) field + @type mname: dns.name.Name object + @ivar rname: the SOA RNAME (responsible name) field + @type rname: dns.name.Name object + @ivar serial: The zone's serial number + @type serial: int + @ivar refresh: The zone's refresh value (in seconds) + @type refresh: int + @ivar retry: The zone's retry value (in seconds) + @type retry: int + @ivar expiration: The zone's expiration value (in seconds) + @type expiration: int + @ivar minimum: The zone's negative caching time (in seconds, called + "minimum" for historical reasons) + @type minimum: int + @see: RFC 1035""" + + __slots__ = ['mname', 'rname', 'serial', 'refresh', 'retry', 'expire', + 'minimum'] + + def __init__(self, rdclass, rdtype, mname, rname, serial, refresh, retry, + expire, minimum): + super(SOA, self).__init__(rdclass, rdtype) + self.mname = mname + self.rname = rname + self.serial = serial + self.refresh = refresh + self.retry = retry + self.expire = expire + self.minimum = minimum + + def to_text(self, origin=None, relativize=True, **kw): + mname = self.mname.choose_relativity(origin, relativize) + rname = self.rname.choose_relativity(origin, relativize) + return '%s %s %d %d %d %d %d' % ( + mname, rname, self.serial, self.refresh, self.retry, + self.expire, self.minimum ) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + mname = tok.get_name() + rname = tok.get_name() + mname = mname.choose_relativity(origin, relativize) + rname = rname.choose_relativity(origin, relativize) + serial = tok.get_uint32() + refresh = tok.get_uint32() + retry = tok.get_uint32() + expire = tok.get_uint32() + minimum = tok.get_uint32() + tok.get_eol() + return cls(rdclass, rdtype, mname, rname, serial, refresh, retry, + expire, minimum ) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + self.mname.to_wire(file, compress, origin) + self.rname.to_wire(file, compress, origin) + five_ints = struct.pack('!IIIII', self.serial, self.refresh, + self.retry, self.expire, self.minimum) + file.write(five_ints) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (mname, cused) = dns.name.from_wire(wire[: current + rdlen], current) + current += cused + rdlen -= cused + (rname, cused) = dns.name.from_wire(wire[: current + rdlen], current) + current += cused + rdlen -= cused + if rdlen != 20: + raise dns.exception.FormError + five_ints = struct.unpack('!IIIII', + wire[current : current + rdlen]) + if not origin is None: + mname = mname.relativize(origin) + rname = rname.relativize(origin) + return cls(rdclass, rdtype, mname, rname, + five_ints[0], five_ints[1], five_ints[2], five_ints[3], + five_ints[4]) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.mname = self.mname.choose_relativity(origin, relativize) + self.rname = self.rname.choose_relativity(origin, relativize) + + def _cmp(self, other): + v = cmp(self.mname, other.mname) + if v == 0: + v = cmp(self.rname, other.rname) + if v == 0: + self_ints = struct.pack('!IIIII', self.serial, self.refresh, + self.retry, self.expire, self.minimum) + other_ints = struct.pack('!IIIII', other.serial, other.refresh, + other.retry, other.expire, + other.minimum) + v = cmp(self_ints, other_ints) + return v diff --git a/dns/rdtypes/ANY/TXT.py b/dns/rdtypes/ANY/TXT.py new file mode 100644 index 0000000..489483f --- /dev/null +++ b/dns/rdtypes/ANY/TXT.py @@ -0,0 +1,89 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: TXT.py,v 1.14 2004/03/19 00:17:27 halley Exp $ + +import socket + +import dns.exception +import dns.rdata +import dns.tokenizer + +class TXT(dns.rdata.Rdata): + """TXT record + + @ivar strings: the text strings + @type strings: list of string + @see: RFC 1035""" + + __slots__ = ['strings'] + + def __init__(self, rdclass, rdtype, strings): + super(TXT, self).__init__(rdclass, rdtype) + if isinstance(strings, str): + strings = [ strings ] + self.strings = strings[:] + + def to_text(self, origin=None, relativize=True, **kw): + txt = '' + prefix = '' + for s in self.strings: + txt += '%s"%s"' % (prefix, dns.rdata._escapify(s)) + prefix = ' ' + return txt + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + strings = [] + while 1: + (ttype, s) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + if ttype != dns.tokenizer.QUOTED_STRING: + raise dns.exception.SyntaxError, "expected a quoted string" + if len(s) > 255: + raise dns.exception.SyntaxError, "string too long" + strings.append(s) + if len(strings) == 0: + raise dns.exception.UnexpectedEnd + return cls(rdclass, rdtype, strings) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + for s in self.strings: + l = len(s) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(s) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + strings = [] + while rdlen > 0: + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen: + raise dns.exception.FormError + s = wire[current : current + l] + current += l + rdlen -= l + strings.append(s) + return cls(rdclass, rdtype, strings) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + return cmp(self.strings, other.strings) diff --git a/dns/rdtypes/ANY/X25.py b/dns/rdtypes/ANY/X25.py new file mode 100644 index 0000000..ad90a48 --- /dev/null +++ b/dns/rdtypes/ANY/X25.py @@ -0,0 +1,64 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: X25.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +import dns.exception +import dns.rdata +import dns.tokenizer + +class X25(dns.rdata.Rdata): + """X25 record + + @ivar address: the PSDN address + @type address: string + @see: RFC 1183""" + + __slots__ = ['address'] + + def __init__(self, rdclass, rdtype, address): + super(X25, self).__init__(rdclass, rdtype) + self.address = address + + def to_text(self, origin=None, relativize=True, **kw): + return '"%s"' % dns.rdata._escapify(self.address) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + address = tok.get_string() + tok.get_eol() + return cls(rdclass, rdtype, address) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + l = len(self.address) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(self.address) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l != rdlen: + raise dns.exception.FormError + address = wire[current : current + l] + return cls(rdclass, rdtype, address) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + return cmp(self.address, other.address) diff --git a/dns/rdtypes/ANY/__init__.py b/dns/rdtypes/ANY/__init__.py new file mode 100644 index 0000000..0e6db1e --- /dev/null +++ b/dns/rdtypes/ANY/__init__.py @@ -0,0 +1,44 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: __init__.py,v 1.18 2004/03/19 00:17:27 halley Exp $ + +"""Class ANY (generic) rdata type classes.""" + +__all__ = [ + 'AFSDB', + 'CERT', + 'CNAME', + 'DNAME', + 'DS', + 'GPOS', + 'HINFO', + 'ISDN', + 'KEY', + 'LOC', + 'MX', + 'NS', + 'NXT', + 'PTR', + 'RP', + 'RT', + 'SIG', + 'SOA', + 'TXT', + 'X25', + 'RRSIG', + 'NSEC', + 'DNSKEY', +] diff --git a/dns/rdtypes/IN/.arch-ids/=id b/dns/rdtypes/IN/.arch-ids/=id new file mode 100644 index 0000000..c883c9e --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:43 2004 4756.1 diff --git a/dns/rdtypes/IN/.arch-ids/A.py.id b/dns/rdtypes/IN/.arch-ids/A.py.id new file mode 100644 index 0000000..9d8c8c6 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/A.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.0 diff --git a/dns/rdtypes/IN/.arch-ids/AAAA.py.id b/dns/rdtypes/IN/.arch-ids/AAAA.py.id new file mode 100644 index 0000000..3bba289 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/AAAA.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.1 diff --git a/dns/rdtypes/IN/.arch-ids/APL.py.id b/dns/rdtypes/IN/.arch-ids/APL.py.id new file mode 100644 index 0000000..d0a0bab --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/APL.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.2 diff --git a/dns/rdtypes/IN/.arch-ids/KX.py.id b/dns/rdtypes/IN/.arch-ids/KX.py.id new file mode 100644 index 0000000..50f1a36 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/KX.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.3 diff --git a/dns/rdtypes/IN/.arch-ids/NAPTR.py.id b/dns/rdtypes/IN/.arch-ids/NAPTR.py.id new file mode 100644 index 0000000..4f1dd33 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/NAPTR.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.4 diff --git a/dns/rdtypes/IN/.arch-ids/NSAP.py.id b/dns/rdtypes/IN/.arch-ids/NSAP.py.id new file mode 100644 index 0000000..0551fc9 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/NSAP.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.5 diff --git a/dns/rdtypes/IN/.arch-ids/NSAP_PTR.py.id b/dns/rdtypes/IN/.arch-ids/NSAP_PTR.py.id new file mode 100644 index 0000000..e083a4a --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/NSAP_PTR.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.6 diff --git a/dns/rdtypes/IN/.arch-ids/PX.py.id b/dns/rdtypes/IN/.arch-ids/PX.py.id new file mode 100644 index 0000000..b043a45 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/PX.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.7 diff --git a/dns/rdtypes/IN/.arch-ids/SRV.py.id b/dns/rdtypes/IN/.arch-ids/SRV.py.id new file mode 100644 index 0000000..b50cfee --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/SRV.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.8 diff --git a/dns/rdtypes/IN/.arch-ids/WKS.py.id b/dns/rdtypes/IN/.arch-ids/WKS.py.id new file mode 100644 index 0000000..eb34bbb --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/WKS.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.9 diff --git a/dns/rdtypes/IN/.arch-ids/__init__.py.id b/dns/rdtypes/IN/.arch-ids/__init__.py.id new file mode 100644 index 0000000..50965d6 --- /dev/null +++ b/dns/rdtypes/IN/.arch-ids/__init__.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:01 2004 4760.10 diff --git a/dns/rdtypes/IN/A.py b/dns/rdtypes/IN/A.py new file mode 100644 index 0000000..91b961e --- /dev/null +++ b/dns/rdtypes/IN/A.py @@ -0,0 +1,63 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: A.py,v 1.14 2004/03/19 00:17:27 halley Exp $ + +import socket + +import dns.exception +import dns.ipv4 +import dns.rdata +import dns.tokenizer + +class A(dns.rdata.Rdata): + """A record. + + @ivar address: an IPv4 address + @type address: string (in the standard "dotted quad" format)""" + + __slots__ = ['address'] + + def __init__(self, rdclass, rdtype, address): + super(A, self).__init__(rdclass, rdtype) + # check that it's OK + junk = dns.ipv4.inet_aton(address) + self.address = address + + def to_text(self, origin=None, relativize=True, **kw): + return self.address + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + (ttype, address) = tok.get() + if ttype != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + t = tok.get_eol() + return cls(rdclass, rdtype, address) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + file.write(dns.ipv4.inet_aton(self.address)) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + address = dns.ipv4.inet_ntoa(wire[current : current + rdlen]) + return cls(rdclass, rdtype, address) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + sa = dns.ipv4.inet_aton(self.address) + oa = dns.ipv4.inet_aton(other.address) + return cmp(sa, oa) diff --git a/dns/rdtypes/IN/AAAA.py b/dns/rdtypes/IN/AAAA.py new file mode 100644 index 0000000..0756a50 --- /dev/null +++ b/dns/rdtypes/IN/AAAA.py @@ -0,0 +1,64 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: AAAA.py,v 1.16 2004/03/19 00:17:27 halley Exp $ + +import socket + +import dns.exception +import dns.inet +import dns.rdata +import dns.tokenizer + +class AAAA(dns.rdata.Rdata): + """AAAA record. + + @ivar address: an IPv6 address + @type address: string (in the standard IPv6 format)""" + + __slots__ = ['address'] + + def __init__(self, rdclass, rdtype, address): + super(AAAA, self).__init__(rdclass, rdtype) + # check that it's OK + junk = dns.inet.inet_pton(dns.inet.AF_INET6, address) + self.address = address + + def to_text(self, origin=None, relativize=True, **kw): + return self.address + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + (ttype, address) = tok.get() + if ttype != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + t = tok.get_eol() + return cls(rdclass, rdtype, address) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + file.write(dns.inet.inet_pton(dns.inet.AF_INET6, self.address)) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + address = dns.inet.inet_ntop(dns.inet.AF_INET6, + wire[current : current + rdlen]) + return cls(rdclass, rdtype, address) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + sa = dns.inet.inet_pton(dns.inet.AF_INET6, self.address) + oa = dns.inet.inet_pton(dns.inet.AF_INET6, other.address) + return cmp(sa, oa) diff --git a/dns/rdtypes/IN/APL.py b/dns/rdtypes/IN/APL.py new file mode 100644 index 0000000..3d1a1c4 --- /dev/null +++ b/dns/rdtypes/IN/APL.py @@ -0,0 +1,172 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: APL.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import socket +import struct + +import dns.exception +import dns.inet +import dns.rdata +import dns.tokenizer + +class APLItem(object): + """An APL list item. + + @ivar family: the address family (IANA address family registry) + @type family: int + @ivar negation: is this item negated? + @type negation: bool + @ivar address: the address + @type address: string + @ivar prefix: the prefix length + @type prefix: int + """ + + __slots__ = ['family', 'negation', 'address', 'prefix'] + + def __init__(self, family, negation, address, prefix): + self.family = family + self.negation = negation + self.address = address + self.prefix = prefix + + def __str__(self): + if self.negation: + return "!%d:%s/%s" % (self.family, self.address, self.prefix) + else: + return "%d:%s/%s" % (self.family, self.address, self.prefix) + + def to_wire(self, file): + if self.family == 1: + address = dns.inet.inet_pton(dns.inet.AF_INET, self.address) + elif self.family == 2: + address = dns.inet.inet_pton(dns.inet.AF_INET6, self.address) + else: + address = self.address.decode('hex_codec') + # + # Truncate least significant zero bytes. + # + last = 0 + for i in xrange(len(address) - 1, -1, -1): + if address[i] != chr(0): + last = i + 1 + break + address = address[0 : last] + l = len(address) + assert l < 128 + if self.negation: + l |= 0x80 + header = struct.pack('!HBB', self.family, self.prefix, l) + file.write(header) + file.write(address) + +class APL(dns.rdata.Rdata): + """APL record. + + @ivar items: a list of APL items + @type items: list of APL_Item + @see: RFC 3123""" + + __slots__ = ['items'] + + def __init__(self, rdclass, rdtype, items): + super(APL, self).__init__(rdclass, rdtype) + self.items = items + + def to_text(self, origin=None, relativize=True, **kw): + return ' '.join(map(lambda x: str(x), self.items)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + items = [] + while 1: + (ttype, item) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + if item[0] == '!': + negation = True + item = item[1:] + else: + negation = False + (family, rest) = item.split(':', 1) + family = int(family) + (address, prefix) = rest.split('/', 1) + prefix = int(prefix) + item = APLItem(family, negation, address, prefix) + items.append(item) + + return cls(rdclass, rdtype, items) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + for item in self.items: + item.to_wire(file) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + items = [] + while 1: + if rdlen < 4: + raise dns.exception.FormError + header = struct.unpack('!HBB', wire[current : current + 4]) + afdlen = header[2] + if afdlen > 127: + negation = True + afdlen -= 128 + else: + negation = False + current += 4 + rdlen -= 4 + if rdlen < afdlen: + raise dns.exception.FormError + address = wire[current : current + afdlen] + l = len(address) + if header[0] == 1: + if l < 4: + address += '\x00' * (4 - l) + address = dns.inet.inet_ntop(dns.inet.AF_INET, address) + elif header[0] == 2: + if l < 16: + address += '\x00' * (16 - l) + address = dns.inet.inet_ntop(dns.inet.AF_INET6, address) + else: + # + # This isn't really right according to the RFC, but it + # seems better than throwing an exception + # + address = address.encode('hex_codec') + current += afdlen + rdlen -= afdlen + item = APLItem(header[0], negation, address, header[1]) + items.append(item) + if rdlen == 0: + break + return cls(rdclass, rdtype, items) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + f = cStringIO.StringIO() + self.to_wire(f) + wire1 = f.getvalue() + f.seek(0) + f.truncate() + other.to_wire(f) + wire2 = f.getvalue() + f.close() + + return cmp(wire1, wire2) diff --git a/dns/rdtypes/IN/KX.py b/dns/rdtypes/IN/KX.py new file mode 100644 index 0000000..59fa8cc --- /dev/null +++ b/dns/rdtypes/IN/KX.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: KX.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.mxbase + +class KX(dns.rdtypes.mxbase.UncompressedMX): + """KX record""" + pass diff --git a/dns/rdtypes/IN/NAPTR.py b/dns/rdtypes/IN/NAPTR.py new file mode 100644 index 0000000..428b78c --- /dev/null +++ b/dns/rdtypes/IN/NAPTR.py @@ -0,0 +1,134 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NAPTR.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.name +import dns.rdata + +def _write_string(file, s): + l = len(s) + assert l < 256 + byte = chr(l) + file.write(byte) + file.write(s) + +class NAPTR(dns.rdata.Rdata): + """NAPTR record + + @ivar order: order + @type order: int + @ivar preference: preference + @type preference: int + @ivar flags: flags + @type flags: string + @ivar service: service + @type service: string + @ivar regexp: regular expression + @type regexp: string + @ivar replacement: replacement name + @type replacement: dns.name.Name object + @see: RFC 3403""" + + __slots__ = ['order', 'preference', 'flags', 'service', 'regexp', + 'replacement'] + + def __init__(self, rdclass, rdtype, order, preference, flags, service, + regexp, replacement): + super(NAPTR, self).__init__(rdclass, rdtype) + self.order = order + self.preference = preference + self.flags = flags + self.service = service + self.regexp = regexp + self.replacement = replacement + + def to_text(self, origin=None, relativize=True, **kw): + replacement = self.replacement.choose_relativity(origin, relativize) + return '%d %d "%s" "%s" "%s" %s' % \ + (self.order, self.preference, + dns.rdata._escapify(self.flags), + dns.rdata._escapify(self.service), + dns.rdata._escapify(self.regexp), + self.replacement) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + order = tok.get_uint16() + preference = tok.get_uint16() + flags = tok.get_string() + service = tok.get_string() + regexp = tok.get_string() + replacement = tok.get_name() + replacement = replacement.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, order, preference, flags, service, + regexp, replacement) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + two_ints = struct.pack("!HH", self.order, self.preference) + file.write(two_ints) + _write_string(file, self.flags) + _write_string(file, self.service) + _write_string(file, self.regexp) + self.replacement.to_wire(file, compress, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (order, preference) = struct.unpack('!HH', wire[current : current + 4]) + current += 4 + rdlen -= 4 + strings = [] + for i in xrange(3): + l = ord(wire[current]) + current += 1 + rdlen -= 1 + if l > rdlen or rdlen < 0: + raise dns.exception.FormError + s = wire[current : current + l] + current += l + rdlen -= l + strings.append(s) + (replacement, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + replacement = replacement.relativize(origin) + return cls(rdclass, rdtype, order, preference, strings[0], strings[1], + strings[2], replacement) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.replacement = self.replacement.choose_relativity(origin, + relativize) + + def _cmp(self, other): + sp = struct.pack("!HH", self.order, self.preference) + op = struct.pack("!HH", other.order, self.preference) + v = cmp(sp, op) + if v == 0: + v = cmp(self.flags, other.flags) + if v == 0: + v = cmp(self.service, other.service) + if v == 0: + v = cmp(self.regexp, other.regexp) + if v == 0: + v = cmp(self.replacement, other.replacement) + return v diff --git a/dns/rdtypes/IN/NSAP.py b/dns/rdtypes/IN/NSAP.py new file mode 100644 index 0000000..8804097 --- /dev/null +++ b/dns/rdtypes/IN/NSAP.py @@ -0,0 +1,63 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NSAP.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +import socket + +import dns.exception +import dns.rdata +import dns.tokenizer + +class NSAP(dns.rdata.Rdata): + """NSAP record. + + @ivar address: a NASP + @type address: string + @see: RFC 1706""" + + __slots__ = ['address'] + + def __init__(self, rdclass, rdtype, address): + super(NSAP, self).__init__(rdclass, rdtype) + self.address = address + + def to_text(self, origin=None, relativize=True, **kw): + return "0x%s" % self.address.encode('hex_codec') + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + address = tok.get_string() + t = tok.get_eol() + if address[0:2] != '0x': + raise dns.exception.SyntaxError, 'string does not start with 0x' + address = address[2:].replace('.', '') + if len(address) % 2 != 0: + raise dns.exception.SyntaxError, 'hexstring has odd length' + address = address.decode('hex_codec') + return cls(rdclass, rdtype, address) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + file.write(self.address) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + address = wire[current : current + rdlen] + return cls(rdclass, rdtype, address) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + return cmp(self.address, other.address) diff --git a/dns/rdtypes/IN/NSAP_PTR.py b/dns/rdtypes/IN/NSAP_PTR.py new file mode 100644 index 0000000..11d167d --- /dev/null +++ b/dns/rdtypes/IN/NSAP_PTR.py @@ -0,0 +1,22 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: NSAP_PTR.py,v 1.3 2004/03/19 00:17:27 halley Exp $ + +import dns.rdtypes.nsbase + +class NSAP_PTR(dns.rdtypes.nsbase.UncompressedNS): + """NSAP-PTR record""" + pass diff --git a/dns/rdtypes/IN/PX.py b/dns/rdtypes/IN/PX.py new file mode 100644 index 0000000..29c4043 --- /dev/null +++ b/dns/rdtypes/IN/PX.py @@ -0,0 +1,99 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: PX.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class PX(dns.rdata.Rdata): + """PX record. + + @ivar preference: the preference value + @type preference: int + @ivar map822: the map822 name + @type map822: dns.name.Name object + @ivar mapx400: the mapx400 name + @type mapx400: dns.name.Name object + @see: RFC 2163""" + + __slots__ = ['preference', 'map822', 'mapx400'] + + def __init__(self, rdclass, rdtype, preference, map822, mapx400): + super(PX, self).__init__(rdclass, rdtype) + self.preference = preference + self.map822 = map822 + self.mapx400 = mapx400 + + def to_text(self, origin=None, relativize=True, **kw): + map822 = self.map822.choose_relativity(origin, relativize) + mapx400 = self.mapx400.choose_relativity(origin, relativize) + return '%d %s %s' % (self.preference, map822, mapx400) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + preference = tok.get_uint16() + map822 = tok.get_name() + map822 = map822.choose_relativity(origin, relativize) + mapx400 = tok.get_name(None) + mapx400 = mapx400.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, preference, map822, mapx400) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + pref = struct.pack("!H", self.preference) + file.write(pref) + self.map822.to_wire(file, None, origin) + self.mapx400.to_wire(file, None, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (preference, ) = struct.unpack('!H', wire[current : current + 2]) + current += 2 + rdlen -= 2 + (map822, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused > rdlen: + raise dns.exception.FormError + current += cused + rdlen -= cused + if not origin is None: + map822 = map822.relativize(origin) + (mapx400, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + mapx400 = mapx400.relativize(origin) + return cls(rdclass, rdtype, preference, map822, mapx400) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.map822 = self.map822.choose_relativity(origin, relativize) + self.mapx400 = self.mapx400.choose_relativity(origin, relativize) + + def _cmp(self, other): + sp = struct.pack("!H", self.preference) + op = struct.pack("!H", other.preference) + v = cmp(sp, op) + if v == 0: + v = cmp(self.map822, other.map822) + if v == 0: + v = cmp(self.mapx400, other.mapx400) + return v diff --git a/dns/rdtypes/IN/SRV.py b/dns/rdtypes/IN/SRV.py new file mode 100644 index 0000000..212dd56 --- /dev/null +++ b/dns/rdtypes/IN/SRV.py @@ -0,0 +1,91 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: SRV.py,v 1.11 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class SRV(dns.rdata.Rdata): + """SRV record + + @ivar priority: the priority + @type priority: int + @ivar weight: the weight + @type weight: int + @ivar port: the port of the service + @type port: int + @ivar target: the target host + @type target: dns.name.Name object + @see: RFC 2782""" + + __slots__ = ['priority', 'weight', 'port', 'target'] + + def __init__(self, rdclass, rdtype, priority, weight, port, target): + super(SRV, self).__init__(rdclass, rdtype) + self.priority = priority + self.weight = weight + self.port = port + self.target = target + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + return '%d %d %d %s' % (self.priority, self.weight, self.port, + target) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + priority = tok.get_uint16() + weight = tok.get_uint16() + port = tok.get_uint16() + target = tok.get_name(None) + target = target.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, priority, weight, port, target) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + three_ints = struct.pack("!HHH", self.priority, self.weight, self.port) + file.write(three_ints) + self.target.to_wire(file, compress, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (priority, weight, port) = struct.unpack('!HHH', + wire[current : current + 6]) + current += 6 + rdlen -= 6 + (target, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + target = target.relativize(origin) + return cls(rdclass, rdtype, priority, weight, port, target) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.target = self.target.choose_relativity(origin, relativize) + + def _cmp(self, other): + sp = struct.pack("!HHH", self.priority, self.weight, self.port) + op = struct.pack("!HHH", other.priority, self.weight, self.port) + v = cmp(sp, op) + if v == 0: + v = cmp(self.target, other.target) + return v diff --git a/dns/rdtypes/IN/WKS.py b/dns/rdtypes/IN/WKS.py new file mode 100644 index 0000000..c0b7c1f --- /dev/null +++ b/dns/rdtypes/IN/WKS.py @@ -0,0 +1,115 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: WKS.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +import socket +import struct + +import dns.ipv4 +import dns.rdata + +_proto_tcp = socket.getprotobyname('tcp') +_proto_udp = socket.getprotobyname('udp') + +class WKS(dns.rdata.Rdata): + """WKS record + + @ivar address: the address + @type address: string + @ivar protocol: the protocol + @type protocol: int + @ivar bitmap: the bitmap + @type bitmap: string + @see: RFC 1035""" + + __slots__ = ['address', 'protocol', 'bitmap'] + + def __init__(self, rdclass, rdtype, address, protocol, bitmap): + super(WKS, self).__init__(rdclass, rdtype) + self.address = address + self.protocol = protocol + self.bitmap = bitmap + + def to_text(self, origin=None, relativize=True, **kw): + bits = [] + for i in xrange(0, len(self.bitmap)): + byte = ord(self.bitmap[i]) + for j in xrange(0, 8): + if byte & (0x80 >> j): + bits.append(str(i * 8 + j)) + text = ' '.join(bits) + return '%s %d %s' % (self.address, self.protocol, text) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + address = tok.get_string() + protocol = tok.get_string() + if protocol.isdigit(): + protocol = int(protocol) + else: + protocol = socket.getprotobyname(protocol) + bitmap = [] + while 1: + (ttype, value) = tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + if value.isdigit(): + serv = int(value) + else: + if protocol != _proto_udp and protocol != _proto_tcp: + raise NotImplementedError, "protocol must be TCP or UDP" + if protocol == _proto_udp: + protocol_text = "udp" + else: + protocol_text = "tcp" + serv = socket.getservbyname(value, protocol_text) + i = serv // 8 + l = len(bitmap) + if l < i + 1: + for j in xrange(l, i + 1): + bitmap.append('\x00') + bitmap[i] = chr(ord(bitmap[i]) | (0x80 >> (serv % 8))) + bitmap = dns.rdata._truncate_bitmap(bitmap) + return cls(rdclass, rdtype, address, protocol, bitmap) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + file.write(dns.ipv4.inet_aton(self.address)) + protocol = struct.pack('!B', self.protocol) + file.write(protocol) + file.write(self.bitmap) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + address = dns.ipv4.inet_ntoa(wire[current : current + 4]) + protocol, = struct.unpack('!B', wire[current + 4 : current + 5]) + current += 5 + rdlen -= 5 + bitmap = wire[current : current + rdlen] + return cls(rdclass, rdtype, address, protocol, bitmap) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + sa = dns.ipv4.inet_aton(self.address) + oa = dns.ipv4.inet_aton(other.address) + v = cmp(sa, oa) + if v == 0: + sp = struct.pack('!B', self.protocol) + op = struct.pack('!B', other.protocol) + v = cmp(sp, op) + if v == 0: + v = cmp(self.bitmap, other.bitmap) + return v diff --git a/dns/rdtypes/IN/__init__.py b/dns/rdtypes/IN/__init__.py new file mode 100644 index 0000000..e003d68 --- /dev/null +++ b/dns/rdtypes/IN/__init__.py @@ -0,0 +1,31 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: __init__.py,v 1.10 2004/03/19 00:17:27 halley Exp $ + +"""Class IN rdata type classes.""" + +__all__ = [ + 'A', + 'AAAA', + 'APL', + 'KX', + 'PX', + 'NAPTR', + 'NSAP', + 'NSAP_PTR', + 'SRV', + 'WKS', +] diff --git a/dns/rdtypes/__init__.py b/dns/rdtypes/__init__.py new file mode 100644 index 0000000..5a260c1 --- /dev/null +++ b/dns/rdtypes/__init__.py @@ -0,0 +1,27 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: __init__.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +"""DNS rdata type classes""" + +__all__ = [ + 'ANY', + 'IN', + 'mxbase', + 'nsbase', + 'sigbase', + 'keybase', +] diff --git a/dns/rdtypes/keybase.py b/dns/rdtypes/keybase.py new file mode 100644 index 0000000..1e6df89 --- /dev/null +++ b/dns/rdtypes/keybase.py @@ -0,0 +1,152 @@ +# Copyright (C) 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: keybase.py,v 1.2 2004/03/19 00:17:27 halley Exp $ + +import struct + +import dns.exception +import dns.dnssec +import dns.rdata + +_flags_from_text = { + 'NOCONF': (0x4000, 0xC000), + 'NOAUTH': (0x8000, 0xC000), + 'NOKEY': (0xC000, 0xC000), + 'FLAG2': (0x2000, 0x2000), + 'EXTEND': (0x1000, 0x1000), + 'FLAG4': (0x0800, 0x0800), + 'FLAG5': (0x0400, 0x0400), + 'USER': (0x0000, 0x0300), + 'ZONE': (0x0100, 0x0300), + 'HOST': (0x0200, 0x0300), + 'NTYP3': (0x0300, 0x0300), + 'FLAG8': (0x0080, 0x0080), + 'FLAG9': (0x0040, 0x0040), + 'FLAG10': (0x0020, 0x0020), + 'FLAG11': (0x0010, 0x0010), + 'SIG0': (0x0000, 0x000f), + 'SIG1': (0x0001, 0x000f), + 'SIG2': (0x0002, 0x000f), + 'SIG3': (0x0003, 0x000f), + 'SIG4': (0x0004, 0x000f), + 'SIG5': (0x0005, 0x000f), + 'SIG6': (0x0006, 0x000f), + 'SIG7': (0x0007, 0x000f), + 'SIG8': (0x0008, 0x000f), + 'SIG9': (0x0009, 0x000f), + 'SIG10': (0x000a, 0x000f), + 'SIG11': (0x000b, 0x000f), + 'SIG12': (0x000c, 0x000f), + 'SIG13': (0x000d, 0x000f), + 'SIG14': (0x000e, 0x000f), + 'SIG15': (0x000f, 0x000f), + } + +_protocol_from_text = { + 'NONE' : 0, + 'TLS' : 1, + 'EMAIL' : 2, + 'DNSSEC' : 3, + 'IPSEC' : 4, + 'ALL' : 255, + } + +class KEYBase(dns.rdata.Rdata): + """KEY-like record base + + @ivar flags: the key flags + @type flags: int + @ivar protocol: the protocol for which this key may be used + @type protocol: int + @ivar algorithm: the algorithm used for the key + @type algorithm: int + @ivar key: the public key + @type key: string""" + + __slots__ = ['flags', 'protocol', 'algorithm', 'key'] + + def __init__(self, rdclass, rdtype, flags, protocol, algorithm, key): + super(KEYBase, self).__init__(rdclass, rdtype) + self.flags = flags + self.protocol = protocol + self.algorithm = algorithm + self.key = key + + def to_text(self, origin=None, relativize=True, **kw): + return '%d %d %d %s' % (self.flags, self.protocol, self.algorithm, + dns.rdata._base64ify(self.key)) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + flags = tok.get_string() + if flags.isdigit(): + flags = int(flags) + else: + flag_names = flags.split('|') + flags = 0 + for flag in flag_names: + v = _flags_from_text.get(flag) + if v is None: + raise dns.exception.SyntaxError, 'unknown flag %s' % flag + flags &= ~v[1] + flags |= v[0] + protocol = tok.get_string() + if protocol.isdigit(): + protocol = int(protocol) + else: + protocol = _protocol_from_text.get(protocol) + if protocol is None: + raise dns.exception.SyntaxError, \ + 'unknown protocol %s' % protocol + + algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) + chunks = [] + while 1: + t = tok.get() + if t[0] == dns.tokenizer.EOL or t[0] == dns.tokenizer.EOF: + break + if t[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + chunks.append(t[1]) + b64 = ''.join(chunks) + key = b64.decode('base64_codec') + return cls(rdclass, rdtype, flags, protocol, algorithm, key) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + header = struct.pack("!HBB", self.flags, self.protocol, self.algorithm) + file.write(header) + file.write(self.key) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + if rdlen < 4: + raise dns.exception.FormError + header = struct.unpack('!HBB', wire[current : current + 4]) + current += 4 + rdlen -= 4 + key = wire[current : current + rdlen] + return cls(rdclass, rdtype, header[0], header[1], header[2], + key) + + from_wire = classmethod(from_wire) + + def _cmp(self, other): + hs = struct.pack("!HBB", self.flags, self.protocol, self.algorithm) + ho = struct.pack("!HBB", other.flags, other.protocol, other.algorithm) + v = cmp(hs, ho) + if v == 0: + v = cmp(self.key, other.key) + return v diff --git a/dns/rdtypes/mxbase.py b/dns/rdtypes/mxbase.py new file mode 100644 index 0000000..1b17ed3 --- /dev/null +++ b/dns/rdtypes/mxbase.py @@ -0,0 +1,89 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: mxbase.py,v 1.12 2004/03/19 00:17:27 halley Exp $ + +"""MX-like base classes.""" + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class MXBase(dns.rdata.Rdata): + """Base class for rdata that is like an MX record. + + @ivar preference: the preference value + @type preference: int + @ivar exchange: the exchange name + @type exchange: dns.name.Name object""" + + __slots__ = ['preference', 'exchange'] + + def __init__(self, rdclass, rdtype, preference, exchange): + super(MXBase, self).__init__(rdclass, rdtype) + self.preference = preference + self.exchange = exchange + + def to_text(self, origin=None, relativize=True, **kw): + exchange = self.exchange.choose_relativity(origin, relativize) + return '%d %s' % (self.preference, exchange) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + preference = tok.get_uint16() + exchange = tok.get_name() + exchange = exchange.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, preference, exchange) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + pref = struct.pack("!H", self.preference) + file.write(pref) + self.exchange.to_wire(file, compress, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (preference, ) = struct.unpack('!H', wire[current : current + 2]) + current += 2 + rdlen -= 2 + (exchange, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + exchange = exchange.relativize(origin) + return cls(rdclass, rdtype, preference, exchange) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.exchange = self.exchange.choose_relativity(origin, relativize) + + def _cmp(self, other): + sp = struct.pack("!H", self.preference) + op = struct.pack("!H", other.preference) + v = cmp(sp, op) + if v == 0: + v = cmp(self.exchange, other.exchange) + return v + +class UncompressedMX(MXBase): + """Base class for rdata that is like an MX record, but whose name + is not compressed when convert to DNS wire format.""" + + def to_wire(self, file, compress = None, origin = None): + super(UncompressedMX, self).to_wire(file, None, origin) diff --git a/dns/rdtypes/nsbase.py b/dns/rdtypes/nsbase.py new file mode 100644 index 0000000..7cdbee5 --- /dev/null +++ b/dns/rdtypes/nsbase.py @@ -0,0 +1,75 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: nsbase.py,v 1.13 2004/03/19 00:17:27 halley Exp $ + +"""NS-like base classes.""" + +import struct + +import dns.exception +import dns.rdata +import dns.name + +class NSBase(dns.rdata.Rdata): + """Base class for rdata that is like an NS record. + + @ivar target: the target name of the rdata + @type target: dns.name.Name object""" + + __slots__ = ['target'] + + def __init__(self, rdclass, rdtype, target): + super(NSBase, self).__init__(rdclass, rdtype) + self.target = target + + def to_text(self, origin=None, relativize=True, **kw): + target = self.target.choose_relativity(origin, relativize) + return str(target) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + target = tok.get_name() + target = target.choose_relativity(origin, relativize) + tok.get_eol() + return cls(rdclass, rdtype, target) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + self.target.to_wire(file, compress, origin) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + (target, cused) = dns.name.from_wire(wire[: current + rdlen], + current) + if cused != rdlen: + raise dns.exception.FormError + if not origin is None: + target = target.relativize(origin) + return cls(rdclass, rdtype, target) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.target = self.target.choose_relativity(origin, relativize) + + def _cmp(self, other): + return cmp(self.target, other.target) + +class UncompressedNS(NSBase): + """Base class for rdata that is like an NS record, but whose name + is not compressed when convert to DNS wire format.""" + + def to_wire(self, file, compress = None, origin = None): + super(UncompressedNS, self).to_wire(file, None, origin) diff --git a/dns/rdtypes/sigbase.py b/dns/rdtypes/sigbase.py new file mode 100644 index 0000000..1755a09 --- /dev/null +++ b/dns/rdtypes/sigbase.py @@ -0,0 +1,170 @@ +# Copyright (C) 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: sigbase.py,v 1.2 2004/03/19 00:17:27 halley Exp $ + +import calendar +import struct +import time + +import dns.dnssec +import dns.exception +import dns.rdata +import dns.rdatatype + +class BadSigTime(dns.exception.DNSException): + """Raised when a SIG or RRSIG RR's time cannot be parsed.""" + pass + +def sigtime_to_posixtime(what): + if len(what) != 14: + raise BadSigTime + year = int(what[0:4]) + month = int(what[4:6]) + day = int(what[6:8]) + hour = int(what[8:10]) + minute = int(what[10:12]) + second = int(what[12:14]) + return calendar.timegm((year, month, day, hour, minute, second, + 0, 0, 0)) + +def posixtime_to_sigtime(what): + return time.strftime('%Y%m%d%H%M%S', time.gmtime(what)) + +class SIGBase(dns.rdata.Rdata): + """SIG-like record base + + @ivar type_covered: the rdata type this signature covers + @type type_covered: int + @ivar algorithm: the algorithm used for the sig + @type algorithm: int + @ivar labels: number of labels + @type labels: int + @ivar original_ttl: the original TTL + @type original_ttl: long + @ivar expiration: signature expiration time + @type expiration: long + @ivar inception: signature inception time + @type inception: long + @ivar key_tag: the key tag + @type key_tag: int + @ivar signer: the signer + @type signer: dns.name.Name object + @ivar signature: the signature + @type signature: string""" + + __slots__ = ['type_covered', 'algorithm', 'labels', 'original_ttl', + 'expiration', 'inception', 'key_tag', 'signer', + 'signature'] + + def __init__(self, rdclass, rdtype, type_covered, algorithm, labels, + original_ttl, expiration, inception, key_tag, signer, + signature): + super(SIGBase, self).__init__(rdclass, rdtype) + self.type_covered = type_covered + self.algorithm = algorithm + self.labels = labels + self.original_ttl = original_ttl + self.expiration = expiration + self.inception = inception + self.key_tag = key_tag + self.signer = signer + self.signature = signature + + def covers(self): + return self.type_covered + + def to_text(self, origin=None, relativize=True, **kw): + return '%s %d %d %d %s %s %d %s %s' % ( + dns.rdatatype.to_text(self.type_covered), + self.algorithm, + self.labels, + self.original_ttl, + posixtime_to_sigtime(self.expiration), + posixtime_to_sigtime(self.inception), + self.key_tag, + self.signer, + dns.rdata._base64ify(self.signature) + ) + + def from_text(cls, rdclass, rdtype, tok, origin = None, relativize = True): + type_covered = dns.rdatatype.from_text(tok.get_string()) + algorithm = dns.dnssec.algorithm_from_text(tok.get_string()) + labels = tok.get_int() + original_ttl = tok.get_uint32() + expiration = sigtime_to_posixtime(tok.get_string()) + inception = sigtime_to_posixtime(tok.get_string()) + key_tag = tok.get_int() + signer = tok.get_name() + signer = signer.choose_relativity(origin, relativize) + chunks = [] + while 1: + t = tok.get() + if t[0] == dns.tokenizer.EOL or t[0] == dns.tokenizer.EOF: + break + if t[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + chunks.append(t[1]) + b64 = ''.join(chunks) + signature = b64.decode('base64_codec') + return cls(rdclass, rdtype, type_covered, algorithm, labels, + original_ttl, expiration, inception, key_tag, signer, + signature) + + from_text = classmethod(from_text) + + def to_wire(self, file, compress = None, origin = None): + header = struct.pack('!HBBIIIH', self.type_covered, + self.algorithm, self.labels, + self.original_ttl, self.expiration, + self.inception, self.key_tag) + file.write(header) + self.signer.to_wire(file, None, origin) + file.write(self.signature) + + def from_wire(cls, rdclass, rdtype, wire, current, rdlen, origin = None): + header = struct.unpack('!HBBIIIH', wire[current : current + 18]) + current += 18 + rdlen -= 18 + (signer, cused) = dns.name.from_wire(wire[: current + rdlen], current) + current += cused + rdlen -= cused + if not origin is None: + signer = signer.relativize(origin) + signature = wire[current : current + rdlen] + return cls(rdclass, rdtype, header[0], header[1], header[2], + header[3], header[4], header[5], header[6], signer, + signature) + + from_wire = classmethod(from_wire) + + def choose_relativity(self, origin = None, relativize = True): + self.signer = self.signer.choose_relativity(origin, relativize) + + def _cmp(self, other): + hs = struct.pack('!HBBIIIH', self.type_covered, + self.algorithm, self.labels, + self.original_ttl, self.expiration, + self.inception, self.key_tag) + ho = struct.pack('!HBBIIIH', other.type_covered, + other.algorithm, other.labels, + other.original_ttl, other.expiration, + other.inception, other.key_tag) + v = cmp(hs, ho) + if v == 0: + v = cmp(self.signer, other.signer) + if v == 0: + v = cmp(self.signature, other.signature) + return v diff --git a/dns/renderer.py b/dns/renderer.py new file mode 100644 index 0000000..b8f433a --- /dev/null +++ b/dns/renderer.py @@ -0,0 +1,299 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: renderer.py,v 1.13 2004/03/19 00:17:27 halley Exp $ + +"""Help for building DNS wire format messages""" + +import cStringIO +import struct +import time + +import dns.exception +import dns.tsig + +QUESTION = 0 +ANSWER = 1 +AUTHORITY = 2 +ADDITIONAL = 3 + +class Renderer(object): + """Helper class for building DNS wire-format messages. + + Most applications can use the higher-level L{dns.message.Message} + class and its to_wire() method to generate wire-format messages. + This class is for those applications which need finer control + over the generation of messages. + + Typical use:: + + r = dns.renderer.Renderer(id=1, flags=0x80, max_size=512) + r.add_question(qname, qtype, qclass) + r.add_rrset(dns.renderer.ANSWER, rrset_1) + r.add_rrset(dns.renderer.ANSWER, rrset_2) + r.add_rrset(dns.renderer.AUTHORITY, ns_rrset) + r.add_edns(0, 0, 4096) + r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_1) + r.add_rrset(dns.renderer.ADDTIONAL, ad_rrset_2) + r.write_header() + r.add_tsig(keyname, secret, 300, 1, 0, '', request_mac) + wire = r.get_wire() + + @ivar output: where rendering is written + @type output: cStringIO.StringIO object + @ivar id: the message id + @type id: int + @ivar flags: the message flags + @type flags: int + @ivar max_size: the maximum size of the message + @type max_size: int + @ivar origin: the origin to use when rendering relative names + @type origin: dns.name.Name object + @ivar compress: the compression table + @type compress: dict + @ivar section: the section currently being rendered + @type section: int (dns.renderer.QUESTION, dns.renderer.ANSWER, + dns.renderer.AUTHORITY, or dns.renderer.ADDITIONAL) + @ivar counts: list of the number of RRs in each section + @type counts: int list of length 4 + @ivar mac: the MAC of the rendered message (if TSIG was used) + @type mac: string + """ + + def __init__(self, id=None, flags=0, max_size=65535, origin=None): + """Initialize a new renderer. + + @param id: the message id + @type id: int + @param flags: the DNS message flags + @type flags: int + @param max_size: the maximum message size; the default is 65535. + If rendering results in a message greater than I{max_size}, + then L{dns.exception.TooBig} will be raised. + @type max_size: int + @param origin: the origin to use when rendering relative names + @type origin: dns.name.Namem or None. + """ + + self.output = cStringIO.StringIO() + if id is None: + self.id = random.randint(0, 65535) + else: + self.id = id + self.flags = flags + self.max_size = max_size + self.origin = origin + self.compress = {} + self.section = QUESTION + self.counts = [0, 0, 0, 0] + self.output.write('\x00' * 12) + self.mac = '' + + def _rollback(self, where): + """Truncate the output buffer at offset I{where}, and remove any + compression table entries that pointed beyond the truncation + point. + + @param where: the offset + @type where: int + """ + + self.output.seek(where) + self.output.truncate() + keys_to_delete = [] + for k, v in self.compress.iteritems(): + if v >= where: + keys_to_delete.append(k) + for k in keys_to_delete: + del self.compress[k] + + def _set_section(self, section): + """Set the renderer's current section. + + Sections must be rendered order: QUESTION, ANSWER, AUTHORITY, + ADDITIONAL. Sections may be empty. + + @param section: the section + @type section: int + @raises dns.exception.FormError: an attempt was made to set + a section value less than the current section. + """ + + if self.section != section: + if self.section > section: + raise dns.exception.FormError + self.section = section + + def add_question(self, qname, rdtype, rdclass=dns.rdataclass.IN): + """Add a question to the message. + + @param qname: the question name + @type qname: dns.name.Name + @param rdtype: the question rdata type + @type rdtype: int + @param rdclass: the question rdata class + @type rdclass: int + """ + + self._set_section(QUESTION) + before = self.output.tell() + qname.to_wire(self.output, self.compress, self.origin) + self.output.write(struct.pack("!HH", rdtype, rdclass)) + after = self.output.tell() + if after >= self.max_size: + self._rollback(before) + raise dns.exception.TooBig + self.counts[QUESTION] += 1 + + def add_rrset(self, section, rrset, **kw): + """Add the rrset to the specified section. + + Any keyword arguments are passed on to the rdataset's to_wire() + routine. + + @param section: the section + @type section: int + @param rrset: the rrset + @type rrset: dns.rrset.RRset object + """ + + self._set_section(section) + before = self.output.tell() + n = rrset.to_wire(self.output, self.compress, self.origin, **kw) + after = self.output.tell() + if after >= self.max_size: + self._rollback(before) + raise dns.exception.TooBig + self.counts[section] += n + + def add_rdataset(self, section, name, rdataset, **kw): + """Add the rdataset to the specified section, using the specified + name as the owner name. + + Any keyword arguments are passed on to the rdataset's to_wire() + routine. + + @param section: the section + @type section: int + @param name: the owner name + @type name: dns.name.Name object + @param rdataset: the rdataset + @type rdataset: dns.rdataset.Rdataset object + """ + + self._set_section(section) + before = self.output.tell() + n = rdataset.to_wire(name, self.output, self.compress, self.origin, + **kw) + after = self.output.tell() + if after >= self.max_size: + self._rollback(before) + raise dns.exception.TooBig + self.counts[section] += n + + def add_edns(self, edns, ednsflags, payload): + """Add an EDNS OPT record to the message. + + @param edns: The EDNS level to use. + @type edns: int + @param ednsflags: EDNS flag values. + @type ednsflags: int + @param payload: The EDNS sender's payload field, which is the maximum + size of UDP datagram the sender can handle. + @type payload: int + @see: RFC 2671 + """ + + self._set_section(ADDITIONAL) + before = self.output.tell() + self.output.write(struct.pack('!BHHIH', 0, dns.rdatatype.OPT, payload, + ednsflags, 0)) + after = self.output.tell() + if after >= self.max_size: + self._rollback(before) + raise dns.exception.TooBig + self.counts[ADDITIONAL] += 1 + + def add_tsig(self, keyname, secret, fudge, id, tsig_error, other_data, + request_mac): + """Add a TSIG signature to the message. + + @param keyname: the TSIG key name + @type keyname: dns.name.Name object + @param secret: the secret to use + @type secret: string + @param fudge: TSIG time fudge; default is 300 seconds. + @type fudge: int + @param id: the message id to encode in the tsig signature + @type id: int + @param tsig_error: TSIG error code; default is 0. + @type tsig_error: int + @param other_data: TSIG other data. + @type other_data: string + @param request_mac: This message is a response to the request which + had the specified MAC. + @type request_mac: string + """ + + self._set_section(ADDITIONAL) + before = self.output.tell() + s = self.output.getvalue() + (tsig_rdata, self.mac, ctx) = dns.tsig.hmac_md5(s, + keyname, + secret, + int(time.time()), + fudge, + id, + tsig_error, + other_data, + request_mac) + keyname.to_wire(self.output, self.compress, self.origin) + self.output.write(struct.pack('!HHIH', dns.rdatatype.TSIG, + dns.rdataclass.ANY, 0, 0)) + rdata_start = self.output.tell() + self.output.write(tsig_rdata) + after = self.output.tell() + assert after - rdata_start < 65536 + if after >= self.max_size: + self._rollback(before) + raise dns.exception.TooBig + self.output.seek(rdata_start - 2) + self.output.write(struct.pack('!H', after - rdata_start)) + self.counts[ADDITIONAL] += 1 + self.output.seek(10) + self.output.write(struct.pack('!H', self.counts[ADDITIONAL])) + self.output.seek(0, 2) + + def write_header(self): + """Write the DNS message header. + + Writing the DNS message header is done asfter all sections + have been rendered, but before the optional TSIG signature + is added. + """ + + self.output.seek(0) + self.output.write(struct.pack('!HHHHHH', self.id, self.flags, + self.counts[0], self.counts[1], + self.counts[2], self.counts[3])) + self.output.seek(0, 2) + + def get_wire(self): + """Return the wire format message. + + @rtype: string + """ + + return self.output.getvalue() diff --git a/dns/resolver.py b/dns/resolver.py new file mode 100644 index 0000000..2e3ca20 --- /dev/null +++ b/dns/resolver.py @@ -0,0 +1,589 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: resolver.py,v 1.22 2004/03/19 00:17:27 halley Exp $ + +"""DNS stub resolver. + +@var default_resolver: The default resolver object +@type default_resolver: dns.resolver.Resolver object""" + +import socket +import sys +import time +import types + +import dns.exception +import dns.message +import dns.name +import dns.query +import dns.rcode +import dns.rdataclass +import dns.rdatatype + +if sys.platform == 'win32': + import _winreg + +class NXDOMAIN(dns.exception.DNSException): + """The query name does not exist.""" + pass + +# The definition of the Timeout exception has moved from here to the +# dns.exception module. We keep dns.resolver.Timeout defined for +# backwards compatibility. + +Timeout = dns.exception.Timeout + +class NoAnswer(dns.exception.DNSException): + """The response did not contain an answer to the question.""" + pass + +class NoNameservers(dns.exception.DNSException): + """No non-broken nameservers are available to answer the query.""" + pass + +class Answer(object): + """DNS stub resolver answer + + Instances of this class bundle up the result of a successful DNS + resolution. + + For convenience, the answer is iterable. "for a in answer" is + equivalent to "for a in answer.rrset". + + Note that CNAMEs or DNAMEs in the response may mean that answer + node's name might not be the query name. + + @ivar qname: The query name + @type qname: dns.name.Name object + @ivar rdtype: The query type + @type rdtype: int + @ivar rdclass: The query class + @type rdclass: int + @ivar response: The response message + @type response: dns.message.Message object + @ivar rrset: The answer + @type rrset: dns.rrset.RRset object + @ivar expiration: The time when the answer expires + @type expiration: float (seconds since the epoch) + """ + def __init__(self, qname, rdtype, rdclass, response): + self.qname = qname + self.rdtype = rdtype + self.rdclass = rdclass + self.response = response + min_ttl = -1 + rrset = None + for count in xrange(0, 15): + try: + rrset = response.find_rrset(response.answer, qname, + rdclass, rdtype) + if min_ttl == -1 or rrset.ttl < min_ttl: + min_ttl = rrset.ttl + break + except KeyError: + if rdtype != dns.rdatatype.CNAME: + try: + crrset = response.find_rrset(response.answer, + qname, + rdclass, + dns.rdatatype.CNAME) + if min_ttl == -1 or crrset.ttl < min_ttl: + min_ttl = crrset.ttl + for rd in crrset: + qname = rd.target + break + continue + except KeyError: + raise NoAnswer + raise NoAnswer + if rrset is None: + raise NoAnswer + self.rrset = rrset + self.expiration = time.time() + min_ttl + + def __getattr__(self, attr): + if attr == 'name': + return self.rrset.name + elif attr == 'ttl': + return self.rrset.ttl + elif attr == 'covers': + return self.rrset.covers + elif attr == 'rdclass': + return self.rrset.rdclass + elif attr == 'rdtype': + return self.rrset.rdtype + else: + raise AttributeError, attr + + def __len__(self): + return len(self.rrset) + + def __iter__(self): + return iter(self.rrset) + +class Cache(object): + """Simple DNS answer cache. + + @ivar data: A dictionary of cached data + @type data: dict + @ivar cleaning_interval: The number of seconds between cleanings. The + default is 300 (5 minutes). + @type cleaning_interval: float + @ivar next_cleaning: The time the cache should next be cleaned (in seconds + since the epoch.) + @type next_cleaning: float + """ + + def __init__(self, cleaning_interval=300.0): + """Initialize a DNS cache. + + @param cleaning_interval: the number of seconds between periodic + cleanings. The default is 300.0 + @type cleaning_interval: float. + """ + + self.data = {} + self.cleaning_interval = cleaning_interval + self.next_cleaning = time.time() + self.cleaning_interval + + def maybe_clean(self): + """Clean the cache if it's time to do so.""" + + now = time.time() + if self.next_cleaning <= now: + keys_to_delete = [] + for (k, v) in self.data.iteritems(): + if v.expiration <= now: + keys_to_delete.append(k) + for k in keys_to_delete: + del self.data[k] + now = time.time() + self.next_cleaning = now + self.cleaning_interval + + def get(self, key): + """Get the answer associated with I{key}. Returns None if + no answer is cached for the key. + @param key: the key + @type key: (dns.name.Name, int, int) tuple whose values are the + query name, rdtype, and rdclass. + @rtype: dns.resolver.Answer object or None + """ + + self.maybe_clean() + v = self.data.get(key) + if v is None or v.expiration <= time.time(): + return None + return v + + def put(self, key, value): + """Associate key and value in the cache. + @param key: the key + @type key: (dns.name.Name, int, int) tuple whose values are the + query name, rdtype, and rdclass. + @param value: The answer being cached + @type value: dns.resolver.Answer object + """ + + self.maybe_clean() + self.data[key] = value + + def flush(self, key=None): + """Flush the cache. + + If I{key} is specified, only that item is flushed. Otherwise + the entire cache is flushed. + + @param key: the key to flush + @type key: (dns.name.Name, int, int) tuple or None + """ + + if not key is None: + if self.data.has_key(key): + del self.data[key] + else: + self.data = {} + self.next_cleaning = time.time() + self.cleaning_interval + +class Resolver(object): + """DNS stub resolver + + @ivar domain: The domain of this host + @type domain: dns.name.Name object + @ivar nameservers: A list of nameservers to query. Each nameserver is + a string which contains the IP address of a nameserver. + @type nameservers: list of strings + @ivar search: The search list. If the query name is a relative name, + the resolver will construct an absolute query name by appending the search + names one by one to the query name. + @type search: list of dns.name.Name objects + @ivar port: The port to which to send queries. The default is 53. + @type port: int + @ivar timeout: The number of seconds to wait for a response from a + server, before timing out. + @type timeout: float + @ivar lifetime: The total number of seconds to spend trying to get an + answer to the question. If the lifetime expires, a Timeout exception + will occur. + @type lifetime: float + @ivar keyring: The TSIG keyring to use. The default is None. + @type keyring: dict + @ivar keyname: The TSIG keyname to use. The default is None. + @type keyname: dns.name.Name object + @ivar edns: The EDNS level to use. The default is -1, no Edns. + @type edns: int + @ivar ednsflags: The EDNS flags + @type ednsflags: int + @ivar payload: The EDNS payload size. The default is 0. + @type payload: int + @ivar cache: The cache to use. The default is None. + @type cache: dns.resolver.Cache object + """ + def __init__(self, filename='/etc/resolv.conf', configure=True): + """Initialize a resolver instance. + + @param filename: The filename of a configuration file in + standard /etc/resolv.conf format. This parameter is meaningful + only when I{configure} is true and the platform is POSIX. + @type filename: string or file object + @param configure: If True (the default), the resolver instance + is configured in the normal fashion for the operating system + the resolver is running on. (I.e. a /etc/resolv.conf file on + POSIX systems and from the registry on Windows systems.) + @type configure: bool""" + + self.reset() + if configure: + if sys.platform == 'win32': + self.read_registry() + elif filename: + self.read_resolv_conf(filename) + + def reset(self): + """Reset all resolver configuration to the defaults.""" + self.domain = \ + dns.name.Name(dns.name.from_text(socket.gethostname())[1:]) + if len(self.domain) == 0: + self.domain = dns.name.root + self.nameservers = [] + self.search = [] + self.port = 53 + self.timeout = 2.0 + self.lifetime = 30.0 + self.keyring = None + self.keyname = None + self.edns = -1 + self.ednsflags = 0 + self.payload = 0 + self.cache = None + + def read_resolv_conf(self, f): + """Process f as a file in the /etc/resolv.conf format. If f is + a string, it is used as the name of the file to open; otherwise it + is treated as the file itself.""" + if isinstance(f, str) or isinstance(f, unicode): + f = open(f, 'r') + want_close = True + else: + want_close = False + try: + for l in f: + if len(l) == 0 or l[0] == '#' or l[0] == ';': + continue + tokens = l.split() + if len(tokens) == 0: + continue + if tokens[0] == 'nameserver': + self.nameservers.append(tokens[1]) + elif tokens[0] == 'domain': + self.domain = dns.name.from_text(tokens[1]) + elif tokens[0] == 'search': + for suffix in tokens[1:]: + self.search.append(dns.name.from_text(suffix)) + finally: + if want_close: + f.close() + if len(self.nameservers) == 0: + self.nameservers.append('127.0.0.1') + + def _config_win32_nameservers(self, nameservers, split_char=','): + """Configure a NameServer registry entry.""" + # we call str() on nameservers to convert it from unicode to ascii + ns_list = str(nameservers).split(split_char) + for ns in ns_list: + if not ns in self.nameservers: + self.nameservers.append(ns) + + def _config_win32_domain(self, domain): + """Configure a Domain registry entry.""" + # we call str() on domain to convert it from unicode to ascii + self.domain = dns.name.from_text(str(domain)) + + def _config_win32_search(self, search): + """Configure a Search registry entry.""" + # we call str() on search to convert it from unicode to ascii + search_list = str(search).split(',') + for s in search_list: + if not s in self.search: + self.search.append(dns.name.from_text(s)) + + def _config_win32_fromkey(self, key): + """Extract DNS info from a registry key.""" + try: + servers, rtype = _winreg.QueryValueEx(key, 'NameServer') + except WindowsError: + servers = None + if servers: + self._config_win32_nameservers(servers) + try: + dom, rtype = _winreg.QueryValueEx(key, 'Domain') + if dom: + self._config_win32_domain(servers) + except WindowsError: + pass + else: + try: + servers, rtype = _winreg.QueryValueEx(key, 'DhcpNameServer') + except WindowsError: + servers = None + if servers: + # Annoyingly, the DhcpNameServer list is apparently space + # separated instead of comma separated like NameServer. + self._config_win32_nameservers(servers, ' ') + try: + dom, rtype = _winreg.QueryValueEx(key, 'DhcpDomain') + if dom: + self._config_win32_domain(servers) + except WindowsError: + pass + try: + search, rtype = _winreg.QueryValueEx(key, 'SearchList') + except WindowsError: + search = None + if search: + self._config_win32_search(servers) + + def read_registry(self): + """Extract resolver configuration from the Windows registry.""" + lm = _winreg.ConnectRegistry(None, _winreg.HKEY_LOCAL_MACHINE) + want_scan = False + try: + try: + # XP, 2000 + tcp_params = _winreg.OpenKey(lm, + r'SYSTEM\CurrentControlSet' + r'\Services\Tcpip\Parameters') + want_scan = True + except EnvironmentError: + # ME + tcp_params = _winreg.OpenKey(lm, + r'SYSTEM\CurrentControlSet' + r'\Services\VxD\MSTCP') + try: + self._config_win32_fromkey(tcp_params) + finally: + tcp_params.Close() + if want_scan: + interfaces = _winreg.OpenKey(lm, + r'SYSTEM\CurrentControlSet' + r'\Services\Tcpip\Parameters' + r'\Interfaces') + try: + i = 0 + while True: + try: + guid = _winreg.EnumKey(interfaces, i) + i += 1 + key = _winreg.OpenKey(interfaces, guid) + try: + # enabled interfaces seem to have a non-empty + # NTEContextList + try: + (nte, ttype) = _winreg.QueryValueEx(key, + 'NTEContextList') + except WindowsError: + nte = None + if nte: + self._config_win32_fromkey(key) + finally: + key.Close() + except EnvironmentError: + break + finally: + interfaces.Close() + finally: + lm.Close() + + def query(self, qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, + tcp=False): + """Query nameservers to find the answer to the question. + + The I{qname}, I{rdtype}, and I{rdclass} parameters may be objects + of the appropriate type, or strings that can be converted into objects + of the appropriate type. E.g. For I{rdtype} the integer 2 and the + the string 'NS' both mean to query for records with DNS rdata type NS. + + @param qname: the query name + @type qname: dns.name.Name object or string + @param rdtype: the query type + @type rdtype: int or string + @param rdclass: the query class + @type rdclass: int or string + @param tcp: use TCP to make the query (default is False). + @type tcp: bool + @rtype: dns.resolver.Answer instance + @raises Timeout: no answers could be found in the specified lifetime + @raises NXDOMAIN: the query name does not exist + @raises NoAnswer: the response did not contain an answer + @raises NoNameservers: no non-broken nameservers are available to + answer the question.""" + + if isinstance(qname, str): + qname = dns.name.from_text(qname, None) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(rdclass, str): + rdclass = dns.rdataclass.from_text(rdclass) + qnames_to_try = [] + if qname.is_absolute(): + qnames_to_try.append(qname) + else: + if len(qname) > 1: + qnames_to_try.append(qname.concatenate(dns.name.root)) + if self.search: + for suffix in self.search: + qnames_to_try.append(qname.concatenate(suffix)) + else: + qnames_to_try.append(qname.concatenate(self.domain)) + all_nxdomain = True + start = time.time() + for qname in qnames_to_try: + if self.cache: + answer = self.cache.get((qname, rdtype, rdclass)) + if answer: + return answer + request = dns.message.make_query(qname, rdtype, rdclass) + if not self.keyname is None: + request.use_tsig(self.keyring, self.keyname) + request.use_edns(self.edns, self.ednsflags, self.payload) + response = None + # + # make a copy of the servers list so we can alter it later. + # + nameservers = self.nameservers[:] + while response is None: + if len(nameservers) == 0: + raise NoNameservers + for nameserver in nameservers: + now = time.time() + if now < start: + # Time going backwards is bad. Just give up. + raise Timeout + duration = now - start + if duration >= self.lifetime: + raise Timeout + timeout = min(self.lifetime - duration, self.timeout) + try: + if tcp: + response = dns.query.tcp(request, nameserver, + timeout, self.port) + else: + response = dns.query.udp(request, nameserver, + timeout, self.port) + except socket.error: + # + # Communication failure or timeout. Go to the + # next server + # + response = None + continue + except dns.query.UnexpectedSource: + # + # Who knows? Keep going. + # + response = None + continue + except dns.exception.FormError: + # + # We don't understand what this server is + # saying. Take it out of the mix and + # continue. + # + nameservers.remove(nameserver) + response = None + continue + rcode = response.rcode() + if rcode == dns.rcode.NOERROR or \ + rcode == dns.rcode.NXDOMAIN: + break + response = None + if response.rcode() == dns.rcode.NXDOMAIN: + continue + all_nxdomain = False + break + if all_nxdomain: + raise NXDOMAIN + answer = Answer(qname, rdtype, rdclass, response) + if self.cache: + self.cache.put((qname, rdtype, rdclass), answer) + return answer + + def use_tsig(self, keyring, keyname=None): + """Add a TSIG signature to the query. + + @param keyring: The TSIG keyring to use; defaults to None. + @type keyring: dict + @param keyname: The name of the TSIG key to use; defaults to None. + The key must be defined in the keyring. If a keyring is specified + but a keyname is not, then the key used will be the first key in the + keyring. Note that the order of keys in a dictionary is not defined, + so applications should supply a keyname when a keyring is used, unless + they know the keyring contains only one key.""" + self.keyring = keyring + if keyname is None: + self.keyname = self.keyring.keys()[0] + else: + self.keyname = keyname + + def use_edns(self, edns, ednsflags, payload): + """Configure Edns. + + @param edns: The EDNS level to use. The default is -1, no Edns. + @type edns: int + @param ednsflags: The EDNS flags + @type ednsflags: int + @param payload: The EDNS payload size. The default is 0. + @type payload: int""" + + if edns is None: + edns = -1 + self.edns = edns + self.ednsflags = ednsflags + self.payload = payload + +default_resolver = None + +def query(qname, rdtype=dns.rdatatype.A, rdclass=dns.rdataclass.IN, + tcp=False): + """Query nameservers to find the answer to the question. + + This is a convenience function that uses the default resolver + object to make the query. + @see: L{dns.resolver.Resolver.query} for more information on the + parameters.""" + global default_resolver + if default_resolver is None: + default_resolver = Resolver() + return default_resolver.query(qname, rdtype, rdclass, tcp) diff --git a/dns/rrset.py b/dns/rrset.py new file mode 100644 index 0000000..7ccec85 --- /dev/null +++ b/dns/rrset.py @@ -0,0 +1,170 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rrset.py,v 1.10 2004/03/19 00:17:27 halley Exp $ + +"""DNS RRsets (an RRset is a named rdataset)""" + +import dns.name +import dns.rdataset +import dns.rdataclass +import dns.renderer + +class RRset(dns.rdataset.Rdataset): + """A DNS RRset (named rdataset). + + RRset inherits from Rdataset, and RRsets can be treated as + Rdatasets in most cases. There are, however, a few notable + exceptions. RRsets have different to_wire() and to_text() method + arguments, reflecting the fact that RRsets always have an owner + name. + """ + + __slots__ = ['name', 'deleting'] + + def __init__(self, name, rdclass, rdtype, covers=dns.rdatatype.NONE, + deleting=None): + """Create a new RRset.""" + + super(RRset, self).__init__(rdclass, rdtype) + self.name = name + self.deleting = deleting + + def _clone(self): + obj = super(RRset, self)._clone() + obj.name = self.name + obj.deleting = self.deleting + return obj + + def __repr__(self): + if self.covers == 0: + ctext = '' + else: + ctext = '(' + dns.rdatatype.to_text(self.covers) + ')' + if not self.deleting is None: + dtext = ' delete=' + dns.rdataclass.to_text(self.deleting) + else: + dtext = '' + return '<DNS ' + str(self.name) + ' ' + \ + dns.rdataclass.to_text(self.rdclass) + ' ' + \ + dns.rdatatype.to_text(self.rdtype) + ctext + dtext + ' RRset>' + + def __str__(self): + return self.to_text() + + def __eq__(self, other): + """Two RRsets are equal if they have the same name and the same + rdataset + + @rtype: bool""" + if not isinstance(other, RRset): + return False + if self.name != other.name: + return False + return super(RRset, self).__eq__(other) + + def match(self, name, rdclass, rdtype, covers, deleting=None): + """Returns True if this rrset matches the specified class, type, + covers, and deletion state.""" + + if not super(RRset, self).match(rdclass, rdtype, covers): + return False + if self.name != name or self.deleting != deleting: + return False + return True + + def to_text(self, origin=None, relativize=True, **kw): + """Convert the RRset into DNS master file format. + + @see: L{dns.name.Name.choose_relativity} for more information + on how I{origin} and I{relativize} determine the way names + are emitted. + + Any additional keyword arguments are passed on to the rdata + to_text() method. + + @param origin: The origin for relative names, or None. + @type origin: dns.name.Name object + @param relativize: True if names should names be relativized + @type relativize: bool""" + + return super(RRset, self).to_text(self.name, origin, relativize, + self.deleting, **kw) + + def to_wire(self, file, compress=None, origin=None, **kw): + """Convert the RRset to wire format.""" + + return super(RRset, self).to_wire(self.name, file, compress, origin, + self.deleting, **kw) + + +def from_text_list(name, ttl, rdclass, rdtype, text_rdatas): + """Create an RRset with the specified name, TTL, class, and type, and with + the specified list of rdatas in text format. + + @rtype: dns.rrset.RRset object + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if isinstance(rdclass, str): + rdclass = dns.rdataclass.from_text(rdclass) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + r = RRset(name, rdclass, rdtype) + r.update_ttl(ttl) + for t in text_rdatas: + rd = dns.rdata.from_text(r.rdclass, r.rdtype, t) + r.add(rd) + return r + +def from_text(name, ttl, rdclass, rdtype, *text_rdatas): + """Create an RRset with the specified name, TTL, class, and type and with + the specified rdatas in text format. + + @rtype: dns.rrset.RRset object + """ + + return from_text_list(name, ttl, rdclass, rdtype, text_rdatas) + +def from_rdata_list(name, ttl, rdatas): + """Create an RRset with the specified name and TTL, and with + the specified list of rdata objects. + + @rtype: dns.rrset.RRset object + """ + + if isinstance(name, str): + name = dns.name.from_text(name, None) + + if len(rdatas) == 0: + raise ValueError, "rdata list must not be empty" + r = None + for rd in rdatas: + if r is None: + r = RRset(name, rd.rdclass, rd.rdtype) + r.update_ttl(ttl) + first_time = False + r.add(rd) + return r + +def from_rdata(name, ttl, *rdatas): + """Create an RRset with the specified name and TTL, and with + the specified rdata objects. + + @rtype: dns.rrset.RRset object + """ + + return from_rdata_list(name, ttl, rdatas) diff --git a/dns/set.py b/dns/set.py new file mode 100644 index 0000000..5f996fe --- /dev/null +++ b/dns/set.py @@ -0,0 +1,253 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: set.py,v 1.6 2004/03/19 00:17:27 halley Exp $ + +"""A simple Set class.""" + +class Set(object): + """A simple set class. + + Sets are not in Python until 2.3, and rdata are not immutable so + we cannot use sets.Set anyway. This class implements subset of + the 2.3 Set interface using a list as the container. + + @ivar items: A list of the items which are in the set + @type items: list""" + + __slots__ = ['items'] + + def __init__(self, items=None): + """Initialize the set. + + @param items: the initial set of items + @type items: any iterable or None + """ + + self.items = [] + if not items is None: + for item in items: + self.add(item) + + def __repr__(self): + return "dns.simpleset.Set(%s)" % repr(self.items) + + def add(self, item): + """Add an item to the set.""" + if not item in self.items: + self.items.append(item) + + def remove(self, item): + """Remove an item from the set.""" + self.items.remove(item) + + def discard(self, item): + """Remove an item from the set if present.""" + try: + self.items.remove(item) + except ValueError: + pass + + def _clone(self): + """Make a (shallow) copy of the set. + + There is a 'clone protocol' that subclasses of this class + should use. To make a copy, first call your super's _clone() + method, and use the object returned as the new instance. Then + make shallow copies of the attributes defined in the subclass. + + This protocol allows us to write the set algorithms that + return new instances (e.g. union) once, and keep using them in + subclasses. + """ + + cls = self.__class__ + obj = cls.__new__(cls) + obj.items = list(self.items) + return obj + + def __copy__(self): + """Make a (shallow) copy of the set.""" + return self._clone() + + def copy(self): + """Make a (shallow) copy of the set.""" + return self._clone() + + def union_update(self, other): + """Update the set, adding any elements from other which are not + already in the set. + @param other: the collection of items with which to update the set + @type other: Set object + """ + if not isinstance(other, Set): + raise ValueError, 'other must be a Set instance' + if self is other: + return + for item in other.items: + self.add(item) + + def intersection_update(self, other): + """Update the set, removing any elements from other which are not + in both sets. + @param other: the collection of items with which to update the set + @type other: Set object + """ + if not isinstance(other, Set): + raise ValueError, 'other must be a Set instance' + if self is other: + return + # we make a copy of the list so that we can remove items from + # the list without breaking the iterator. + for item in list(self.items): + if item not in other.items: + self.items.remove(item) + + def difference_update(self, other): + """Update the set, removing any elements from other which are in + the set. + @param other: the collection of items with which to update the set + @type other: Set object + """ + if not isinstance(other, Set): + raise ValueError, 'other must be a Set instance' + if self is other: + self.items = [] + else: + for item in other.items: + self.discard(item) + + def union(self, other): + """Return a new set which is the union of I{self} and I{other}. + + @param other: the other set + @type other: Set object + @rtype: the same type as I{self} + """ + + obj = self._clone() + obj.union_update(other) + return obj + + def intersection(self, other): + """Return a new set which is the intersection of I{self} and I{other}. + + @param other: the other set + @type other: Set object + @rtype: the same type as I{self} + """ + + obj = self._clone() + obj.intersection_update(other) + return obj + + def difference(self, other): + """Return a new set which I{self} - I{other}, i.e. the items + in I{self} which are not also in I{other}. + + @param other: the other set + @type other: Set object + @rtype: the same type as I{self} + """ + + obj = self._clone() + obj.difference_update(other) + return obj + + def __or__(self, other): + return self.union(other) + + def __and__(self, other): + return self.intersection(other) + + def __add__(self, other): + return self.union(other) + + def __sub__(self, other): + return self.difference(other) + + def __ior__(self, other): + self.union_update(other) + return self + + def __iand__(self, other): + self.intersection_update(other) + return self + + def __iadd__(self, other): + self.union_update(other) + return self + + def __isub__(self, other): + self.difference_update(other) + return self + + def update(self, other): + """Update the set, adding any elements from other which are not + already in the set. + @param other: the collection of items with which to update the set + @type other: any iterable type""" + for item in other: + self.add(item) + + def clear(self): + """Make the set empty.""" + self.items = [] + + def __eq__(self, other): + # Yes, this is inefficient but the sets we're dealing with are + # usually quite small, so it shouldn't hurt too much. + for item in self.items: + if not item in other.items: + return False + for item in other.items: + if not item in self.items: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __len__(self): + return len(self.items) + + def __iter__(self): + return iter(self.items) + + def issubset(self, other): + """Is I{self} a subset of I{other}? + + @rtype: bool + """ + + if not isinstance(other, Set): + raise ValueError, 'other must be a Set instance' + for item in self.items: + if not item in other.items: + return False + return True + + def issuperset(self, other): + """Is I{self} a superset of I{other}? + + @rtype: bool + """ + + if not isinstance(other, Set): + raise ValueError, 'other must be a Set instance' + for item in other.items: + if not item in self.items: + return False + return True diff --git a/dns/tokenizer.py b/dns/tokenizer.py new file mode 100644 index 0000000..ad078ff --- /dev/null +++ b/dns/tokenizer.py @@ -0,0 +1,424 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: tokenizer.py,v 1.24 2004/03/19 00:17:27 halley Exp $ + +"""Tokenize DNS master file format""" + +import cStringIO +import sys + +import dns.exception +import dns.name + +_DELIMITERS = { + ' ' : True, + '\t' : True, + '\n' : True, + ';' : True, + '(' : True, + ')' : True, + '"' : True } + +_QUOTING_DELIMITERS = { '"' : True } + +EOF = 0 +EOL = 1 +WHITESPACE = 2 +IDENTIFIER = 3 +QUOTED_STRING = 4 +COMMENT = 5 +DELIMITER = 6 + +class UngetBufferFull(dns.exception.DNSException): + """Raised when an attempt is made to unget a token when the unget + buffer is full.""" + pass + +class Tokenizer(object): + """A DNS master file format tokenizer. + + A token is a (type, value) tuple, where I{type} is an int, and + I{value} is a string. The valid types are EOF, EOL, WHITESPACE, + IDENTIFIER, QUOTED_STRING, COMMENT, and DELIMITER. + + @ivar file: The file to tokenize + @type file: file + @ivar ungotten_char: The most recently ungotten character, or None. + @type ungotten_char: string + @ivar ungotten_token: The most recently ungotten token, or None. + @type ungotten_token: string + @ivar multiline: The current multiline level. This value is increased + by one every time a '(' delimiter is read, and decreased by one every time + a ')' delimiter is read. + @type multiline: int + @ivar quoting: This variable is true if the tokenizer is currently + reading a quoted string. + @type quoting: bool + @ivar eof: This variable is true if the tokenizer has encountered EOF. + @type eof: bool + @ivar delimiters: The current delimiter dictionary. + @type delimiters: dict + @ivar line_number: The current line number + @type line_number: int + @ivar filename: A filename that will be returned by the L{where} method. + @type filename: string + """ + + def __init__(self, f=sys.stdin, filename=None): + """Initialize a tokenizer instance. + + @param f: The file to tokenize. The default is sys.stdin. + This parameter may also be a string, in which case the tokenizer + will take its input from the contents of the string. + @type f: file or string + @param filename: the name of the filename that the L{where} method + will return. + @type filename: string + """ + + if isinstance(f, str): + f = cStringIO.StringIO(f) + if filename is None: + filename = '<string>' + else: + if filename is None: + if f is sys.stdin: + filename = '<stdin>' + else: + filename = '<file>' + self.file = f + self.ungotten_char = None + self.ungotten_token = None + self.multiline = 0 + self.quoting = False + self.eof = False + self.delimiters = _DELIMITERS + self.line_number = 1 + self.filename = filename + + def _get_char(self): + """Read a character from input. + @rtype: string + """ + + if self.ungotten_char is None: + if self.eof: + c = '' + else: + c = self.file.read(1) + if c == '': + self.eof = True + elif c == '\n': + self.line_number += 1 + else: + c = self.ungotten_char + self.ungotten_char = None + return c + + def where(self): + """Return the current location in the input. + + @rtype: (string, int) tuple. The first item is the filename of + the input, the second is the current line number. + """ + + return (self.filename, self.line_number) + + def _unget_char(self, c): + """Unget a character. + + The unget buffer for characters is only one character large; it is + an error to try to unget a character when the unget buffer is not + empty. + + @param c: the character to unget + @type c: string + @raises UngetBufferFull: there is already an ungotten char + """ + + if not self.ungotten_char is None: + raise UngetBufferFull + self.ungotten_char = c + + def skip_whitespace(self): + """Consume input until a non-whitespace character is encountered. + + The non-whitespace character is then ungotten, and the number of + whitespace characters consumed is returned. + + If the tokenizer is in multiline mode, then newlines are whitespace. + + @rtype: int + """ + + skipped = 0 + while True: + c = self._get_char() + if c != ' ' and c != '\t': + if (c != '\n') or not self.multiline: + self._unget_char(c) + return skipped + skipped += 1 + + def get(self, want_leading = False, want_comment = False): + """Get the next token. + + @param want_leading: If True, return a WHITESPACE token if the + first character read is whitespace. The default is False. + @type want_leading: bool + @param want_comment: If True, return a COMMENT token if the + first token read is a comment. The default is False. + @type want_comment: bool + @rtype: (int, string) tuple + @raises dns.exception.UnexpectedEnd: input ended prematurely + @raises dns.exception.SyntaxError: input was badly formed + """ + + if not self.ungotten_token is None: + token = self.ungotten_token + self.ungotten_token = None + if token[0] == WHITESPACE: + if want_leading: + return token + elif token[0] == COMMENT: + if want_comment: + return token + else: + return token + skipped = self.skip_whitespace() + if want_leading and skipped > 0: + return (WHITESPACE, ' ') + token = '' + ttype = IDENTIFIER + while True: + c = self._get_char() + if c == '' or c in self.delimiters: + if c == '' and self.quoting: + raise dns.exception.UnexpectedEnd + if token == '' and ttype != QUOTED_STRING: + if c == '(': + self.multiline += 1 + self.skip_whitespace() + continue + elif c == ')': + if not self.multiline > 0: + raise dns.exception.SyntaxError + self.multiline -= 1 + self.skip_whitespace() + continue + elif c == '"': + if not self.quoting: + self.quoting = True + self.delimiters = _QUOTING_DELIMITERS + ttype = QUOTED_STRING + continue + else: + self.quoting = False + self.delimiters = _DELIMITERS + self.skip_whitespace() + continue + elif c == '\n': + return (EOL, '\n') + elif c == ';': + while 1: + c = self._get_char() + if c == '\n' or c == '': + break + token += c + if want_comment: + self._unget_char(c) + return (COMMENT, token) + elif c == '': + if self.multiline: + raise dns.exception.SyntaxError, \ + 'unbalanced parentheses' + return (EOF, '') + elif self.multiline: + self.skip_whitespace() + token = '' + continue + else: + return (EOL, '\n') + else: + # This code exists in case we ever want a + # delimiter to be returned. It never produces + # a token currently. + token = c + ttype = DELIMITER + else: + self._unget_char(c) + break + elif self.quoting: + if c == '\\': + c = self._get_char() + if c == '': + raise dns.exception.UnexpectedEnd + if c.isdigit(): + c2 = self._get_char() + if c2 == '': + raise dns.exception.UnexpectedEnd + c3 = self._get_char() + if c == '': + raise dns.exception.UnexpectedEnd + if not (c2.isdigit() and c3.isdigit()): + raise dns.exception.SyntaxError + c = chr(int(c) * 100 + int(c2) * 10 + int(c3)) + elif c == '\n': + raise dns.exception.SyntaxError, 'newline in quoted string' + elif c == '\\': + # + # Treat \ followed by a delimiter as the + # delimiter, otherwise leave it alone. + # + c = self._get_char() + if c == '' or not c in self.delimiters: + self._unget_char(c) + c = '\\' + token += c + if token == '' and ttype != QUOTED_STRING: + if self.multiline: + raise dns.exception.SyntaxError, 'unbalanced parentheses' + ttype = EOF + return (ttype, token) + + def unget(self, token): + """Unget a token. + + The unget buffer for tokens is only one token large; it is + an error to try to unget a token when the unget buffer is not + empty. + + @param token: the token to unget + @type token: string + @raises UngetBufferFull: there is already an ungotten char + """ + + if not self.ungotten_token is None: + raise UngetBufferFull + self.ungotten_token = token + + def next(self): + """Return the next item in an iteration. + @rtype: (int, string) + """ + + token = self.get() + if token[0] == EOF: + raise StopIteration + return token + + def __iter__(self): + return self + + # Helpers + + def get_int(self): + """Read the next token and interpret it as an integer. + + @raises dns.exception.SyntaxError: + @rtype: int + """ + + (ttype, value) = self.get() + if ttype != IDENTIFIER: + raise dns.exception.SyntaxError, 'expecting an identifier' + if not value.isdigit(): + raise dns.exception.SyntaxError, 'expecting an integer' + return int(value) + + def get_uint8(self): + """Read the next token and interpret it as an 8-bit unsigned + integer. + + @raises dns.exception.SyntaxError: + @rtype: int + """ + + value = self.get_int() + if value < 0 or value > 255: + raise dns.exception.SyntaxError, \ + '%d is not an unsigned 8-bit integer' % value + return value + + def get_uint16(self): + """Read the next token and interpret it as a 16-bit unsigned + integer. + + @raises dns.exception.SyntaxError: + @rtype: int + """ + + value = self.get_int() + if value < 0 or value > 65535: + raise dns.exception.SyntaxError, \ + '%d is not an unsigned 16-bit integer' % value + return value + + def get_uint32(self): + """Read the next token and interpret it as a 32-bit unsigned + integer. + + @raises dns.exception.SyntaxError: + @rtype: int + """ + + (ttype, value) = self.get() + if ttype != IDENTIFIER: + raise dns.exception.SyntaxError, 'expecting an identifier' + if not value.isdigit(): + raise dns.exception.SyntaxError, 'expecting an integer' + value = long(value) + if value < 0 or value > 4294967296L: + raise dns.exception.SyntaxError, \ + '%d is not an unsigned 32-bit integer' % value + return value + + def get_string(self, origin=None): + """Read the next token and interpret it as a string. + + @raises dns.exception.SyntaxError: + @rtype: string + """ + + (ttype, t) = self.get() + if ttype != IDENTIFIER and ttype != QUOTED_STRING: + raise dns.exception.SyntaxError, 'expecting a string' + return t + + def get_name(self, origin=None): + """Read the next token and interpret it as a DNS name. + + @raises dns.exception.SyntaxError: + @rtype: dns.name.Name object""" + + (ttype, t) = self.get() + if ttype != IDENTIFIER: + raise dns.exception.SyntaxError, 'expecting an identifier' + return dns.name.from_text(t, origin) + + def get_eol(self): + """Read the next token and raise an exception if it isn't EOL or + EOF. + + @raises dns.exception.SyntaxError: + @rtype: string + """ + + (ttype, t) = self.get() + if ttype != EOL and ttype != EOF: + raise dns.exception.SyntaxError, \ + 'expected EOL or EOF, got %d "%s"' % (ttype, t) + return t diff --git a/dns/tsig.py b/dns/tsig.py new file mode 100644 index 0000000..10787d8 --- /dev/null +++ b/dns/tsig.py @@ -0,0 +1,125 @@ +# Copyright (C) 2001-2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: tsig.py,v 1.10 2004/03/19 00:17:27 halley Exp $ + +"""DNS TSIG support.""" + +import hmac +import struct + +import dns.exception +import dns.rdataclass +import dns.name + +class BadTime(dns.exception.DNSException): + """Raised if the current time is not within the TSIG's validity time.""" + pass + +class BadSignature(dns.exception.DNSException): + """Raised if the TSIG signature fails to verify.""" + pass + +_alg_name = dns.name.from_text('HMAC-MD5.SIG-ALG.REG.INT.').to_digestable() + +def hmac_md5(wire, keyname, secret, time, fudge, original_id, error, + other_data, request_mac, ctx=None, multi=False, first=True): + """Return a (tsig_rdata, mac, ctx) tuple containing the HMAC-MD5 TSIG rdata + for the input parameters, the HMAC-MD5 MAC calculated by applying the + TSIG signature algorithm, and the TSIG digest context. + @rtype: (string, string, hmac.HMAC object) + @raises ValueError: I{other_data} is too long + """ + + if first: + ctx = hmac.new(secret) + ml = len(request_mac) + if ml > 0: + ctx.update(struct.pack('!H', ml)) + ctx.update(request_mac) + id = struct.pack('!H', original_id) + ctx.update(id) + ctx.update(wire[2:]) + if first: + ctx.update(keyname.to_digestable()) + ctx.update(struct.pack('!H', dns.rdataclass.ANY)) + ctx.update(struct.pack('!I', 0)) + long_time = time + 0L + upper_time = (long_time >> 32) & 0xffffL + lower_time = long_time & 0xffffffffL + time_mac = struct.pack('!HIH', upper_time, lower_time, fudge) + pre_mac = _alg_name + time_mac + ol = len(other_data) + if ol > 65535: + raise ValueError, 'TSIG Other Data is > 65535 bytes' + post_mac = struct.pack('!HH', error, ol) + other_data + if first: + ctx.update(pre_mac) + ctx.update(post_mac) + else: + ctx.update(time_mac) + mac = ctx.digest() + mpack = struct.pack('!H', len(mac)) + tsig_rdata = pre_mac + mpack + mac + id + post_mac + if multi: + ctx = hmac.new(secret) + ml = len(mac) + ctx.update(struct.pack('!H', ml)) + ctx.update(mac) + else: + ctx = None + return (tsig_rdata, mac, ctx) + +def validate(wire, keyname, secret, now, request_mac, tsig_start, tsig_rdata, + tsig_rdlen, ctx=None, multi=False, first=True): + """Validate the specified TSIG rdata against the other input parameters. + + @raises FormError: The TSIG is badly formed. + @raises BadTime: There is too much time skew between the client and the + server. + @raises BadSignature: The TSIG signature did not validate + @rtype: hmac.HMAC object""" + + (adcount,) = struct.unpack("!H", wire[10:12]) + if adcount == 0: + raise dns.exception.FormError + adcount -= 1 + new_wire = wire[0:10] + struct.pack("!H", adcount) + wire[12:tsig_start] + current = tsig_rdata + (aname, used) = dns.name.from_wire(wire, current) + current = current + used + (upper_time, lower_time, fudge, mac_size) = \ + struct.unpack("!HIHH", wire[current:current + 10]) + time = ((upper_time + 0L) << 32) + (lower_time + 0L) + current += 10 + mac = wire[current:current + mac_size] + current += mac_size + (original_id, error, other_size) = \ + struct.unpack("!HHH", wire[current:current + 6]) + current += 6 + other_data = wire[current:current + other_size] + current += other_size + if current != tsig_rdata + tsig_rdlen: + raise dns.exception.FormError + time_low = time - fudge + time_high = time + fudge + if now < time_low or now > time_high: + raise BadTime + (junk, our_mac, ctx) = hmac_md5(new_wire, keyname, secret, time, fudge, + original_id, error, other_data, + request_mac, ctx, multi, first) + if (our_mac != mac): + raise BadSignature + return ctx diff --git a/dns/tsigkeyring.py b/dns/tsigkeyring.py new file mode 100644 index 0000000..8294f91 --- /dev/null +++ b/dns/tsigkeyring.py @@ -0,0 +1,46 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: tsigkeyring.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +"""A place to store TSIG keys.""" + +import base64 + +import dns.name + +def from_text(textring): + """Convert a dictionary containing (textual DNS name, base64 secret) pairs + into a binary keyring which has (dns.name.Name, binary secret) pairs. + @rtype: dict""" + + keyring = {} + for keytext in textring: + keyname = dns.name.from_text(keytext) + secret = base64.decodestring(textring[keytext]) + keyring[keyname] = secret + return keyring + +def to_text(keyring): + """Convert a dictionary containing (dns.name.Name, binary secret) pairs + into a text keyring which has (textual DNS name, base64 secret) pairs. + @rtype: dict""" + + textring = {} + for keyname in keyring: + keytext = dns.name.to_text(keyname) + secret = base64.encodestring(keyring[keyname]) + textring[keytext] = secret + return textring diff --git a/dns/ttl.py b/dns/ttl.py new file mode 100644 index 0000000..2feb33f --- /dev/null +++ b/dns/ttl.py @@ -0,0 +1,63 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: ttl.py,v 1.2 2004/03/19 00:17:27 halley Exp $ + +"""DNS TTL conversion.""" + +import dns.exception + +class BadTTL(dns.exception.SyntaxError): + pass + +def from_text(text): + """Convert the text form of a TTL to an integer. + + The BIND 8 units syntax for TTLs (e.g. '1w6d4h3m10s') is supported. + + @param text: the textual TTL + @type text: string + @raises dns.ttl.BadTTL: the TTL is not well-formed + @rtype: int + """ + + if text.isdigit(): + return int(text) + if not text[0].isdigit(): + raise BadTTL + total = 0 + current = 0 + for c in text: + if c.isdigit(): + current *= 10 + current += int(c) + else: + c = c.lower() + if c == 'w': + total += current * 604800 + elif c == 'd': + total += current * 86400 + elif c == 'h': + total += current * 3600 + elif c == 'm': + total += current * 60 + elif c == 's': + total += current + else: + raise BadTTL, "unknown unit '%s'" % c + current = 0 + if not current == 0: + raise BadTTL, "trailing integer" + return total diff --git a/dns/update.py b/dns/update.py new file mode 100644 index 0000000..973610e --- /dev/null +++ b/dns/update.py @@ -0,0 +1,242 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: update.py,v 1.15 2004/03/19 00:17:27 halley Exp $ + +"""DNS Dynamic Update Support""" + +import dns.message +import dns.name +import dns.opcode +import dns.rdata +import dns.rdataclass +import dns.rdataset + +class Update(dns.message.Message): + def __init__(self, zone, rdclass=dns.rdataclass.IN, keyring=None, + keyname=None): + """Initialize a new DNS Update object. + + @param zone: The zone which is being updated. + @type zone: A dns.name.Name or string + @param rdclass: The class of the zone; defaults to dns.rdataclass.IN. + @type rdclass: An int designating the class, or a string whose value + is the name of a class. + @param keyring: The TSIG keyring to use; defaults to None. + @type keyring: dict + @param keyname: The name of the TSIG key to use; defaults to None. + The key must be defined in the keyring. If a keyring is specified + but a keyname is not, then the key used will be the first key in the + keyring. Note that the order of keys in a dictionary is not defined, + so applications should supply a keyname when a keyring is used, unless + they know the keyring contains only one key. + @type keyname: dns.name.Name or string + """ + super(Update, self).__init__() + self.flags |= dns.opcode.to_flags(dns.opcode.UPDATE) + if isinstance(zone, str): + zone = dns.name.from_text(zone) + else: + zone = zone.copy() + self.origin = zone + if isinstance(rdclass, str): + rdclass = dns.rdataclass.from_text(rdclass) + self.zone_rdclass = rdclass + self.find_rrset(self.question, self.origin, rdclass, dns.rdatatype.SOA, + create=True, force_unique=True) + if not keyring is None: + self.use_tsig(keyring, keyname) + + def _add_rr(self, name, ttl, rd, deleting=None, section=None): + """Add a single RR to the update section.""" + + if section is None: + section = self.authority + covers = rd.covers() + rrset = self.find_rrset(section, name, self.zone_rdclass, rd.rdtype, + covers, deleting, True, True) + rrset.add(rd, ttl) + + def _add(self, replace, section, name, *args): + """Add records. The first argument is the replace mode. If + false, RRs are added to an existing RRset; if true, the RRset + is replaced with the specified contents. The second + argument is the section to add to. The third argument + is always a name. The other arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string...""" + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if isinstance(args[0], dns.rdataset.Rdataset): + for rds in args: + if replace: + self.delete(name, rds.rdtype) + for rd in rds: + self._add_rr(name, rds.ttl, rd, section=section) + else: + args = list(args) + ttl = int(args.pop(0)) + if isinstance(args[0], dns.rdata.Rdata): + if replace: + self.delete(name, args[0].rdtype) + for rd in args: + self._add_rr(name, ttl, rd, section=section) + else: + rdtype = args.pop(0) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if replace: + self.delete(name, rdtype) + for s in args: + rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, + self.origin) + self._add_rr(name, ttl, rd, section=section) + + def add(self, name, *args): + """Add records. The first argument is always a name. The other + arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string...""" + self._add(False, self.authority, name, *args) + + def delete(self, name, *args): + """Delete records. The first argument is always a name. The other + arguments can be: + + - I{nothing} + + - rdataset... + + - rdata... + + - rdtype, [string...]""" + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if len(args) == 0: + rrset = self.find_rrset(self.authority, name, dns.rdataclass.ANY, + dns.rdatatype.ANY, dns.rdatatype.NONE, + dns.rdatatype.ANY, True, True) + elif isinstance(args[0], dns.rdataset.Rdataset): + for rds in args: + for rd in rds: + self._add_rr(name, 0, rd, dns.rdatatype.NONE) + else: + args = list(args) + if isinstance(args[0], dns.rdata.Rdata): + for rd in args: + self._add_rr(name, 0, rd, dns.rdatatype.NONE) + else: + rdtype = args.pop(0) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if len(args) == 0: + rrset = self.find_rrset(self.authority, name, + self.zone_rdclass, rdtype, + dns.rdatatype.NONE, + dns.rdataclass.ANY, + True, True) + else: + for s in args: + rd = dns.rdata.from_text(self.zone_rdclass, rdtype, s, + self.origin) + self._add_rr(name, 0, rd, dns.rdataclass.NONE) + + def replace(self, name, *args): + """Replace records. The first argument is always a name. The other + arguments can be: + + - rdataset... + + - ttl, rdata... + + - ttl, rdtype, string... + + Note that if you want to replace the entire node, you should do + a delete of the name followed by one or more calls to add.""" + + self._add(True, self.authority, name, *args) + + def present(self, name, *args): + """Require that an owner name (and optionally an rdata type, + or specific rdataset) exists as a prerequisite to the + execution of the update. The first argument is always a name. + The other arguments can be: + + - rdataset... + + - rdata... + + - rdtype, string...""" + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if len(args) == 0: + rrset = self.find_rrset(self.answer, name, + dns.rdataclass.ANY, dns.rdatatype.ANY, + dns.rdatatype.NONE, None, + True, True) + elif isinstance(args[0], dns.rdataset.Rdataset) or \ + isinstance(args[0], dns.rdata.Rdata) or \ + len(args) > 1: + if len(args) > 1: + # Add a 0 TTL + args = list(args) + args.insert(0, 0) + self._add(False, self.answer, name, *args) + else: + rdtype = args[0] + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + rrset = self.find_rrset(self.answer, name, + dns.rdataclass.ANY, rdtype, + dns.rdatatype.NONE, None, + True, True) + + def absent(self, name, rdtype=None): + """Require that an owner name (and optionally an rdata type) does + not exist as a prerequisite to the execution of the update.""" + + if isinstance(name, str): + name = dns.name.from_text(name, None) + if rdtype is None: + rrset = self.find_rrset(self.answer, name, + dns.rdataclass.NONE, dns.rdatatype.ANY, + dns.rdatatype.NONE, None, + True, True) + else: + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + rrset = self.find_rrset(self.answer, name, + dns.rdataclass.NONE, rdtype, + dns.rdatatype.NONE, None, + True, True) + + def to_wire(self, origin=None, max_size=65535): + """Return a string containing the update in DNS compressed wire + format. + @rtype: string""" + if origin is None: + origin = self.origin + return super(Update, self).to_wire(origin, max_size) diff --git a/dns/version.py b/dns/version.py new file mode 100644 index 0000000..1141b1b --- /dev/null +++ b/dns/version.py @@ -0,0 +1,36 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: version.py,v 1.20 2004/03/19 00:17:27 halley Exp $ + +"""dnspython release version information.""" + +MAJOR = 1 +MINOR = 3 +MICRO = 0 +RELEASELEVEL = 0x0f +SERIAL = 0 + +if RELEASELEVEL == 0x0f: + version = '%d.%d.%d' % (MAJOR, MINOR, MICRO) +elif RELEASELEVEL == 0x00: + version = '%d.%d.%dx%d' % \ + (MAJOR, MINOR, MICRO, SERIAL) +else: + version = '%d.%d.%d%x%d' % \ + (MAJOR, MINOR, MICRO, RELEASELEVEL, SERIAL) + +hexversion = MAJOR << 24 | MINOR << 16 | MICRO << 8 | RELEASELEVEL << 4 | \ + SERIAL diff --git a/dns/zone.py b/dns/zone.py new file mode 100644 index 0000000..711e26d --- /dev/null +++ b/dns/zone.py @@ -0,0 +1,827 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: zone.py,v 1.45 2004/03/19 00:17:27 halley Exp $ + +"""DNS Zones.""" + +from __future__ import generators + +import sys + +import dns.exception +import dns.name +import dns.node +import dns.rdataclass +import dns.rdatatype +import dns.rdata +import dns.tokenizer +import dns.ttl + +class BadZone(dns.exception.DNSException): + """The zone is malformed.""" + pass + +class NoSOA(BadZone): + """The zone has no SOA RR at its origin.""" + pass + +class NoNS(BadZone): + """The zone has no NS RRset at its origin.""" + pass + +class Zone(object): + """A DNS zone. + + A Zone is a mapping from names to nodes. The zone object may be + treated like a Python dictionary, e.g. zone[name] will retrieve + the node associated with that name. The I{name} may be a + dns.name.Name object, or it may be a string. In the either case, + if the name is relative it is treated as relative to the origin of + the zone. + + @ivar rdclass: The zone's rdata class; the default is class IN. + @type rdclass: int + @ivar origin: The origin of the zone. + @type origin: dns.name.Name object + @ivar nodes: A dictionary mapping the names of nodes in the zone to the + nodes themselves. + @type nodes: dict + @ivar relativize: should names in the zone be relativized? + @type relativize: bool + @cvar node_factory: the factory used to create a new node + @type node_factory: class or callable + """ + + node_factory = dns.node.Node + + __slots__ = ['rdclass', 'origin', 'nodes', 'relativize'] + + def __init__(self, origin, rdclass=dns.rdataclass.IN, relativize=True): + """Initialize a zone object. + + @param origin: The origin of the zone. + @type origin: dns.name.Name object + @param rdclass: The zone's rdata class; the default is class IN. + @type rdclass: int""" + + self.rdclass = rdclass + self.origin = origin + self.nodes = {} + self.relativize = relativize + + def __eq__(self, other): + """Two zones are equal if they have the same origin, class, and + nodes. + @rtype: bool + """ + + if not isinstance(other, Zone): + return False + if self.rdclass != other.rdclass or \ + self.origin != other.origin or \ + self.nodes != other.nodes: + return False + return True + + def __ne__(self, other): + """Are two zones not equal? + @rtype: bool + """ + + return not self.__eq__(other) + + def _validate_name(self, name): + if isinstance(name, str): + name = dns.name.from_text(name, None) + elif not isinstance(name, dns.name.Name): + raise KeyError, \ + "name parameter must be convertable to a DNS name" + if name.is_absolute(): + if not name.is_subdomain(self.origin): + raise KeyError, \ + "name parameter must be a subdomain of the zone origin" + if self.relativize: + name = name.relativize(self.origin) + return name + + def __getitem__(self, key): + key = self._validate_name(key) + return self.nodes[key] + + def __setitem__(self, key, value): + key = self._validate_name(key) + self.nodes[key] = value + + def __delitem__(self, key): + key = self._validate_name(key) + del self.nodes[key] + + def __iter__(self): + return self.nodes.iterkeys() + + def iterkeys(self): + return self.nodes.iterkeys() + + def keys(self): + return self.nodes.keys() + + def itervalues(self): + return self.nodes.itervalues() + + def values(self): + return self.nodes.values() + + def iteritems(self): + return self.nodes.iteritems() + + def items(self): + return self.nodes.items() + + def get(self, key): + key = self._validate_name(key) + return self.nodes.get(key) + + def __contains__(self, other): + return other in self.nodes + + def find_node(self, name, create=False): + """Find a node in the zone, possibly creating it. + + @param name: the name of the node to find + @type name: dns.name.Name object or string + @param create: should the node be created if it doesn't exist? + @type create: bool + @raises KeyError: the name is not known and create was not specified. + @rtype dns.node.Node object + """ + + name = self._validate_name(name) + node = self.nodes.get(name) + if node is None: + if not create: + raise KeyError + node = self.node_factory() + self.nodes[name] = node + return node + + def get_node(self, name, create=False): + """Get a node in the zone, possibly creating it. + + This method is like L{find_node}, except it returns None instead + of raising an exception if the node does not exist and creation + has not been requested. + + @param name: the name of the node to find + @type name: dns.name.Name object or string + @param create: should the node be created if it doesn't exist? + @type create: bool + @rtype dns.node.Node object or None + """ + + try: + node = self.find_node(name, create) + except KeyError: + node = None + return node + + def delete_node(self, name): + """Delete the specified node if it exists. + + It is not an error if the node does not exist. + """ + + name = self._validate_name(name) + if self.nodes.has_key(name): + del self.nodes[name] + + def find_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, + create=False): + """Look for rdata with the specified name and type in the zone, + and return an rdataset encapsulating it. + + The I{name}, I{rdtype}, and I{covers} parameters may be + strings, in which case they will be converted to their proper + type. + + The rdataset returned is not a copy; changes to it will change + the zone. + + KeyError is raised if the name or type are not found. + Use L{get_rdataset} if you want to have None returned instead. + + @param name: the owner name to look for + @type name: DNS.name.Name object or string + @param rdtype: the rdata type desired + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + @param create: should the node and rdataset be created if they do not + exist? + @type create: bool + @raises KeyError: the node or rdata could not be found + @rtype: dns.rrset.RRset object + """ + + name = self._validate_name(name) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(covers, str): + covers = dns.rdatatype.from_text(covers) + node = self.find_node(name, create) + return node.find_rdataset(self.rdclass, rdtype, covers, create) + + def get_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE, + create=False): + """Look for rdata with the specified name and type in the zone, + and return an rdataset encapsulating it. + + The I{name}, I{rdtype}, and I{covers} parameters may be + strings, in which case they will be converted to their proper + type. + + The rdataset returned is not a copy; changes to it will change + the zone. + + None is returned if the name or type are not found. + Use L{find_rdataset} if you want to have KeyError raised instead. + + @param name: the owner name to look for + @type name: DNS.name.Name object or string + @param rdtype: the rdata type desired + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + @param create: should the node and rdataset be created if they do not + exist? + @type create: bool + @rtype: dns.rrset.RRset object + """ + + try: + rdataset = self.find_rdataset(name, rdtype, covers, create) + except KeyError: + rdataset = None + return rdataset + + def delete_rdataset(self, name, rdtype, covers=dns.rdatatype.NONE): + """Delete the rdataset matching I{rdtype} and I{covers}, if it + exists at the node specified by I{name}. + + The I{name}, I{rdtype}, and I{covers} parameters may be + strings, in which case they will be converted to their proper + type. + + It is not an error if the node does not exist, or if there is no + matching rdataset at the node. + + If the node has no rdatasets after the deletion, it will itself + be deleted. + + @param name: the owner name to look for + @type name: DNS.name.Name object or string + @param rdtype: the rdata type desired + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + """ + + name = self._validate_name(name) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(covers, str): + covers = dns.rdatatype.from_text(covers) + node = self.get_node(name) + if not node is None: + node.delete_rdataset(self.rdclass, rdtype, covers) + if len(node) == 0: + self.delete_node(name) + + def replace_rdataset(self, name, replacement): + """Replace an rdataset at name. + + It is not an error if there is no rdataset matching I{replacement}. + + Ownership of the I{replacement} object is transferred to the zone; + in other words, this method does not store a copy of I{replacement} + at the node, it stores I{replacement} itself. + + If the I{name} node does not exist, it is created. + + @param name: the owner name + @type name: DNS.name.Name object or string + @param replacement: the replacement rdataset + @type replacement: dns.rdataset.Rdataset + """ + + if replacement.rdclass != self.rdclass: + raise ValueError, 'replacement.rdclass != zone.rdclass' + node = self.find_node(name, True) + node.replace_rdataset(replacement) + + def find_rrset(self, name, rdtype, covers=dns.rdatatype.NONE): + """Look for rdata with the specified name and type in the zone, + and return an RRset encapsulating it. + + The I{name}, I{rdtype}, and I{covers} parameters may be + strings, in which case they will be converted to their proper + type. + + This method is less efficient than the similar + L{find_rdataset} because it creates an RRset instead of + returning the matching rdataset. It may be more convenient + for some uses since it returns an object which binds the owner + name to the rdata. + + This method may not be used to create new nodes or rdatasets; + use L{find_rdataset} instead. + + KeyError is raised if the name or type are not found. + Use L{get_rrset} if you want to have None returned instead. + + @param name: the owner name to look for + @type name: DNS.name.Name object or string + @param rdtype: the rdata type desired + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + @raises KeyError: the node or rdata could not be found + @rtype: dns.rrset.RRset object + """ + + name = self._validate_name(name) + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(covers, str): + covers = dns.rdatatype.from_text(covers) + rdataset = self.nodes[name].find_rdataset(self.rdclass, rdtype, covers) + rrset = dns.rrset.RRset(name, self.rdclass, rdtype, covers) + rrset.update(rdataset) + return rrset + + def get_rrset(self, name, rdtype, covers=dns.rdatatype.NONE): + """Look for rdata with the specified name and type in the zone, + and return an RRset encapsulating it. + + The I{name}, I{rdtype}, and I{covers} parameters may be + strings, in which case they will be converted to their proper + type. + + This method is less efficient than the similar L{get_rdataset} + because it creates an RRset instead of returning the matching + rdataset. It may be more convenient for some uses since it + returns an object which binds the owner name to the rdata. + + This method may not be used to create new nodes or rdatasets; + use L{find_rdataset} instead. + + None is returned if the name or type are not found. + Use L{find_rrset} if you want to have KeyError raised instead. + + @param name: the owner name to look for + @type name: DNS.name.Name object or string + @param rdtype: the rdata type desired + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + @rtype: dns.rrset.RRset object + """ + + try: + rrset = self.find_rrset(name, rdtype, covers) + except KeyError: + rrset = None + return rrset + + def iterate_rdatasets(self, rdtype=None, covers=dns.rdatatype.NONE): + """Return a generator which yields (name, rdataset) tuples for + all rdatasets in the zone which have the specified I{rdtype} + and I{covers}. If I{rdtype} is dns.rdatatype.ANY, the default, + then all rdatasets will be matched. + + @param rdtype: int or string + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + """ + + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(covers, str): + covers = dns.rdatatype.from_text(covers) + for (name, node) in self.iteritems(): + for rds in node: + if rdtype == dns.rdatatype.ANY or \ + (rds.rdtype == rdtype and rds.covers == covers): + yield (name, rds) + + def iterate_rdatas(self, rdtype=None, covers=dns.rdatatype.NONE): + """Return a generator which yields (name, ttl, rdata) tuples for + all rdatas in the zone which have the specified I{rdtype} + and I{covers}. If I{rdtype} is dns.rdatatype.ANY, the default, + then all rdatas will be matched. + + @param rdtype: int or string + @type rdtype: int or string + @param covers: the covered type (defaults to None) + @type covers: int or string + """ + + if isinstance(rdtype, str): + rdtype = dns.rdatatype.from_text(rdtype) + if isinstance(covers, str): + covers = dns.rdatatype.from_text(covers) + for (name, node) in self.iteritems(): + for rds in node: + if rdtype == dns.rdatatype.ANY or \ + (rds.rdtype == rdtype and rds.covers == covers): + for rdata in rds: + yield (name, rds.ttl, rdata) + + def to_file(self, f, sorted=True, relativize=True, nl=None): + """Write a zone to a file. + + @param f: file or string. If I{f} is a string, it is treated + as the name of a file to open. + @param sorted: if True, the file will be written with the + names sorted in DNSSEC order from least to greatest. Otherwise + the names will be written in whatever order they happen to have + in the zone's dictionary. + @param relativize: if True, domain names in the output will be + relativized to the zone's origin (if possible). + @type relativize: bool + @param nl: The end of line string. If not specified, the + output will use the platform's native end-of-line marker (i.e. + LF on POSIX, CRLF on Windows, CR on Macintosh). + @type nl: string or None + """ + + if sys.hexversion >= 0x02030000: + # allow Unicode filenames + str_type = basestring + else: + str_type = str + if nl is None: + opts = 'w' + else: + opts = 'wb' + if isinstance(f, str_type): + f = file(f, opts) + want_close = True + else: + want_close = False + try: + if sorted: + names = self.keys() + names.sort() + else: + names = self.iterkeys() + for n in names: + l = self[n].to_text(n, origin=self.origin, + relativize=relativize) + if nl is None: + print >> f, l + else: + f.write(l) + f.write(nl) + finally: + if want_close: + f.close() + + def check_origin(self): + """Do some simple checking of the zone's origin. + + @raises dns.zone.NoSOA: there is no SOA RR + @raises dns.zone.NoNS: there is no NS RRset + @raises KeyError: there is no origin node + """ + if self.relativize: + name = dns.name.empty + else: + name = self.origin + if self.get_rdataset(name, dns.rdatatype.SOA) is None: + raise NoSOA + if self.get_rdataset(name, dns.rdatatype.NS) is None: + raise NoNS + + +class _MasterReader(object): + """Read a DNS master file + + @ivar tok: The tokenizer + @type tok: dns.tokenizer.Tokenizer object + @ivar ttl: The default TTL + @type ttl: int + @ivar last_name: The last name read + @type last_name: dns.name.Name object + @ivar current_origin: The current origin + @type current_origin: dns.name.Name object + @ivar relativize: should names in the zone be relativized? + @type relativize: bool + @ivar zone: the zone + @type zone: dns.zone.Zone object + @ivar saved_state: saved reader state (used when processing $INCLUDE) + @type saved_state: list of (tokenizer, current_origin, last_name, file) + tuples. + @ivar current_file: the file object of the $INCLUDed file being parsed + (None if no $INCLUDE is active). + @ivar allow_include: is $INCLUDE allowed? + @type allow_include: bool + """ + + def __init__(self, tok, origin, rdclass, relativize, zone_factory=Zone, + allow_include=False): + if isinstance(origin, str): + origin = dns.name.from_text(origin) + self.tok = tok + self.current_origin = origin + self.relativize = relativize + self.ttl = 0 + self.last_name = None + self.zone = zone_factory(origin, rdclass, relativize=relativize) + self.saved_state = [] + self.current_file = None + self.allow_include = allow_include + + def _eat_line(self): + while 1: + (ttype, t) = self.tok.get() + if ttype == dns.tokenizer.EOL or ttype == dns.tokenizer.EOF: + break + + def _rr_line(self): + """Process one line from a DNS master file.""" + # Name + token = self.tok.get(want_leading = True) + if token[0] != dns.tokenizer.WHITESPACE: + self.last_name = dns.name.from_text(token[1], self.current_origin) + name = self.last_name + if not name.is_subdomain(self.zone.origin): + self._eat_line() + return + if self.relativize: + name = name.relativize(self.zone.origin) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + # TTL + try: + ttl = dns.ttl.from_text(token[1]) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + except dns.ttl.BadTTL: + ttl = self.ttl + # Class + try: + rdclass = dns.rdataclass.from_text(token[1]) + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError + except dns.exception.SyntaxError: + raise dns.exception.SyntaxError + except: + rdclass = self.zone.rdclass + if rdclass != self.zone.rdclass: + raise dns.exception.SyntaxError, "RR class is not zone's class" + # Type + try: + rdtype = dns.rdatatype.from_text(token[1]) + except: + raise dns.exception.SyntaxError, \ + "unknown rdatatype '%s'" % token[1] + n = self.zone.nodes.get(name) + if n is None: + n = self.zone.node_factory() + self.zone.nodes[name] = n + try: + rd = dns.rdata.from_text(rdclass, rdtype, self.tok, + self.current_origin, False) + except dns.exception.SyntaxError: + # Catch and reraise. + (ty, va) = sys.exc_info()[:2] + raise ty, va + except: + # All exceptions that occur in the processing of rdata + # are treated as syntax errors. This is not strictly + # correct, but it is correct almost all of the time. + # We convert them to syntax errors so that we can emit + # helpful filename:line info. + + (ty, va) = sys.exc_info()[:2] + raise dns.exception.SyntaxError, \ + "caught exception %s: %s" % (str(ty), str(va)) + + rd.choose_relativity(self.zone.origin, self.relativize) + covers = rd.covers() + rds = n.find_rdataset(rdclass, rdtype, covers, True) + rds.add(rd, ttl) + + def read(self): + """Read a DNS master file and build a zone object. + + @raises dns.zone.NoSOA: No SOA RR was found at the zone origin + @raises dns.zone.NoNS: No NS RRset was found at the zone origin + """ + + try: + while 1: + token = self.tok.get(True, True) + if token[0] == dns.tokenizer.EOF: + if not self.current_file is None: + self.current_file.close() + if len(self.saved_state) > 0: + (self.tok, + self.current_origin, + self.last_name, + self.current_file, + self.ttl) = self.saved_state.pop(-1) + continue + break + elif token[0] == dns.tokenizer.EOL: + continue + elif token[0] == dns.tokenizer.COMMENT: + self.tok.get_eol() + continue + elif token[1][0] == '$': + u = token[1].upper() + if u == '$TTL': + token = self.tok.get() + if token[0] != dns.tokenizer.IDENTIFIER: + raise dns.exception.SyntaxError, "bad $TTL" + self.ttl = dns.ttl.from_text(token[1]) + self.tok.get_eol() + elif u == '$ORIGIN': + self.current_origin = self.tok.get_name() + self.tok.get_eol() + elif u == '$INCLUDE' and self.allow_include: + token = self.tok.get() + if token[0] != dns.tokenizer.QUOTED_STRING: + raise dns.exception.SyntaxError, \ + "bad filename in $INCLUDE" + filename = token[1] + token = self.tok.get() + if token[0] == dns.tokenizer.IDENTIFIER: + new_origin = dns.name.from_text(token[1], \ + self.current_origin) + self.tok.get_eol() + elif token[0] != dns.tokenizer.EOL and \ + token[0] != dns.tokenizer.EOF: + raise dns.exception.SyntaxError, \ + "bad origin in $INCLUDE" + else: + new_origin = self.current_origin + self.saved_state.append((self.tok, + self.current_origin, + self.last_name, + self.current_file, + self.ttl)) + self.current_file = file(filename, 'r') + self.tok = dns.tokenizer.Tokenizer(self.current_file, + filename) + self.current_origin = new_origin + else: + raise dns.exception.SyntaxError, \ + "Unknown master file directive '" + u + "'" + continue + self.tok.unget(token) + self._rr_line() + except dns.exception.SyntaxError, detail: + (filename, line_number) = self.tok.where() + if detail is None: + detail = "syntax error" + raise dns.exception.SyntaxError, \ + "%s:%d: %s" % (filename, line_number, detail) + + # Now that we're done reading, do some basic checking of the zone. + self.zone.check_origin() + +def from_text(text, origin, rdclass = dns.rdataclass.IN, relativize = True, + zone_factory=Zone, filename=None, allow_include=False): + """Build a zone object from a master file format string. + + @param text: the master file format input + @type text: string. + @param origin: The origin of the zone. + @type origin: dns.name.Name object or string + @param rdclass: The zone's rdata class; the default is class IN. + @type rdclass: int + @param relativize: should names be relativized? The default is True + @type relativize: bool + @param zone_factory: The zone factory to use + @type zone_factory: function returning a Zone + @param filename: The filename to emit when describing where an error + occurred; the default is '<string>'. + @param allow_include: is $INCLUDE allowed? + @type allow_include: bool + @type filename: string + @raises dns.zone.NoSOA: No SOA RR was found at the zone origin + @raises dns.zone.NoNS: No NS RRset was found at the zone origin + @rtype: dns.zone.Zone object + """ + + # 'text' can also be a file, but we don't publish that fact + # since it's an implementation detail. The official file + # interface is from_file(). + + if filename is None: + filename = '<string>' + tok = dns.tokenizer.Tokenizer(text, filename) + reader = _MasterReader(tok, origin, rdclass, relativize, zone_factory, + allow_include=allow_include) + reader.read() + return reader.zone + +def from_file(f, origin, rdclass = dns.rdataclass.IN, relativize = True, + zone_factory=Zone, filename=None, allow_include=True): + """Read a master file and build a zone object. + + @param f: file or string. If I{f} is a string, it is treated + as the name of a file to open. + @param origin: The origin of the zone. + @type origin: dns.name.Name object or string + @param rdclass: The zone's rdata class; the default is class IN. + @type rdclass: int + @param relativize: should names be relativized? The default is True + @type relativize: bool + @param zone_factory: The zone factory to use + @type zone_factory: function returning a Zone + @param filename: The filename to emit when describing where an error + occurred; the default is '<file>', or the value of I{f} if I{f} is a + string. + @type filename: string + @param allow_include: is $INCLUDE allowed? + @type allow_include: bool + @raises dns.zone.NoSOA: No SOA RR was found at the zone origin + @raises dns.zone.NoNS: No NS RRset was found at the zone origin + @rtype: dns.zone.Zone object + """ + + if sys.hexversion >= 0x02030000: + # allow Unicode filenames; turn on universal newline support + str_type = basestring + opts = 'rU' + else: + str_type = str + opts = 'r' + if isinstance(f, str_type): + if filename is None: + filename = f + f = file(f, opts) + want_close = True + else: + if filename is None: + filename = '<file>' + want_close = False + + try: + z = from_text(f, origin, rdclass, relativize, zone_factory, + filename, allow_include) + finally: + if want_close: + f.close() + return z + +def from_xfr(xfr, zone_factory=Zone, relativize=True): + """Convert the output of a zone transfer generator into a zone object. + + @param xfr: The xfr generator + @type xfr: generator of dns.message.Message objects + @param relativize: should names be relativized? The default is True + @type relativize: bool + @raises dns.zone.NoSOA: No SOA RR was found at the zone origin + @raises dns.zone.NoNS: No NS RRset was found at the zone origin + @rtype: dns.zone.Zone object + """ + + z = None + for r in xfr: + if z is None: + origin = r.answer[0].name + rdclass = r.answer[0].rdclass + z = zone_factory(origin, rdclass, relativize=relativize) + for rrset in r.answer: + znode = z.nodes.get(rrset.name) + if not znode: + znode = z.node_factory() + z.nodes[rrset.name] = znode + zrds = znode.find_rdataset(rrset.rdclass, rrset.rdtype, + rrset.covers, True) + zrds.update_ttl(rrset.ttl) + for rd in rrset: + rd.choose_relativity(z.origin, relativize) + zrds.add(rd) + z.check_origin() + return z diff --git a/examples/.arch-ids/=id b/examples/.arch-ids/=id new file mode 100644 index 0000000..4ddde91 --- /dev/null +++ b/examples/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.7 diff --git a/examples/.arch-ids/mx.py.id b/examples/.arch-ids/mx.py.id new file mode 100644 index 0000000..dd66337 --- /dev/null +++ b/examples/.arch-ids/mx.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:59 2004 4771.0 diff --git a/examples/.arch-ids/name.py.id b/examples/.arch-ids/name.py.id new file mode 100644 index 0000000..11a3966 --- /dev/null +++ b/examples/.arch-ids/name.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:59 2004 4771.1 diff --git a/examples/.arch-ids/reverse.py.id b/examples/.arch-ids/reverse.py.id new file mode 100644 index 0000000..ad99422 --- /dev/null +++ b/examples/.arch-ids/reverse.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:59 2004 4771.2 diff --git a/examples/.arch-ids/xfr.py.id b/examples/.arch-ids/xfr.py.id new file mode 100644 index 0000000..6f7fd73 --- /dev/null +++ b/examples/.arch-ids/xfr.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:59 2004 4771.3 diff --git a/examples/mx.py b/examples/mx.py new file mode 100755 index 0000000..3036e70 --- /dev/null +++ b/examples/mx.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python + +import dns.resolver + +answers = dns.resolver.query('nominum.com', 'MX') +for rdata in answers: + print 'Host', rdata.exchange, 'has preference', rdata.preference diff --git a/examples/name.py b/examples/name.py new file mode 100755 index 0000000..b099c49 --- /dev/null +++ b/examples/name.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +import dns.name + +n = dns.name.from_text('www.dnspython.org') +o = dns.name.from_text('dnspython.org') +print n.is_subdomain(o) # True +print n.is_superdomain(o) # False +print n > o # True +rel = n.relativize(o) # rel is the relative name www +n2 = rel + o +print n2 == n # True +print n.labels # ['www', 'dnspython', 'org', ''] diff --git a/examples/reverse.py b/examples/reverse.py new file mode 100755 index 0000000..47facc7 --- /dev/null +++ b/examples/reverse.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python + +# Usage: reverse.py <zone_filename>... +# +# This demo script will load in all of the zones specified by the +# filenames on the command line, find all the A RRs in them, and +# construct a reverse mapping table that maps each IP address used to +# the list of names mapping to that address. The table is then sorted +# nicely and printed. +# +# Note! The zone name is taken from the basename of the filename, so +# you must use filenames like "/wherever/you/like/dnspython.org" and +# not something like "/wherever/you/like/foo.db" (unless you're +# working with the ".db" GTLD, of course :)). +# +# If this weren't a demo script, there'd be a way of specifying the +# origin for each zone instead of constructing it from the filename. + +import dns.zone +import dns.ipv4 +import os.path +import sys + +reverse_map = {} + +for filename in sys.argv[1:]: + zone = dns.zone.from_file(filename, os.path.basename(filename), + relativize=False) + for (name, ttl, rdata) in zone.iterate_rdatas('A'): + l = reverse_map.get(rdata.address) + if l is None: + l = [] + reverse_map[rdata.address] = l + l.append(name) + +keys = reverse_map.keys() +keys.sort(lambda a1, a2: cmp(dns.ipv4.inet_aton(a1), dns.ipv4.inet_aton(a2))) +for k in keys: + v = reverse_map[k] + v.sort() + l = map(str, v) # convert names to strings for prettier output + print k, l diff --git a/examples/xfr.py b/examples/xfr.py new file mode 100755 index 0000000..5cd6f55 --- /dev/null +++ b/examples/xfr.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python + +import dns.query +import dns.zone + +z = dns.zone.from_xfr(dns.query.xfr('204.152.189.147', 'dnspython.org')) +names = z.nodes.keys() +names.sort() +for n in names: + print z[n].to_text(n) diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..9432c7e --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: setup.py,v 1.26 2004/03/19 00:17:27 halley Exp $ + +from distutils.core import setup + +setup( + name = "dnspython", + version = "1.3.0", + description = "DNS toolkit", + long_description = \ + """dnspython is a DNS toolkit for Python. It supports almost all +record types. It can be used for queries, zone transfers, and dynamic +updates. It supports TSIG authenticated messages and EDNS0. + +dnspython provides both high and low level access to DNS. The high +level classes perform queries for data of a given name, type, and +class, and return an answer set. The low level classes allow +direct manipulation of DNS zones, messages, names, and records.""", + + author = "Bob Halley", + author_email = "halley@dnspython.org", + license = "BSD-like", + url = "http://www.dnspython.org", + packages = ['dns', 'dns.rdtypes', 'dns.rdtypes.IN', 'dns.rdtypes.ANY'] + ) diff --git a/tests/.arch-ids/=id b/tests/.arch-ids/=id new file mode 100644 index 0000000..f704598 --- /dev/null +++ b/tests/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.9 diff --git a/tests/.arch-ids/Makefile.id b/tests/.arch-ids/Makefile.id new file mode 100644 index 0000000..e304867 --- /dev/null +++ b/tests/.arch-ids/Makefile.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:47 2004 4768.0 diff --git a/tests/.arch-ids/example.id b/tests/.arch-ids/example.id new file mode 100644 index 0000000..73a2373 --- /dev/null +++ b/tests/.arch-ids/example.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:47 2004 4768.1 diff --git a/tests/.arch-ids/example1.good.id b/tests/.arch-ids/example1.good.id new file mode 100644 index 0000000..4fa1afb --- /dev/null +++ b/tests/.arch-ids/example1.good.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:47 2004 4768.2 diff --git a/tests/.arch-ids/example2.good.id b/tests/.arch-ids/example2.good.id new file mode 100644 index 0000000..0098daf --- /dev/null +++ b/tests/.arch-ids/example2.good.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:47 2004 4768.3 diff --git a/tests/.arch-ids/flags.py.id b/tests/.arch-ids/flags.py.id new file mode 100644 index 0000000..27676dd --- /dev/null +++ b/tests/.arch-ids/flags.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.0 diff --git a/tests/.arch-ids/message.py.id b/tests/.arch-ids/message.py.id new file mode 100644 index 0000000..2de23bc --- /dev/null +++ b/tests/.arch-ids/message.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.1 diff --git a/tests/.arch-ids/name.py.id b/tests/.arch-ids/name.py.id new file mode 100644 index 0000000..c1f7d2c --- /dev/null +++ b/tests/.arch-ids/name.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.2 diff --git a/tests/.arch-ids/namedict.py.id b/tests/.arch-ids/namedict.py.id new file mode 100644 index 0000000..3d43530 --- /dev/null +++ b/tests/.arch-ids/namedict.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.3 diff --git a/tests/.arch-ids/ntoaaton.py.id b/tests/.arch-ids/ntoaaton.py.id new file mode 100644 index 0000000..3eda100 --- /dev/null +++ b/tests/.arch-ids/ntoaaton.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.4 diff --git a/tests/.arch-ids/rdtypeandclass.py.id b/tests/.arch-ids/rdtypeandclass.py.id new file mode 100644 index 0000000..334a901 --- /dev/null +++ b/tests/.arch-ids/rdtypeandclass.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.5 diff --git a/tests/.arch-ids/resolver.py.id b/tests/.arch-ids/resolver.py.id new file mode 100644 index 0000000..6ba850a --- /dev/null +++ b/tests/.arch-ids/resolver.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.6 diff --git a/tests/.arch-ids/rrset.py.id b/tests/.arch-ids/rrset.py.id new file mode 100644 index 0000000..e8cd418 --- /dev/null +++ b/tests/.arch-ids/rrset.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.7 diff --git a/tests/.arch-ids/set.py.id b/tests/.arch-ids/set.py.id new file mode 100644 index 0000000..c2cf5c1 --- /dev/null +++ b/tests/.arch-ids/set.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.8 diff --git a/tests/.arch-ids/tokenizer.py.id b/tests/.arch-ids/tokenizer.py.id new file mode 100644 index 0000000..521e3e8 --- /dev/null +++ b/tests/.arch-ids/tokenizer.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.9 diff --git a/tests/.arch-ids/update.py.id b/tests/.arch-ids/update.py.id new file mode 100644 index 0000000..177ccfd --- /dev/null +++ b/tests/.arch-ids/update.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.10 diff --git a/tests/.arch-ids/zone.py.id b/tests/.arch-ids/zone.py.id new file mode 100644 index 0000000..2187624 --- /dev/null +++ b/tests/.arch-ids/zone.py.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:35 2004 4766.11 diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..9e0c6c2 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,26 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: Makefile,v 1.5 2004/03/19 00:17:27 halley Exp $ + +PYTHON=python + +check: test + +test: + @for i in *.py; do \ + echo "Running $$i:"; \ + ${PYTHON} $$i || exit 1; \ + done diff --git a/tests/example b/tests/example new file mode 100644 index 0000000..1b96f1b --- /dev/null +++ b/tests/example @@ -0,0 +1,192 @@ +; Copyright (C) 2000, 2001 Internet Software Consortium. +; +; Permission to use, copy, modify, and distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM +; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +; INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING +; FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, +; NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION +; WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +; $Id: example,v 1.13 2004/03/19 00:06:37 halley Exp $ + +$ORIGIN . +$TTL 300 ; 5 minutes +example IN SOA ns1.example. hostmaster.example. ( + 1 ; serial + 2000 ; refresh (2000 seconds) + 2000 ; retry (2000 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) +example. NS ns1.example. +ns1.example. A 10.53.0.1 +example. NS ns2.example. +ns2.example. A 10.53.0.2 + +$ORIGIN example. +* MX 10 mail +a TXT "foo foo foo" + PTR foo.net. +$TTL 3600 ; 1 hour +a01 A 0.0.0.0 +a02 A 255.255.255.255 +;; +;; XXXRTH dnspython doesn't currently implement A6, and since +;; A6 records are effectively dead, it may never do so. +;; +;;a601 A6 0 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff +;; A6 64 ::ffff:ffff:ffff:ffff foo. +;; A6 127 ::1 foo. +;; A6 128 . +aaaa01 AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff +aaaa02 AAAA ::1 +afsdb01 AFSDB 0 hostname +afsdb02 AFSDB 65535 . +$TTL 300 ; 5 minutes +b CNAME foo.net. +c A 73.80.65.49 +$TTL 3600 ; 1 hour +cert01 CERT 65534 65535 PRIVATEOID ( + MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi + WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl + d80jEeC8aTrO+KKmCaY= ) +cname01 CNAME cname-target. +cname02 CNAME cname-target +cname03 CNAME . +$TTL 300 ; 5 minutes +d A 73.80.65.49 +$TTL 3600 ; 1 hour +dname01 DNAME dname-target. +dname02 DNAME dname-target +dname03 DNAME . +$TTL 300 ; 5 minutes +e MX 10 mail + TXT "one" + TXT "three" + TXT "two" + A 73.80.65.49 + A 73.80.65.50 + A 73.80.65.52 + A 73.80.65.51 +f A 73.80.65.52 +$TTL 3600 ; 1 hour +gpos01 GPOS "-22.6882" "116.8652" "250.0" +;; +;; XXXRTH I have commented out the following line because I don't think +;; it is a valid GPOS record. +;; +;;gpos02 GPOS "" "" "" +hinfo01 HINFO "Generic PC clone" "NetBSD-1.4" +hinfo02 HINFO "PC" "NetBSD" +isdn01 ISDN "isdn-address" +isdn02 ISDN "isdn-address" "subaddress" +isdn03 ISDN "isdn-address" +isdn04 ISDN "isdn-address" "subaddress" +key01 KEY 512 255 1 ( + AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR + yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 + GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o + jqf0BaqHT+8= ) +key02 KEY HOST|FLAG4 DNSSEC RSAMD5 ( + AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR + yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 + GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o + jqf0BaqHT+8= ) +kx01 KX 10 kdc +kx02 KX 10 . +loc01 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m +loc02 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20m 2000m 20m +loc03 LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000m 20m +;; +;; XXXRTH These are all obsolete and unused. dnspython doesn't implement +;; them +;;mb01 MG madname +;;mb02 MG . +;;mg01 MG mgmname +;;mg02 MG . +;;minfo01 MINFO rmailbx emailbx +;;minfo02 MINFO . . +;;mr01 MR mrname +;;mr02 MR . +mx01 MX 10 mail +mx02 MX 10 . +naptr01 NAPTR 0 0 "" "" "" . +naptr02 NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. +nsap-ptr01 NSAP-PTR foo. + NSAP-PTR . +nsap01 NSAP 0x47000580005a0000000001e133ffffff00016100 +nsap02 NSAP 0x47.000580005a0000000001e133ffffff000161.00 +nxt01 NXT a.secure ( NS SOA MX SIG KEY LOC NXT ) +nxt02 NXT . ( NSAP-PTR NXT ) +nxt03 NXT . ( A ) +nxt04 NXT . ( 127 ) +ptr01 PTR example. +px01 PX 65535 foo. bar. +px02 PX 65535 . . +rp01 RP mbox-dname txt-dname +rp02 RP . . +rt01 RT 0 intermediate-host +rt02 RT 65535 . +$TTL 300 ; 5 minutes +s NS ns.s +$ORIGIN s.example. +ns A 73.80.65.49 +$ORIGIN example. +$TTL 3600 ; 1 hour +sig01 SIG NXT 1 3 3600 ( + 20200101000000 20030101000000 2143 foo + MxFcby9k/yvedMfQgKzhH5er0Mu/vILz45IkskceFGgi + WCn/GxHhai6VAuHAoNUz4YoU1tVfSCSqQYn6//11U6Nl + d80jEeC8aTrO+KKmCaY= ) +srv01 SRV 0 0 0 . +srv02 SRV 65535 65535 65535 old-slow-box.example.com. +$TTL 301 ; 5 minutes 1 second +t A 73.80.65.49 +$TTL 3600 ; 1 hour +txt01 TXT "foo" +txt02 TXT "foo" "bar" +txt03 TXT "foo" +txt04 TXT "foo" "bar" +txt05 TXT "foo bar" +txt06 TXT "foo bar" +txt07 TXT "foo bar" +txt08 TXT "foo\010bar" +txt09 TXT "foo\010bar" +txt10 TXT "foo bar" +txt11 TXT "\"foo\"" +txt12 TXT "\"foo\"" +$TTL 300 ; 5 minutes +u TXT "txt-not-in-nxt" +$ORIGIN u.example. +a A 73.80.65.49 +b A 73.80.65.49 +$ORIGIN example. +$TTL 3600 ; 1 hour +wks01 WKS 10.0.0.1 6 ( 0 1 2 21 23 ) +wks02 WKS 10.0.0.1 17 ( 0 1 2 53 ) +wks03 WKS 10.0.0.2 6 ( 65535 ) +x2501 X25 "123456789" +ds01 DS 12345 3 1 123456789abcdef67890123456789abcdef67890 +apl01 APL 1:192.168.32.0/21 !1:192.168.38.0/28 +apl02 APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8 +unknown2 TYPE999 \# 8 0a0000010a000001 +rrsig01 RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/ vILz45IkskceFGgiWCn/GxHhai6V AuHAoNUz4YoU1tVfSCSqQYn6//11 U6Nld80jEeC8aTrO+KKmCaY= +nsec01 NSEC a.secure. A MX RRSIG NSEC TYPE1234 +nsec02 NSEC . NSAP-PTR NSEC +nsec03 NSEC . NSEC TYPE65535 +dnskey01 DNSKEY 512 255 1 ( + AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR + yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 + GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o + jqf0BaqHT+8= ) +dnskey02 DNSKEY HOST|FLAG4 DNSSEC RSAMD5 ( + AQMFD5raczCJHViKtLYhWGz8hMY9UGRuniJDBzC7w0aR + yzWZriO6i2odGWWQVucZqKVsENW91IOW4vqudngPZsY3 + GvQ/xVA8/7pyFj6b7Esga60zyGW6LFe9r8n6paHrlG5o + jqf0BaqHT+8= ) diff --git a/tests/example1.good b/tests/example1.good new file mode 100644 index 0000000..610aee6 --- /dev/null +++ b/tests/example1.good @@ -0,0 +1,101 @@ +@ 300 IN SOA ns1 hostmaster 1 2000 2000 1814400 3600 +@ 300 IN NS ns1 +@ 300 IN NS ns2 +* 300 IN MX 10 mail +a 300 IN TXT "foo foo foo" +a 300 IN PTR foo.net. +a01 3600 IN A 0.0.0.0 +a02 3600 IN A 255.255.255.255 +aaaa01 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff +aaaa02 3600 IN AAAA ::1 +afsdb01 3600 IN AFSDB 0 hostname +afsdb02 3600 IN AFSDB 65535 . +apl01 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 +apl02 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8 +b 300 IN CNAME foo.net. +c 300 IN A 73.80.65.49 +cert01 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +cname01 3600 IN CNAME cname-target. +cname02 3600 IN CNAME cname-target +cname03 3600 IN CNAME . +d 300 IN A 73.80.65.49 +dname01 3600 IN DNAME dname-target. +dname02 3600 IN DNAME dname-target +dname03 3600 IN DNAME . +dnskey01 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +dnskey02 3600 IN DNSKEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +ds01 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 +e 300 IN MX 10 mail +e 300 IN TXT "one" +e 300 IN TXT "three" +e 300 IN TXT "two" +e 300 IN A 73.80.65.49 +e 300 IN A 73.80.65.50 +e 300 IN A 73.80.65.52 +e 300 IN A 73.80.65.51 +f 300 IN A 73.80.65.52 +gpos01 3600 IN GPOS -22.6882 116.8652 250.0 +hinfo01 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" +hinfo02 3600 IN HINFO "PC" "NetBSD" +isdn01 3600 IN ISDN "isdn-address" +isdn02 3600 IN ISDN "isdn-address" "subaddress" +isdn03 3600 IN ISDN "isdn-address" +isdn04 3600 IN ISDN "isdn-address" "subaddress" +key01 3600 IN KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +key02 3600 IN KEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +kx01 3600 IN KX 10 kdc +kx02 3600 IN KX 10 . +loc01 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc02 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc03 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m +mx01 3600 IN MX 10 mail +mx02 3600 IN MX 10 . +naptr01 3600 IN NAPTR 0 0 "" "" "" . +naptr02 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. +ns1 300 IN A 10.53.0.1 +ns2 300 IN A 10.53.0.2 +nsap-ptr01 3600 IN NSAP-PTR foo. +nsap-ptr01 3600 IN NSAP-PTR . +nsap01 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsap02 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsec01 3600 IN NSEC a.secure. TYPE1234 +nsec02 3600 IN NSEC . NSAP-PTR NSEC +nsec03 3600 IN NSEC . TYPE65535 +nxt01 3600 IN NXT a.secure NS SOA MX SIG KEY LOC NXT +nxt02 3600 IN NXT . NSAP-PTR NXT +nxt03 3600 IN NXT . A +nxt04 3600 IN NXT . TYPE127 +ptr01 3600 IN PTR @ +px01 3600 IN PX 65535 foo. bar. +px02 3600 IN PX 65535 . . +rp01 3600 IN RP mbox-dname txt-dname +rp02 3600 IN RP . . +rrsig01 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +rt01 3600 IN RT 0 intermediate-host +rt02 3600 IN RT 65535 . +s 300 IN NS ns.s +ns.s 300 IN A 73.80.65.49 +sig01 3600 IN SIG NXT 1 3 3600 20200101000000 20030101000000 2143 foo MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +srv01 3600 IN SRV 0 0 0 . +srv02 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. +t 301 IN A 73.80.65.49 +txt01 3600 IN TXT "foo" +txt02 3600 IN TXT "foo" "bar" +txt03 3600 IN TXT "foo" +txt04 3600 IN TXT "foo" "bar" +txt05 3600 IN TXT "foo bar" +txt06 3600 IN TXT "foo bar" +txt07 3600 IN TXT "foo bar" +txt08 3600 IN TXT "foo\010bar" +txt09 3600 IN TXT "foo\010bar" +txt10 3600 IN TXT "foo bar" +txt11 3600 IN TXT "\"foo\"" +txt12 3600 IN TXT "\"foo\"" +u 300 IN TXT "txt-not-in-nxt" +a.u 300 IN A 73.80.65.49 +b.u 300 IN A 73.80.65.49 +unknown2 3600 IN TYPE999 \# 8 0a0000010a000001 +wks01 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 +wks02 3600 IN WKS 10.0.0.1 17 0 1 2 53 +wks03 3600 IN WKS 10.0.0.2 6 65535 +x2501 3600 IN X25 "123456789" diff --git a/tests/example2.good b/tests/example2.good new file mode 100644 index 0000000..feec82a --- /dev/null +++ b/tests/example2.good @@ -0,0 +1,101 @@ +example. 300 IN SOA ns1.example. hostmaster.example. 1 2000 2000 1814400 3600 +example. 300 IN NS ns1.example. +example. 300 IN NS ns2.example. +*.example. 300 IN MX 10 mail.example. +a.example. 300 IN TXT "foo foo foo" +a.example. 300 IN PTR foo.net. +a01.example. 3600 IN A 0.0.0.0 +a02.example. 3600 IN A 255.255.255.255 +aaaa01.example. 3600 IN AAAA ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff +aaaa02.example. 3600 IN AAAA ::1 +afsdb01.example. 3600 IN AFSDB 0 hostname.example. +afsdb02.example. 3600 IN AFSDB 65535 . +apl01.example. 3600 IN APL 1:192.168.32.0/21 !1:192.168.38.0/28 +apl02.example. 3600 IN APL 1:224.0.0.0/4 2:FF00:0:0:0:0:0:0:0/8 +b.example. 300 IN CNAME foo.net. +c.example. 300 IN A 73.80.65.49 +cert01.example. 3600 IN CERT 65534 65535 PRIVATEOID MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +cname01.example. 3600 IN CNAME cname-target. +cname02.example. 3600 IN CNAME cname-target.example. +cname03.example. 3600 IN CNAME . +d.example. 300 IN A 73.80.65.49 +dname01.example. 3600 IN DNAME dname-target. +dname02.example. 3600 IN DNAME dname-target.example. +dname03.example. 3600 IN DNAME . +dnskey01.example. 3600 IN DNSKEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +dnskey02.example. 3600 IN DNSKEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +ds01.example. 3600 IN DS 12345 3 1 123456789abcdef67890123456789abcdef67890 +e.example. 300 IN MX 10 mail.example. +e.example. 300 IN TXT "one" +e.example. 300 IN TXT "three" +e.example. 300 IN TXT "two" +e.example. 300 IN A 73.80.65.49 +e.example. 300 IN A 73.80.65.50 +e.example. 300 IN A 73.80.65.52 +e.example. 300 IN A 73.80.65.51 +f.example. 300 IN A 73.80.65.52 +gpos01.example. 3600 IN GPOS -22.6882 116.8652 250.0 +hinfo01.example. 3600 IN HINFO "Generic PC clone" "NetBSD-1.4" +hinfo02.example. 3600 IN HINFO "PC" "NetBSD" +isdn01.example. 3600 IN ISDN "isdn-address" +isdn02.example. 3600 IN ISDN "isdn-address" "subaddress" +isdn03.example. 3600 IN ISDN "isdn-address" +isdn04.example. 3600 IN ISDN "isdn-address" "subaddress" +key01.example. 3600 IN KEY 512 255 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +key02.example. 3600 IN KEY 2560 3 1 AQMFD5raczCJHViKtLYhWGz8hMY9UGRu niJDBzC7w0aRyzWZriO6i2odGWWQVucZ qKVsENW91IOW4vqudngPZsY3GvQ/xVA8 /7pyFj6b7Esga60zyGW6LFe9r8n6paHr lG5ojqf0BaqHT+8= +kx01.example. 3600 IN KX 10 kdc.example. +kx02.example. 3600 IN KX 10 . +loc01.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc02.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 20.00m 2000.00m 20.00m +loc03.example. 3600 IN LOC 60 9 0.000 N 24 39 0.000 E 10.00m 90000000.00m 2000.00m 20.00m +mx01.example. 3600 IN MX 10 mail.example. +mx02.example. 3600 IN MX 10 . +naptr01.example. 3600 IN NAPTR 0 0 "" "" "" . +naptr02.example. 3600 IN NAPTR 65535 65535 "blurgh" "blorf" "blegh" foo. +ns1.example. 300 IN A 10.53.0.1 +ns2.example. 300 IN A 10.53.0.2 +nsap-ptr01.example. 3600 IN NSAP-PTR foo. +nsap-ptr01.example. 3600 IN NSAP-PTR . +nsap01.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsap02.example. 3600 IN NSAP 0x47000580005a0000000001e133ffffff00016100 +nsec01.example. 3600 IN NSEC a.secure. TYPE1234 +nsec02.example. 3600 IN NSEC . NSAP-PTR NSEC +nsec03.example. 3600 IN NSEC . TYPE65535 +nxt01.example. 3600 IN NXT a.secure.example. NS SOA MX SIG KEY LOC NXT +nxt02.example. 3600 IN NXT . NSAP-PTR NXT +nxt03.example. 3600 IN NXT . A +nxt04.example. 3600 IN NXT . TYPE127 +ptr01.example. 3600 IN PTR example. +px01.example. 3600 IN PX 65535 foo. bar. +px02.example. 3600 IN PX 65535 . . +rp01.example. 3600 IN RP mbox-dname.example. txt-dname.example. +rp02.example. 3600 IN RP . . +rrsig01.example. 3600 IN RRSIG NSEC 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +rt01.example. 3600 IN RT 0 intermediate-host.example. +rt02.example. 3600 IN RT 65535 . +s.example. 300 IN NS ns.s.example. +ns.s.example. 300 IN A 73.80.65.49 +sig01.example. 3600 IN SIG NXT 1 3 3600 20200101000000 20030101000000 2143 foo.example. MxFcby9k/yvedMfQgKzhH5er0Mu/vILz 45IkskceFGgiWCn/GxHhai6VAuHAoNUz 4YoU1tVfSCSqQYn6//11U6Nld80jEeC8 aTrO+KKmCaY= +srv01.example. 3600 IN SRV 0 0 0 . +srv02.example. 3600 IN SRV 65535 65535 65535 old-slow-box.example.com. +t.example. 301 IN A 73.80.65.49 +txt01.example. 3600 IN TXT "foo" +txt02.example. 3600 IN TXT "foo" "bar" +txt03.example. 3600 IN TXT "foo" +txt04.example. 3600 IN TXT "foo" "bar" +txt05.example. 3600 IN TXT "foo bar" +txt06.example. 3600 IN TXT "foo bar" +txt07.example. 3600 IN TXT "foo bar" +txt08.example. 3600 IN TXT "foo\010bar" +txt09.example. 3600 IN TXT "foo\010bar" +txt10.example. 3600 IN TXT "foo bar" +txt11.example. 3600 IN TXT "\"foo\"" +txt12.example. 3600 IN TXT "\"foo\"" +u.example. 300 IN TXT "txt-not-in-nxt" +a.u.example. 300 IN A 73.80.65.49 +b.u.example. 300 IN A 73.80.65.49 +unknown2.example. 3600 IN TYPE999 \# 8 0a0000010a000001 +wks01.example. 3600 IN WKS 10.0.0.1 6 0 1 2 21 23 +wks02.example. 3600 IN WKS 10.0.0.1 17 0 1 2 53 +wks03.example. 3600 IN WKS 10.0.0.2 6 65535 +x2501.example. 3600 IN X25 "123456789" diff --git a/tests/flags.py b/tests/flags.py new file mode 100644 index 0000000..662b1cb --- /dev/null +++ b/tests/flags.py @@ -0,0 +1,61 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: flags.py,v 1.2 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.flags +import dns.rcode +import dns.opcode + +class FlagsTestCase(unittest.TestCase): + + def test_rcode1(self): + self.failUnless(dns.rcode.from_text('FORMERR') == dns.rcode.FORMERR) + + def test_rcode2(self): + self.failUnless(dns.rcode.to_text(dns.rcode.FORMERR) == "FORMERR") + + def test_rcode3(self): + self.failUnless(dns.rcode.to_flags(dns.rcode.FORMERR) == (1, 0)) + + def test_rcode4(self): + self.failUnless(dns.rcode.to_flags(dns.rcode.BADVERS) == \ + (0, 0x01000000)) + + def test_rcode6(self): + self.failUnless(dns.rcode.from_flags(0, 0x01000000) == \ + dns.rcode.BADVERS) + + def test_rcode6(self): + self.failUnless(dns.rcode.from_flags(5, 0) == dns.rcode.REFUSED) + + def test_rcode7(self): + def bad(): + dns.rcode.to_flags(4096) + self.failUnlessRaises(ValueError, bad) + + def test_flags1(self): + self.failUnless(dns.flags.from_text("RA RD AA QR") == \ + dns.flags.QR|dns.flags.AA|dns.flags.RD|dns.flags.RA) + + def test_flags2(self): + flags = dns.flags.QR|dns.flags.AA|dns.flags.RD|dns.flags.RA + self.failUnless(dns.flags.to_text(flags) == "QR AA RD RA") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/message.py b/tests/message.py new file mode 100644 index 0000000..7207e1d --- /dev/null +++ b/tests/message.py @@ -0,0 +1,156 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: message.py,v 1.8 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import os +import unittest + +import dns.exception +import dns.message + +query_text = """id 1234 +opcode QUERY +rcode NOERROR +flags RD +edns 0 +eflags DO +payload 4096 +;QUESTION +wwww.dnspython.org. IN A +;ANSWER +;AUTHORITY +;ADDITIONAL""" + +goodhex = '04d201000001000000000001047777777709646e73707974686f6e' \ + '036f726700000100010000291000000080000000' + +goodwire = goodhex.decode('hex_codec') + +answer_text = """id 1234 +opcode QUERY +rcode NOERROR +flags QR AA RD +;QUESTION +dnspython.org. IN SOA +;ANSWER +dnspython.org. 3600 IN SOA woof.dnspython.org. hostmaster.dnspython.org. 2003052700 3600 1800 604800 3600 +;AUTHORITY +dnspython.org. 3600 IN NS ns1.staff.nominum.org. +dnspython.org. 3600 IN NS ns2.staff.nominum.org. +dnspython.org. 3600 IN NS woof.play-bow.org. +;ADDITIONAL +woof.play-bow.org. 3600 IN A 204.152.186.150 +""" + +goodhex2 = '04d2 8500 0001 0001 0003 0001' \ + '09646e73707974686f6e036f726700 0006 0001' \ + 'c00c 0006 0001 00000e10 0028 ' \ + '04776f6f66c00c 0a686f73746d6173746572c00c' \ + '7764289c 00000e10 00000708 00093a80 00000e10' \ + 'c00c 0002 0001 00000e10 0014' \ + '036e7331057374616666076e6f6d696e756dc016' \ + 'c00c 0002 0001 00000e10 0006 036e7332c063' \ + 'c00c 0002 0001 00000e10 0010 04776f6f6608706c61792d626f77c016' \ + 'c091 0001 0001 00000e10 0004 cc98ba96' + + +goodwire2 = goodhex2.replace(' ', '').decode('hex_codec') + +query_text_2 = """id 1234 +opcode QUERY +rcode 4095 +flags RD +edns 0 +eflags DO +payload 4096 +;QUESTION +wwww.dnspython.org. IN A +;ANSWER +;AUTHORITY +;ADDITIONAL""" + +goodhex3 = '04d2010f0001000000000001047777777709646e73707974686f6e' \ + '036f726700000100010000291000ff0080000000' + +goodwire3 = goodhex3.decode('hex_codec') + +class MessageTestCase(unittest.TestCase): + + def test_comparison_eq1(self): + q1 = dns.message.from_text(query_text) + q2 = dns.message.from_text(query_text) + self.failUnless(q1 == q2) + + def test_comparison_ne1(self): + q1 = dns.message.from_text(query_text) + q2 = dns.message.from_text(query_text) + q2.id = 10 + self.failUnless(q1 != q2) + + def test_comparison_ne2(self): + q1 = dns.message.from_text(query_text) + q2 = dns.message.from_text(query_text) + q2.question = [] + self.failUnless(q1 != q2) + + def test_comparison_ne3(self): + q1 = dns.message.from_text(query_text) + self.failUnless(q1 != 1) + + def test_EDNS_to_wire1(self): + q = dns.message.from_text(query_text) + w = q.to_wire() + self.failUnless(w == goodwire) + + def test_EDNS_from_wire1(self): + m = dns.message.from_wire(goodwire) + self.failUnless(str(m) == query_text) + + def test_EDNS_to_wire2(self): + q = dns.message.from_text(query_text_2) + w = q.to_wire() + self.failUnless(w == goodwire3) + + def test_EDNS_from_wire2(self): + m = dns.message.from_wire(goodwire3) + self.failUnless(str(m) == query_text_2) + + def test_TooBig(self): + def bad(): + q = dns.message.from_text(query_text) + w = q.to_wire(max_size=15) + self.failUnlessRaises(dns.exception.TooBig, bad) + + def test_answer1(self): + a = dns.message.from_text(answer_text) + wire = a.to_wire(want_shuffle=False) + self.failUnless(wire == goodwire2) + + def test_TrailingJunk(self): + def bad(): + badwire = goodwire + '\x00' + m = dns.message.from_wire(badwire) + self.failUnlessRaises(dns.message.TrailingJunk, bad) + + def test_ShortHeader(self): + def bad(): + badwire = '\x00' * 11 + m = dns.message.from_wire(badwire) + self.failUnlessRaises(dns.message.ShortHeader, bad) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/name.py b/tests/name.py new file mode 100644 index 0000000..3a0e334 --- /dev/null +++ b/tests/name.py @@ -0,0 +1,550 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: name.py,v 1.13 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import cStringIO + +import dns.name + +class NameTestCase(unittest.TestCase): + def setUp(self): + self.origin = dns.name.from_text('example.') + + def testFromTextRel1(self): + n = dns.name.from_text('foo.bar') + self.failUnless(n.labels == ('foo', 'bar', '')) + + def testFromTextRel2(self): + n = dns.name.from_text('foo.bar', origin=self.origin) + self.failUnless(n.labels == ('foo', 'bar', 'example', '')) + + def testFromTextRel3(self): + n = dns.name.from_text('foo.bar', origin=None) + self.failUnless(n.labels == ('foo', 'bar')) + + def testFromTextRel4(self): + n = dns.name.from_text('@', origin=None) + self.failUnless(n == dns.name.empty) + + def testFromTextRel5(self): + n = dns.name.from_text('@', origin=self.origin) + self.failUnless(n == self.origin) + + def testFromTextAbs1(self): + n = dns.name.from_text('foo.bar.') + self.failUnless(n.labels == ('foo', 'bar', '')) + + def testImmutable1(self): + def bad(): + self.origin.labels = () + self.failUnlessRaises(TypeError, bad) + + def testImmutable2(self): + def bad(): + self.origin.labels[0] = 'foo' + self.failUnlessRaises(TypeError, bad) + + def testAbs1(self): + self.failUnless(dns.name.root.is_absolute()) + + def testAbs2(self): + self.failUnless(not dns.name.empty.is_absolute()) + + def testAbs3(self): + self.failUnless(self.origin.is_absolute()) + + def testAbs3(self): + n = dns.name.from_text('foo', origin=None) + self.failUnless(not n.is_absolute()) + + def testWild1(self): + n = dns.name.from_text('*.foo', origin=None) + self.failUnless(n.is_wild()) + + def testWild2(self): + n = dns.name.from_text('*a.foo', origin=None) + self.failUnless(not n.is_wild()) + + def testWild3(self): + n = dns.name.from_text('a.*.foo', origin=None) + self.failUnless(not n.is_wild()) + + def testWild4(self): + self.failUnless(not dns.name.root.is_wild()) + + def testWild5(self): + self.failUnless(not dns.name.empty.is_wild()) + + def testHash1(self): + n1 = dns.name.from_text('fOo.COM') + n2 = dns.name.from_text('foo.com') + self.failUnless(hash(n1) == hash(n2)) + + def testCompare1(self): + n1 = dns.name.from_text('a') + n2 = dns.name.from_text('b') + self.failUnless(n1 < n2) + self.failUnless(n2 > n1) + + def testCompare2(self): + n1 = dns.name.from_text('') + n2 = dns.name.from_text('b') + self.failUnless(n1 < n2) + self.failUnless(n2 > n1) + + def testCompare3(self): + self.failUnless(dns.name.empty < dns.name.root) + self.failUnless(dns.name.root > dns.name.empty) + + def testCompare4(self): + self.failUnless(dns.name.root != 1) + + def testCompare5(self): + self.failUnless(dns.name.root < 1 or dns.name.root > 1) + + def testSubdomain1(self): + self.failUnless(not dns.name.empty.is_subdomain(dns.name.root)) + + def testSubdomain2(self): + self.failUnless(not dns.name.root.is_subdomain(dns.name.empty)) + + def testSubdomain3(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(n.is_subdomain(self.origin)) + + def testSubdomain4(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(n.is_subdomain(dns.name.root)) + + def testSubdomain5(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(n.is_subdomain(n)) + + def testSuperdomain1(self): + self.failUnless(not dns.name.empty.is_superdomain(dns.name.root)) + + def testSuperdomain2(self): + self.failUnless(not dns.name.root.is_superdomain(dns.name.empty)) + + def testSuperdomain3(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(self.origin.is_superdomain(n)) + + def testSuperdomain4(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(dns.name.root.is_superdomain(n)) + + def testSuperdomain5(self): + n = dns.name.from_text('foo', origin=self.origin) + self.failUnless(n.is_superdomain(n)) + + def testCanonicalize1(self): + n = dns.name.from_text('FOO.bar', origin=self.origin) + c = n.canonicalize() + self.failUnless(c.labels == ('foo', 'bar', 'example', '')) + + def testToText1(self): + n = dns.name.from_text('FOO.bar', origin=self.origin) + t = n.to_text() + self.failUnless(t == 'FOO.bar.example.') + + def testToText2(self): + n = dns.name.from_text('FOO.bar', origin=self.origin) + t = n.to_text(True) + self.failUnless(t == 'FOO.bar.example') + + def testToText3(self): + n = dns.name.from_text('FOO.bar', origin=None) + t = n.to_text() + self.failUnless(t == 'FOO.bar') + + def testToText4(self): + t = dns.name.empty.to_text() + self.failUnless(t == '@') + + def testToText5(self): + t = dns.name.root.to_text() + self.failUnless(t == '.') + + def testToText6(self): + n = dns.name.from_text('FOO bar', origin=None) + t = n.to_text() + self.failUnless(t == r'FOO\032bar') + + def testToText7(self): + n = dns.name.from_text(r'FOO\.bar', origin=None) + t = n.to_text() + self.failUnless(t == r'FOO\.bar') + + def testToText8(self): + n = dns.name.from_text(r'\070OO\.bar', origin=None) + t = n.to_text() + self.failUnless(t == r'FOO\.bar') + + def testSlice1(self): + n = dns.name.from_text(r'a.b.c.', origin=None) + s = n[:] + self.failUnless(s == ('a', 'b', 'c', '')) + + def testSlice2(self): + n = dns.name.from_text(r'a.b.c.', origin=None) + s = n[:2] + self.failUnless(s == ('a', 'b')) + + def testSlice3(self): + n = dns.name.from_text(r'a.b.c.', origin=None) + s = n[2:] + self.failUnless(s == ('c', '')) + + def testEmptyLabel1(self): + def bad(): + n = dns.name.Name(['a', '', 'b']) + self.failUnlessRaises(dns.name.EmptyLabel, bad) + + def testEmptyLabel2(self): + def bad(): + n = dns.name.Name(['', 'b']) + self.failUnlessRaises(dns.name.EmptyLabel, bad) + + def testEmptyLabel3(self): + n = dns.name.Name(['b', '']) + self.failUnless(n) + + def testLongLabel(self): + n = dns.name.Name(['a' * 63]) + self.failUnless(n) + + def testLabelTooLong(self): + def bad(): + n = dns.name.Name(['a' * 64, 'b']) + self.failUnlessRaises(dns.name.LabelTooLong, bad) + + def testLongName(self): + n = dns.name.Name(['a' * 63, 'a' * 63, 'a' * 63, 'a' * 62]) + self.failUnless(n) + + def testLabelTooLong(self): + def bad(): + n = dns.name.Name(['a' * 63, 'a' * 63, 'a' * 63, 'a' * 63]) + self.failUnlessRaises(dns.name.NameTooLong, bad) + + def testConcat1(self): + n1 = dns.name.Name(['a', 'b']) + n2 = dns.name.Name(['c', 'd']) + e = dns.name.Name(['a', 'b', 'c', 'd']) + r = n1 + n2 + self.failUnless(r == e) + + def testConcat2(self): + n1 = dns.name.Name(['a', 'b']) + n2 = dns.name.Name([]) + e = dns.name.Name(['a', 'b']) + r = n1 + n2 + self.failUnless(r == e) + + def testConcat2(self): + n1 = dns.name.Name([]) + n2 = dns.name.Name(['a', 'b']) + e = dns.name.Name(['a', 'b']) + r = n1 + n2 + self.failUnless(r == e) + + def testConcat3(self): + n1 = dns.name.Name(['a', 'b', '']) + n2 = dns.name.Name([]) + e = dns.name.Name(['a', 'b', '']) + r = n1 + n2 + self.failUnless(r == e) + + def testConcat4(self): + n1 = dns.name.Name(['a', 'b']) + n2 = dns.name.Name(['c', '']) + e = dns.name.Name(['a', 'b', 'c', '']) + r = n1 + n2 + self.failUnless(r == e) + + def testConcat5(self): + def bad(): + n1 = dns.name.Name(['a', 'b', '']) + n2 = dns.name.Name(['c']) + r = n1 + n2 + self.failUnlessRaises(dns.name.AbsoluteConcatenation, bad) + + def testBadEscape(self): + def bad(): + n = dns.name.from_text(r'a.b\0q1.c.') + print n + self.failUnlessRaises(dns.name.BadEscape, bad) + + def testDigestable1(self): + n = dns.name.from_text('FOO.bar') + d = n.to_digestable() + self.failUnless(d == '\x03foo\x03bar\x00') + + def testDigestable2(self): + n1 = dns.name.from_text('FOO.bar') + n2 = dns.name.from_text('foo.BAR.') + d1 = n1.to_digestable() + d2 = n2.to_digestable() + self.failUnless(d1 == d2) + + def testDigestable3(self): + d = dns.name.root.to_digestable() + self.failUnless(d == '\x00') + + def testDigestable4(self): + n = dns.name.from_text('FOO.bar', None) + d = n.to_digestable(dns.name.root) + self.failUnless(d == '\x03foo\x03bar\x00') + + def testBadDigestable(self): + def bad(): + n = dns.name.from_text('FOO.bar', None) + d = n.to_digestable() + self.failUnlessRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) + + def testToWire1(self): + n = dns.name.from_text('FOO.bar') + f = cStringIO.StringIO() + compress = {} + n.to_wire(f, compress) + self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00') + + def testToWire2(self): + n = dns.name.from_text('FOO.bar') + f = cStringIO.StringIO() + compress = {} + n.to_wire(f, compress) + n.to_wire(f, compress) + self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\xc0\x00') + + def testToWire3(self): + n1 = dns.name.from_text('FOO.bar') + n2 = dns.name.from_text('foo.bar') + f = cStringIO.StringIO() + compress = {} + n1.to_wire(f, compress) + n2.to_wire(f, compress) + self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\xc0\x00') + + def testToWire4(self): + n1 = dns.name.from_text('FOO.bar') + n2 = dns.name.from_text('a.foo.bar') + f = cStringIO.StringIO() + compress = {} + n1.to_wire(f, compress) + n2.to_wire(f, compress) + self.failUnless(f.getvalue() == '\x03FOO\x03bar\x00\x01\x61\xc0\x00') + + def testToWire5(self): + n1 = dns.name.from_text('FOO.bar') + n2 = dns.name.from_text('a.foo.bar') + f = cStringIO.StringIO() + compress = {} + n1.to_wire(f, compress) + n2.to_wire(f, None) + self.failUnless(f.getvalue() == \ + '\x03FOO\x03bar\x00\x01\x61\x03foo\x03bar\x00') + + def testBadToWire(self): + def bad(): + n = dns.name.from_text('FOO.bar', None) + f = cStringIO.StringIO() + compress = {} + n.to_wire(f, compress) + self.failUnlessRaises(dns.name.NeedAbsoluteNameOrOrigin, bad) + + def testSplit1(self): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(2) + ep = dns.name.from_text('foo', None) + es = dns.name.from_text('bar.', None) + self.failUnless(prefix == ep and suffix == es) + + def testSplit2(self): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(1) + ep = dns.name.from_text('foo.bar', None) + es = dns.name.from_text('.', None) + self.failUnless(prefix == ep and suffix == es) + + def testSplit3(self): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(0) + ep = dns.name.from_text('foo.bar.', None) + es = dns.name.from_text('', None) + self.failUnless(prefix == ep and suffix == es) + + def testSplit4(self): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(3) + ep = dns.name.from_text('', None) + es = dns.name.from_text('foo.bar.', None) + self.failUnless(prefix == ep and suffix == es) + + def testBadSplit1(self): + def bad(): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(-1) + self.failUnlessRaises(ValueError, bad) + + def testBadSplit2(self): + def bad(): + n = dns.name.from_text('foo.bar.') + (prefix, suffix) = n.split(4) + self.failUnlessRaises(ValueError, bad) + + def testRelativize1(self): + n = dns.name.from_text('a.foo.bar.', None) + o = dns.name.from_text('bar.', None) + e = dns.name.from_text('a.foo', None) + self.failUnless(n.relativize(o) == e) + + def testRelativize2(self): + n = dns.name.from_text('a.foo.bar.', None) + o = n + e = dns.name.empty + self.failUnless(n.relativize(o) == e) + + def testRelativize3(self): + n = dns.name.from_text('a.foo.bar.', None) + o = dns.name.from_text('blaz.', None) + e = n + self.failUnless(n.relativize(o) == e) + + def testRelativize4(self): + n = dns.name.from_text('a.foo', None) + o = dns.name.root + e = n + self.failUnless(n.relativize(o) == e) + + def testDerelativize1(self): + n = dns.name.from_text('a.foo', None) + o = dns.name.from_text('bar.', None) + e = dns.name.from_text('a.foo.bar.', None) + self.failUnless(n.derelativize(o) == e) + + def testDerelativize2(self): + n = dns.name.empty + o = dns.name.from_text('a.foo.bar.', None) + e = o + self.failUnless(n.derelativize(o) == e) + + def testDerelativize3(self): + n = dns.name.from_text('a.foo.bar.', None) + o = dns.name.from_text('blaz.', None) + e = n + self.failUnless(n.derelativize(o) == e) + + def testChooseRelativity1(self): + n = dns.name.from_text('a.foo.bar.', None) + o = dns.name.from_text('bar.', None) + e = dns.name.from_text('a.foo', None) + self.failUnless(n.choose_relativity(o, True) == e) + + def testChooseRelativity2(self): + n = dns.name.from_text('a.foo.bar.', None) + o = dns.name.from_text('bar.', None) + e = n + self.failUnless(n.choose_relativity(o, False) == e) + + def testChooseRelativity3(self): + n = dns.name.from_text('a.foo', None) + o = dns.name.from_text('bar.', None) + e = dns.name.from_text('a.foo.bar.', None) + self.failUnless(n.choose_relativity(o, False) == e) + + def testChooseRelativity4(self): + n = dns.name.from_text('a.foo', None) + o = None + e = n + self.failUnless(n.choose_relativity(o, True) == e) + + def testChooseRelativity5(self): + n = dns.name.from_text('a.foo', None) + o = None + e = n + self.failUnless(n.choose_relativity(o, False) == e) + + def testChooseRelativity6(self): + n = dns.name.from_text('a.foo.', None) + o = None + e = n + self.failUnless(n.choose_relativity(o, True) == e) + + def testChooseRelativity7(self): + n = dns.name.from_text('a.foo.', None) + o = None + e = n + self.failUnless(n.choose_relativity(o, False) == e) + + def testFromWire1(self): + w = '\x03foo\x00\xc0\x00' + (n1, cused1) = dns.name.from_wire(w, 0) + (n2, cused2) = dns.name.from_wire(w, cused1) + en1 = dns.name.from_text('foo.') + en2 = en1 + ecused1 = 5 + ecused2 = 2 + self.failUnless(n1 == en1 and cused1 == ecused1 and \ + n2 == en2 and cused2 == ecused2) + + def testFromWire1(self): + w = '\x03foo\x00\x01a\xc0\x00\x01b\xc0\x05' + current = 0 + (n1, cused1) = dns.name.from_wire(w, current) + current += cused1 + (n2, cused2) = dns.name.from_wire(w, current) + current += cused2 + (n3, cused3) = dns.name.from_wire(w, current) + en1 = dns.name.from_text('foo.') + en2 = dns.name.from_text('a.foo.') + en3 = dns.name.from_text('b.a.foo.') + ecused1 = 5 + ecused2 = 4 + ecused3 = 4 + self.failUnless(n1 == en1 and cused1 == ecused1 and \ + n2 == en2 and cused2 == ecused2 and \ + n3 == en3 and cused3 == ecused3) + + def testBadFromWire1(self): + def bad(): + w = '\x03foo\xc0\x04' + (n, cused) = dns.name.from_wire(w, 0) + self.failUnlessRaises(dns.name.BadPointer, bad) + + def testBadFromWire2(self): + def bad(): + w = '\x03foo\xc0\x05' + (n, cused) = dns.name.from_wire(w, 0) + self.failUnlessRaises(dns.name.BadPointer, bad) + + def testBadFromWire3(self): + def bad(): + w = '\xbffoo' + (n, cused) = dns.name.from_wire(w, 0) + self.failUnlessRaises(dns.name.BadLabelType, bad) + + def testBadFromWire4(self): + def bad(): + w = '\x41foo' + (n, cused) = dns.name.from_wire(w, 0) + self.failUnlessRaises(dns.name.BadLabelType, bad) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/namedict.py b/tests/namedict.py new file mode 100644 index 0000000..4180d25 --- /dev/null +++ b/tests/namedict.py @@ -0,0 +1,104 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: namedict.py,v 1.5 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.name +import dns.namedict + +class NameTestCase(unittest.TestCase): + + def setUp(self): + self.ndict = dns.namedict.NameDict() + n1 = dns.name.from_text('foo.bar.') + n2 = dns.name.from_text('bar.') + self.ndict[n1] = 1 + self.ndict[n2] = 2 + self.rndict = dns.namedict.NameDict() + n1 = dns.name.from_text('foo.bar', None) + n2 = dns.name.from_text('bar', None) + self.rndict[n1] = 1 + self.rndict[n2] = 2 + + def testDepth(self): + self.failUnless(self.ndict.max_depth == 3) + + def testLookup1(self): + k = dns.name.from_text('foo.bar.') + self.failUnless(self.ndict[k] == 1) + + def testLookup2(self): + k = dns.name.from_text('foo.bar.') + self.failUnless(self.ndict.get_deepest_match(k)[1] == 1) + + def testLookup3(self): + k = dns.name.from_text('a.b.c.foo.bar.') + self.failUnless(self.ndict.get_deepest_match(k)[1] == 1) + + def testLookup4(self): + k = dns.name.from_text('a.b.c.bar.') + self.failUnless(self.ndict.get_deepest_match(k)[1] == 2) + + def testLookup5(self): + def bad(): + n = dns.name.from_text('a.b.c.') + (k, v) = self.ndict.get_deepest_match(n) + self.failUnlessRaises(KeyError, bad) + + def testLookup6(self): + def bad(): + (k, v) = self.ndict.get_deepest_match(dns.name.empty) + self.failUnlessRaises(KeyError, bad) + + def testLookup7(self): + self.ndict[dns.name.empty] = 100 + n = dns.name.from_text('a.b.c.') + (k, v) = self.ndict.get_deepest_match(n) + self.failUnless(v == 100) + + def testLookup8(self): + def bad(): + self.ndict['foo'] = 100 + self.failUnlessRaises(ValueError, bad) + + def testRelDepth(self): + self.failUnless(self.rndict.max_depth == 2) + + def testRelLookup1(self): + k = dns.name.from_text('foo.bar', None) + self.failUnless(self.rndict[k] == 1) + + def testRelLookup2(self): + k = dns.name.from_text('foo.bar', None) + self.failUnless(self.rndict.get_deepest_match(k)[1] == 1) + + def testRelLookup3(self): + k = dns.name.from_text('a.b.c.foo.bar', None) + self.failUnless(self.rndict.get_deepest_match(k)[1] == 1) + + def testRelLookup4(self): + k = dns.name.from_text('a.b.c.bar', None) + self.failUnless(self.rndict.get_deepest_match(k)[1] == 2) + + def testRelLookup7(self): + self.rndict[dns.name.empty] = 100 + n = dns.name.from_text('a.b.c', None) + (k, v) = self.rndict.get_deepest_match(n) + self.failUnless(v == 100) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/ntoaaton.py b/tests/ntoaaton.py new file mode 100644 index 0000000..52cbc13 --- /dev/null +++ b/tests/ntoaaton.py @@ -0,0 +1,158 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: ntoaaton.py,v 1.6 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.exception +import dns.ipv6 + +class NtoAAtoNTestCase(unittest.TestCase): + + def test_aton1(self): + a = dns.ipv6.inet_aton('::') + self.failUnless(a == '\x00' * 16) + + def test_aton2(self): + a = dns.ipv6.inet_aton('::1') + self.failUnless(a == '\x00' * 15 + '\x01') + + def test_aton3(self): + a = dns.ipv6.inet_aton('::10.0.0.1') + self.failUnless(a == '\x00' * 12 + '\x0a\x00\x00\x01') + + def test_aton4(self): + a = dns.ipv6.inet_aton('abcd::dcba') + self.failUnless(a == '\xab\xcd' + '\x00' * 12 + '\xdc\xba') + + def test_aton5(self): + a = dns.ipv6.inet_aton('1:2:3:4:5:6:7:8') + self.failUnless(a == \ + '00010002000300040005000600070008'.decode('hex_codec')) + + def test_bad_aton1(self): + def bad(): + a = dns.ipv6.inet_aton('abcd:dcba') + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def test_bad_aton2(self): + def bad(): + a = dns.ipv6.inet_aton('abcd::dcba::1') + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def test_bad_aton3(self): + def bad(): + a = dns.ipv6.inet_aton('1:2:3:4:5:6:7:8:9') + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def test_aton1(self): + a = dns.ipv6.inet_aton('::') + self.failUnless(a == '\x00' * 16) + + def test_aton2(self): + a = dns.ipv6.inet_aton('::1') + self.failUnless(a == '\x00' * 15 + '\x01') + + def test_aton3(self): + a = dns.ipv6.inet_aton('::10.0.0.1') + self.failUnless(a == '\x00' * 12 + '\x0a\x00\x00\x01') + + def test_aton4(self): + a = dns.ipv6.inet_aton('abcd::dcba') + self.failUnless(a == '\xab\xcd' + '\x00' * 12 + '\xdc\xba') + + def test_ntoa1(self): + b = '00010002000300040005000600070008'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '1:2:3:4:5:6:7:8') + + def test_ntoa2(self): + b = '\x00' * 16 + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::') + + def test_ntoa3(self): + b = '\x00' * 15 + '\x01' + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::1') + + def test_ntoa4(self): + b = '\x80' + '\x00' * 15 + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '8000::') + + def test_ntoa5(self): + b = '\x01\xcd' + '\x00' * 12 + '\x03\xef' + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '1cd::3ef') + + def test_ntoa6(self): + b = 'ffff00000000ffff000000000000ffff'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == 'ffff:0:0:ffff::ffff') + + def test_ntoa7(self): + b = '00000000ffff000000000000ffffffff'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '0:0:ffff::ffff:ffff') + + def test_ntoa8(self): + b = 'ffff0000ffff00000000ffff00000000'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == 'ffff:0:ffff::ffff:0:0') + + def test_ntoa9(self): + b = '0000000000000000000000000a000001'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::10.0.0.1') + + def test_ntoa10(self): + b = '0000000000000000000000010a000001'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::1:a00:1') + + def test_ntoa11(self): + b = '00000000000000000000ffff0a000001'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::ffff:10.0.0.1') + + def test_ntoa12(self): + b = '000000000000000000000000ffffffff'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::255.255.255.255') + + def test_ntoa13(self): + b = '00000000000000000000ffffffffffff'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::ffff:255.255.255.255') + + def test_ntoa14(self): + b = '0000000000000000000000000001ffff'.decode('hex_codec') + t = dns.ipv6.inet_ntoa(b) + self.failUnless(t == '::0.1.255.255') + + def test_bad_ntoa1(self): + def bad(): + a = dns.ipv6.inet_ntoa('') + self.failUnlessRaises(ValueError, bad) + + def test_bad_ntoa2(self): + def bad(): + a = dns.ipv6.inet_ntoa('\x00' * 17) + self.failUnlessRaises(ValueError, bad) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/rdtypeandclass.py b/tests/rdtypeandclass.py new file mode 100644 index 0000000..3336538 --- /dev/null +++ b/tests/rdtypeandclass.py @@ -0,0 +1,125 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rdtypeandclass.py,v 1.3 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.rdataclass +import dns.rdatatype + +class RdTypeAndClassTestCase(unittest.TestCase): + + # Classes + + def test_class_meta1(self): + self.failUnless(dns.rdataclass.is_metaclass(dns.rdataclass.ANY)) + + def test_class_meta2(self): + self.failUnless(not dns.rdataclass.is_metaclass(dns.rdataclass.IN)) + + def test_class_bytext1(self): + self.failUnless(dns.rdataclass.from_text('IN') == dns.rdataclass.IN) + + def test_class_bytext2(self): + self.failUnless(dns.rdataclass.from_text('CLASS1') == + dns.rdataclass.IN) + + def test_class_bytext_bounds1(self): + self.failUnless(dns.rdataclass.from_text('CLASS0') == 0) + self.failUnless(dns.rdataclass.from_text('CLASS65535') == 65535) + + def test_class_bytext_bounds2(self): + def bad(): + junk = dns.rdataclass.from_text('CLASS65536') + self.failUnlessRaises(ValueError, bad) + + def test_class_bytext_unknown(self): + def bad(): + junk = dns.rdataclass.from_text('XXX') + self.failUnlessRaises(dns.rdataclass.UnknownRdataclass, bad) + + def test_class_totext1(self): + self.failUnless(dns.rdataclass.to_text(dns.rdataclass.IN) == 'IN') + + def test_class_totext1(self): + self.failUnless(dns.rdataclass.to_text(999) == 'CLASS999') + + def test_class_totext_bounds1(self): + def bad(): + junk = dns.rdataclass.to_text(-1) + self.failUnlessRaises(ValueError, bad) + + def test_class_totext_bounds2(self): + def bad(): + junk = dns.rdataclass.to_text(65536) + self.failUnlessRaises(ValueError, bad) + + # Types + + def test_type_meta1(self): + self.failUnless(dns.rdatatype.is_metatype(dns.rdatatype.ANY)) + + def test_type_meta2(self): + self.failUnless(dns.rdatatype.is_metatype(dns.rdatatype.OPT)) + + def test_type_meta3(self): + self.failUnless(not dns.rdatatype.is_metatype(dns.rdatatype.A)) + + def test_type_singleton1(self): + self.failUnless(dns.rdatatype.is_singleton(dns.rdatatype.SOA)) + + def test_type_singleton2(self): + self.failUnless(not dns.rdatatype.is_singleton(dns.rdatatype.A)) + + def test_type_bytext1(self): + self.failUnless(dns.rdatatype.from_text('A') == dns.rdatatype.A) + + def test_type_bytext2(self): + self.failUnless(dns.rdatatype.from_text('TYPE1') == + dns.rdatatype.A) + + def test_type_bytext_bounds1(self): + self.failUnless(dns.rdatatype.from_text('TYPE0') == 0) + self.failUnless(dns.rdatatype.from_text('TYPE65535') == 65535) + + def test_type_bytext_bounds2(self): + def bad(): + junk = dns.rdatatype.from_text('TYPE65536') + self.failUnlessRaises(ValueError, bad) + + def test_type_bytext_unknown(self): + def bad(): + junk = dns.rdatatype.from_text('XXX') + self.failUnlessRaises(dns.rdatatype.UnknownRdatatype, bad) + + def test_type_totext1(self): + self.failUnless(dns.rdatatype.to_text(dns.rdatatype.A) == 'A') + + def test_type_totext1(self): + self.failUnless(dns.rdatatype.to_text(999) == 'TYPE999') + + def test_type_totext_bounds1(self): + def bad(): + junk = dns.rdatatype.to_text(-1) + self.failUnlessRaises(ValueError, bad) + + def test_type_totext_bounds2(self): + def bad(): + junk = dns.rdatatype.to_text(65536) + self.failUnlessRaises(ValueError, bad) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/resolver.py b/tests/resolver.py new file mode 100644 index 0000000..1ca8047 --- /dev/null +++ b/tests/resolver.py @@ -0,0 +1,83 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: resolver.py,v 1.4 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import sys +import time +import unittest + +import dns.name +import dns.message +import dns.name +import dns.rdataclass +import dns.rdatatype +import dns.resolver + +resolv_conf = """ + /t/t +# comment 1 +; comment 2 +domain foo +nameserver 10.0.0.1 +nameserver 10.0.0.2 +""" + +message_text = """id 1234 +opcode QUERY +rcode NOERROR +flags QR AA RD +;QUESTION +example. IN A +;ANSWER +example. 1 IN A 10.0.0.1 +;AUTHORITY +;ADDITIONAL +""" + +class ResolverTestCase(unittest.TestCase): + + if sys.platform != 'win32': + def testRead(self): + f = cStringIO.StringIO(resolv_conf) + r = dns.resolver.Resolver(f) + self.failUnless(r.nameservers == ['10.0.0.1', '10.0.0.2'] and + r.domain == dns.name.from_text('foo')) + + def testCacheExpiration(self): + message = dns.message.from_text(message_text) + name = dns.name.from_text('example.') + answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN, + message) + cache = dns.resolver.Cache() + cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) + time.sleep(2) + self.failUnless(cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) + is None) + + def testCacheCleaning(self): + message = dns.message.from_text(message_text) + name = dns.name.from_text('example.') + answer = dns.resolver.Answer(name, dns.rdatatype.A, dns.rdataclass.IN, + message) + cache = dns.resolver.Cache(cleaning_interval=1.0) + cache.put((name, dns.rdatatype.A, dns.rdataclass.IN), answer) + time.sleep(2) + self.failUnless(cache.get((name, dns.rdatatype.A, dns.rdataclass.IN)) + is None) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/rrset.py b/tests/rrset.py new file mode 100644 index 0000000..d3704c6 --- /dev/null +++ b/tests/rrset.py @@ -0,0 +1,56 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: rrset.py,v 1.2 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.rrset + +class RRsetTestCase(unittest.TestCase): + + def testEqual1(self): + r1 = dns.rrset.from_text('foo', 300, 'in', 'a', '10.0.0.1', '10.0.0.2') + r2 = dns.rrset.from_text('FOO', 300, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 == r2) + + def testEqual2(self): + r1 = dns.rrset.from_text('foo', 300, 'in', 'a', '10.0.0.1', '10.0.0.2') + r2 = dns.rrset.from_text('FOO', 600, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 == r2) + + def testNotEqual1(self): + r1 = dns.rrset.from_text('fooa', 30, 'in', 'a', '10.0.0.1', '10.0.0.2') + r2 = dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 != r2) + + def testNotEqual2(self): + r1 = dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1', '10.0.0.3') + r2 = dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 != r2) + + def testNotEqual3(self): + r1 = dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1', '10.0.0.2', + '10.0.0.3') + r2 = dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 != r2) + + def testNotEqual4(self): + r1 = dns.rrset.from_text('foo', 30, 'in', 'a', '10.0.0.1') + r2 = dns.rrset.from_text('FOO', 30, 'in', 'a', '10.0.0.2', '10.0.0.1') + self.failUnless(r1 != r2) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/set.py b/tests/set.py new file mode 100644 index 0000000..734b44c --- /dev/null +++ b/tests/set.py @@ -0,0 +1,178 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: set.py,v 1.4 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.set + +# for convenience +S = dns.set.Set + +class SimpleSetTestCase(unittest.TestCase): + + def testLen1(self): + s1 = S() + self.failUnless(len(s1) == 0) + + def testLen2(self): + s1 = S([1, 2, 3]) + self.failUnless(len(s1) == 3) + + def testLen3(self): + s1 = S([1, 2, 3, 3, 3]) + self.failUnless(len(s1) == 3) + + def testUnion1(self): + s1 = S([1, 2, 3]) + s2 = S([1, 2, 3]) + e = S([1, 2, 3]) + self.failUnless(s1 | s2 == e) + + def testUnion2(self): + s1 = S([1, 2, 3]) + s2 = S([]) + e = S([1, 2, 3]) + self.failUnless(s1 | s2 == e) + + def testUnion3(self): + s1 = S([1, 2, 3]) + s2 = S([3, 4]) + e = S([1, 2, 3, 4]) + self.failUnless(s1 | s2 == e) + + def testIntersection1(self): + s1 = S([1, 2, 3]) + s2 = S([1, 2, 3]) + e = S([1, 2, 3]) + self.failUnless(s1 & s2 == e) + + def testIntersection2(self): + s1 = S([0, 1, 2, 3]) + s2 = S([1, 2, 3, 4]) + e = S([1, 2, 3]) + self.failUnless(s1 & s2 == e) + + def testIntersection3(self): + s1 = S([1, 2, 3]) + s2 = S([]) + e = S([]) + self.failUnless(s1 & s2 == e) + + def testIntersection4(self): + s1 = S([1, 2, 3]) + s2 = S([5, 4]) + e = S([]) + self.failUnless(s1 & s2 == e) + + def testDifference1(self): + s1 = S([1, 2, 3]) + s2 = S([5, 4]) + e = S([1, 2, 3]) + self.failUnless(s1 - s2 == e) + + def testDifference2(self): + s1 = S([1, 2, 3]) + s2 = S([]) + e = S([1, 2, 3]) + self.failUnless(s1 - s2 == e) + + def testDifference3(self): + s1 = S([1, 2, 3]) + s2 = S([3, 2]) + e = S([1]) + self.failUnless(s1 - s2 == e) + + def testDifference4(self): + s1 = S([1, 2, 3]) + s2 = S([3, 2, 1]) + e = S([]) + self.failUnless(s1 - s2 == e) + + def testSubset1(self): + s1 = S([1, 2, 3]) + s2 = S([3, 2, 1]) + self.failUnless(s1.issubset(s2)) + + def testSubset2(self): + s1 = S([1, 2, 3]) + self.failUnless(s1.issubset(s1)) + + def testSubset3(self): + s1 = S([]) + s2 = S([1, 2, 3]) + self.failUnless(s1.issubset(s2)) + + def testSubset4(self): + s1 = S([1]) + s2 = S([1, 2, 3]) + self.failUnless(s1.issubset(s2)) + + def testSubset5(self): + s1 = S([]) + s2 = S([]) + self.failUnless(s1.issubset(s2)) + + def testSubset6(self): + s1 = S([1, 4]) + s2 = S([1, 2, 3]) + self.failUnless(not s1.issubset(s2)) + + def testSuperset1(self): + s1 = S([1, 2, 3]) + s2 = S([3, 2, 1]) + self.failUnless(s1.issuperset(s2)) + + def testSuperset2(self): + s1 = S([1, 2, 3]) + self.failUnless(s1.issuperset(s1)) + + def testSuperset3(self): + s1 = S([1, 2, 3]) + s2 = S([]) + self.failUnless(s1.issuperset(s2)) + + def testSuperset4(self): + s1 = S([1, 2, 3]) + s2 = S([1]) + self.failUnless(s1.issuperset(s2)) + + def testSuperset5(self): + s1 = S([]) + s2 = S([]) + self.failUnless(s1.issuperset(s2)) + + def testSuperset6(self): + s1 = S([1, 2, 3]) + s2 = S([1, 4]) + self.failUnless(not s1.issuperset(s2)) + + def testUpdate1(self): + s1 = S([1, 2, 3]) + u = (4, 5, 6) + e = S([1, 2, 3, 4, 5, 6]) + s1.update(u) + self.failUnless(s1 == e) + + def testUpdate2(self): + s1 = S([1, 2, 3]) + u = [] + e = S([1, 2, 3]) + s1.update(u) + self.failUnless(s1 == e) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/tokenizer.py b/tests/tokenizer.py new file mode 100644 index 0000000..5e6118e --- /dev/null +++ b/tests/tokenizer.py @@ -0,0 +1,177 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: tokenizer.py,v 1.7 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.exception +import dns.tokenizer + +class TokenizerTestCase(unittest.TestCase): + + def testQuotedString1(self): + tok = dns.tokenizer.Tokenizer(r'"foo"') + (ttype, value) = tok.get() + self.failUnless(ttype == dns.tokenizer.QUOTED_STRING and + value == 'foo') + + def testQuotedString2(self): + tok = dns.tokenizer.Tokenizer(r'""') + (ttype, value) = tok.get() + self.failUnless(ttype == dns.tokenizer.QUOTED_STRING and + value == '') + + def testQuotedString3(self): + tok = dns.tokenizer.Tokenizer(r'"\"foo\""') + (ttype, value) = tok.get() + self.failUnless(ttype == dns.tokenizer.QUOTED_STRING and + value == '"foo"') + + def testQuotedString4(self): + tok = dns.tokenizer.Tokenizer(r'"foo\010bar"') + (ttype, value) = tok.get() + self.failUnless(ttype == dns.tokenizer.QUOTED_STRING and + value == 'foo\x0abar') + + def testQuotedString5(self): + def bad(): + tok = dns.tokenizer.Tokenizer(r'"foo') + (ttype, value) = tok.get() + self.failUnlessRaises(dns.exception.UnexpectedEnd, bad) + + def testQuotedString6(self): + def bad(): + tok = dns.tokenizer.Tokenizer(r'"foo\01') + (ttype, value) = tok.get() + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def testQuotedString7(self): + def bad(): + tok = dns.tokenizer.Tokenizer('"foo\nbar"') + (ttype, value) = tok.get() + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def testEmpty1(self): + tok = dns.tokenizer.Tokenizer('') + (ttype, value) = tok.get() + self.failUnless(ttype == dns.tokenizer.EOF) + + def testEmpty2(self): + tok = dns.tokenizer.Tokenizer('') + (ttype1, value1) = tok.get() + (ttype2, value2) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.EOF and + ttype2 == dns.tokenizer.EOF) + + def testEOL(self): + tok = dns.tokenizer.Tokenizer('\n') + (ttype1, value1) = tok.get() + (ttype2, value2) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.EOL and + ttype2 == dns.tokenizer.EOF) + + def testWS1(self): + tok = dns.tokenizer.Tokenizer(' \n') + (ttype1, value1) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.EOL) + + def testWS2(self): + tok = dns.tokenizer.Tokenizer(' \n') + (ttype1, value1) = tok.get(want_leading=True) + self.failUnless(ttype1 == dns.tokenizer.WHITESPACE) + + def testComment1(self): + tok = dns.tokenizer.Tokenizer(' ;foo\n') + (ttype1, value1) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.EOL) + + def testComment2(self): + tok = dns.tokenizer.Tokenizer(' ;foo\n') + (ttype1, value1) = tok.get(want_comment = True) + (ttype2, value2) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.COMMENT and + value1 == 'foo' and + ttype2 == dns.tokenizer.EOL) + + def testComment3(self): + tok = dns.tokenizer.Tokenizer(' ;foo bar\n') + (ttype1, value1) = tok.get(want_comment = True) + (ttype2, value2) = tok.get() + self.failUnless(ttype1 == dns.tokenizer.COMMENT and + value1 == 'foo bar' and + ttype2 == dns.tokenizer.EOL) + + def testMultiline1(self): + tok = dns.tokenizer.Tokenizer('( foo\n\n bar\n)') + tokens = list(iter(tok)) + self.failUnless(tokens == [(dns.tokenizer.IDENTIFIER, 'foo'), + (dns.tokenizer.IDENTIFIER, 'bar')]) + + def testMultiline2(self): + tok = dns.tokenizer.Tokenizer('( foo\n\n bar\n)\n') + tokens = list(iter(tok)) + self.failUnless(tokens == [(dns.tokenizer.IDENTIFIER, 'foo'), + (dns.tokenizer.IDENTIFIER, 'bar'), + (dns.tokenizer.EOL, '\n')]) + def testMultiline3(self): + def bad(): + tok = dns.tokenizer.Tokenizer('foo)') + tokens = list(iter(tok)) + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def testMultiline4(self): + def bad(): + tok = dns.tokenizer.Tokenizer('((foo)') + tokens = list(iter(tok)) + self.failUnlessRaises(dns.exception.SyntaxError, bad) + + def testUnget1(self): + tok = dns.tokenizer.Tokenizer('foo') + t1 = tok.get() + tok.unget(t1) + t2 = tok.get() + self.failUnless(t1 == t2 and t1 == (dns.tokenizer.IDENTIFIER, 'foo')) + + def testUnget2(self): + def bad(): + tok = dns.tokenizer.Tokenizer('foo') + t1 = tok.get() + tok.unget(t1) + tok.unget(t1) + self.failUnlessRaises(dns.tokenizer.UngetBufferFull, bad) + + def testGetEOL1(self): + tok = dns.tokenizer.Tokenizer('\n') + t = tok.get_eol() + self.failUnless(t == '\n') + + def testGetEOL2(self): + tok = dns.tokenizer.Tokenizer('') + t = tok.get_eol() + self.failUnless(t == '') + + def testEscapedDelimiter1(self): + tok = dns.tokenizer.Tokenizer(r'ch\ ld') + t = tok.get() + self.failUnless(t == (dns.tokenizer.IDENTIFIER, r'ch ld')) + + def testEscapedDelimiter2(self): + tok = dns.tokenizer.Tokenizer(r'ch\0ld') + t = tok.get() + self.failUnless(t == (dns.tokenizer.IDENTIFIER, r'ch\0ld')) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/update.py b/tests/update.py new file mode 100644 index 0000000..5eecf7f --- /dev/null +++ b/tests/update.py @@ -0,0 +1,116 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: update.py,v 1.5 2004/03/19 00:17:27 halley Exp $ + +import unittest + +import dns.update +import dns.rdata +import dns.rdataset + +goodhex = '0001 2800 0001 0005 0007 0000' \ + '076578616d706c6500 0006 0001' \ + '03666f6fc00c 00ff 00ff 00000000 0000' \ + 'c019 0001 00ff 00000000 0000' \ + '03626172c00c 0001 0001 00000000 0004 0a000005' \ + '05626c617a32c00c 00ff 00fe 00000000 0000' \ + 'c049 0001 00fe 00000000 0000' \ + 'c019 0001 00ff 00000000 0000' \ + 'c019 0001 0001 0000012c 0004 0a000001' \ + 'c019 0001 0001 0000012c 0004 0a000002' \ + 'c035 0001 0001 0000012c 0004 0a000003' \ + 'c035 0001 00fe 00000000 0004 0a000004' \ + '04626c617ac00c 0001 00ff 00000000 0000' \ + 'c049 00ff 00ff 00000000 0000' + +goodwire = goodhex.replace(' ', '').decode('hex_codec') + +update_text="""id 1 +opcode UPDATE +rcode NOERROR +;ZONE +example. IN SOA +;PREREQ +foo ANY ANY +foo ANY A +bar 0 IN A 10.0.0.5 +blaz2 NONE ANY +blaz2 NONE A +;UPDATE +foo ANY A +foo 300 IN A 10.0.0.1 +foo 300 IN A 10.0.0.2 +bar 300 IN A 10.0.0.3 +bar 0 NONE A 10.0.0.4 +blaz ANY A +blaz2 ANY ANY +""" + +class UpdateTestCase(unittest.TestCase): + + def test_to_wire1(self): + update = dns.update.Update('example') + update.id = 1 + update.present('foo') + update.present('foo', 'a') + update.present('bar', 'a', '10.0.0.5') + update.absent('blaz2') + update.absent('blaz2', 'a') + update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2') + update.add('bar', 300, 'a', '10.0.0.3') + update.delete('bar', 'a', '10.0.0.4') + update.delete('blaz','a') + update.delete('blaz2') + self.failUnless(update.to_wire() == goodwire) + + def test_to_wire2(self): + update = dns.update.Update('example') + update.id = 1 + update.present('foo') + update.present('foo', 'a') + update.present('bar', 'a', '10.0.0.5') + update.absent('blaz2') + update.absent('blaz2', 'a') + update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2') + update.add('bar', 300, dns.rdata.from_text(1, 1, '10.0.0.3')) + update.delete('bar', 'a', '10.0.0.4') + update.delete('blaz','a') + update.delete('blaz2') + self.failUnless(update.to_wire() == goodwire) + + def test_to_wire3(self): + update = dns.update.Update('example') + update.id = 1 + update.present('foo') + update.present('foo', 'a') + update.present('bar', 'a', '10.0.0.5') + update.absent('blaz2') + update.absent('blaz2', 'a') + update.replace('foo', 300, 'a', '10.0.0.1', '10.0.0.2') + update.add('bar', dns.rdataset.from_text(1, 1, 300, '10.0.0.3')) + update.delete('bar', 'a', '10.0.0.4') + update.delete('blaz','a') + update.delete('blaz2') + self.failUnless(update.to_wire() == goodwire) + + def test_from_text1(self): + update = dns.message.from_text(update_text) + w = update.to_wire(origin=dns.name.from_text('example'), + want_shuffle=False) + self.failUnless(w == goodwire) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/zone.py b/tests/zone.py new file mode 100644 index 0000000..2e3ebf1 --- /dev/null +++ b/tests/zone.py @@ -0,0 +1,347 @@ +# Copyright (C) 2003, 2004 Nominum, Inc. +# +# Permission to use, copy, modify, and distribute this software and its +# documentation for any purpose with or without fee is hereby granted, +# provided that the above copyright notice and this permission notice +# appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +# $Id: zone.py,v 1.21 2004/03/19 00:17:27 halley Exp $ + +import cStringIO +import filecmp +import os +import unittest + +import dns.exception +import dns.rdata +import dns.rdataclass +import dns.rdatatype +import dns.rrset +import dns.zone + +example_text = """$TTL 3600 +$ORIGIN example. +@ soa foo bar 1 2 3 4 5 +@ ns ns1 +@ ns ns2 +ns1 a 10.0.0.1 +ns2 a 10.0.0.2 +$TTL 300 +$ORIGIN foo.example. +bar mx 0 blaz +""" + +example_text_output = """@ 3600 IN SOA foo bar 1 2 3 4 5 +@ 3600 IN NS ns1 +@ 3600 IN NS ns2 +bar.foo 300 IN MX 0 blaz.foo +ns1 3600 IN A 10.0.0.1 +ns2 3600 IN A 10.0.0.2 +""" + +something_quite_similar = """@ 3600 IN SOA foo bar 1 2 3 4 5 +@ 3600 IN NS ns1 +@ 3600 IN NS ns2 +bar.foo 300 IN MX 0 blaz.foo +ns1 3600 IN A 10.0.0.1 +ns2 3600 IN A 10.0.0.3 +""" + +something_different = """@ 3600 IN SOA fooa bar 1 2 3 4 5 +@ 3600 IN NS ns11 +@ 3600 IN NS ns21 +bar.fooa 300 IN MX 0 blaz.fooa +ns11 3600 IN A 10.0.0.11 +ns21 3600 IN A 10.0.0.21 +""" + +ttl_example_text = """$TTL 1h +$ORIGIN example. +@ soa foo bar 1 2 3 4 5 +@ ns ns1 +@ ns ns2 +ns1 1d1s a 10.0.0.1 +ns2 1w1D1h1m1S a 10.0.0.2 +""" + +no_soa_text = """$TTL 1h +$ORIGIN example. +@ ns ns1 +@ ns ns2 +ns1 1d1s a 10.0.0.1 +ns2 1w1D1h1m1S a 10.0.0.2 +""" + +no_ns_text = """$TTL 1h +$ORIGIN example. +@ soa foo bar 1 2 3 4 5 +""" + +include_text = """$INCLUDE "example" +""" + +bad_directive_text = """$FOO bar +$ORIGIN example. +@ soa foo bar 1 2 3 4 5 +@ ns ns1 +@ ns ns2 +ns1 1d1s a 10.0.0.1 +ns2 1w1D1h1m1S a 10.0.0.2 +""" + +class ZoneTestCase(unittest.TestCase): + + def testFromFile1(self): + z = dns.zone.from_file('example', 'example') + ok = False + try: + z.to_file('example1.out', nl='\x0a') + ok = filecmp.cmp('example1.out', 'example1.good') + finally: + os.unlink('example1.out') + self.failUnless(ok) + + def testFromFile2(self): + z = dns.zone.from_file('example', 'example', relativize=False) + ok = False + try: + z.to_file('example2.out', relativize=False, nl='\x0a') + ok = filecmp.cmp('example2.out', 'example2.good') + finally: + os.unlink('example2.out') + self.failUnless(ok) + + def testFromText(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + f = cStringIO.StringIO() + names = z.nodes.keys() + names.sort() + for n in names: + print >> f, z[n].to_text(n) + self.failUnless(f.getvalue() == example_text_output) + + def testTorture1(self): + # + # Read a zone containing all our supported RR types, and + # for each RR in the zone, convert the rdata into wire format + # and then back out, and see if we get equal rdatas. + # + f = cStringIO.StringIO() + o = dns.name.from_text('example.') + z = dns.zone.from_file('example', o) + for (name, node) in z.iteritems(): + for rds in node: + for rd in rds: + f.seek(0) + f.truncate() + rd.to_wire(f, origin=o) + wire = f.getvalue() + rd2 = dns.rdata.from_wire(rds.rdclass, rds.rdtype, + wire, 0, len(wire), + origin = o) + self.failUnless(rd == rd2) + + def testEqual(self): + z1 = dns.zone.from_text(example_text, 'example.', relativize=True) + z2 = dns.zone.from_text(example_text_output, 'example.', + relativize=True) + self.failUnless(z1 == z2) + + def testNotEqual1(self): + z1 = dns.zone.from_text(example_text, 'example.', relativize=True) + z2 = dns.zone.from_text(something_quite_similar, 'example.', + relativize=True) + self.failUnless(z1 != z2) + + def testNotEqual2(self): + z1 = dns.zone.from_text(example_text, 'example.', relativize=True) + z2 = dns.zone.from_text(something_different, 'example.', + relativize=True) + self.failUnless(z1 != z2) + + def testNotEqual3(self): + z1 = dns.zone.from_text(example_text, 'example.', relativize=True) + z2 = dns.zone.from_text(something_different, 'example2.', + relativize=True) + self.failUnless(z1 != z2) + + def testFindRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rds = z.find_rdataset('@', 'soa') + exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') + self.failUnless(rds == exrds) + + def testFindRdataset2(self): + def bad(): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rds = z.find_rdataset('@', 'loc') + self.failUnlessRaises(KeyError, bad) + + def testFindRRset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rrs = z.find_rrset('@', 'soa') + exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5') + self.failUnless(rrs == exrrs) + + def testFindRRset2(self): + def bad(): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rrs = z.find_rrset('@', 'loc') + self.failUnlessRaises(KeyError, bad) + + def testGetRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rds = z.get_rdataset('@', 'soa') + exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') + self.failUnless(rds == exrds) + + def testGetRdataset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rds = z.get_rdataset('@', 'loc') + self.failUnless(rds == None) + + def testGetRRset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rrs = z.get_rrset('@', 'soa') + exrrs = dns.rrset.from_text('@', 300, 'IN', 'SOA', 'foo bar 1 2 3 4 5') + self.failUnless(rrs == exrrs) + + def testGetRRset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rrs = z.get_rrset('@', 'loc') + self.failUnless(rrs == None) + + def testReplaceRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rdataset = dns.rdataset.from_text('in', 'ns', 300, 'ns3', 'ns4') + z.replace_rdataset('@', rdataset) + rds = z.get_rdataset('@', 'ns') + self.failUnless(rds is rdataset) + + def testReplaceRdataset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + rdataset = dns.rdataset.from_text('in', 'txt', 300, '"foo"') + z.replace_rdataset('@', rdataset) + rds = z.get_rdataset('@', 'txt') + self.failUnless(rds is rdataset) + + def testDeleteRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + z.delete_rdataset('@', 'ns') + rds = z.get_rdataset('@', 'ns') + self.failUnless(rds is None) + + def testDeleteRdataset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + z.delete_rdataset('ns1', 'a') + node = z.get_node('ns1') + self.failUnless(node is None) + + def testNodeFindRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) + exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') + self.failUnless(rds == exrds) + + def testNodeFindRdataset2(self): + def bad(): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.find_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) + self.failUnlessRaises(KeyError, bad) + + def testNodeGetRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) + exrds = dns.rdataset.from_text('IN', 'SOA', 300, 'foo bar 1 2 3 4 5') + self.failUnless(rds == exrds) + + def testNodeGetRdataset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) + self.failUnless(rds == None) + + def testNodeDeleteRdataset1(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) + rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) + self.failUnless(rds == None) + + def testNodeDeleteRdataset2(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + node = z['@'] + rds = node.delete_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) + rds = node.get_rdataset(dns.rdataclass.IN, dns.rdatatype.LOC) + self.failUnless(rds == None) + + def testIterateRdatasets(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + ns = [n for n, r in z.iterate_rdatasets('A')] + ns.sort() + self.failUnless(ns == [dns.name.from_text('ns1', None), + dns.name.from_text('ns2', None)]) + + def testIterateRdatas(self): + z = dns.zone.from_text(example_text, 'example.', relativize=True) + l = list(z.iterate_rdatas('A')) + l.sort() + exl = [(dns.name.from_text('ns1', None), + 3600, + dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, + '10.0.0.1')), + (dns.name.from_text('ns2', None), + 3600, + dns.rdata.from_text(dns.rdataclass.IN, dns.rdatatype.A, + '10.0.0.2'))] + self.failUnless(l == exl) + + def testTTLs(self): + z = dns.zone.from_text(ttl_example_text, 'example.', relativize=True) + n = z['@'] + rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.SOA) + self.failUnless(rds.ttl == 3600) + n = z['ns1'] + rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) + self.failUnless(rds.ttl == 86401) + n = z['ns2'] + rds = n.get_rdataset(dns.rdataclass.IN, dns.rdatatype.A) + self.failUnless(rds.ttl == 694861) + + def testNoSOA(self): + def bad(): + z = dns.zone.from_text(no_soa_text, 'example.', + relativize=True) + self.failUnlessRaises(dns.zone.NoSOA, bad) + + def testNoNS(self): + def bad(): + z = dns.zone.from_text(no_ns_text, 'example.', + relativize=True) + self.failUnlessRaises(dns.zone.NoNS, bad) + + def testInclude(self): + z1 = dns.zone.from_text(include_text, 'example.', relativize=True, + allow_include=True) + z2 = dns.zone.from_file('example', 'example.', relativize=True) + self.failUnless(z1 == z2) + + def testBadDirective(self): + def bad(): + z = dns.zone.from_text(bad_directive_text, 'example.', + relativize=True) + self.failUnlessRaises(dns.exception.SyntaxError, bad) + +if __name__ == '__main__': + unittest.main() diff --git a/util/.arch-ids/=id b/util/.arch-ids/=id new file mode 100644 index 0000000..4dbf7c6 --- /dev/null +++ b/util/.arch-ids/=id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:21:09 2004 4751.10 diff --git a/util/.arch-ids/COPYRIGHT.id b/util/.arch-ids/COPYRIGHT.id new file mode 100644 index 0000000..32568de --- /dev/null +++ b/util/.arch-ids/COPYRIGHT.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:29 2004 4765.1 diff --git a/util/.arch-ids/copyrights.id b/util/.arch-ids/copyrights.id new file mode 100644 index 0000000..8ab67ce --- /dev/null +++ b/util/.arch-ids/copyrights.id @@ -0,0 +1 @@ +Bob Halley <halley@dnspython.org> Wed Mar 24 08:22:29 2004 4765.0 diff --git a/util/COPYRIGHT b/util/COPYRIGHT new file mode 100644 index 0000000..7390363 --- /dev/null +++ b/util/COPYRIGHT @@ -0,0 +1,14 @@ +Copyright (C) @YEARS@ Nominum, Inc. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose with or without fee is hereby granted, +provided that the above copyright notice and this permission notice +appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND NOMINUM DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NOMINUM BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/util/copyrights b/util/copyrights new file mode 100644 index 0000000..41a3b35 --- /dev/null +++ b/util/copyrights @@ -0,0 +1,100 @@ +./.cvsignore X 2003,2004 +./ChangeLog X 2003,2004 +./LICENSE X 2003,2004 +./MANIFEST.in X 2003,2004 +./Makefile MAKE 2003,2004 +./README X 2003,2004 +./TODO X 2003,2004 +./dns/__init__.py PYTHON 2003,2004 +./dns/dnssec.py PYTHON 2003,2004 +./dns/exception.py PYTHON 2003,2004 +./dns/flags.py PYTHON 2001,2002,2003,2004 +./dns/inet.py PYTHON 2003,2004 +./dns/ipv4.py PYTHON 2003,2004 +./dns/ipv6.py PYTHON 2003,2004 +./dns/message.py PYTHON 2001,2002,2003,2004 +./dns/name.py PYTHON 2001,2002,2003,2004 +./dns/namedict.py PYTHON 2003,2004 +./dns/node.py PYTHON 2001,2002,2003,2004 +./dns/opcode.py PYTHON 2001,2002,2003,2004 +./dns/query.py PYTHON 2003,2004 +./dns/rcode.py PYTHON 2001,2002,2003,2004 +./dns/rdata.py PYTHON 2001,2002,2003,2004 +./dns/rdataclass.py PYTHON 2001,2002,2003,2004 +./dns/rdataset.py PYTHON 2001,2002,2003,2004 +./dns/rdatatype.py PYTHON 2001,2002,2003,2004 +./dns/rdtypes/ANY/AFSDB.py PYTHON 2003,2004 +./dns/rdtypes/ANY/CERT.py PYTHON 2003,2004 +./dns/rdtypes/ANY/CNAME.py PYTHON 2003,2004 +./dns/rdtypes/ANY/DNAME.py PYTHON 2003,2004 +./dns/rdtypes/ANY/DNSKEY.py PYTHON 2004 +./dns/rdtypes/ANY/DS.py PYTHON 2003,2004 +./dns/rdtypes/ANY/GPOS.py PYTHON 2003,2004 +./dns/rdtypes/ANY/HINFO.py PYTHON 2003,2004 +./dns/rdtypes/ANY/ISDN.py PYTHON 2003,2004 +./dns/rdtypes/ANY/KEY.py PYTHON 2003,2004 +./dns/rdtypes/ANY/LOC.py PYTHON 2003,2004 +./dns/rdtypes/ANY/MX.py PYTHON 2003,2004 +./dns/rdtypes/ANY/NS.py PYTHON 2003,2004 +./dns/rdtypes/ANY/NSEC.py PYTHON 2004 +./dns/rdtypes/ANY/NXT.py PYTHON 2003,2004 +./dns/rdtypes/ANY/PTR.py PYTHON 2003,2004 +./dns/rdtypes/ANY/RP.py PYTHON 2003,2004 +./dns/rdtypes/ANY/RRSIG.py PYTHON 2004 +./dns/rdtypes/ANY/RT.py PYTHON 2003,2004 +./dns/rdtypes/ANY/SIG.py PYTHON 2003,2004 +./dns/rdtypes/ANY/SOA.py PYTHON 2003,2004 +./dns/rdtypes/ANY/TXT.py PYTHON 2003,2004 +./dns/rdtypes/ANY/X25.py PYTHON 2003,2004 +./dns/rdtypes/ANY/__init__.py PYTHON 2003,2004 +./dns/rdtypes/IN/A.py PYTHON 2003,2004 +./dns/rdtypes/IN/AAAA.py PYTHON 2003,2004 +./dns/rdtypes/IN/APL.py PYTHON 2003,2004 +./dns/rdtypes/IN/KX.py PYTHON 2003,2004 +./dns/rdtypes/IN/NAPTR.py PYTHON 2003,2004 +./dns/rdtypes/IN/NSAP.py PYTHON 2003,2004 +./dns/rdtypes/IN/NSAP_PTR.py PYTHON 2003,2004 +./dns/rdtypes/IN/PX.py PYTHON 2003,2004 +./dns/rdtypes/IN/SRV.py PYTHON 2003,2004 +./dns/rdtypes/IN/WKS.py PYTHON 2003,2004 +./dns/rdtypes/IN/__init__.py PYTHON 2003,2004 +./dns/rdtypes/__init__.py PYTHON 2003,2004 +./dns/rdtypes/keybase.py PYTHON 2004 +./dns/rdtypes/mxbase.py PYTHON 2003,2004 +./dns/rdtypes/nsbase.py PYTHON 2003,2004 +./dns/rdtypes/sigbase.py PYTHON 2004 +./dns/renderer.py PYTHON 2001,2002,2003,2004 +./dns/resolver.py PYTHON 2003,2004 +./dns/rrset.py PYTHON 2003,2004 +./dns/set.py PYTHON 2003,2004 +./dns/tokenizer.py PYTHON 2003,2004 +./dns/tsig.py PYTHON 2001,2002,2003,2004 +./dns/tsigkeyring.py PYTHON 2003,2004 +./dns/ttl.py PYTHON 2003,2004 +./dns/update.py PYTHON 2003,2004 +./dns/version.py PYTHON 2003,2004 +./dns/zone.py PYTHON 2003,2004 +./examples/mx.py X 2003,2004 +./examples/name.py X 2003,2004 +./examples/reverse.py X 2003,2004 +./examples/xfr.py X 2003,2004 +./setup.py PYTHON 2003,2004 +./tests/.cvsignore X 2003,2004 +./tests/Makefile MAKE 2003,2004 +./tests/example X 2003,2004 +./tests/example1.good X 2003,2004 +./tests/example2.good X 2003,2004 +./tests/flags.py PYTHON 2003,2004 +./tests/message.py PYTHON 2003,2004 +./tests/name.py PYTHON 2003,2004 +./tests/namedict.py PYTHON 2003,2004 +./tests/ntoaaton.py PYTHON 2003,2004 +./tests/rdtypeandclass.py PYTHON 2003,2004 +./tests/resolver.py PYTHON 2003,2004 +./tests/rrset.py PYTHON 2003,2004 +./tests/set.py PYTHON 2003,2004 +./tests/tokenizer.py PYTHON 2003,2004 +./tests/update.py PYTHON 2003,2004 +./tests/zone.py PYTHON 2003,2004 +./util/COPYRIGHT X 2003,2004 +./util/copyrights X 2003,2004 |