python-dns is very feature-rich and it can help us a lot with our DNS
related code. This patch does the first step, i.e. replaces acutil use
with python-dns, which is more convenient to use as you will see in the
patch. More integration will follow in the future.

I send this patch rather early, so that I can get responses to this
patch early and also so that we are able to catch issues in a safe
distance from the next release.

---
IPA client and server tool set used authconfig acutil module to
for client DNS operations. This is not optimal DNS interface for
several reasons:
- does not provide native Python object oriented interface
  but but rather C-like interface based on functions and
  structures which is not easy to use and extend
- acutil is not meant to be used by third parties besides
  authconfig and thus can break without notice

Replace the acutil with python-dns package which has a feature rich
interface for dealing with all different aspects of DNS including
DNSSEC. The main target of this patch is to replace all uses of
acutil DNS library with a use python-dns. In most cases, even
though the larger parts of the code are changed, the actual
functionality is changed only in the following cases:
- redundant DNS checks were removed from verify_fqdn function
  in installutils to make the whole DNS check simpler and
  less error-prone. Logging was improves for the remaining
  checks
- improved logging for ipa-client-install DNS discovery

https://fedorahosted.org/freeipa/ticket/2730

>From 3236b414ce71ee38cfb078069a8e9a637c286903 Mon Sep 17 00:00:00 2001
From: Martin Kosek <mko...@redhat.com>
Date: Fri, 11 May 2012 14:38:09 +0200
Subject: [PATCH] Replace DNS client based on acutil with python-dns

IPA client and server tool set used authconfig acutil module to
for client DNS operations. This is not optimal DNS interface for
several reasons:
- does not provide native Python object oriented interface
  but but rather C-like interface based on functions and
  structures which is not easy to use and extend
- acutil is not meant to be used by third parties besides
  authconfig and thus can break without notice

Replace the acutil with python-dns package which has a feature rich
interface for dealing with all different aspects of DNS including
DNSSEC. The main target of this patch is to replace all uses of
acutil DNS library with a use python-dns. In most cases, even
though the larger parts of the code are changed, the actual
functionality is changed only in the following cases:
- redundant DNS checks were removed from verify_fqdn function
  in installutils to make the whole DNS check simpler and
  less error-prone. Logging was improves for the remaining
  checks
- improved logging for ipa-client-install DNS discovery

https://fedorahosted.org/freeipa/ticket/2730
---
 freeipa.spec.in                           |    7 +-
 ipa-client/ipa-install/ipa-client-install |   16 +-
 ipa-client/ipaclient/ipadiscovery.py      |  120 ++++----
 ipalib/plugins/dns.py                     |   14 +-
 ipalib/rpc.py                             |   21 +-
 ipalib/util.py                            |   16 +-
 ipapython/README                          |    3 +-
 ipapython/config.py                       |   69 +++--
 ipapython/dnsclient.py                    |  469 -----------------------------
 ipapython/ipautil.py                      |   13 +
 ipaserver/install/installutils.py         |  120 ++------
 11 files changed, 188 insertions(+), 680 deletions(-)
 delete mode 100644 ipapython/dnsclient.py

diff --git a/freeipa.spec.in b/freeipa.spec.in
index a1e43a0ad48a227baee860f4d97ce589d74db37d..3574d1f7e82f64ed1ca7bdafdd058a24f0460c62 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -79,6 +79,7 @@ BuildRequires:  python-memcached
 BuildRequires:  sssd >= 1.8.0
 BuildRequires:  python-lxml
 BuildRequires:  python-pyasn1 >= 0.0.9a
+BuildRequires:  python-dns
 
 %description
 IPA is an integrated solution to provide centrally managed Identity (machine,
@@ -151,6 +152,7 @@ Requires(postun): python systemd-units
 Requires(preun):  python initscripts chkconfig
 Requires(postun): python initscripts chkconfig
 %endif
+Requires: python-dns
 
 # We have a soft-requires on bind. It is an optional part of
 # IPA but if it is configured we need a way to require versions
@@ -220,6 +222,7 @@ Requires: nss-tools
 Requires: bind-utils
 Requires: oddjob-mkhomedir
 Requires: python-krbV
+Requires: python-dns
 
 Obsoletes: ipa-client >= 1.0
 
@@ -256,7 +259,6 @@ Group: System Environment/Libraries
 %if 0%{?fedora} >= 12 || 0%{?rhel} >= 6
 Requires: python-kerberos >= 1.1-3
 %endif
-Requires: authconfig
 Requires: gnupg
 Requires: iproute
 Requires: pyOpenSSL
@@ -683,6 +685,9 @@ fi
 %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
 
 %changelog
+* Fri May 11 2012 Martin Kosek <mko...@redhat.com> - 2.99.0-29
+- Replace used DNS client library (acutil) with python-dns
+
 * Tue Apr 10 2012 Rob Crittenden <rcrit...@redhat.com> - 2.99.0-28
 - Set min for selinux-policy to 3.10.0-110 on F-17 to pick up certmonger
   policy for restarting services.
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 67279b3ed8ae8a25e845ccbcce7143efcaf6d467..92c960a836081a08a8b72a87e71b5a7941088f80 100755
--- a/ipa-client/ipa-install/ipa-client-install
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -25,6 +25,7 @@ try:
     import os
     import time
     import socket
+
     from ipapython.ipa_log_manager import *
     import tempfile
     import getpass
@@ -35,7 +36,6 @@ try:
     from ipapython.ipautil import run, user_input, CalledProcessError, file_exists, realm_to_suffix
     import ipapython.services as ipaservices
     from ipapython import ipautil
-    from ipapython import dnsclient
     from ipapython import sysrestore
     from ipapython import version
     from ipapython import certmonger
@@ -996,18 +996,10 @@ def update_dns(server, hostname):
 
 def client_dns(server, hostname, dns_updates=False):
 
-    dns_ok = False
+    dns_ok = ipautil.is_host_resolvable(hostname)
 
-    # Check if the client has an A record registered in its name.
-    rs = dnsclient.query(hostname+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
-    if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
-        dns_ok = True
-    else:
-        rs = dnsclient.query(hostname+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
-        if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
-            dns_ok = True
-        else:
-            print "Warning: Hostname (%s) not found in DNS" % hostname
+    if not dns_ok:
+        print "Warning: Hostname (%s) not found in DNS" % hostname
 
     if dns_updates or not dns_ok:
         update_dns(server, hostname)
diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py
index 86bef28b2d7fdfc8111b493bddec7ac6888f021a..800b4ef9a6396884a1fe2e93bdf94e948f35c57e 100644
--- a/ipa-client/ipaclient/ipadiscovery.py
+++ b/ipa-client/ipaclient/ipadiscovery.py
@@ -20,10 +20,12 @@
 import socket
 import os
 from ipapython.ipa_log_manager import *
-import ipapython.dnsclient
 import tempfile
 import ldap
 from ldap import LDAPError
+from dns import resolver, rdatatype
+from dns.exception import DNSException
+
 from ipapython.ipautil import run, CalledProcessError, valid_ip, get_ipa_basedn, \
                               realm_to_suffix, format_netloc, parse_items
 
@@ -311,81 +313,89 @@ class IPADiscovery:
 
 
     def ipadnssearchldap(self, tdomain):
-        servers = ""
-        rserver = ""
+        server = ""
 
-        qname = "_ldap._tcp."+tdomain
-        # terminate the name
-        if not qname.endswith("."):
-            qname += "."
-        results = ipapython.dnsclient.query(qname, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
+        qname = "_ldap._tcp." + tdomain
 
-        for result in results:
-            if result.dns_type == ipapython.dnsclient.DNS_T_SRV:
-                rserver = result.rdata.server.rstrip(".")
-                if result.rdata.port and result.rdata.port != 389:
-                    rserver += ":" + str(result.rdata.port)
-                if servers:
-                    servers += "," + rserver
-                else:
-                    servers = rserver
-                break
+        root_logger.debug("Search DNS for SRV record of %s", qname)
 
-        return servers
+        try:
+            answers = resolver.query(qname, rdatatype.SRV)
+        except DNSException, e:
+            root_logger.debug("DNS record not found: %s", e.__class__.__name__)
+            answers = []
+
+        for answer in answers:
+            root_logger.debug("DNS record found: %s", answer)
+            server = str(answer.target).rstrip(".")
+            if answer.port != 389:
+                server += ":" + str(answer.port)
+            break
+
+        return server
 
     def ipadnssearchntp(self, tdomain):
-        servers = ""
-        rserver = ""
+        server = ""
 
-        qname = "_ntp._udp."+tdomain
-        # terminate the name
-        if not qname.endswith("."):
-            qname += "."
-        results = ipapython.dnsclient.query(qname, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
+        qname = "_ntp._udp." + tdomain
 
-        for result in results:
-            if result.dns_type == ipapython.dnsclient.DNS_T_SRV:
-                rserver = result.rdata.server.rstrip(".")
-                if servers:
-                    servers += "," + rserver
-                else:
-                    servers = rserver
-                break
+        root_logger.debug("Search DNS for SRV record of %s", qname)
 
-        return servers
+        try:
+            answers = resolver.query(qname, rdatatype.SRV)
+        except DNSException, e:
+            root_logger.debug("DNS record not found: %s", e.__class__.__name__)
+            answers = []
+
+        for answer in answers:
+            root_logger.debug("DNS record found: %s", answer)
+            server = str(answer.target).rstrip(".")
+            break
+
+        return server
 
     def ipadnssearchkrb(self, tdomain):
         realm = None
         kdc = None
         # now, check for a Kerberos realm the local host or domain is in
         qname = "_kerberos." + tdomain
-        # terminate the name
-        if not qname.endswith("."):
-            qname += "."
-        results = ipapython.dnsclient.query(qname, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_TXT)
 
-        for result in results:
-            if result.dns_type == ipapython.dnsclient.DNS_T_TXT:
-                realm = result.rdata.data
+        root_logger.debug("Search DNS for TXT record of %s", qname)
+
+        try:
+            answers = resolver.query(qname, rdatatype.TXT)
+        except DNSException, e:
+            root_logger.debug("DNS record not found: %s", e.__class__.__name__)
+            answers = []
+
+        for answer in answers:
+            root_logger.debug("DNS record found: %s", answer)
+            if answer.strings:
+                realm = answer.strings[0]
                 if realm:
                     break
 
         if realm:
             # now fetch server information for the realm
             qname = "_kerberos._udp." + realm.lower()
-            # terminate the name
-            if not qname.endswith("."):
-                qname += "."
-            results = ipapython.dnsclient.query(qname, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
-            for result in results:
-                if result.dns_type == ipapython.dnsclient.DNS_T_SRV:
-                    qname = result.rdata.server.rstrip(".")
-                    if result.rdata.port and result.rdata.port != 88:
-                        qname += ":" + str(result.rdata.port)
-                    if kdc:
-                        kdc += "," + qname
-                    else:
-                        kdc = qname
+
+            root_logger.debug("Search DNS for SRV record of %s", qname)
+
+            try:
+                answers = resolver.query(qname, rdatatype.SRV)
+            except DNSException, e:
+                root_logger.debug("DNS record not found: %s", e.__class__.__name__)
+                answers = []
+
+            for answer in answers:
+                root_logger.debug("DNS record found: %s", answer)
+                server = str(answer.target).rstrip(".")
+                if answer.port != 88:
+                    server += ":" + str(answer.port)
+                if kdc:
+                    kdc += "," + server
+                else:
+                    kdc = server
 
             if not kdc:
                 root_logger.debug("SRV record for KDC not found! Realm: %s, SRV record: %s" % (realm, qname))
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index b0e65ab943c1aece36061255726261f3e32488e0..e26332d46832622ad0321ef3083aec4eef3db1b3 100644
--- a/ipalib/plugins/dns.py
+++ b/ipalib/plugins/dns.py
@@ -30,8 +30,7 @@ from ipalib.plugins.baseldap import *
 from ipalib import _, ngettext
 from ipalib.util import (validate_zonemgr, normalize_zonemgr,
         validate_hostname, validate_dns_label, validate_domain_name)
-from ipapython import dnsclient
-from ipapython.ipautil import valid_ip, CheckedIPAddress
+from ipapython.ipautil import valid_ip, CheckedIPAddress, is_host_resolvable
 from ldap import explode_dn
 
 __doc__ = _("""
@@ -2610,17 +2609,8 @@ class dns_resolve(Command):
             query = '%s.%s.' % (query, api.env.domain)
         if query[-1] != '.':
             query = query + '.'
-        reca = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
-        rec6 = dnsclient.query(query, dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
-        records = reca + rec6
-        found = False
-        for rec in records:
-            if rec.dns_type == dnsclient.DNS_T_A or \
-              rec.dns_type == dnsclient.DNS_T_AAAA:
-                found = True
-                break
 
-        if not found:
+        if not is_host_resolvable(query):
             raise errors.NotFound(
                 reason=_('Host \'%(host)s\' not found') % {'host': query}
             )
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index 04a3f3e35cee62ee3900fc33c6c71fbb0067e882..bd18b6bbf566362e9954bd25235512dda7baaffc 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -39,11 +39,15 @@ import errno
 import locale
 from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, Transport, ProtocolError
 import kerberos
+from dns import resolver, rdatatype
+from dns.exception import DNSException
+
 from ipalib.backend import Connectible
 from ipalib.errors import public_errors, PublicError, UnknownError, NetworkError, KerberosError, XMLRPCMarshallError
 from ipalib import errors
 from ipalib.request import context, Connection
-from ipapython import ipautil, dnsclient
+from ipapython import ipautil
+
 import httplib
 import socket
 from ipapython.nsslib import NSSHTTPS, NSSConnection
@@ -349,11 +353,16 @@ class xmlclient(Connectible):
         (scheme, netloc, path, params, query, fragment) = urlparse.urlparse(self.env.xmlrpc_uri)
         servers = []
         name = '_ldap._tcp.%s.' % self.env.domain
-        rs = dnsclient.query(name, dnsclient.DNS_C_IN, dnsclient.DNS_T_SRV)
-        for r in rs:
-            if r.dns_type == dnsclient.DNS_T_SRV:
-                rsrv = r.rdata.server.rstrip('.')
-                servers.append('https://%s%s' % (ipautil.format_netloc(rsrv), path))
+
+        try:
+            answers = resolver.query(name, rdatatype.SRV)
+        except DNSException, e:
+            answers = []
+
+        for answer in answers:
+            server = str(answer.target).rstrip(".")
+            servers.append('https://%s%s' % (ipautil.format_netloc(server), path))
+
         servers = list(set(servers))
         # the list/set conversion won't preserve order so stick in the
         # local config file version here.
diff --git a/ipalib/util.py b/ipalib/util.py
index 64ac6b2cf2a693f68e4ae5f333b22be16c27d690..50da7432759cb9d1c4fa41a6c303383ba095f777 100644
--- a/ipalib/util.py
+++ b/ipalib/util.py
@@ -28,11 +28,12 @@ import socket
 import re
 from types import NoneType
 from weakref import WeakKeyDictionary
+from dns import resolver, rdatatype
+from dns.exception import DNSException
 
 from ipalib import errors
 from ipalib.text import _
 from ipalib.dn import DN, RDN
-from ipapython import dnsclient
 from ipapython.ipautil import decode_ssh_pubkey
 
 
@@ -88,16 +89,17 @@ def validate_host_dns(log, fqdn):
     """
     See if the hostname has a DNS A record.
     """
-    rs = dnsclient.query(fqdn + '.', dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
-    if len(rs) == 0:
+    try:
+        answers = resolver.query(fqdn, rdatatype.A)
+        log.debug(
+            'IPA: found %d records for %s: %s' % (len(answers), fqdn,
+                ' '.join(str(answer) for answer in answers))
+        )
+    except DNSException, e:
         log.debug(
             'IPA: DNS A record lookup failed for %s' % fqdn
         )
         raise errors.DNSNotARecordError()
-    else:
-        log.debug(
-            'IPA: found %d records for %s' % (len(rs), fqdn)
-        )
 
 def isvalid_base64(data):
     """
diff --git a/ipapython/README b/ipapython/README
index ec2bb3a52cbd08073bfc4851f2fe3d3e3934ea10..a724a7faa4c9a7997b55a620654e31cd16cf8d16 100644
--- a/ipapython/README
+++ b/ipapython/README
@@ -3,10 +3,9 @@ geared currently towards command-line tools.
 
 A brief overview:
 
-config.py - identify the IPA server domain and realm. It uses dnsclient to
+config.py - identify the IPA server domain and realm. It uses python-dns to
             try to detect this information first and will fall back to
             /etc/ipa/default.conf if that fails.
-dnsclient.py - find IPA information via DNS
 
 ipautil.py - helper functions
 
diff --git a/ipapython/config.py b/ipapython/config.py
index d4c724dc9ac754cb221fe60d7c13bd0c716dd296..e06f51a318a2b20c53a8c6933c43d58c34075407 100644
--- a/ipapython/config.py
+++ b/ipapython/config.py
@@ -20,9 +20,11 @@
 import ConfigParser
 from optparse import Option, Values, OptionParser, IndentedHelpFormatter, OptionValueError
 from copy import copy
+from dns import resolver, rdatatype
+from dns.exception import DNSException
+import dns.name
 
 import socket
-import ipapython.dnsclient
 import re
 import urlparse
 
@@ -153,7 +155,7 @@ def __parse_config(discover_server = True):
         try:
             s = p.get("global", "xmlrpc_uri")
             server = urlparse.urlsplit(s)
-            config.default_server.extend(server.netloc)
+            config.default_server.append(server.netloc)
         except:
             pass
     try:
@@ -163,7 +165,7 @@ def __parse_config(discover_server = True):
         pass
 
 def __discover_config(discover_server = True):
-    rl = 0
+    servers = []
     try:
         if not config.default_realm:
             try:
@@ -177,34 +179,49 @@ def __discover_config(discover_server = True):
                 return False
 
         if not config.default_domain:
-            #try once with REALM -> domain
-            dom_name = str(config.default_realm).lower()
-            name = "_ldap._tcp."+dom_name+"."
-            rs = ipapython.dnsclient.query(name, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
-            rl = len(rs)
-            if rl == 0:
-                #try cycling on domain components of FQDN
-                dom_name = socket.getfqdn()
-            while rl == 0:
-                tok = dom_name.find(".")
-                if tok == -1:
+            # try once with REALM -> domain
+            domain = str(config.default_realm).lower()
+            domain_ok = False
+            name = "_ldap._tcp." + domain
+
+            try:
+                servers = resolver.query(name, rdatatype.SRV)
+                domain_ok = True
+            except DNSException:
+                pass
+
+            if not domain_ok:
+                # try cycling on domain components of FQDN
+                try:
+                    domain = dns.name.from_text(socket.getfqdn())
+                except DNSException:
                     return False
-                dom_name = dom_name[tok+1:]
-                name = "_ldap._tcp." + dom_name + "."
-                rs = ipapython.dnsclient.query(name, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
-                rl = len(rs)
 
-            config.default_domain = dom_name
+                while not domain_ok:
+                    domain = domain.parent()
+
+                    if str(domain) == '.':
+                        return False
+                    name = "_ldap._tcp.%s" % domain
+                    try:
+                        servers = resolver.query(name, rdatatype.SRV)
+                        domain_ok = True
+                    except DNSException:
+                        pass
+
+            config.default_domain = str(domain).rstrip(".")
 
         if discover_server:
-            if rl == 0:
-                name = "_ldap._tcp."+config.default_domain+"."
-                rs = ipapython.dnsclient.query(name, ipapython.dnsclient.DNS_C_IN, ipapython.dnsclient.DNS_T_SRV)
+            if not servers:
+                name = "_ldap._tcp.%s." % config.default_domain
+                try:
+                    servers = resolver.query(name, rdatatype.SRV)
+                except DNSException:
+                    pass
 
-            for r in rs:
-                if r.dns_type == ipapython.dnsclient.DNS_T_SRV:
-                    rsrv = r.rdata.server.rstrip(".")
-                    config.default_server.append(rsrv)
+            for server in servers:
+                hostname = str(server.target).rstrip(".")
+                config.default_server.append(hostname)
 
     except:
         pass
diff --git a/ipapython/dnsclient.py b/ipapython/dnsclient.py
deleted file mode 100644
index 3f08866a6403c3f2616ec954ef5c70aa4c849a92..0000000000000000000000000000000000000000
--- a/ipapython/dnsclient.py
+++ /dev/null
@@ -1,469 +0,0 @@
-#
-# Copyright 2001, 2005 Red Hat, Inc.
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-import struct
-import socket
-import sys
-
-import acutil
-
-DNS_C_IN = 1
-DNS_C_CS = 2
-DNS_C_CHAOS = 3
-DNS_C_HS = 4
-DNS_C_ANY = 255
-
-DNS_T_A = 1
-DNS_T_NS = 2
-DNS_T_CNAME = 5
-DNS_T_SOA = 6
-DNS_T_NULL = 10
-DNS_T_WKS = 11
-DNS_T_PTR = 12
-DNS_T_HINFO = 13
-DNS_T_MX = 15
-DNS_T_TXT = 16
-DNS_T_AAAA = 28
-DNS_T_SRV = 33
-DNS_T_ANY = 255
-
-DNS_S_QUERY = 1
-DNS_S_ANSWER = 2
-DNS_S_AUTHORITY = 3
-DNS_S_ADDITIONAL = 4
-
-DEBUG_DNSCLIENT = False
-
-class DNSQueryHeader:
-	FORMAT = "!HBBHHHH"
-	def __init__(self):
-		self.dns_id = 0
-		self.dns_rd = 0
-		self.dns_tc = 0
-		self.dns_aa = 0
-		self.dns_opcode = 0
-		self.dns_qr = 0
-		self.dns_rcode = 0
-		self.dns_z = 0
-		self.dns_ra = 0
-		self.dns_qdcount = 0
-		self.dns_ancount = 0
-		self.dns_nscount = 0
-		self.dns_arcount = 0
-
-	def pack(self):
-		return struct.pack(DNSQueryHeader.FORMAT,
-			self.dns_id,
-			(self.dns_rd & 1) |
-			(self.dns_tc & 1) << 1 |
-			(self.dns_aa & 1) << 2 |
-			(self.dns_opcode & 15) << 3 |
-			(self.dns_qr & 1) << 7,
-			(self.dns_rcode & 15) |
-			(self.dns_z & 7) << 4 |
-			(self.dns_ra & 1) << 7,
-			self.dns_qdcount,
-			self.dns_ancount,
-			self.dns_nscount,
-			self.dns_arcount)
-
-	def unpack(self, data):
-		(self.dns_id, byte1, byte2, self.dns_qdcount, self.dns_ancount,
-			self.dns_nscount, self.dns_arcount) = struct.unpack(DNSQueryHeader.FORMAT, data[0:self.size()])
-		self.dns_rd = byte1 & 1
-		self.dns_tc = (byte1 >> 1) & 1
-		self.dns_aa = (byte1 >> 2) & 1
-		self.dns_opcode = (byte1 >> 3) & 15
-		self.dns_qr = (byte1 >> 7) & 1
-		self.dns_rcode = byte2 & 15
-		self.dns_z = (byte2 >> 4) & 7
-		self.dns_ra = (byte1 >> 7) & 1
-	
-	def size(self):
-		return struct.calcsize(DNSQueryHeader.FORMAT)
-
-def unpackQueryHeader(data):
-	header = DNSQueryHeader()
-	header.unpack(data)
-	return header
-
-class DNSResult:
-	FORMAT = "!HHIH"
-	QFORMAT = "!HH"
-	def __init__(self):
-		self.dns_name = ""
-		self.dns_type = 0
-		self.dns_class = 0
-		self.dns_ttl = 0
-		self.dns_rlength = 0
-		self.rdata = None
-		self.section = None
-
-	def unpack(self, data):
-		(self.dns_type, self.dns_class, self.dns_ttl,
-			self.dns_rlength) = struct.unpack(DNSResult.FORMAT, data[0:self.size()])
-			
-	def qunpack(self, data):
-		(self.dns_type, self.dns_class) = struct.unpack(DNSResult.QFORMAT, data[0:self.qsize()])
-
-	def size(self):
-		return struct.calcsize(DNSResult.FORMAT)
-
-	def qsize(self):
-		return struct.calcsize(DNSResult.QFORMAT)
-
-class DNSRData:
-	def __init__(self):
-		pass	
-
-#typedef struct dns_rr_a {
-#	u_int32_t address;
-#} dns_rr_a_t;
-#
-#typedef struct dns_rr_aaaa {
-#       unsigned char address[16];
-#} dns_rr_aaaa_t;
-#
-#typedef struct dns_rr_cname {
-#	const char *cname;
-#} dns_rr_cname_t;
-#
-#typedef struct dns_rr_hinfo {
-#	const char *cpu, *os;
-#} dns_rr_hinfo_t;
-#
-#typedef struct dns_rr_mx {
-#	u_int16_t preference;
-#	const char *exchange;
-#} dns_rr_mx_t;
-#
-#typedef struct dns_rr_null {
-#	unsigned const char *data;
-#} dns_rr_null_t;
-#
-#typedef struct dns_rr_ns {
-#	const char *nsdname;
-#} dns_rr_ns_t;
-#
-#typedef struct dns_rr_ptr {
-#	const char *ptrdname;
-#} dns_rr_ptr_t;
-#
-#typedef struct dns_rr_soa {
-#	const char *mname;
-#	const char *rname;
-#	u_int32_t serial;
-#	int32_t refresh;
-#	int32_t retry;
-#	int32_t expire;
-#	int32_t minimum;
-#} dns_rr_soa_t;
-#
-#typedef struct dns_rr_txt {
-#	const char *data;
-#} dns_rr_txt_t;
-#
-#typedef struct dns_rr_srv {
-#	const char *server;
-#	u_int16_t priority;
-#	u_int16_t weight;
-#	u_int16_t port;
-#} dns_rr_srv_t;
-
-def dnsNameToLabel(name):
-	out = ""
-	name = name.split(".")
-	for part in name:
-		out += chr(len(part)) + part
-	return out
-
-def dnsFormatQuery(query, qclass, qtype):
-	header = DNSQueryHeader()
-
-	header.dns_id = 0 # FIXME: id = 0
-	header.dns_rd = 1 # don't know why the original code didn't request recursion for non SOA requests
-	header.dns_qr = 0 # query
-	header.dns_opcode = 0 # standard query
-	header.dns_qdcount = 1 # single query
-
-	qlabel = dnsNameToLabel(query)
-	if not qlabel:
-		return ""
-
-	out = header.pack() + qlabel
-	out += chr(qtype >> 8)
-	out += chr(qtype & 0xff)
-	out += chr(qclass >> 8)
-	out += chr(qclass & 0xff)
-
-	return out
-
-def dnsParseLabel(label, base):
-	# returns (output, rest)
-	if not label:
-		return ("", None)
-
-	update = 1
-	rest = label
-	output = ""
-	skip = 0
-	
-	try:
-		while ord(rest[0]):
-			if ord(rest[0]) & 0xc0:
-				rest = base[((ord(rest[0]) & 0x3f) << 8) + ord(rest[1]):]
-				if update:
-					skip += 2
-				update = 0
-				continue
-			output += rest[1:ord(rest[0]) + 1] + "."
-			if update:
-				skip += ord(rest[0]) + 1
-			rest = rest[ord(rest[0]) + 1:]
-	except IndexError:
-		return ("", None)
-	return (label[skip+update:], output)
-
-def dnsParseA(data, base):
-	rdata = DNSRData()
-	if len(data) < 4:
-		rdata.address = 0
-		return None
-		
-	rdata.address = (ord(data[0])<<24) | (ord(data[1])<<16) | (ord(data[2])<<8) | (ord(data[3])<<0)
-		
-	if DEBUG_DNSCLIENT:
-		print "A = %d.%d.%d.%d." % (ord(data[0]), ord(data[1]), ord(data[2]), ord(data[3]))
-	return rdata
-
-def dnsParseAAAA(data, base):
-	rdata = DNSRData()
-	if len(data) < 16:
-		rdata.address = 0
-		return None
-
-        rdata.address = list(struct.unpack('!16B', data))
-        if DEBUG_DNSCLIENT:
-            print socket.inet_ntop(socket.AF_INET6,
-                                   struct.pack('!16B', *rdata.address))
-        return rdata
-
-def dnsParseText(data):	
-	if len(data) < 1:
-		return ("", None)
-	tlen = ord(data[0])
-	if len(data) < tlen + 1:
-		return ("", None)
-	return (data[tlen+1:], data[1:tlen+1])
-
-def dnsParseNS(data, base):
-	rdata = DNSRData()
-	(rest, rdata.nsdname) = dnsParseLabel(data, base)
-	if DEBUG_DNSCLIENT:
-		print "NS DNAME = \"%s\"." % (rdata.nsdname)
-	return rdata
-
-def dnsParseCNAME(data, base):
-	rdata = DNSRData()
-	(rest, rdata.cname) = dnsParseLabel(data, base)
-	if DEBUG_DNSCLIENT:
-		print "CNAME = \"%s\"." % (rdata.cname)
-	return rdata
-
-def dnsParseSOA(data, base):
-	rdata = DNSRData()
-	format = "!IIIII"
-	
-	(rest, rdata.mname) = dnsParseLabel(data, base)
-	if rdata.mname is None:
-		return None
-	(rest, rdata.rname) = dnsParseLabel(rest, base)
-	if rdata.rname is None:
-		return None
-	if len(rest) < struct.calcsize(format):
-		return None
-
-	(rdata.serial, rdata.refresh, rdata.retry, rdata.expire,
-		rdata.minimum) = struct.unpack(format, rest[:struct.calcsize(format)])
-	
-	if DEBUG_DNSCLIENT:
-		print "SOA(mname) = \"%s\"." % rdata.mname
-		print "SOA(rname) = \"%s\"." % rdata.rname
-		print "SOA(serial) = %d." % rdata.serial
-		print "SOA(refresh) = %d." % rdata.refresh
-		print "SOA(retry) = %d." % rdata.retry
-		print "SOA(expire) = %d." % rdata.expire
-		print "SOA(minimum) = %d." % rdata.minimum
-	return rdata
-
-def dnsParseNULL(data, base):
-	# um, yeah
-	return None
-
-def dnsParseWKS(data, base):
-	return None
-
-def dnsParseHINFO(data, base):
-	rdata = DNSRData()
-	(rest, rdata.cpu) = dnsParseText(data)
-	if rest:
-		(rest, rdata.os) = dnsParseText(rest)
-	if DEBUG_DNSCLIENT:
-		print "HINFO(cpu) = \"%s\"." % rdata.cpu
-		print "HINFO(os) = \"%s\"." % rdata.os
-	return rdata
-
-def dnsParseMX(data, base):
-	rdata = DNSRData()
-	if len(data) < 2:
-		return None
-	rdata.preference = (ord(data[0]) << 8) | ord(data[1])
-	(rest, rdata.exchange) = dnsParseLabel(data[2:], base)
-	if DEBUG_DNSCLIENT:
-		print "MX(exchanger) = \"%s\"." % rdata.exchange
-		print "MX(preference) = %d." % rdata.preference
-	return rdata
-
-def dnsParseTXT(data, base):
-	rdata = DNSRData()
-	(rest, rdata.data) = dnsParseText(data)
-	if DEBUG_DNSCLIENT:
-		print "TXT = \"%s\"." % rdata.data
-	return rdata
-
-def dnsParsePTR(data, base):
-	rdata = DNSRData()
-	(rest, rdata.ptrdname) = dnsParseLabel(data, base)
-	if DEBUG_DNSCLIENT:
-		print "PTR = \"%s\"." % rdata.ptrdname
-        return rdata
-
-def dnsParseSRV(data, base):
-	rdata = DNSRData()
-	format = "!HHH"
-	flen = struct.calcsize(format)
-	if len(data) < flen:
-		return None
-		
-	(rdata.priority, rdata.weight, rdata.port) = struct.unpack(format, data[:flen])
-	(rest, rdata.server) = dnsParseLabel(data[flen:], base)
-	if DEBUG_DNSCLIENT:
-		print "SRV(server) = \"%s\"." % rdata.server
-		print "SRV(weight) = %d." % rdata.weight
-		print "SRV(priority) = %d." % rdata.priority
-		print "SRV(port) = %d." % rdata.port
-	return rdata
-
-def dnsParseResults(results):
-	try:
-		header = unpackQueryHeader(results)
-	except struct.error:
-		return []
-	
-	if header.dns_qr != 1: # should be a response
-		return []
-
-	if header.dns_rcode != 0: # should be no error
-		return []
-
-	rest = results[header.size():]
-	
-	rrlist = []
-
-	for i in xrange(header.dns_qdcount):
-		if not rest:
-			return []
-		
-		qq = DNSResult()
-
-		(rest, label) = dnsParseLabel(rest, results)
-		if label is None:
-			return []
-
-		if len(rest) < qq.qsize():
-			return []
-		
-		qq.qunpack(rest)
-		
-		rest = rest[qq.qsize():]
-
-		if DEBUG_DNSCLIENT:
-			print "Queried for '%s', class = %d, type = %d." % (label,
-				qq.dns_class, qq.dns_type)
-
-	for (rec_count, section_id) in ((header.dns_ancount, DNS_S_ANSWER),
-									(header.dns_nscount, DNS_S_AUTHORITY),
-									(header.dns_arcount, DNS_S_ADDITIONAL)):
-		for i in xrange(rec_count):
-			(rest, label) = dnsParseLabel(rest, results)
-			if label is None:
-				return []
-
-			rr = DNSResult()
-
-			rr.dns_name = label
-			rr.section = section_id
-
-			if len(rest) < rr.size():
-				return []
-
-			rr.unpack(rest)
-
-			rest = rest[rr.size():]
-
-			if DEBUG_DNSCLIENT:
-				print "Answer %d for '%s', class = %d, type = %d, ttl = %d." % (i,
-					rr.dns_name, rr.dns_class, rr.dns_type,
-					rr.dns_ttl)
-
-			if len(rest) < rr.dns_rlength:
-				if DEBUG_DNSCLIENT:
-					print "Answer too short."
-				return []
-
-			fmap = { DNS_T_A: dnsParseA, DNS_T_NS: dnsParseNS,
-				DNS_T_CNAME: dnsParseCNAME, DNS_T_SOA: dnsParseSOA,
-				DNS_T_NULL: dnsParseNULL, DNS_T_WKS: dnsParseWKS,
-				DNS_T_PTR: dnsParsePTR, DNS_T_HINFO: dnsParseHINFO,
-				DNS_T_MX: dnsParseMX, DNS_T_TXT: dnsParseTXT,
-				DNS_T_AAAA : dnsParseAAAA, DNS_T_SRV: dnsParseSRV}
-
-			if not rr.dns_type in fmap:
-				if DEBUG_DNSCLIENT:
-					print "Don't know how to parse RR type %d!" %	rr.dns_type
-			else:
-				rr.rdata = fmap[rr.dns_type](rest[:rr.dns_rlength], results)
-
-			rest = rest[rr.dns_rlength:]
-			rrlist += [rr]
-
-	return rrlist
-
-def query(query, qclass, qtype):
-	qdata = dnsFormatQuery(query, qclass, qtype)
-	if not qdata:
-		return []
-	answer = acutil.res_send(qdata)
-	if not answer:
-		return []
-	return dnsParseResults(answer)
-
-if __name__ == '__main__':
-	DEBUG_DNSCLIENT = True
-	print "Sending query."
-	rr = query(len(sys.argv) > 1 and sys.argv[1] or "devserv.devel.redhat.com.",
-		DNS_C_IN, DNS_T_ANY)
-	sys.exit(0)
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 4a9db11e23c1b9ab76c9fce9150bc1546426452f..71be39132ea40172595e026a9815acc58d29f4ff 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -41,6 +41,8 @@ import re
 import xmlrpclib
 import datetime
 import netaddr
+from dns import resolver, rdatatype
+from dns.exception import DNSException
 
 from ipapython.ipa_log_manager import *
 from ipapython import ipavalidate
@@ -747,6 +749,17 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
     finally:
         s.close()
 
+def is_host_resolvable(fqdn):
+    found = False
+    for rdtype in (rdatatype.A, rdatatype.AAAA):
+        try:
+            resolver.query(fqdn, rdtype)
+            found = True
+        except DNSException:
+            continue
+
+    return found
+
 def get_ipa_basedn(conn):
     """
     Get base DN of IPA suffix in given LDAP server.
diff --git a/ipaserver/install/installutils.py b/ipaserver/install/installutils.py
index 3e7ae41b5fdbc11353e43a63424f19fbc331435a..76b54c94c9d1dad169545dd30c0b9a926f19a3c6 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -30,9 +30,11 @@ import netaddr
 import time
 import tempfile
 import shutil
+from dns import resolver, rdatatype
+from dns.exception import DNSException
+
 from ConfigParser import SafeConfigParser
-
-from ipapython import ipautil, dnsclient, sysrestore
+from ipapython import ipautil, sysrestore
 from ipapython.ipa_log_manager import *
 from ipalib.util import validate_hostname
 
@@ -76,68 +78,6 @@ def get_fqdn():
             fqdn = ""
     return fqdn
 
-def verify_dns_records(host_name, responses, resaddr, family):
-    familykw = { 'ipv4' : {
-                             'dns_type' : dnsclient.DNS_T_A,
-                             'socket_family' : socket.AF_INET,
-                          },
-                 'ipv6' : {
-                             'dns_type' : dnsclient.DNS_T_AAAA,
-                             'socket_family' : socket.AF_INET6,
-                          },
-               }
-
-    family = family.lower()
-    if family not in familykw.keys():
-        raise RuntimeError("Unknown faimily %s\n" % family)
-
-    rec_list = []
-    for rsn in responses:
-        if rsn.section == dnsclient.DNS_S_ANSWER and \
-                rsn.dns_type == familykw[family]['dns_type']:
-            rec_list.append(rsn)
-
-    if not rec_list:
-        raise IOError(errno.ENOENT,
-                      "Warning: Hostname (%s) not found in DNS" % host_name)
-
-    if family == 'ipv4':
-        familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET,
-                                                        struct.pack('!L',rec.rdata.address)) \
-                                                                for rec in rec_list]
-    else:
-        familykw[family]['address'] = [socket.inet_ntop(socket.AF_INET6,
-                                                        struct.pack('!16B', *rec.rdata.address)) \
-                                                                for rec in rec_list]
-
-    # Check that DNS address is the same is address returned via standard glibc calls
-    dns_addrs = [netaddr.IPAddress(addr) for addr in familykw[family]['address']]
-    dns_addr = None
-    for addr in dns_addrs:
-        if addr.format() == resaddr:
-            dns_addr = addr
-            break
-
-    if dns_addr is None:
-        raise RuntimeError("Host address %s does not match any address in DNS lookup."  % resaddr)
-
-    rs = dnsclient.query(dns_addr.reverse_dns, dnsclient.DNS_C_IN, dnsclient.DNS_T_PTR)
-    if len(rs) == 0:
-        raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format()))
-
-    rev = None
-    for rsn in rs:
-        if rsn.dns_type == dnsclient.DNS_T_PTR:
-            rev = rsn
-            break
-
-    if rev == None:
-        raise RuntimeError("Cannot find Reverse Address for %s (%s)" % (host_name, dns_addr.format()))
-
-    if rec.dns_name != rev.rdata.ptrdname:
-        raise RuntimeError("The DNS forward record %s does not match the reverse address %s" % (rec.dns_name, rev.rdata.ptrdname))
-
-
 def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
     """
     Run fqdn checks for given host:
@@ -168,7 +108,9 @@ def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
 
     if local_hostname:
         try:
+            root_logger.debug('Check if %s is a primary hostname for localhost', host_name)
             ex_name = socket.gethostbyaddr(host_name)
+            root_logger.debug('Primary hostname for localhost: %s', ex_name[0])
             if host_name != ex_name[0]:
                 raise HostLookupError("The host name %s does not match the primary host name %s. "\
                         "Please check /etc/hosts or DNS name resolution" % (host_name, ex_name[0]))
@@ -180,43 +122,41 @@ def verify_fqdn(host_name, no_host_dns=False, local_hostname=True):
         return
 
     try:
+        root_logger.debug('Search DNS for %s', host_name)
         hostaddr = socket.getaddrinfo(host_name, None)
-    except:
+    except Exception, e:
+        root_logger.debug('Search failed: %s', e)
         raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution")
 
     if len(hostaddr) == 0:
         raise HostForwardLookupError("Unable to resolve host name, check /etc/hosts or DNS name resolution")
 
+    # Verify this is NOT a CNAME
+    try:
+        root_logger.debug('Check if %s is not a CNAME', host_name)
+        resolver.query(host_name, rdatatype.CNAME)
+        raise HostReverseLookupError("The IPA Server Hostname cannot be a CNAME, only A and AAAA names are allowed.")
+    except DNSException:
+        pass
+
+    # list of verified addresses to prevent multiple searches for the same address
+    verified = []
     for a in hostaddr:
-        if a[4][0] == '127.0.0.1' or a[4][0] == '::1':
-            raise HostForwardLookupError("The IPA Server hostname must not resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (a[4][0], host_name, a[4][0]))
+        address = a[4][0]
+        if address in verified:
+            continue
+        if address == '127.0.0.1' or address == '::1':
+            raise HostForwardLookupError("The IPA Server hostname must not resolve to localhost (%s). A routable IP address must be used. Check /etc/hosts to see if %s is an alias for %s" % (address, host_name, address))
         try:
-            resaddr = a[4][0]
-            revname = socket.gethostbyaddr(a[4][0])[0]
-        except:
+            root_logger.debug('Check reverse address of %s', address)
+            revname = socket.gethostbyaddr(address)[0]
+        except Exception, e:
+            root_logger.debug('Check failed: %s', e)
             raise HostReverseLookupError("Unable to resolve the reverse ip address, check /etc/hosts or DNS name resolution")
+        root_logger.debug('Found reverse name: %s', revname)
         if revname != host_name:
             raise HostReverseLookupError("The host name %s does not match the reverse lookup %s" % (host_name, revname))
-
-    # Verify this is NOT a CNAME
-    rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_CNAME)
-    if len(rs) != 0:
-        for rsn in rs:
-            if rsn.dns_type == dnsclient.DNS_T_CNAME:
-                raise HostReverseLookupError("The IPA Server Hostname cannot be a CNAME, only A and AAAA names are allowed.")
-
-    # Verify that it is a DNS A or AAAA record
-    rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
-    if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
-        verify_dns_records(host_name, rs, resaddr, 'ipv4')
-        return
-
-    rs = dnsclient.query(host_name+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
-    if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
-        verify_dns_records(host_name, rs, resaddr, 'ipv6')
-        return
-    else:
-        print "Warning: Hostname (%s) not found in DNS" % host_name
+        verified.append(address)
 
 def record_in_hosts(ip, host_name=None, file="/etc/hosts"):
     """
-- 
1.7.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to