summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Kosek <mkosek@redhat.com>2012-05-11 14:38:09 +0200
committerMartin Kosek <mkosek@redhat.com>2012-05-24 13:55:56 +0200
commitf1ed123caddd7525a0081c4a9de931cabdfda43f (patch)
treef615dabc3535203fbd2777166dbe150f6d31197e
parent6bb462e26a814e683b3ec5b39d2ff9a1db8fa4ec (diff)
downloadfreeipa-f1ed123caddd7525a0081c4a9de931cabdfda43f.tar.gz
freeipa-f1ed123caddd7525a0081c4a9de931cabdfda43f.tar.xz
freeipa-f1ed123caddd7525a0081c4a9de931cabdfda43f.zip
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 https://fedorahosted.org/freeipa/ticket/1837
-rw-r--r--freeipa.spec.in8
-rwxr-xr-xinstall/tools/ipa-dns-install2
-rwxr-xr-xipa-client/ipa-install/ipa-client-install24
-rw-r--r--ipa-client/ipaclient/ipadiscovery.py142
-rw-r--r--ipa-client/ipaclient/ntpconf.py14
-rw-r--r--ipalib/plugins/dns.py14
-rw-r--r--ipalib/rpc.py21
-rw-r--r--ipalib/util.py16
-rw-r--r--ipapython/README3
-rw-r--r--ipapython/config.py64
-rw-r--r--ipapython/dnsclient.py469
-rw-r--r--ipapython/ipautil.py24
-rw-r--r--ipaserver/install/installutils.py120
13 files changed, 200 insertions, 721 deletions
diff --git a/freeipa.spec.in b/freeipa.spec.in
index a1e43a0ad..de93aecb6 100644
--- a/freeipa.spec.in
+++ b/freeipa.spec.in
@@ -59,7 +59,6 @@ BuildRequires: m4
BuildRequires: libtool
BuildRequires: gettext
BuildRequires: python-devel
-BuildRequires: authconfig
BuildRequires: python-ldap
BuildRequires: python-setuptools
BuildRequires: python-krbV
@@ -79,6 +78,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 +151,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 +221,7 @@ Requires: nss-tools
Requires: bind-utils
Requires: oddjob-mkhomedir
Requires: python-krbV
+Requires: python-dns
Obsoletes: ipa-client >= 1.0
@@ -256,7 +258,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 +684,9 @@ fi
%ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/ca.crt
%changelog
+* Fri May 11 2012 Martin Kosek <mkosek@redhat.com> - 2.99.0-29
+- Replace used DNS client library (acutil) with python-dns
+
* Tue Apr 10 2012 Rob Crittenden <rcritten@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/install/tools/ipa-dns-install b/install/tools/ipa-dns-install
index b540630f4..10547c342 100755
--- a/install/tools/ipa-dns-install
+++ b/install/tools/ipa-dns-install
@@ -223,7 +223,7 @@ def main():
zone_notif=options.zone_notif)
bind.create_instance()
- # Restart http instance to make sure acutil has the right resolver
+ # Restart http instance to make sure that python-dns has the right resolver
# https://bugzilla.redhat.com/show_bug.cgi?id=800368
http = httpinstance.HTTPInstance(fstore)
service.print_msg("Restarting the web server")
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
index 67279b3ed..6854581d2 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)
@@ -1243,15 +1235,15 @@ def install(options, env, fstore, statestore):
# We assume that NTP servers are discoverable through SRV records in the DNS
# If that fails, we try to sync directly with IPA server, assuming it runs NTP
print 'Synchronizing time with KDC...'
- ntp_servers = ipautil.parse_items(ds.ipadnssearchntp(cli_domain))
+ ntp_servers = ds.ipadns_search_srv(cli_domain, '_ntp._udp', None, break_on_first=False)
synced_ntp = False
- if len(ntp_servers) > 0:
+ if ntp_servers:
for s in ntp_servers:
- synced_ntp = ipaclient.ntpconf.synconce_ntp(s)
+ synced_ntp = ipaclient.ntpconf.synconce_ntp(s, debug=True)
if synced_ntp:
break
if not synced_ntp:
- synced_ntp = ipaclient.ntpconf.synconce_ntp(cli_server)
+ synced_ntp = ipaclient.ntpconf.synconce_ntp(cli_server, debug=True)
if not synced_ntp:
print "Unable to sync time with IPA NTP server, assuming the time is in sync."
(krb_fd, krb_name) = tempfile.mkstemp()
diff --git a/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py
index 86bef28b2..281daf42a 100644
--- a/ipa-client/ipaclient/ipadiscovery.py
+++ b/ipa-client/ipaclient/ipadiscovery.py
@@ -20,12 +20,14 @@
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
+ realm_to_suffix, format_netloc
NOT_FQDN = -1
@@ -93,11 +95,12 @@ class IPADiscovery:
isn't found.
"""
server = None
+ root_logger.debug("Start searching for LDAP SRV record in %s and"
+ " its sub-domains", domain)
while not server:
- root_logger.debug("[ipadnssearchldap("+domain+")]")
- server = self.ipadnssearchldap(domain)
+ server = self.ipadns_search_srv(domain, '_ldap._tcp', 389)
if server:
- return (server, domain)
+ return (server[0], domain)
else:
p = domain.find(".")
if p == -1: #no ldap server found and last component of the domain already tested
@@ -148,11 +151,13 @@ class IPADiscovery:
if not self.domain: #no ldap server found
return NO_LDAP_SERVER
else:
- root_logger.debug("[ipadnssearchldap]")
- self.server = self.ipadnssearchldap(domain)
- if self.server:
+ root_logger.debug("Search for LDAP SRV record in %s", domain)
+ server = self.ipadns_search_srv(domain, '_ldap._tcp', 389)
+ if server:
+ self.server = server[0]
self.domain = domain
else:
+ self.server = None
return NO_LDAP_SERVER
else: #server forced on us, this means DNS doesn't work :/
@@ -172,19 +177,16 @@ class IPADiscovery:
root_logger.debug("[ipacheckldap]")
# We may have received multiple servers corresponding to the domain
# Iterate through all of those to check if it is IPA LDAP server
- servers = parse_items(self.server)
ldapret = [NOT_IPA_SERVER]
ldapaccess = True
- for server in servers:
+ if self.server:
# check ldap now
- ldapret = self.ipacheckldap(server, self.realm)
+ ldapret = self.ipacheckldap(self.server, self.realm)
if ldapret[0] == 0:
self.server = ldapret[1]
self.realm = ldapret[2]
- break
-
- if ldapret[0] == NO_ACCESS_TO_LDAP:
+ elif ldapret[0] == NO_ACCESS_TO_LDAP:
ldapaccess = False
# If one of LDAP servers checked rejects access (may be anonymous
@@ -310,46 +312,43 @@ class IPADiscovery:
os.rmdir(temp_ca_dir)
- def ipadnssearchldap(self, tdomain):
- servers = ""
- rserver = ""
+ def ipadns_search_srv(self, domain, srv_record_name, default_port,
+ break_on_first=True):
+ """
+ Search for SRV records in given domain. When no record is found,
+ en empty string is returned
+
+ :param domain: Search domain name
+ :param srv_record_name: SRV record name, e.g. "_ldap._tcp"
+ :param default_port: When default_port is not None, it is being
+ checked with the port in SRV record and if they don't
+ match, the port from SRV record is appended to
+ found hostname in this format: "hostname:port"
+ :param break_on_first: break on the first find and return just one
+ entry
+ """
+ servers = []
- 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 = '%s.%s' % (srv_record_name, domain)
- 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
-
- def ipadnssearchntp(self, tdomain):
- servers = ""
- rserver = ""
-
- 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)
-
- 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
+ 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 not server:
+ root_logger.debug("Cannot parse the hostname from SRV record: %s", answer)
+ continue
+ if default_port is not None and answer.port != default_port:
+ server = "%s:%s" % (server, str(answer.port))
+ servers.append(server)
+ if break_on_first:
break
return servers
@@ -359,35 +358,32 @@ class IPADiscovery:
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
+ domain = realm.lower()
+
+ kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88,
+ break_on_first=False)
if not kdc:
root_logger.debug("SRV record for KDC not found! Realm: %s, SRV record: %s" % (realm, qname))
+ kdc = None
+ kdc = ','.join(kdc)
return [realm, kdc]
diff --git a/ipa-client/ipaclient/ntpconf.py b/ipa-client/ipaclient/ntpconf.py
index e71692f40..aa9261cb2 100644
--- a/ipa-client/ipaclient/ntpconf.py
+++ b/ipa-client/ipaclient/ntpconf.py
@@ -133,7 +133,7 @@ def config_ntp(server_fqdn, fstore = None, sysstore = None):
# Restart ntpd
ipaservices.knownservices.ntpd.restart()
-def synconce_ntp(server_fqdn):
+def synconce_ntp(server_fqdn, debug=False):
"""
Syncs time with specified server using ntpdate.
Primarily designed to be used before Kerberos setup
@@ -142,15 +142,17 @@ def synconce_ntp(server_fqdn):
Returns True if sync was successful
"""
ntpdate="/usr/sbin/ntpdate"
- result = False
if os.path.exists(ntpdate):
# retry several times -- logic follows /etc/init.d/ntpdate
# implementation
+ cmd = [ntpdate, "-U", "ntp", "-s", "-b"]
+ if debug:
+ cmd.append('-d')
+ cmd.append(server_fqdn)
for retry in range(0,3):
try:
- ipautil.run([ntpdate, "-U", "ntp", "-s", "-b", server_fqdn])
- result = True
- break
+ ipautil.run(cmd)
+ return True
except:
pass
- return result
+ return False
diff --git a/ipalib/plugins/dns.py b/ipalib/plugins/dns.py
index b0e65ab94..e26332d46 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 04a3f3e35..bd18b6bbf 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 64ac6b2cf..50da74327 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: DNS A record lookup failed for %s' % fqdn
+ 'IPA: found %d records for %s: %s' % (len(answers), fqdn,
+ ' '.join(str(answer) for answer in answers))
)
- raise errors.DNSNotARecordError()
- else:
+ except DNSException, e:
log.debug(
- 'IPA: found %d records for %s' % (len(rs), fqdn)
+ 'IPA: DNS A record lookup failed for %s' % fqdn
)
+ raise errors.DNSNotARecordError()
def isvalid_base64(data):
"""
diff --git a/ipapython/README b/ipapython/README
index ec2bb3a52..a724a7faa 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 d4c724dc9..d428b1e27 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
@@ -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,44 @@ 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()
+ name = "_ldap._tcp." + domain
+
+ try:
+ servers = resolver.query(name, rdatatype.SRV)
+ except DNSException:
+ # 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 True:
+ domain = domain.parent()
+
+ if str(domain) == '.':
+ return False
+ name = "_ldap._tcp.%s" % domain
+ try:
+ servers = resolver.query(name, rdatatype.SRV)
+ break
+ 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)
-
- for r in rs:
- if r.dns_type == ipapython.dnsclient.DNS_T_SRV:
- rsrv = r.rdata.server.rstrip(".")
- config.default_server.append(rsrv)
+ if not servers:
+ name = "_ldap._tcp.%s." % config.default_domain
+ try:
+ servers = resolver.query(name, rdatatype.SRV)
+ except DNSException:
+ pass
+
+ 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 3f08866a6..000000000
--- 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 4a9db11e2..8884e7be9 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
@@ -611,17 +613,6 @@ def ipa_generate_password(characters=None,pwd_len=None):
rndpwd += rndchar
return rndpwd
-def parse_items(text):
- '''Given text with items separated by whitespace or comma, return a list of those items
-
- The returned list only contains non-empty items.
- '''
- split_re = re.compile('[ ,\t\n]+')
- items = split_re.split(text)
- for item in items[:]:
- if not item: items.remove(item)
- return items
-
def user_input(prompt, default = None, allow_empty = True):
if default == None:
while True:
@@ -747,6 +738,17 @@ def bind_port_responder(port, socket_type=socket.SOCK_STREAM, socket_timeout=Non
finally:
s.close()
+def is_host_resolvable(fqdn):
+ for rdtype in (rdatatype.A, rdatatype.AAAA):
+ try:
+ resolver.query(fqdn, rdtype)
+ except DNSException:
+ continue
+ else:
+ return True
+
+ return False
+
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 fc54b74b3..362723614 100644
--- a/ipaserver/install/installutils.py
+++ b/ipaserver/install/installutils.py
@@ -30,9 +30,11 @@ import netaddr
import time
import tempfile
import shutil
-from ConfigParser import SafeConfigParser
+from dns import resolver, rdatatype
+from dns.exception import DNSException
-from ipapython import ipautil, dnsclient, sysrestore
+from ConfigParser import SafeConfigParser
+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 = set()
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.add(address)
def record_in_hosts(ip, host_name=None, file="/etc/hosts"):
"""