summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile26
-rw-r--r--ipa-client/Makefile23
-rwxr-xr-xipa-client/freeipa-client.spec47
-rw-r--r--ipa-client/freeipa-client.spec.in47
-rw-r--r--ipa-client/ipa-install/Makefile10
-rw-r--r--ipa-client/ipa-install/ipa-client-install109
-rw-r--r--ipa-client/ipaclient/__init__.py23
-rw-r--r--ipa-client/ipaclient/dnsclient.py445
-rw-r--r--ipa-client/ipaclient/ipadiscovery.py239
9 files changed, 967 insertions, 2 deletions
diff --git a/Makefile b/Makefile
index 2e920830..93ffe09b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS=ipa-server ipa-admintools ipa-python
+SUBDIRS=ipa-server ipa-admintools ipa-python ipa-client
PRJ_PREFIX=freeipa
@@ -31,6 +31,13 @@ PYTHON_VERSION=$(PYTHON_MAJOR).$(PYTHON_MINOR).$(PYTHON_RELEASE)
PYTHON_TARBALL_PREFIX=$(PRJ_PREFIX)-python-$(PYTHON_VERSION)
PYTHON_TARBALL=$(PYTHON_TARBALL_PREFIX).tgz
+CLI_MAJOR=0
+CLI_MINOR=1
+CLI_RELEASE=0
+CLI_VERSION=$(CLI_MAJOR).$(CLI_MINOR).$(CLI_RELEASE)
+CLI_TARBALL_PREFIX=$(PRJ_PREFIX)-client-$(CLI_VERSION)
+CLI_TARBALL=$(CLI_TARBALL_PREFIX).tgz
+
ifeq ($(DEBUG),1)
export CFLAGS = -g -Wall -Wshadow
export LDFLAGS = -g
@@ -63,6 +70,9 @@ version-update:
sed s/VERSION/$(PYTHON_VERSION)/ ipa-python/freeipa-python.spec.in \
> ipa-python/freeipa-python.spec
+ sed s/VERSION/$(CLI_VERSION)/ ipa-client/freeipa-client.spec.in \
+ > ipa-client/freeipa-client.spec
+
archive:
-mkdir -p dist
@@ -96,6 +106,12 @@ tarballs:
cd dist; tar cfz $(PYTHON_TARBALL) $(PYTHON_TARBALL_PREFIX)
rm -fr dist/$(PYTHON_TARBALL_PREFIX)
+ # ipa-client
+ mv dist/freeipa/ipa-client dist/$(CLI_TARBALL_PREFIX)
+ rm -f dist/$(CLI_TARBALL)
+ cd dist; tar cfz $(CLI_TARBALL) $(CLI_TARBALL_PREFIX)
+ rm -fr dist/$(CLI_TARBALL_PREFIX)
+
rpmroot:
mkdir -p $(RPMBUILD)/BUILD
mkdir -p $(RPMBUILD)/RPMS
@@ -121,7 +137,13 @@ rpm-ipa-python:
cp rpmbuild/RPMS/noarch/$(PRJ_PREFIX)-python-$(PYTHON_VERSION)-*.rpm dist/.
cp rpmbuild/SRPMS/$(PRJ_PREFIX)-python-$(PYTHON_VERSION)-*.src.rpm dist/.
-rpms: rpmroot rpm-ipa-server rpm-ipa-admin rpm-ipa-python
+rpm-ipa-client:
+ cp dist/$(CLI_TARBALL) $(RPMBUILD)/SOURCES/.
+ rpmbuild --define "_topdir $(RPMBUILD)" -ba ipa-client/freeipa-client.spec
+ cp rpmbuild/RPMS/*/$(PRJ_PREFIX)-client-$(CLI_VERSION)-*.rpm dist/.
+ cp rpmbuild/SRPMS/$(PRJ_PREFIX)-client-$(CLI_VERSION)-*.src.rpm dist/.
+
+rpms: rpmroot rpm-ipa-server rpm-ipa-admin rpm-ipa-python rpm-ipa-client
dist: version-update archive tarballs archive-cleanup rpms
diff --git a/ipa-client/Makefile b/ipa-client/Makefile
new file mode 100644
index 00000000..a00d30bb
--- /dev/null
+++ b/ipa-client/Makefile
@@ -0,0 +1,23 @@
+SUBDIRS=ipa-install
+PYTHONDIR=$(DESTDIR)/usr/share/ipa/ipaclient
+
+all:
+ @for subdir in $(SUBDIRS); do \
+ (cd $$subdir && $(MAKE) $@) || exit 1; \
+ done
+
+install-ipaclient:
+ -mkdir -p $(PYTHONDIR)
+ install -m 644 ipaclient/*.py $(PYTHONDIR)
+
+install: install-ipaclient
+ @for subdir in $(SUBDIRS); do \
+ (cd $$subdir && $(MAKE) $@) || exit 1; \
+ done
+
+clean:
+ @for subdir in $(SUBDIRS); do \
+ (cd $$subdir && $(MAKE) $@) || exit 1; \
+ done
+ rm -f *~
+ rm -f ipaclient/*~
diff --git a/ipa-client/freeipa-client.spec b/ipa-client/freeipa-client.spec
new file mode 100755
index 00000000..f8db90dc
--- /dev/null
+++ b/ipa-client/freeipa-client.spec
@@ -0,0 +1,47 @@
+Name: freeipa-client
+Version: 0.1.0
+Release: 1%{?dist}
+Summary: FreeIPA client
+
+Group: System Environment/Base
+License: GPL
+URL: http://www.freeipa.org
+Source0: %{name}-%{version}.tgz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+Requires: python python-ldap freeipa-python
+
+%description
+FreeIPA is a server for identity, policy, and audit.
+The client package provide install and configuration scripts for clients.
+
+%prep
+%setup -q
+
+%build
+
+make DESTDIR=%{buildroot}
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}%{_sbindir}
+
+make install DESTDIR=%{buildroot}
+
+
+%clean
+rm -rf %{buildroot}
+
+
+%files
+%defattr(-,root,root,-)
+%{_sbindir}/ipa-client-install
+
+%dir %{_usr}/share/ipa
+%{_usr}/share/ipa/*
+
+%changelog
+* Thu Aug 16 2007 Simo Sorce <ssorce@redhat.com> - 0.1.0-1
+- Initial rpm version
+
+
diff --git a/ipa-client/freeipa-client.spec.in b/ipa-client/freeipa-client.spec.in
new file mode 100644
index 00000000..0f733bef
--- /dev/null
+++ b/ipa-client/freeipa-client.spec.in
@@ -0,0 +1,47 @@
+Name: freeipa-client
+Version: VERSION
+Release: 1%{?dist}
+Summary: FreeIPA client
+
+Group: System Environment/Base
+License: GPL
+URL: http://www.freeipa.org
+Source0: %{name}-%{version}.tgz
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
+
+Requires: python python-ldap freeipa-python
+
+%description
+FreeIPA is a server for identity, policy, and audit.
+The client package provide install and configuration scripts for clients.
+
+%prep
+%setup -q
+
+%build
+
+make DESTDIR=%{buildroot}
+
+%install
+rm -rf %{buildroot}
+mkdir -p %{buildroot}%{_sbindir}
+
+make install DESTDIR=%{buildroot}
+
+
+%clean
+rm -rf %{buildroot}
+
+
+%files
+%defattr(-,root,root,-)
+%{_sbindir}/ipa-client-install
+
+%dir %{_usr}/share/ipa
+%{_usr}/share/ipa/*
+
+%changelog
+* Thu Aug 16 2007 Simo Sorce <ssorce@redhat.com> - 0.1.0-1
+- Initial rpm version
+
+
diff --git a/ipa-client/ipa-install/Makefile b/ipa-client/ipa-install/Makefile
new file mode 100644
index 00000000..7b30547e
--- /dev/null
+++ b/ipa-client/ipa-install/Makefile
@@ -0,0 +1,10 @@
+SBINDIR=$(DESTDIR)/usr/sbin
+
+all: ;
+
+install:
+ -mkdir $(SBINDIR)
+ install -m 755 ipa-client-install $(SBINDIR)
+
+clean:
+ rm -f *~ *.pyc
diff --git a/ipa-client/ipa-install/ipa-client-install b/ipa-client/ipa-install/ipa-client-install
new file mode 100644
index 00000000..d50a736b
--- /dev/null
+++ b/ipa-client/ipa-install/ipa-client-install
@@ -0,0 +1,109 @@
+#! /usr/bin/python -E
+# Authors: Simo Sorce <ssorce@redhat.com>
+# Karl MacMillan <kmacmillan@mentalrootkit.com>
+#
+# Copyright (C) 2007 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 only
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+VERSION = "%prog .1"
+
+import sys
+sys.path.append("/usr/share/ipa")
+
+import socket
+import logging
+from optparse import OptionParser
+import ipaclient.ipadiscovery
+from ipaserver.util import run
+
+def parse_options():
+ parser = OptionParser(version=VERSION)
+ parser.add_option("--domain", dest="domain", help="domain name")
+ parser.add_option("--server", dest="server", help="IPA server")
+ parser.add_option("--realm", dest="realm_name", help="realm name")
+ parser.add_option("-d", "--debug", dest="debug", action="store_true",
+ dest="debug", default=False, help="print debugging information")
+ parser.add_option("-u", "--unattended", dest="unattended",
+ help="unattended installation never prompts the user")
+
+ options, args = parser.parse_args()
+
+ return options
+
+def logging_setup(options):
+ # Always log everything (i.e., DEBUG) to the log
+ # file.
+ logging.basicConfig(level=logging.DEBUG,
+ format='%(asctime)s %(levelname)s %(message)s',
+ filename='ipaclient-install.log',
+ filemode='w')
+
+ console = logging.StreamHandler()
+ # If the debug option is set, also log debug messages to the console
+ if options.debug:
+ console.setLevel(logging.DEBUG)
+ else:
+ # Otherwise, log critical and error messages
+ console.setLevel(logging.ERROR)
+ formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
+ console.setFormatter(formatter)
+ logging.getLogger('').addHandler(console)
+
+def main():
+ options = parse_options()
+ logging_setup(options)
+
+ # Create the discovery instance
+ ds = ipaclient.ipadiscovery.IPADiscovery()
+
+ ret = ds.search()
+ if ret == -10:
+ print "Can't get the fully qualified name of this host"
+ print "Please check that the client is properly configured"
+ return ret
+ if ret == -1:
+ logging.debug("Domain not found")
+ if options.domain:
+ dom = options.domain
+ elif options.unattended:
+ return ret
+ else:
+ print "Failed to determine your DNS domain (DNS misconfigured?)"
+ dom = raw_input("Please provide your domain name (ex: example.com):")
+ ret = ds.search(domain=dom)
+ if ret == -2:
+ logging.debug("IPA Server not found")
+ if options.server:
+ srv = options.server
+ elif options.unattended:
+ return ret
+ else:
+ print "Failed to find the IPA Server (DNS misconfigured?)"
+ srv = raw_input("Please provide your server name (ex: ipa.example.com):")
+ ret = ds.search(domain=dom, server=srv)
+ if ret != 0:
+ print "Failed to verify that "+srv+" is an IPA Server, aborting!"
+ return ret
+
+ print "Discovery was successful!"
+ print "Realm: "+ds.getRealmName()
+ print "DNS Domain: "+ds.getDomainName()
+ print "IPA Server: "+ds.getServerName()
+
+ return 0
+
+main()
diff --git a/ipa-client/ipaclient/__init__.py b/ipa-client/ipaclient/__init__.py
new file mode 100644
index 00000000..66a4eb14
--- /dev/null
+++ b/ipa-client/ipaclient/__init__.py
@@ -0,0 +1,23 @@
+#! /usr/bin/python -E
+# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
+# see inline
+#
+# Copyright (C) 2007 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 or later
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+__all__ = ["ipadiscovery"]
+
diff --git a/ipa-client/ipaclient/dnsclient.py b/ipa-client/ipaclient/dnsclient.py
new file mode 100644
index 00000000..bc8a229c
--- /dev/null
+++ b/ipa-client/ipaclient/dnsclient.py
@@ -0,0 +1,445 @@
+#
+# Copyright 2001, 2005 Red Hat, Inc.
+#
+# This 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 2 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, write to the Free Software
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+#
+
+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_SRV = 33
+DNS_T_ANY = 255
+
+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
+
+ 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_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 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
+
+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 []
+
+ rr = DNSResult()
+
+ (rest, label) = dnsParseLabel(rest, results)
+ if label is None:
+ return []
+
+ if len(rest) < rr.qsize():
+ return []
+
+ rr.qunpack(rest)
+
+ rest = rest[rr.qsize():]
+
+ if DEBUG_DNSCLIENT:
+ print "Queried for '%s', class = %d, type = %d." % (label,
+ rr.dns_class, rr.dns_type)
+
+ for i in xrange(header.dns_ancount + header.dns_nscount + header.dns_arcount):
+ (rest, label) = dnsParseLabel(rest, results)
+ if label is None:
+ return []
+
+ rr = DNSResult()
+
+ rr.dns_name = label
+
+ 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_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]
+
+ if not rrlist:
+ 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/ipa-client/ipaclient/ipadiscovery.py b/ipa-client/ipaclient/ipadiscovery.py
new file mode 100644
index 00000000..312c8ba4
--- /dev/null
+++ b/ipa-client/ipaclient/ipadiscovery.py
@@ -0,0 +1,239 @@
+#! /usr/bin/python -E
+# Authors: Simo Sorce <ssorce@redhat.com>
+#
+# Copyright (C) 2007 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# 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; version 2 or later
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+import socket
+import logging
+import dnsclient
+import ldap
+from ldap import LDAPError
+
+class IPADiscovery:
+
+ def __init__(self):
+ self.realm = None
+ self.domain = None
+ self.server = None
+
+ def getServerName(self):
+ return str(self.server)
+
+ def getDomainName(self):
+ return str(self.domain)
+
+ def getRealmName(self):
+ return str(self.realm)
+
+ def search(self, domain = "", server = ""):
+ hostname = ""
+ qname = ""
+ results = []
+ result = []
+ krbret = []
+ ldapret = []
+
+ if not server:
+
+ if not domain: #domain not provided do full DNS discovery
+
+ # get the local host name
+ hostname = socket.getfqdn()
+ if not hostname:
+ return -10 #bad host configuration
+
+ # first, check for an LDAP server for the local domain
+ p = hostname.find(".")
+ if p == -1: #no domain name
+ return -1
+ domain = hostname[p+1:]
+
+ while not self.server:
+ logging.debug("[ipadnssearchldap("+domain+")]")
+ self.server = self.ipadnssearchldap(domain)
+ if self.server:
+ self.domain = domain
+ else:
+ p = domain.find(".")
+ if p == -1: #no ldap server found and last component of the domain already tested
+ return -1
+ domain = domain[p+1:]
+ else:
+ logging.debug("[ipadnssearchldap]")
+ self.server = self.ipadnssearchldap(domain)
+ if self.server:
+ self.domain = domain
+ else:
+ return -2 #no ldap server found
+
+
+ #search for kerberos TODO: move this after ipacheckldap()
+ logging.debug("[ipadnssearchkrb]")
+ krbret = self.ipadnssearchkrb(self.domain)
+ if not krbret:
+ return -3 #no krb server found
+
+ self.realm = krbret[0]
+
+ else: #server forced on us, this means DNS doesn't work :/
+
+ self.domain = domain
+ self.server = server
+
+ logging.debug("[ipacheckldap]")
+ # check ldap now
+ ldapret = self.ipacheckldap(self.server, self.realm);
+
+ if not ldapret:
+ return -4 # not an IPA server (or broken config)
+
+ self.server = ldapret[0]
+ self.realm = ldapret[1]
+
+ return 0
+
+ def ipacheckldap(self, thost, trealm):
+
+ lret = []
+ lres = []
+ lattr = ""
+ linfo = ""
+ lrealms = []
+
+ i = 0
+
+ #now verify the server is really an IPA server
+ try:
+ logging.debug("Init ldap with: ldap://"+thost+":389")
+ lh = ldap.initialize("ldap://"+thost+":389")
+ lh.simple_bind_s("","")
+
+ logging.debug("Search rootdse")
+ lret = lh.search_s("", ldap.SCOPE_BASE, "(objectClass=*)")
+ for lattr in lret[0][1]:
+ if lattr.lower() == "namingcontexts":
+ lbase = lret[0][1][lattr][0]
+
+ logging.debug("Search for (info=*) in "+lbase+"(base)")
+ lret = lh.search_s(lbase, ldap.SCOPE_BASE, "(info=IPA*)")
+ if not lret:
+ return []
+ logging.debug("Found: "+str(lret))
+
+ for lattr in lret[0][1]:
+ if lattr.lower() == "info":
+ linfo = lret[0][1][lattr][0].lower()
+ break
+
+ if not linfo:
+ return []
+
+ #search and return known realms
+ logging.debug("Search for (objectClass=krbRealmContainer) in "+lbase+"(sub)")
+ lret = lh.search_s("cn=kerberos,"+lbase, ldap.SCOPE_SUBTREE, "(objectClass=krbRealmContainer)")
+ if not lret:
+ #something very wrong
+ return []
+ logging.debug("Found: "+str(lret))
+
+ for lres in lret:
+ for lattr in lres[1]:
+ if lattr.lower() == "cn":
+ lrealms.append(lres[1][lattr][0])
+
+
+ if trealm:
+ for r in lrealms:
+ if trealm == r:
+ return [thost, trealm]
+ # must match or something is very wrong
+ return []
+ else:
+ if len(lrealms) != 1:
+ #which one? we can't attach to a multi-realm server without DNS working
+ return []
+ else:
+ return [thost, lrealms[0]]
+
+ #we shouldn't get here
+ return []
+
+ except LDAPError, err:
+ #no good
+ logging.error("Ldap Error: "+str(err))
+ return []
+
+
+ def ipadnssearchldap(self, tdomain):
+ servers = ""
+ rserver = ""
+
+ qname = "_ldap._tcp."+tdomain
+ # terminate the name
+ if not qname.endswith("."):
+ qname += "."
+ results = dnsclient.query(qname, dnsclient.DNS_C_IN, dnsclient.DNS_T_SRV)
+
+ for result in results:
+ if result.dns_type == 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
+
+ def ipadnssearchkrb(self, tdomain):
+ realm = ""
+ kdc = ""
+ # 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 = dnsclient.query(qname, dnsclient.DNS_C_IN, dnsclient.DNS_T_TXT)
+
+ for result in results:
+ if result.dns_type == dnsclient.DNS_T_TXT:
+ realm = result.rdata.data
+ if realm:
+ break
+
+ if realm:
+ # now fetch server information for the realm
+ qname = "_kerberos._udp." + tdomain
+ # terminate the name
+ if not qname.endswith("."):
+ qname += "."
+ results = dnsclient.query(qname, dnsclient.DNS_C_IN, dnsclient.DNS_T_SRV)
+ for result in results:
+ if result.dns_type == 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
+
+ print "["+realm+", "+kdc+"]"
+ return [realm, kdc]