On Tue, 2012-05-15 at 14:02 +0200, Petr Viktorin wrote: > On 05/11/2012 06:52 PM, Martin Kosek wrote: > > 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 > > Also relevant: https://fedorahosted.org/freeipa/ticket/1837
Yup, added to commit message. > > There is a forgotten acutil reference in a comment in > install/tools/ipa-dns-install:226 Fixed. > > I did find some style issues/suggestions. Stop me if the bikeshedding is > too useless :) > > [...] > > 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 > > These function ars mostly the same, except ipadnssearchntp uses "_ntp" > instead of "_ldap", and it doesn't take the port into account (which it > probably should?). > The last part of ipadnssearchkrb is also very similar. > Please merge the common parts. Yup, these parts could be merged - done. > > > def ipadnssearchkrb(self, tdomain): > > [...] > > > diff --git a/ipapython/config.py b/ipapython/config.py > > index > > d4c724dc9ac754cb221fe60d7c13bd0c716dd296..e06f51a318a2b20c53a8c6933c43d58c34075407 > > 100644 > > --- a/ipapython/config.py > > +++ b/ipapython/config.py > [...] > > @@ -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) > > This is unrelated to the DNS library replacement, right? It should go in > a separate patch, in case e.g. the big patch gets reverted. Ok. > > [...] > > + try: > > + servers = resolver.query(name, rdatatype.SRV) > > + domain_ok = True > > + except DNSException: > > + pass > > + > > + if not domain_ok: > > + # try cycling on domain components of FQDN > > This can be just: > > try: > servers = resolver.query(name, rdatatype.SRV) > domain_ok = True > except DNSException: > # try cycling on domain components of FQDN > > The extra if and flag variable just makes it less clear. > > > + 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(".") > > Drop the `domain_ok` variable and just use a while True/break. > The code flow would be more clear that way, IMO. > > [...] > > > diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py > > index > > 4a9db11e23c1b9ab76c9fce9150bc1546426452f..71be39132ea40172595e026a9815acc58d29f4ff > > 100644 > > --- a/ipapython/ipautil.py > > +++ b/ipapython/ipautil.py > [...] > > +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 > > + > > This seems too complicated; why not return directly? > > for rdtype in rdatatype.A, rdatatype.AAAA: > try: > resolver.query(fqdn, rdtype) > except DNSException: > pass > 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 > > 3e7ae41b5fdbc11353e43a63424f19fbc331435a..76b54c94c9d1dad169545dd30c0b9a926f19a3c6 > > 100644 > > --- a/ipaserver/install/installutils.py > > +++ b/ipaserver/install/installutils.py > [...] > > + > > + # list of verified addresses to prevent multiple searches for the same > > address > > + verified = [] > > Use a set for this. > > > 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 > [...] > > Fixed. Martin
>From e968df78b5dc6a1401f12874910973d2cbb1a995 Mon Sep 17 00:00:00 2001 From: Martin Kosek <mko...@redhat.com> Date: Fri, 11 May 2012 14:38:09 +0200 Subject: [PATCH 1/2] 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 --- freeipa.spec.in | 7 +- install/tools/ipa-dns-install | 2 +- ipa-client/ipa-install/ipa-client-install | 23 +- ipa-client/ipaclient/ipadiscovery.py | 117 ++++---- ipa-client/ipaclient/ntpconf.py | 14 +- ipalib/plugins/dns.py | 14 +- ipalib/rpc.py | 21 +- ipalib/util.py | 16 +- ipapython/README | 3 +- ipapython/config.py | 62 +++-- ipapython/dnsclient.py | 469 ----------------------------- ipapython/ipautil.py | 13 + ipaserver/install/installutils.py | 120 ++------ 13 files changed, 185 insertions(+), 696 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/install/tools/ipa-dns-install b/install/tools/ipa-dns-install index b540630f4f2782603c31ce1905870c38c9af98ab..10547c3425c0ac3d356665d47c5407e2180b1fab 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 67279b3ed8ae8a25e845ccbcce7143efcaf6d467..61be5f705e2c7962dd6b4d3a576b7a036b7dd637 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,16 @@ 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, get_first=False) + ntp_servers = ipautil.parse_items(ntp_servers) synced_ntp = False if len(ntp_servers) > 0: 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 86bef28b2d7fdfc8111b493bddec7ac6888f021a..1889d1918c01fe0c80a3d56b9a7ef304c5a7d97c 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 @@ -93,9 +95,10 @@ 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) else: @@ -148,8 +151,8 @@ class IPADiscovery: if not self.domain: #no ldap server found return NO_LDAP_SERVER else: - root_logger.debug("[ipadnssearchldap]") - self.server = self.ipadnssearchldap(domain) + root_logger.debug("Search for LDAP SRV record in %s", domain) + self.server = self.ipadns_search_srv(domain, '_ldap._tcp', 389) if self.server: self.domain = domain else: @@ -310,84 +313,74 @@ class IPADiscovery: os.rmdir(temp_ca_dir) - def ipadnssearchldap(self, tdomain): - servers = "" - rserver = "" + def ipadns_search_srv(self, domain, srv_record_name, default_port, + get_first=True): + """ + Search for SRV records in given domain. When no record is found, + en empty string is returned - 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) + :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 get_first: break on first find, otherwise multiple finds + separated by ":" may be returned + """ + servers = [] - 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 - - return servers + qname = '%s.%s' % (srv_record_name, domain) - def ipadnssearchntp(self, tdomain): - servers = "" - rserver = "" + root_logger.debug("Search DNS for SRV record of %s", qname) - 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) + try: + answers = resolver.query(qname, rdatatype.SRV) + except DNSException, e: + root_logger.debug("DNS record not found: %s", e.__class__.__name__) + answers = [] - 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 + for answer in answers: + root_logger.debug("DNS record found: %s", answer) + server = str(answer.target).rstrip(".") + if default_port is not None and answer.port != default_port: + server = "%s:%s" % (server, str(answer.port)) + servers.append(server) + if get_first: break - return servers + return ",".join(servers) 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 + domain = realm.lower() + + kdc = self.ipadns_search_srv(domain, '_kerberos._udp', 88, + get_first=False) if not kdc: root_logger.debug("SRV record for KDC not found! Realm: %s, SRV record: %s" % (realm, qname)) + kdc = None return [realm, kdc] diff --git a/ipa-client/ipaclient/ntpconf.py b/ipa-client/ipaclient/ntpconf.py index e71692f4019bf410d6107a471330edc98146c29c..aa9261cb29e285973d0eae0270de701bb7197497 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 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..d428b1e27247d62628333a45e54458ca9195e7d8 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) + 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..b759b7dd658481ae14d18fa6b25e56fdb214fb90 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): + 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 3e7ae41b5fdbc11353e43a63424f19fbc331435a..32728bc5fcde42573bdbde9279d6c2262ee2456b 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 = 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"): """ -- 1.7.7.6
>From 9ecf6806628d65c8567caa5e82db83a79f870364 Mon Sep 17 00:00:00 2001 From: Martin Kosek <mko...@redhat.com> Date: Wed, 16 May 2012 09:36:17 +0200 Subject: [PATCH 2/2] Fix default_server configuration in ipapython.config When default server was being parsed from IPA's default.conf configuration file, the parsed server was not appended correctly to the default_server list. --- ipapython/config.py | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) diff --git a/ipapython/config.py b/ipapython/config.py index d428b1e27247d62628333a45e54458ca9195e7d8..349c91767976ca5e157dddc560083ce8d81e733d 100644 --- a/ipapython/config.py +++ b/ipapython/config.py @@ -155,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: -- 1.7.7.6
_______________________________________________ Freeipa-devel mailing list Freeipa-devel@redhat.com https://www.redhat.com/mailman/listinfo/freeipa-devel