]> git.ipfire.org Git - thirdparty/ldns.git/commitdiff
Added the ldnsx python module from Christopher Olah to the contrib directory.
authorWillem Toorop <willem@NLnetLabs.nl>
Tue, 20 Sep 2011 22:35:17 +0000 (22:35 +0000)
committerWillem Toorop <willem@NLnetLabs.nl>
Tue, 20 Sep 2011 22:35:17 +0000 (22:35 +0000)
Also provided a --with-pyldnsx option with configuring that defaults to installing pyldnsx when pyldns is installed too.

23 files changed:
Makefile.in
ax_python_module.m4 [new file with mode: 0644]
configure.ac
contrib/ldnsx/LICENSE [new file with mode: 0644]
contrib/ldnsx/Makefile [new file with mode: 0644]
contrib/ldnsx/README [new file with mode: 0644]
contrib/ldnsx/examples/ldnsx-axfr.py [new file with mode: 0644]
contrib/ldnsx/examples/ldnsx-dnssec.py [new file with mode: 0644]
contrib/ldnsx/examples/ldnsx-mx1.py [new file with mode: 0644]
contrib/ldnsx/examples/ldnsx-mx2.py [new file with mode: 0644]
contrib/ldnsx/examples/ldnsx-walk.py [new file with mode: 0755]
contrib/ldnsx/ldnsx.py [new file with mode: 0644]
contrib/ldnsx/source/api/ldnsx.rst [new file with mode: 0644]
contrib/ldnsx/source/api/packet.rst [new file with mode: 0644]
contrib/ldnsx/source/api/resolver.rst [new file with mode: 0644]
contrib/ldnsx/source/api/resource_record.rst [new file with mode: 0644]
contrib/ldnsx/source/conf.py [new file with mode: 0644]
contrib/ldnsx/source/examples/ldnsx-axfr.rst [new file with mode: 0644]
contrib/ldnsx/source/examples/ldnsx-dnssec.rst [new file with mode: 0644]
contrib/ldnsx/source/examples/ldnsx-mx1.rst [new file with mode: 0644]
contrib/ldnsx/source/examples/ldnsx-mx2.rst [new file with mode: 0644]
contrib/ldnsx/source/examples/ldnsx-walk.rst [new file with mode: 0644]
contrib/ldnsx/source/index.rst [new file with mode: 0644]

index 42a69b2184297441127a4e1f071a514963697c36..d0234aa03cd0357b007690143c73644c1f945b44 100644 (file)
@@ -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 (file)
index 0000000..bd70a06
--- /dev/null
@@ -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 <colliera@ukzn.ac.za>
+#
+#   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
+])
index 0d0b3a251233a7ab7d63bf484a156989b9fa52c3..c3be2ddae3d579e72a89759e4e34bc7db4353710 100644 (file)
@@ -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 (file)
index 0000000..070658d
--- /dev/null
@@ -0,0 +1,28 @@
+Copyright (c) 2011, Xelerance
+Author: Christopher Olah <chris@xelerance.com>
+
+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 (file)
index 0000000..92e70ce
--- /dev/null
@@ -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 <target>' where <target> 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 (file)
index 0000000..66f89d2
--- /dev/null
@@ -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 <chris@xelerance.com>
+
+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 (file)
index 0000000..224bb57
--- /dev/null
@@ -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 (file)
index 0000000..c28ad5c
--- /dev/null
@@ -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 (file)
index 0000000..60dfc42
--- /dev/null
@@ -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 (file)
index 0000000..8bee67f
--- /dev/null
@@ -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 (executable)
index 0000000..9810939
--- /dev/null
@@ -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 (file)
index 0000000..9b59c4b
--- /dev/null
@@ -0,0 +1,920 @@
+# (c) Christopher Olah <colah@xelerance.com>, 2011. Xelerance <http://www.xelerance.com/>.
+# 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: 192.168.111.9>
+                       >>> resolver("") # resolver with no nameservers
+                       <resolver: >
+                       >>> resolver("193.110.157.135") #resolver pointing to ip addr
+                       <resolver: 193.110.157.135>
+                       >>> resolver("f.root-servers.net") # resolver pointing ip address(es) resolved from name
+                       <resolver: 2001:500:2f::f, 192.5.5.241>
+                       >>> resolver("193.110.157.135, 193.110.157.136") 
+                       >>> # resolver pointing to multiple ip addr, first takes precedence.
+                       <resolver: 193.110.157.136, 193.110.157.135>
+
+                       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 "<resolver: %s>" % ", ".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 (file)
index 0000000..7610adb
--- /dev/null
@@ -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 (file)
index 0000000..8149940
--- /dev/null
@@ -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 (file)
index 0000000..2e2fba0
--- /dev/null
@@ -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 (file)
index 0000000..429df0d
--- /dev/null
@@ -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 (file)
index 0000000..5b856d4
--- /dev/null
@@ -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
+# "<project> v<release> 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 <link> 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 (file)
index 0000000..de51038
--- /dev/null
@@ -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 (file)
index 0000000..b936999
--- /dev/null
@@ -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 (file)
index 0000000..a6d7bbb
--- /dev/null
@@ -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 (file)
index 0000000..105c75e
--- /dev/null
@@ -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 (file)
index 0000000..6cfb103
--- /dev/null
@@ -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 (file)
index 0000000..43de8b5
--- /dev/null
@@ -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`
+