From: Willem Toorop Date: Tue, 20 Sep 2011 22:35:17 +0000 (+0000) Subject: Added the ldnsx python module from Christopher Olah to the contrib directory. X-Git-Tag: release-1.6.11rc1~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a94ab9b1eae20f6ff0216b8ada044b419e3b5b66;p=thirdparty%2Fldns.git Added the ldnsx python module from Christopher Olah to the contrib directory. Also provided a --with-pyldnsx option with configuring that defaults to installing pyldnsx when pyldns is installed too. --- diff --git a/Makefile.in b/Makefile.in index 42a69b21..d0234aa0 100644 --- a/Makefile.in +++ b/Makefile.in @@ -15,6 +15,7 @@ libdir = @libdir@ includedir = @includedir@ doxygen = @doxygen@ pywrapdir = ${srcdir}/contrib/python +pyldnsxwrapdir = ${srcdir}/contrib/ldnsx swig = @swig@ swigpy_flags = -python @SWIGPY3@ python_site =@PYTHON_SITE_PKG@ @@ -27,6 +28,15 @@ else pyldns_inst= pyldns_uninst= endif +pyldnsx_inst =@PYLDNSX@ +pyldnsx_uninst =@PYLDNSX@ +ifeq "$(pyldnsx_inst)" "pyldnsx" + pyldnsx_inst=install-@PYLDNSX@ + pyldnsx_uninst=uninstall-@PYLDNSX@ +else + pyldnsx_inst= + pyldnsx_uninst= +endif glibtool = @libtool@ libtool = ./libtool ifdef glibtool @@ -181,9 +191,9 @@ ldns_wrapper.lo: $(pywrapdir)/ldns_wrapper.c $(LIBDNS_HEADERS) ldns/common.h ldn _ldns.la: ldns_wrapper.lo libldns.la $(LIBTOOL) --tag=CC --mode=link $(CC) $(strip $(CFLAGS) $(PYTHON_CFLAGS) $(LDFLAGS) $(PYTHON_LDFLAGS) -module -version-number $(version_info) -no-undefined -o $@ $< -rpath $(python_site) -L. -L.libs -lldns $(LIBS)) -install: install-h install-lib install-config install-manpages $(pyldns_inst) +install: install-h install-lib install-config install-manpages $(pyldns_inst) $(pyldnsx_inst) -uninstall: uninstall-manpages uninstall-config uninstall-h uninstall-lib $(pyldns_uninst) +uninstall: uninstall-manpages uninstall-config uninstall-h uninstall-lib $(pyldns_uninst) $(pyldnsx_uninst) destclean: uninstall @@ -246,6 +256,12 @@ uninstall-pyldns: rm -f $(DESTDIR)$(python_site)/ldns/* rmdir -p $(DESTDIR)$(python_site)/ldns +install-pyldnsx: + $(INSTALL) -c -m 644 $(pyldnsxwrapdir)/ldnsx.py $(DESTDIR)$(python_site)/ldnsx.py + +uninstall-pyldnsx: + rm -f $(DESTDIR)$(python_site)/ldnsx.py + clean: rm -f *.o *.d *.lo rm -f *~ diff --git a/ax_python_module.m4 b/ax_python_module.m4 new file mode 100644 index 00000000..bd70a06b --- /dev/null +++ b/ax_python_module.m4 @@ -0,0 +1,49 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_python_module.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_MODULE(modname[, fatal]) +# +# DESCRIPTION +# +# Checks for Python module. +# +# If fatal is non-empty then absence of a module will trigger an error. +# +# LICENSE +# +# Copyright (c) 2008 Andrew Collier +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 5 + +AU_ALIAS([AC_PYTHON_MODULE], [AX_PYTHON_MODULE]) +AC_DEFUN([AX_PYTHON_MODULE],[ + if test -z $PYTHON; + then + PYTHON="python" + fi + PYTHON_NAME=`basename $PYTHON` + AC_MSG_CHECKING($PYTHON_NAME module: $1) + $PYTHON -c "import $1" 2>/dev/null + if test $? -eq 0; + then + AC_MSG_RESULT(yes) + eval AS_TR_CPP(HAVE_PYMOD_$1)=yes + else + AC_MSG_RESULT(no) + eval AS_TR_CPP(HAVE_PYMOD_$1)=no + # + if test -n "$2" + then + AC_MSG_ERROR(failed to find required module $1) + exit 1 + fi + fi +]) diff --git a/configure.ac b/configure.ac index 0d0b3a25..c3be2dda 100644 --- a/configure.ac +++ b/configure.ac @@ -85,6 +85,7 @@ AC_SEARCH_LIBS([inet_pton], [nsl]) # check for python PYTHON_X_CFLAGS="" +ldns_with_pyldns=no AC_ARG_WITH(pyldns, AC_HELP_STRING([--with-pyldns], [generate python library, or --without-pyldns to disable Python support.]), [],[ withval="no" ]) @@ -124,6 +125,7 @@ if test x_$withval != x_no; then AC_DEFINE(HAVE_SWIG,1,[Define if you have SWIG libraries and header files.]) AC_SUBST(PYLDNS, "pyldns") AC_SUBST(swig, "$SWIG") + ldns_with_pyldns=yes fi else AC_MSG_RESULT([*** don't have Python, skipping SWIG, no pyldns ***]) # ' @@ -139,6 +141,25 @@ if test x_$withval != x_no; then fi AC_SUBST(PYTHON_X_CFLAGS) +# Check for pyldnsx +AC_ARG_WITH(pyldnsx, AC_HELP_STRING([--without-pyldnsx], + [Do not install the ldnsx python module, or --with-pyldnsx to install it.]), + [],[ withval="with_pyldns" ]) +if test x_$withval != x_no; then + if test x_$ldns_with_pyldns != x_no; then + # The python ipcalc module is needed for pyldnsx, but it is not + # a build dependency + # + #sinclude(ax_python_module.m4) + #AX_PYTHON_MODULE(ipcalc) + AC_SUBST(PYLDNSX, "pyldnsx") + else + if test x_$withval != x_with_pyldns; then + AC_MSG_ERROR([--with-pyldns is needed for the ldnsx python module]) + fi + fi +fi + # Use libtool ACX_LIBTOOL_C_ONLY diff --git a/contrib/ldnsx/LICENSE b/contrib/ldnsx/LICENSE new file mode 100644 index 00000000..070658dd --- /dev/null +++ b/contrib/ldnsx/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2011, Xelerance +Author: Christopher Olah + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Xelerance nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/contrib/ldnsx/Makefile b/contrib/ldnsx/Makefile new file mode 100644 index 00000000..92e70ce3 --- /dev/null +++ b/contrib/ldnsx/Makefile @@ -0,0 +1,89 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ldnsx.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ldnsx.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/contrib/ldnsx/README b/contrib/ldnsx/README new file mode 100644 index 00000000..66f89d2d --- /dev/null +++ b/contrib/ldnsx/README @@ -0,0 +1,36 @@ +LDNSX: Easy DNS (including DNSSEC) via ldns. + +ldns is a great library. It is a powerfull tool for +working with DNS. python-ldns it is a straight up clone of the C +interface, howver that is not a very good interface for python. Its +documentation is incomplete and some functions don't work as +described. And some objects don't have a full python API. + +ldnsx aims to fix this. It wraps around the ldns python bindings, +working around its limitations and providing a well-documented, more +pythonistic interface. + +Written by Christopher Olah + +Examples +======== + +Query the default resolver for google.com's A records. Print the response +packet. + +>>> import ldnsx +>>> resolver = ldnsx.resolver() +>>> print resolver.query("google.com","A") + +Print the NS records for com. from f.root-servers.net if we get a +response, else an error message. + +>>> import ldnsx +>>> pkt = ldnsx.resolver("f.root-servers.net").query("com.","NS") +>>> if pkt: +>>> for rr in pkt.answer(): +>>> print rr +>>> else: +>>> print "response not received" + + diff --git a/contrib/ldnsx/examples/ldnsx-axfr.py b/contrib/ldnsx/examples/ldnsx-axfr.py new file mode 100644 index 00000000..224bb572 --- /dev/null +++ b/contrib/ldnsx/examples/ldnsx-axfr.py @@ -0,0 +1,30 @@ +#!/usr/bin/python +# vim:fileencoding=utf-8 +# +# AXFR client with IDN (Internationalized Domain Names) support +# + +import ldns +import encodings.idna + +def utf2name(name): + return '.'.join([encodings.idna.ToASCII(a) for a in name.split('.')]) +def name2utf(name): + return '.'.join([encodings.idna.ToUnicode(a) for a in name.split('.')]) + +resolver = ldnsx.resolver("zone.nic.cz") + +#Print results +for rr in resolver.AXFR(utf2name(u"háčkyčárky.cz")): + # rdf = rr.owner() + # if (rdf.get_type() == ldns.LDNS_RDF_TYPE_DNAME): + # print "RDF owner: type=",rr.type(),"data=",name2utf(rr.owner()) + # else: + # print "RDF owner: type=",rdf.get_type_str(),"data=",str(rdf) + # print " RR type=", rr.get_type_str()," ttl=",rr.ttl() + # for rdf in rr.rdfs(): + # if (rdf.get_type() == ldns.LDNS_RDF_TYPE_DNAME): + # print " RDF: type=",rdf.get_type_str(),"data=",name2utf(str(rdf)) + # else: + # print " RDF: type=",rdf.get_type_str(),"data=",str(rdf) + diff --git a/contrib/ldnsx/examples/ldnsx-dnssec.py b/contrib/ldnsx/examples/ldnsx-dnssec.py new file mode 100644 index 00000000..c28ad5cc --- /dev/null +++ b/contrib/ldnsx/examples/ldnsx-dnssec.py @@ -0,0 +1,39 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +import ldnsx +import sys + +debug = True + +if len(sys.argv) < 2: + print "Usage:", sys.argv[0], "domain [resolver_addr]" + sys.exit(1) + +name = sys.argv[1] + +# Create resolver +resolver = ldnsx.resolver(dnssec=True) + +# Custom resolver +if len(sys.argv) > 2: + # Clear previous nameservers + resolver.set_nameservers(sys.argv[2:]) + +# Resolve DNS name +pkt = resolver.query(name, "A") + +if pkt and pkt.answer(): + + # Debug + if debug: + print "NS returned:", pkt.rcode(), "(AA: %d AD: %d)" % ( "AA" in pkt.flags(), "AD" in pkt.flags() ) + + # SERVFAIL indicated bogus name + if pkt.rcode() == "SERVFAIL": + print name, "failed to resolve" + + # Check AD (Authenticated) bit + if pkt.rcode() == "NOERROR": + if "AD" in pkt.flags(): print name, "is secure" + else: print name, "is insecure" + diff --git a/contrib/ldnsx/examples/ldnsx-mx1.py b/contrib/ldnsx/examples/ldnsx-mx1.py new file mode 100644 index 00000000..60dfc426 --- /dev/null +++ b/contrib/ldnsx/examples/ldnsx-mx1.py @@ -0,0 +1,11 @@ +import ldnsx + +resolver = ldnsx.resolver() + +pkt = resolver.query("nic.cz", "MX") + +if (pkt): + mx = pkt.answer() + if (mx): + mx.sort() + print mx diff --git a/contrib/ldnsx/examples/ldnsx-mx2.py b/contrib/ldnsx/examples/ldnsx-mx2.py new file mode 100644 index 00000000..8bee67ff --- /dev/null +++ b/contrib/ldnsx/examples/ldnsx-mx2.py @@ -0,0 +1,17 @@ +#!/usr/bin/python +# +# MX is a small program that prints out the mx records for a particular domain +# +import ldnsx + +resolver = ldnsx.resolver() + +pkt = resolver.query("nic.cz", "MX") +if pkt: + for rr in pkt.answer(rr_type = "MX"): + rdf = rr.owner() + print rr + #Could also do: + #print rr[0], rr[1], rr[2], rr[3], " ".join(rr[4:]) + #print rr.owner(), rr.ttl(), rr.rr_clas(), rr.rr_type(), " ".join(rr[4:]) + diff --git a/contrib/ldnsx/examples/ldnsx-walk.py b/contrib/ldnsx/examples/ldnsx-walk.py new file mode 100755 index 00000000..98109390 --- /dev/null +++ b/contrib/ldnsx/examples/ldnsx-walk.py @@ -0,0 +1,25 @@ +#!/usr/bin/python +# vim:fileencoding=utf-8 +# +# Walk a domain that's using NSEC and print in zonefile format. + +import sys +import ldnsx + +def walk(domain): + res = ldnsx.resolver("193.110.157.136", dnssec=True) + pkt = res.query(domain, 666) + try: + nsec_rr = pkt.authority(rr_type="NSEC")[0] + except: + print "no NSEC found, domain is not signed or using NSEC3" + sys.exit() + for rr_type in nsec_rr[5].split(' ')[:-1]: + for rr in ldnsx.get_rrs(domain, rr_type): + print str(rr)[:-1] + next_rec = nsec_rr[4] + if (next_rec != domain) and (next_rec[-len(domain):] == domain): + walk(next_rec) + +walk("xelerance.com") + diff --git a/contrib/ldnsx/ldnsx.py b/contrib/ldnsx/ldnsx.py new file mode 100644 index 00000000..9b59c4b2 --- /dev/null +++ b/contrib/ldnsx/ldnsx.py @@ -0,0 +1,920 @@ +# (c) Christopher Olah , 2011. Xelerance . +# License: BSD + +""" Easy DNS (including DNSSEC) via ldns. + +ldns is a great library. It is a powerfull tool for +working with DNS. python-ldns it is a straight up clone of the C +interface, howver that is not a very good interface for python. Its +documentation is incomplete and some functions don't work as +described. And some objects don't have a full python API. + +ldnsx aims to fix this. It wraps around the ldns python bindings, +working around its limitations and providing a well-documented, more +pythonistic interface. + +**WARNING:** + +**API subject to change.** No backwards compatibility guarantee. Write software using this version at your own risk! + +Examples +-------- + +Query the default resolver for google.com's A records. Print the response +packet. + +>>> import ldnsx +>>> resolver = ldnsx.resolver() +>>> print resolver.query("google.com","A") + + +Print the root NS records from f.root-servers.net; if we get a +response, else an error message. + +>>> import ldnsx +>>> pkt = ldnsx.resolver("f.root-servers.net").query(".", "NS") +>>> if pkt: +>>> for rr in pkt.answer(): +>>> print rr +>>> else: +>>> print "response not received" + +""" + +import time, sys, calendar, warnings +try: + import ipcalc +except ImportError: + print >> sys.stderr, "ldnsx requires the python-ipcalc" + print >> sys.stderr, "Fedora/CentOS: yum install python-ipcalc" + print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ipcalc" + print >> sys.stderr, "openSUSE: zypper in python-ipcalc" + sys.exit(1) +try: + import ldns +except ImportError: + print >> sys.stderr, "ldnsx requires the ldns-python sub-package from http://www.nlnetlabs.nl/projects/ldns/" + print >> sys.stderr, "Fedora/CentOS: yum install ldns-python" + print >> sys.stderr, "Debian/Ubuntu: apt-get install python-ldns" + print >> sys.stderr, "openSUSE: zypper in python-ldns" + sys.exit(1) + +__version__ = "0.1" + +def isValidIP(ipaddr): + try: + bits_to_type = { 32 : 4, 128 : 6} + bits = len(ipcalc.IP(ipaddr).bin()) + return bits_to_type[bits] + except: + return 0 + +def query(name, rr_type, rr_class="IN", flags=["RD"], tries = 3, res=None): + """Convenience function. Creates a resolver and then queries it. Refer to resolver.query() + * name -- domain to query for + * rr_type -- rr_type to query for + * flags -- flags for query (list of strings) + * tries -- number of times to retry the query on failure + * res -- configurations for the resolver as a dict -- see resolver() + """ + if isinstance(res, list) or isinstance(res, tuple): + res = resolver(*res) + elif isinstance(res, dict): + res = resolver(**res) + else: + res = resolver(res) + return res.query(name, rr_type, rr_class, flags, tries) + +def get_rrs(name, rr_type, rr_class="IN", tries = 3, strict = False, res=None, **kwds): + """Convenience function. Gets RRs for name of type rr_type trying tries times. + If strict, it raises and exception on failure, otherwise it returns []. + * name -- domain to query for + * rr_type -- rr_type to query for + * flags -- flags for query (list of strings) + * tries -- number of times to retry the query on failure + * strict -- if the query fails, do we return [] or raise an exception? + * res -- configurations for the resolver as a dict -- see resolver() + * kwds -- query filters, refer to packet.answer() + """ + if isinstance(res, list) or isinstance(res, tuple): + res = resolver(*res) + elif isinstance(res, dict): + res = resolver(**res) + else: + res = resolver(res) + if "|" in rr_type: + pkt = res.query(name, "ANY", rr_class=rr_class, tries=tries) + else: + pkt = res.query(name, rr_type, rr_class=rr_class, tries=tries) + if pkt: + if rr_type in ["", "ANY", "*"]: + return pkt.answer( **kwds) + else: + return pkt.answer(rr_type=rr_type, **kwds) + else: + if strict: + raise Exception("LDNS couldn't complete query") + else: + return [] + +def secure_query(name, rr_type, rr_class="IN", flags=["RD"], tries = 1, flex=False, res=None): + """Convenience function. Creates a resolver and then does a DNSSEC query. Refer to resolver.query() + * name -- domain to query for + * rr_type -- rr_type to query for + * flags -- flags for query (list of strings) + * tries -- number of times to retry the query on failure + * flex -- if we can't verify data, exception or warning? + * res -- configurations for the resolver as a dict -- see resolver()""" + if isinstance(res, list) or isinstance(res, tuple): + res = resolver(*res) + elif isinstance(res, dict): + res = resolver(**res) + else: + res = resolver(res) + pkt = res.query(name, rr_type, rr_class, flags, tries) + if pkt.rcode() == "SERVFAIL": + raise Exception("%s lookup failed (server error or dnssec validation failed)" % name) + if pkt.rcode() == "NXDOMAIN": + if "AD" in pkt.flags(): + raise Exception("%s lookup failed (non-existence proven by DNSSEC)" % hostname ) + else: + raise Exception("%s lookup failed" % hostname ) + if pkt.rcode() == "NOERROR": + if "AD" not in pkt.flags(): + if not flex: + raise Exception("DNS lookup was insecure") + else: + warnings.warn("DNS lookup was insecure") + return pkt + else: + raise Exception("unknown ldns error, %s" % pkt.rcode()) + + + +class resolver: + """ A wrapper around ldns.ldns_resolver. + + **Examples** + + Making resolvers is easy! + + >>> from ldnsx import resolver + >>> resolver() # from /etc/resolv.conf + + >>> resolver("") # resolver with no nameservers + + >>> resolver("193.110.157.135") #resolver pointing to ip addr + + >>> resolver("f.root-servers.net") # resolver pointing ip address(es) resolved from name + + >>> resolver("193.110.157.135, 193.110.157.136") + >>> # resolver pointing to multiple ip addr, first takes precedence. + + + So is playing around with their nameservers! + + >>> import ldnsx + >>> res = ldnsx.resolver("192.168.1.1") + >>> res.add_nameserver("192.168.1.2") + >>> res.add_nameserver("192.168.1.3") + >>> res.nameservers_ip() + ["192.168.1.1","192.168.1.2","192.168.1.3"] + + And querying! + + >>> from ldnsx import resolver + >>> res= resolver() + >>> res.query("cow.com","A") + ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 7663 + ;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 + ;; QUESTION SECTION: + ;; cow.com. IN A + ;; ANSWER SECTION: + cow.com. 300 IN A 208.87.34.18 + ;; AUTHORITY SECTION: + ;; ADDITIONAL SECTION: + ;; Query time: 313 msec + ;; SERVER: 192.168.111.9 + ;; WHEN: Fri Jun 3 11:01:02 2011 + ;; MSG SIZE rcvd: 41 + + + """ + + def __init__(self, ns = None, dnssec = False, tcp = False, port = 53): + """resolver constructor + + * ns -- the nameserver/comma delimited nameserver list + defaults to settings from /etc/resolv.conf + * dnssec -- should the resolver try and use dnssec or not? + * tcp -- should the resolver use TCP + 'auto' is a depricated work around for old ldns problems + * port -- the port to use, must be the same for all nameservers + + """ + # We construct based on a file and dump the nameservers rather than using + # ldns_resolver_new() to avoid environment/configuration/magic specific + # bugs. + self._ldns_resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf") + if ns != None: + self.drop_nameservers() + nm_list = ns.split(',') + nm_list = map(lambda s: s.strip(), nm_list) + nm_list = filter(lambda s: s != "", nm_list) + nm_list.reverse() + for nm in nm_list: + self.add_nameserver(nm) + # Configure DNSSEC, tcp and port + self.set_dnssec(dnssec) + if tcp == 'auto': + self.autotcp = True + self._ldns_resolver.set_usevc(False) + else: + self.autotcp = False + self._ldns_resolver.set_usevc(tcp) + self._ldns_resolver.set_port(port) + + + def query(self, name, rr_type, rr_class="IN", flags=["RD"], tries = 3): + """Run a query on the resolver. + + * name -- name to query for + * rr_type -- the record type to query for + * rr_class -- the class to query for, defaults to IN (Internet) + * flags -- the flags to send the query with + * tries -- the number of times to attempt to acheive query in case of packet loss, etc + + **Examples** + + Let's get some A records! + + >>> google_a_records = resolver.query("google.com","A").answer() + + Using DNSSEC is easy :) + + >>> dnssec_pkt = ldnsx.resolver(dnssec=True).query("xelerance.com") + + We let you use strings to make things easy, but if you prefer stay close to DNS... + + >>> AAAA = 28 + >>> resolver.query("ipv6.google.com", AAAA) + + **More about rr_type** + + rr_type must be a supported resource record type. There are a large number of RR types: + + =========== =================================== ================== + TYPE Value and meaning Reference + =========== =================================== ================== + A 1 a host address [RFC1035] + NS 2 an authoritative name server [RFC1035] + ... + AAAA 28 IP6 Address [RFC3596] + ... + DS 43 Delegation Signer [RFC4034][RFC3658] + ... + DNSKEY 48 DNSKEY [RFC4034][RFC3755] + ... + Unassigned 32770-65279 + Private use 65280-65534 + Reserved 65535 + =========== =================================== ================== + + (From http://www.iana.org/assignments/dns-parameters) + + RR types are given as a string (eg. "A"). In the case of Unassigned/Private use/Reserved ones, + they are given as "TYPEXXXXX" where XXXXX is the number. ie. RR type 65280 is "TYPE65280". You + may also pass the integer, but you always be given the string. + + If the version of ldnsx you are using is old, it is possible that there could be new rr_types that + we don't recognise mnemonic for. You can still use the number XXX or the string "TYPEXXX". To + determine what rr_type menmonics we support, please refer to resolver.supported_rr_types() + + """ + # Determine rr_type int + if rr_type in _rr_types.keys(): + _rr_type = _rr_types[rr_type] + elif isinstance(rr_type,int): + _rr_type = rr_type + elif isinstance(rr_type,str) and rr_type[0:4] == "TYPE": + try: + _rr_type = int(rr_type[4:]) + except: + raise Exception("%s is a bad RR type. TYPEXXXX: XXXX must be a number") + else: + raise Exception("ldnsx (version %s) does not support the RR type %s." % (__version__, str(rr_type)) ) + # Determine rr_class int + if rr_class == "IN": _rr_class = ldns.LDNS_RR_CLASS_IN + elif rr_class == "CH": _rr_class = ldns.LDNS_RR_CLASS_CH + elif rr_class == "HS": _rr_class = ldns.LDNS_RR_CLASS_HS + else: + raise Exception("ldnsx (version %s) does not support the RR class %s." % (__version__, str(rr_class)) ) + # Determine flags int + _flags = 0 + if "QR" in flags: _flags |= ldns.LDNS_QR + if "AA" in flags: _flags |= ldns.LDNS_AA + if "TC" in flags: _flags |= ldns.LDNS_TC + if "RD" in flags: _flags |= ldns.LDNS_RD + if "CD" in flags: _flags |= ldns.LDNS_CD + if "RA" in flags: _flags |= ldns.LDNS_RA + if "AD" in flags: _flags |= ldns.LDNS_AD + # Query + if tries == 0: return None + try: + pkt = self._ldns_resolver.query(name, _rr_type, _rr_class, _flags) + except KeyboardInterrupt: #Since so much time is spent waiting on ldns, this is very common place for Ctr-C to fall + raise + except: #Since the ldns exceptiion is not very descriptive... + raise Exception("ldns backend ran into problems. Likely, the name you were querying for, %s, was invalid." % name) + #Deal with failed queries + if not pkt: + if tries <= 1: + return None + else: + # One of the major causes of none-packets is truncation of packets + # When autotcp is set, we are in a flexible enough position to try and use tcp + # to get around this. + # Either way, we want to replace the resolver, since resolvers will sometimes + # just freeze up. + if self.autotcp: + self = resolver( ",".join(self.nameservers_ip()),tcp=True, dnssec = self._ldns_resolver.dnssec()) + self.autotcp = True + pkt = self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) + self._ldns_resolver.set_usevc(False) + return pkt + else: + self = resolver( ",".join(self.nameservers_ip()), tcp = self._ldns_resolver.usevc(), dnssec = self._ldns_resolver.dnssec() ) + time.sleep(1) # It could be that things are failing because of a brief outage + return self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) + elif self.autotcp: + pkt = packet(pkt) + if "TC" in pkt.flags(): + self._ldns_resolver.set_usevc(True) + pkt2 = self.query(name, rr_type, rr_class=rr_class, flags=flags, tries = tries-1) + self._ldns_resolver.set_usevc(False) + if pkt2: return packet(pkt2) + return pkt + return packet(pkt) + #ret = [] + #for rr in pkt.answer().rrs(): + # ret.append([str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()]) + #return ret + + def suported_rr_types(self): + """ Returns the supported DNS resource record types. + + Refer to resolver.query() for thorough documentation of resource + record types or refer to: + + http://www.iana.org/assignments/dns-parameters + + """ + return _rr_types.keys() + + def AXFR(self,name): + """AXFR for name + + * name -- name to AXFR for + + This function is a generator. As it AXFRs it will yield you the records. + + **Example** + + Let's get a list of the tlds (gotta catch em all!): + + >>> tlds = [] + >>> for rr in resolver("f.root-servers.net").AXFR("."): + >>> if rr.rr_type() == "NS": + >>> tlds.append(rr.owner()) + + """ + #Dname seems to be unecessary on some computers, but it is on others. Avoid bugs. + if self._ldns_resolver.axfr_start(ldns.ldns_dname(name), ldns.LDNS_RR_CLASS_IN) != ldns.LDNS_STATUS_OK: + raise Exception("Starting AXFR failed. Error: %s" % ldns.ldns_get_errorstr_by_id(status)) + pres = self._ldns_resolver.axfr_next() + while pres: + yield resource_record(pres) + pres = self._ldns_resolver.axfr_next() + + def nameservers_ip(self): + """ returns a list of the resolvers nameservers (as IP addr) + + """ + nm_stack2 =[] + nm_str_stack2=[] + nm = self._ldns_resolver.pop_nameserver() + while nm: + nm_stack2.append(nm) + nm_str_stack2.append(str(nm)) + nm = self._ldns_resolver.pop_nameserver() + for nm in nm_stack2: + self._ldns_resolver.push_nameserver(nm) + nm_str_stack2.reverse() + return nm_str_stack2 + + + def add_nameserver(self,ns): + """ Add a nameserver, IPv4/IPv6/name. + + """ + if isValidIP(ns) == 4: + address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_A,ns) + self._ldns_resolver.push_nameserver(address) + elif isValidIP(ns) == 6: + address = ldns.ldns_rdf_new_frm_str(ldns.LDNS_RDF_TYPE_AAAA,ns) + self._ldns_resolver.push_nameserver(address) + else: + resolver = ldns.ldns_resolver.new_frm_file("/etc/resolv.conf") + #address = resolver.get_addr_by_name(ns) + address = resolver.get_addr_by_name(ldns.ldns_dname(ns)) + if not address: + address = resolver.get_addr_by_name(ldns.ldns_dname(ns)) + if not address: + raise Exception("Failed to resolve address for %s" % ns) + for rr in address.rrs(): + self._ldns_resolver.push_nameserver_rr(rr) + + def drop_nameservers(self): + """Drops all nameservers. + This function causes the resolver to forget all nameservers. + + """ + while self._ldns_resolver.pop_nameserver(): + pass + + def set_nameservers(self, nm_list): + """Takes a list of nameservers and sets the resolver to use them + + """ + self.drop_nameservers() + for nm in nm_list: + self.add_nameserver(nm) + + def __repr__(self): + return "" % ", ".join(self.nameservers_ip()) + __str__ = __repr__ + + def set_dnssec(self,new_dnssec_status): + """Set whether the resolver uses DNSSEC. + + """ + self._ldns_resolver.set_dnssec(new_dnssec_status) + +class packet: + + def _construct_rr_filter(self, **kwds): + def match(pattern, target): + if pattern[0] in ["<",">","!"]: + rel = pattern[0] + pattern=pattern[1:] + elif pattern[0:2] in ["<=","=>"]: + rel = pattern[0:2] + pattern=pattern[2:] + else: + rel = "=" + for val in pattern.split("|"): + if {"<" : target < val, + ">" : target > val, + "!" : target != val, + "=" : target == val, + ">=": target >= val, + "<=": target <= val}[rel]: + return True + return False + def f(rr): + for key in kwds.keys(): + if ( ( isinstance(kwds[key], list) and str(rr[key]) not in map(str,kwds[key]) ) + or ( not isinstance(kwds[key], list) and not match(str(kwds[key]), str(rr[key])))): + return False + return True + return f + + def __init__(self, pkt): + self._ldns_pkt = pkt + + def __repr__(self): + return str(self._ldns_pkt) + __str__ = __repr__ + + def rcode(self): + """Returns the rcode. + + Example returned value: "NOERROR" + + possilbe rcodes (via ldns): "FORMERR", "MASK", "NOERROR", + "NOTAUTH", "NOTIMPL", "NOTZONE", "NXDOMAIN", + "NXRSET", "REFUSED", "SERVFAIL", "SHIFT", + "YXDOMAIN", "YXRRSET" + + Refer to http://www.iana.org/assignments/dns-parameters + section: DNS RCODEs + """ + return self._ldns_pkt.rcode2str() + + def opcode(self): + """Returns the rcode. + + Example returned value: "QUERY" + + """ + return self._ldns_pkt.opcode2str() + + def flags(self): + """Return packet flags (as list of strings). + + Example returned value: ['QR', 'RA', 'RD'] + + **What are the flags?** + + ======== ==== ===================== ========= + Bit Flag Description Reference + ======== ==== ===================== ========= + bit 5 AA Authoritative Answer [RFC1035] + bit 6 TC Truncated Response [RFC1035] + bit 7 RD Recursion Desired [RFC1035] + bit 8 RA Recursion Allowed [RFC1035] + bit 9 Reserved + bit 10 AD Authentic Data [RFC4035] + bit 11 CD Checking Disabled [RFC4035] + ======== ==== ===================== ========= + + (from http://www.iana.org/assignments/dns-parameters) + + There is also QR. It is mentioned in other sources, + though not the above page. It being false means that + the packet is a query, it being true means that it is + a response. + + """ + ret = [] + if self._ldns_pkt.aa(): ret += ["AA"] + if self._ldns_pkt.ad(): ret += ["AD"] + if self._ldns_pkt.cd(): ret += ["CD"] + if self._ldns_pkt.qr(): ret += ["QR"] + if self._ldns_pkt.ra(): ret += ["RA"] + if self._ldns_pkt.rd(): ret += ["RD"] + if self._ldns_pkt.tc(): ret += ["TC"] + return ret + + def answer(self, **filters): + """Returns the answer section. + + * filters -- a filtering mechanism + + Since a very common desire is to filter the resource records in a packet + section, we provide a special tool for doing this: filters. They are a + lot like regular python filters, but more convenient. If you set a + field equal to some value, you will only receive resource records for which + it holds true. + + **Examples** + + >>> res = ldnsx.resolver() + >>> pkt = res.query("google.ca","A") + >>> pkt.answer() + [google.ca. 28 IN A 74.125.91.99 + , google.ca. 28 IN A 74.125.91.105 + , google.ca. 28 IN A 74.125.91.147 + , google.ca. 28 IN A 74.125.91.103 + , google.ca. 28 IN A 74.125.91.104 + , google.ca. 28 IN A 74.125.91.106 + ] + + To understand filters, consider the following: + + >>> pkt = ldnsx.query("cow.com","ANY") + >>> pkt.answer() + [cow.com. 276 IN A 208.87.32.75 + , cow.com. 3576 IN NS sell.internettraffic.com. + , cow.com. 3576 IN NS buy.internettraffic.com. + , cow.com. 3576 IN SOA buy.internettraffic.com. hostmaster.hostingnet.com. 1308785320 10800 3600 604800 3600 + ] + >>> pkt.answer(rr_type="A") + [cow.com. 276 IN A 208.87.32.75 + ] + >>> pkt.answer(rr_type="A|NS") + [cow.com. 276 IN A 208.87.32.75 + , cow.com. 3576 IN NS sell.internettraffic.com. + , cow.com. 3576 IN NS buy.internettraffic.com. + ] + >>> pkt.answer(rr_type="!NS") + [cow.com. 276 IN A 208.87.32.75 + , cow.com. 3576 IN SOA buy.internettraffic.com. hostmaster.hostingnet.com. 1308785320 10800 3600 604800 3600 + ] + + fields are the same as when indexing a resource record. + note: ordering is alphabetical. + """ + ret = [resource_record(rr) for rr in self._ldns_pkt.answer().rrs()] + return filter(self._construct_rr_filter(**filters), ret) + + def authority(self, **filters): + """Returns the authority section. + + * filters -- a filtering mechanism + + Since a very common desire is to filter the resource records in a packet + section, we provide a special tool for doing this: filters. They are a + lot like regular python filters, but more convenient. If you set a + field equal to some value, you will only receive resource records for which + it holds true. See answer() for details. + + **Examples** + + >>> res = ldnsx.resolver() + >>> pkt = res.query("google.ca","A") + >>> pkt.authority() + [google.ca. 251090 IN NS ns3.google.com. + , google.ca. 251090 IN NS ns1.google.com. + , google.ca. 251090 IN NS ns2.google.com. + , google.ca. 251090 IN NS ns4.google.com. + ] + + """ + ret = [resource_record(rr) for rr in self._ldns_pkt.authority().rrs()] + return filter(self._construct_rr_filter(**filters), ret) + + def additional(self, **filters): + """Returns the additional section. + + * filters -- a filtering mechanism + + Since a very common desire is to filter the resource records in a packet + section, we provide a special tool for doing this: filters. They are a + lot like regular python filters, but more convenient. If you set a + field equal to some value, you will only receive resource records for which + it holds true. See answer() for details. + + **Examples** + + >>> res = ldnsx.resolver() + >>> pkt = res.query("google.ca","A") + >>> pkt.additional() + [ns3.google.com. 268778 IN A 216.239.36.10 + , ns1.google.com. 262925 IN A 216.239.32.10 + , ns2.google.com. 255659 IN A 216.239.34.10 + , ns4.google.com. 264489 IN A 216.239.38.10 + ] + + """ + ret = [resource_record(rr) for rr in self._ldns_pkt.additional().rrs()] + return filter(self._construct_rr_filter(**filters), ret) + + def question(self, **filters): + """Returns the question section. + + * filters -- a filtering mechanism + + Since a very common desire is to filter the resource records in a packet + section, we provide a special tool for doing this: filters. They are a + lot like regular python filters, but more convenient. If you set a + field equal to some value, you will only receive resource records for which + it holds true. See answer() for details. + + """ + ret = [resource_record(rr) for rr in self._ldns_pkt.question().rrs()] + return filter(self._construct_rr_filter(**filters), ret) + +class resource_record: + + _rdfs = None + _iter_pos = None + + def __init__(self, rr): + self._ldns_rr = rr + self._rdfs = [str(rr.owner()),rr.ttl(),rr.get_class_str(),rr.get_type_str()]+[str(rdf) for rdf in rr.rdfs()] + + def __repr__(self): + return str(self._ldns_rr) + + __str__ = __repr__ + + def __iter__(self): + self._iter_pos = 0 + return self + + def next(self): + if self._iter_pos < len(self._rdfs): + self._iter_pos += 1 + return self._rdfs[self._iter_pos-1] + else: + raise StopIteration + + def __len__(self): + try: + return len(_rdfs) + except: + return 0 + + def __getitem__(self, n): + if isinstance(n, int): + return self._rdfs[n] + elif isinstance(n, str): + n = n.lower() + if n in ["owner"]: + return self.owner() + elif n in ["rr_type", "rr type", "type"]: + return self.rr_type() + elif n in ["rr_class", "rr class", "class"]: + return self.rr_class() + elif n in ["covered_type", "covered type", "type2"]: + return self.covered_type() + elif n in ["ttl"]: + return self.ttl() + elif n in ["ip"]: + return self.ip() + elif n in ["alg", "algorithm"]: + return self.alg() + elif n in ["protocol"]: + return self.protocol() + elif n in ["flags"]: + return self.flags() + else: + raise Exception("ldnsx (version %s) does not recognize the rr field %s" % (__version__,n) ) + else: + raise TypeError("bad type %s for index resource record" % type(n) ) + + + #def rdfs(self): + # return self._rdfs.clone() + + def owner(self): + """Get the RR's owner""" + return str(self._ldns_rr.owner()) + + def rr_type(self): + """Get a RR's type """ + return self._ldns_rr.get_type_str() + + def covered_type(self): + """Get an RRSIG RR's covered type""" + if self.rr_type() == "RRSIG": + return self[4] + else: + return "" + + def rr_class(self): + """Get the RR's collapse""" + return self._ldns_rr.get_class_str() + + def ttl(self): + """Get the RR's TTL""" + return self._ldns_rr.ttl() + + def inception(self, out_format="UTC"): + """returns the inception time in format out_format, defaulting to a UTC string. + options for out_format are: + + UTC -- a UTC string eg. 20110712192610 (2011/07/12 19:26:10) + unix -- number of seconds since the epoch, Jan 1, 1970 + struct_time -- the format used by python's time library + """ + # Something very strange is going on with inception/expiration dates in DNS. + # According to RFC 4034 section 3.1.5 (http://tools.ietf.org/html/rfc4034#page-9) + # the inception/expiration fields should be in seconds since Jan 1, 1970, the Unix + # epoch (as is standard in unix). Yet all the packets I've seen provide UTC encoded + # as a string instead, eg. "20110712192610" which is 2011/07/12 19:26:10. + # + # It turns out that this is a standard thing that ldns is doing before the data gets + # to us. + if self.rr_type() == "RRSIG": + if out_format.lower() in ["utc", "utc str", "utc_str"]: + return self[9] + elif out_format.lower() in ["unix", "posix", "ctime"]: + return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S")) + elif out_format.lower() in ["relative"]: + return calendar.timegm(time.strptime(self[9], "%Y%m%d%H%M%S")) - time.time() + elif out_format.lower() in ["struct_time", "time.struct_time"]: + return time.strptime(self[9], "%Y%m%d%H%M%S") + else: + raise Exception("unrecognized time format") + else: + return "" + + def expiration(self, out_format="UTC"): + """get expiration time. see inception() for more information""" + if self.rr_type() == "RRSIG": + if out_format.lower() in ["utc", "utc str", "utc_str"]: + return self[8] + elif out_format.lower() in ["unix", "posix", "ctime"]: + return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S")) + elif out_format.lower() in ["relative"]: + return calendar.timegm(time.strptime(self[8], "%Y%m%d%H%M%S")) - time.time() + elif out_format.lower() in ["struct_time", "time.struct_time"]: + return time.strptime(self[8], "%Y%m%d%H%M%S") + else: + raise Exception("unrecognized time format") + else: + return "" + + def ip(self): + """ IP address form A/AAAA record""" + if self.rr_type() in ["A", "AAAA"]: + return self[4] + else: + raise Exception("ldnsx does not support ip for records other than A/AAAA") + + def alg(self): + """Returns algorithm of RRSIG/DNSKEY/DS""" + t = self.rr_type() + if t == "RRSIG": + return int(self[5]) + elif t == "DNSKEY": + return int(self[6]) + elif t == "DS": + return int(self[5]) + else: + return -1 + + def protocol(self): + """ Returns proticol of the DNSKEY""" + t = self.rr_type() + if t == "DNSKEY": + return int(self[5]) + else: + return -1 + + def flags(self): + """Return RR flags for DNSKEY """ + t = self.rr_type() + if t == "DNSKEY": + ret = [] + n = int(self[4]) + for m in range(1): + if 2**(15-m) & n: + if m == 7: ret.append("ZONE") + elif m == 8: ret.append("REVOKE") + elif m ==15: ret.append("SEP") + else: ret.append(m) + return ret + else: + return [] + +_rr_types={ + "A" : ldns.LDNS_RR_TYPE_A, + "A6" : ldns.LDNS_RR_TYPE_A6, + "AAAA" : ldns.LDNS_RR_TYPE_AAAA, + "AFSDB": ldns.LDNS_RR_TYPE_AFSDB, + "ANY" : ldns.LDNS_RR_TYPE_ANY, + "APL" : ldns.LDNS_RR_TYPE_APL, + "ATMA" : ldns.LDNS_RR_TYPE_ATMA, + "AXFR" : ldns.LDNS_RR_TYPE_AXFR, + "CERT" : ldns.LDNS_RR_TYPE_CERT, + "CNAME": ldns.LDNS_RR_TYPE_CNAME, + "COUNT": ldns.LDNS_RR_TYPE_COUNT, + "DHCID": ldns.LDNS_RR_TYPE_DHCID, + "DLV" : ldns.LDNS_RR_TYPE_DLV, + "DNAME": ldns.LDNS_RR_TYPE_DNAME, + "DNSKEY": ldns.LDNS_RR_TYPE_DNSKEY, + "DS" : ldns.LDNS_RR_TYPE_DS, + "EID" : ldns.LDNS_RR_TYPE_EID, + "FIRST": ldns.LDNS_RR_TYPE_FIRST, + "GID" : ldns.LDNS_RR_TYPE_GID, + "GPOS" : ldns.LDNS_RR_TYPE_GPOS, + "HINFO": ldns.LDNS_RR_TYPE_HINFO, + "IPSECKEY": ldns.LDNS_RR_TYPE_IPSECKEY, + "ISDN" : ldns.LDNS_RR_TYPE_ISDN, + "IXFR" : ldns.LDNS_RR_TYPE_IXFR, + "KEY" : ldns.LDNS_RR_TYPE_KEY, + "KX" : ldns.LDNS_RR_TYPE_KX, + "LAST" : ldns.LDNS_RR_TYPE_LAST, + "LOC" : ldns.LDNS_RR_TYPE_LOC, + "MAILA": ldns.LDNS_RR_TYPE_MAILA, + "MAILB": ldns.LDNS_RR_TYPE_MAILB, + "MB" : ldns.LDNS_RR_TYPE_MB, + "MD" : ldns.LDNS_RR_TYPE_MD, + "MF" : ldns.LDNS_RR_TYPE_MF, + "MG" : ldns.LDNS_RR_TYPE_MG, + "MINFO": ldns.LDNS_RR_TYPE_MINFO, + "MR" : ldns.LDNS_RR_TYPE_MR, + "MX" : ldns.LDNS_RR_TYPE_MX, + "NAPTR": ldns.LDNS_RR_TYPE_NAPTR, + "NIMLOC": ldns.LDNS_RR_TYPE_NIMLOC, + "NS" : ldns.LDNS_RR_TYPE_NS, + "NSAP" : ldns.LDNS_RR_TYPE_NSAP, + "NSAP_PTR" : ldns.LDNS_RR_TYPE_NSAP_PTR, + "NSEC" : ldns.LDNS_RR_TYPE_NSEC, + "NSEC3": ldns.LDNS_RR_TYPE_NSEC3, + "NSEC3PARAMS" : ldns.LDNS_RR_TYPE_NSEC3PARAMS, + "NULL" : ldns.LDNS_RR_TYPE_NULL, + "NXT" : ldns.LDNS_RR_TYPE_NXT, + "OPT" : ldns.LDNS_RR_TYPE_OPT, + "PTR" : ldns.LDNS_RR_TYPE_PTR, + "PX" : ldns.LDNS_RR_TYPE_PX, + "RP" : ldns.LDNS_RR_TYPE_RP, + "RRSIG": ldns.LDNS_RR_TYPE_RRSIG, + "RT" : ldns.LDNS_RR_TYPE_RT, + "SIG" : ldns.LDNS_RR_TYPE_SIG, + "SINK" : ldns.LDNS_RR_TYPE_SINK, + "SOA" : ldns.LDNS_RR_TYPE_SOA, + "SRV" : ldns.LDNS_RR_TYPE_SRV, + "SSHFP": ldns.LDNS_RR_TYPE_SSHFP, + "TSIG" : ldns.LDNS_RR_TYPE_TSIG, + "TXT" : ldns.LDNS_RR_TYPE_TXT, + "UID" : ldns.LDNS_RR_TYPE_UID, + "UINFO": ldns.LDNS_RR_TYPE_UINFO, + "UNSPEC": ldns.LDNS_RR_TYPE_UNSPEC, + "WKS" : ldns.LDNS_RR_TYPE_WKS, + "X25" : ldns.LDNS_RR_TYPE_X25 +} + diff --git a/contrib/ldnsx/source/api/ldnsx.rst b/contrib/ldnsx/source/api/ldnsx.rst new file mode 100644 index 00000000..7610adb2 --- /dev/null +++ b/contrib/ldnsx/source/api/ldnsx.rst @@ -0,0 +1,15 @@ +LDNSX API Reference +=================== + +.. automodule:: ldnsx + :members: query, get_rrs, secure_query + +Classes +------- +.. toctree:: + :maxdepth: 1 + :glob: + + resolver + packet + resource_record diff --git a/contrib/ldnsx/source/api/packet.rst b/contrib/ldnsx/source/api/packet.rst new file mode 100644 index 00000000..81499407 --- /dev/null +++ b/contrib/ldnsx/source/api/packet.rst @@ -0,0 +1,6 @@ +Class packet +============== + +.. autoclass:: ldnsx.packet + :members: + :undoc-members: diff --git a/contrib/ldnsx/source/api/resolver.rst b/contrib/ldnsx/source/api/resolver.rst new file mode 100644 index 00000000..2e2fba05 --- /dev/null +++ b/contrib/ldnsx/source/api/resolver.rst @@ -0,0 +1,6 @@ +Class resolver +=============== + +.. autoclass:: ldnsx.resolver + :members: + :undoc-members: diff --git a/contrib/ldnsx/source/api/resource_record.rst b/contrib/ldnsx/source/api/resource_record.rst new file mode 100644 index 00000000..429df0df --- /dev/null +++ b/contrib/ldnsx/source/api/resource_record.rst @@ -0,0 +1,6 @@ +Class resource_record +===================== + +.. autoclass:: ldnsx.resource_record + :members: + :undoc-members: diff --git a/contrib/ldnsx/source/conf.py b/contrib/ldnsx/source/conf.py new file mode 100644 index 00000000..5b856d4c --- /dev/null +++ b/contrib/ldnsx/source/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# ldnsx documentation build configuration file, created by +# sphinx-quickstart on Mon May 30 16:56:19 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.append(os.path.abspath('..')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest']#, 'sphinx.ext.jsmath'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'ldnsx' +copyright = u'2011, Christopher Olah' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.0' +# The full version, including alpha/beta/rc tags. +release = '-1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ldnsxdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ldnsx.tex', u'ldnsx Documentation', + u'Christopher Olah', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/contrib/ldnsx/source/examples/ldnsx-axfr.rst b/contrib/ldnsx/source/examples/ldnsx-axfr.rst new file mode 100644 index 00000000..de51038d --- /dev/null +++ b/contrib/ldnsx/source/examples/ldnsx-axfr.rst @@ -0,0 +1,6 @@ +AXFR Example +============ + +.. literalinclude:: ../../examples/ldnsx-axfr.py + :language: python + :linenos: diff --git a/contrib/ldnsx/source/examples/ldnsx-dnssec.rst b/contrib/ldnsx/source/examples/ldnsx-dnssec.rst new file mode 100644 index 00000000..b9369992 --- /dev/null +++ b/contrib/ldnsx/source/examples/ldnsx-dnssec.rst @@ -0,0 +1,6 @@ +DNSSEC Example +============== + +.. literalinclude:: ../../examples/ldnsx-dnssec.py + :language: python + :linenos: diff --git a/contrib/ldnsx/source/examples/ldnsx-mx1.rst b/contrib/ldnsx/source/examples/ldnsx-mx1.rst new file mode 100644 index 00000000..a6d7bbba --- /dev/null +++ b/contrib/ldnsx/source/examples/ldnsx-mx1.rst @@ -0,0 +1,6 @@ +MX1 +=== + +.. literalinclude:: ../../examples/ldnsx-mx1.py + :language: python + :linenos: diff --git a/contrib/ldnsx/source/examples/ldnsx-mx2.rst b/contrib/ldnsx/source/examples/ldnsx-mx2.rst new file mode 100644 index 00000000..105c75ef --- /dev/null +++ b/contrib/ldnsx/source/examples/ldnsx-mx2.rst @@ -0,0 +1,6 @@ +MX2 +=== + +.. literalinclude:: ../../examples/ldnsx-mx2.py + :language: python + :linenos: diff --git a/contrib/ldnsx/source/examples/ldnsx-walk.rst b/contrib/ldnsx/source/examples/ldnsx-walk.rst new file mode 100644 index 00000000..6cfb103c --- /dev/null +++ b/contrib/ldnsx/source/examples/ldnsx-walk.rst @@ -0,0 +1,6 @@ +NSEC Walker +=========== + +.. literalinclude:: ../../examples/ldnsx-walk.py + :language: python + :linenos: diff --git a/contrib/ldnsx/source/index.rst b/contrib/ldnsx/source/index.rst new file mode 100644 index 00000000..43de8b5d --- /dev/null +++ b/contrib/ldnsx/source/index.rst @@ -0,0 +1,57 @@ +Welcome to ldnsx's documentation! +================================= + +LDNSX: Easy DNS (including DNSSEC) via ldns. + +ldns is a great library. It is a powerfull tool for +working with DNS. python-ldns it is a straight up clone of the C +interface, howver that is not a very good interface for python. Its +documentation is incomplete and some functions don't work as +described. And some objects don't have a full python API. + +ldnsx aims to fix this. It wraps around the ldns python bindings, +working around its limitations and providing a well-documented, more +pythonistic interface. + +Reference +========= + +.. toctree:: + :maxdepth: 1 + + api/ldnsx + +.. toctree:: + :maxdepth: 2 + + api/resolver + api/packet + api/resource_record + +Examples +======== + +Examples translated from ldns examples: + +.. toctree:: + :maxdepth: 1 + + examples/ldnsx-axfr + examples/ldnsx-dnssec + examples/ldnsx-mx1 + examples/ldnsx-mx2 + +Others: + +.. toctree:: + :maxdepth: 1 + + examples/ldnsx-walk + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` +