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
This commit is contained in:
Martin Kosek 2012-05-11 14:38:09 +02:00
parent 6bb462e26a
commit f1ed123cad
13 changed files with 197 additions and 718 deletions

View File

@ -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.

View File

@ -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")

View File

@ -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()

View File

@ -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
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 break_on_first: break on the first find and return just one
entry
"""
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
qname = '%s.%s' % (srv_record_name, domain)
return servers
root_logger.debug("Search DNS for SRV record of %s", qname)
def ipadnssearchntp(self, tdomain):
servers = ""
rserver = ""
try:
answers = resolver.query(qname, rdatatype.SRV)
except DNSException, e:
root_logger.debug("DNS record not found: %s", e.__class__.__name__)
answers = []
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
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]

View File

@ -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

View File

@ -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}
)

View File

@ -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.

View File

@ -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):
"""

View File

@ -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

View File

@ -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:
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)
# try once with REALM -> domain
domain = str(config.default_realm).lower()
name = "_ldap._tcp." + domain
config.default_domain = dom_name
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
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

View File

@ -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)

View File

@ -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.

View File

@ -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"):
"""