[freeipa/f16] - Force to use 389-ds 1.2.10-0.8.a7 or above - Improve upgrade script to handle systemd 389-ds chang

abbra abbra at fedoraproject.org
Wed Feb 1 19:24:16 UTC 2012


commit 29ff9b554f1f5caf87c97208fc270b6639269ec7
Author: Alexander Bokovoy <abokovoy at redhat.com>
Date:   Wed Feb 1 21:22:18 2012 +0200

    - Force to use 389-ds 1.2.10-0.8.a7 or above
    - Improve upgrade script to handle systemd 389-ds change
      - fixes FreeIPA tickets 2117 and 2300
    - Fix freeipa to work with python-ldap 2.4.6

 freeipa-2.1.4-inifiles-support.patch          |  139 ++++++++++++++++
 freeipa-2.1.4-python-ldap-2.4.6-support.patch |   26 +++
 freeipa-2.1.4-upgrade-systemd.patch           |  214 +++++++++++++++++++++++++
 freeipa-systemd-upgrade                       |   73 ---------
 freeipa.spec                                  |   22 ++-
 5 files changed, 395 insertions(+), 79 deletions(-)
---
diff --git a/freeipa-2.1.4-inifiles-support.patch b/freeipa-2.1.4-inifiles-support.patch
new file mode 100644
index 0000000..c4f4154
--- /dev/null
+++ b/freeipa-2.1.4-inifiles-support.patch
@@ -0,0 +1,139 @@
+From 16d3d30130215d74295e89ba5a51522eed45e180 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy at redhat.com>
+Date: Wed, 1 Feb 2012 14:20:53 +0200
+Subject: [PATCH 1/3] Add management of inifiles to allow manipulation of
+ systemd units
+
+inifile_replace_variables() works similar to config_replace_variables() but
+allows to apply changes to specific section of an inifile. Inifiles are
+commonly used by freedesktop.org software and particularly used by systemd.
+
+When modifying inifile, all changes will be applied to specific section.
+
+Also fixes corner case in config_replace_variables() which would dublicate
+variables when adding them.
+---
+ ipapython/ipautil.py |  100 +++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 files changed, 99 insertions(+), 1 deletions(-)
+
+diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
+index 718f209b32649df23177dcab7d5105d01c0cd7bc..e141e00171cb86bec58a6be0b3e7d1f51a24faf1 100644
+--- a/ipapython/ipautil.py
++++ b/ipapython/ipautil.py
+@@ -1245,7 +1245,7 @@ $)''', re.VERBOSE)
+         new_vars = replacevars.copy()
+         new_vars.update(appendvars)
+         newvars_view = set(new_vars.keys()) - set(old_values.keys())
+-        append_view = (set(appendvars.keys()) - set(replacevars.keys())) - set(old_values.keys())
++        append_view = (set(appendvars.keys()) - newvars_view)
+         for item in newvars_view:
+             new_config.write("%s=%s\n" % (item,new_vars[item]))
+         for item in append_view:
+@@ -1262,6 +1262,104 @@ $)''', re.VERBOSE)
+ 
+     return old_values
+ 
++def inifile_replace_variables(filepath, section, replacevars=dict(), appendvars=dict()):
++    """
++    Take a section-structured key=value based configuration file, and write new version
++    with certain values replaced or appended within the section
++
++    All (key,value) pairs from replacevars and appendvars that were not found
++    in the configuration file, will be added there.
++
++    It is responsibility of a caller to ensure that replacevars and
++    appendvars do not overlap.
++
++    It is responsibility of a caller to back up file.
++
++    returns dictionary of affected keys and their previous values
++
++    One have to run restore_context(filepath) afterwards or
++    security context of the file will not be correct after modification
++    """
++    pattern = re.compile('''
++(^
++                        \[
++        (?P<section>    .+) \]
++                        (\s+((\#|;).*)?)?
++$)|(^
++                        \s*
++        (?P<option>     [^\#;]+?)
++                        (\s*=\s*)
++        (?P<value>      .+?)?
++                        (\s*((\#|;).*)?)?
++$)''', re.VERBOSE)
++    def add_options(config, replacevars, appendvars, oldvars):
++        # add all options from replacevars and appendvars that were not found in the file
++        new_vars = replacevars.copy()
++        new_vars.update(appendvars)
++        newvars_view = set(new_vars.keys()) - set(oldvars.keys())
++        append_view = (set(appendvars.keys()) - newvars_view)
++        for item in newvars_view:
++            config.write("%s=%s\n" % (item,new_vars[item]))
++        for item in append_view:
++            config.write("%s=%s\n" % (item,appendvars[item]))
++
++    orig_stat = os.stat(filepath)
++    old_values = dict()
++    temp_filename = None
++    with tempfile.NamedTemporaryFile(delete=False) as new_config:
++        temp_filename = new_config.name
++        with open(filepath, 'r') as f:
++            in_section = False
++            finished = False
++            line_idx = 1
++            for line in f:
++                line_idx = line_idx + 1
++                new_line = line
++                m = pattern.match(line)
++                if m:
++                    sect, option, value = m.group('section', 'option', 'value')
++                    if in_section and sect is not None:
++                        # End of the searched section, add remaining options
++                        add_options(new_config, replacevars, appendvars, old_values)
++                        finished = True
++                    if sect is not None:
++                        # New section is found, check whether it is the one we are looking for
++                        in_section = (str(sect).lower() == str(section).lower())
++                    if option is not None and in_section:
++                        # Great, this is an option from the section we are loking for
++                        if replacevars and option in replacevars:
++                            # replace value completely
++                            new_line = u"%s=%s\n" % (option, replacevars[option])
++                            old_values[option] = value
++                        if appendvars and option in appendvars:
++                            # append a new value unless it is already existing in the original one
++                            if not value:
++                                new_line = u"%s=%s\n" % (option, appendvars[option])
++                            elif value.find(appendvars[option]) == -1:
++                                new_line = u"%s=%s %s\n" % (option, value, appendvars[option])
++                            old_values[option] = value
++                    new_config.write(new_line)
++            # We have finished parsing the original file.
++            # There are two remaining cases:
++            # 1. Section we were looking for was not found, we need to add it.
++            if not (in_section or finished):
++                new_config.write("[%s]\n" % (section))
++            # 2. The section is the last one but some options were not found, add them.
++            if in_section or not finished:
++                add_options(new_config, replacevars, appendvars, old_values)
++
++        new_config.flush()
++        # Make sure the resulting file is readable by others before installing it
++        os.fchmod(new_config.fileno(), orig_stat.st_mode)
++        os.fchown(new_config.fileno(), orig_stat.st_uid, orig_stat.st_gid)
++
++    # At this point new_config is closed but not removed due to 'delete=False' above
++    # Now, install the temporary file as configuration and ensure old version is available as .orig
++    # While .orig file is not used during uninstall, it is left there for administrator.
++    install_file(temp_filename, filepath)
++
++    return old_values
++
+ def backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), appendvars=dict()):
+     """
+     Take a key=value based configuration file, back up it, and
+-- 
+1.7.8.3
+
diff --git a/freeipa-2.1.4-python-ldap-2.4.6-support.patch b/freeipa-2.1.4-python-ldap-2.4.6-support.patch
new file mode 100644
index 0000000..84e1cd1
--- /dev/null
+++ b/freeipa-2.1.4-python-ldap-2.4.6-support.patch
@@ -0,0 +1,26 @@
+From a639ff31c65b6fabfa916e0ea9256fad9e90d3cf Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy at redhat.com>
+Date: Wed, 1 Feb 2012 14:25:46 +0200
+Subject: [PATCH 2/3] Adopt to python-ldap 2.4.6 by removing unused references
+ which are not available in python-ldap anymore
+
+---
+ ipaserver/ipaldap.py |    2 +-
+ 1 files changed, 1 insertions(+), 1 deletions(-)
+
+diff --git a/ipaserver/ipaldap.py b/ipaserver/ipaldap.py
+index 1820e690b10c820efcd3217801bde6b685bbf20b..89c031290acb5c041e0fa5e9412bbc85eb0288ec 100644
+--- a/ipaserver/ipaldap.py
++++ b/ipaserver/ipaldap.py
+@@ -31,7 +31,7 @@ import time
+ import struct
+ import ldap.sasl
+ import ldapurl
+-from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
++from ldap.controls import LDAPControl
+ from ldap.ldapobject import SimpleLDAPObject
+ from ipaserver import ipautil
+ from ipaserver.install import installutils
+-- 
+1.7.8.3
+
diff --git a/freeipa-2.1.4-upgrade-systemd.patch b/freeipa-2.1.4-upgrade-systemd.patch
new file mode 100644
index 0000000..0eb004e
--- /dev/null
+++ b/freeipa-2.1.4-upgrade-systemd.patch
@@ -0,0 +1,214 @@
+From a9c0a0bc8d3fcf27bb16a92002d944c2a71f7ce7 Mon Sep 17 00:00:00 2001
+From: Alexander Bokovoy <abokovoy at redhat.com>
+Date: Wed, 1 Feb 2012 17:51:24 +0200
+Subject: [PATCH 3/3] Handle upgrade issues with systemd in Fedora 16 and
+ above
+
+Since 389-ds-base-1.2.10-0.8.a7 Directory Server's systemd settings are
+configured via /etc/sysconfig/dirsrv.systemd. It means logic change in
+systemd/fedora16 platform of FreeIPA.
+
+Additionally, existing installs need to be handled during upgrade.
+
+Fixes:
+    https://fedorahosted.org/freeipa/ticket/2117
+    https://fedorahosted.org/freeipa/ticket/2300
+---
+ init/systemd/freeipa-systemd-upgrade |   96 ++++++++++++++++++++++++++++++++++
+ ipapython/platform/fedora16.py       |   22 ++++----
+ ipapython/platform/systemd.py        |   16 ++----
+ 3 files changed, 113 insertions(+), 21 deletions(-)
+ create mode 100755 init/systemd/freeipa-systemd-upgrade
+
+diff --git a/init/systemd/freeipa-systemd-upgrade b/init/systemd/freeipa-systemd-upgrade
+new file mode 100755
+index 0000000000000000000000000000000000000000..572d69df64b335e1a06b358fc9a0f2132807d6a6
+--- /dev/null
++++ b/init/systemd/freeipa-systemd-upgrade
+@@ -0,0 +1,96 @@
++#! /usr/bin/python -E
++from ipaserver.install.krbinstance import update_key_val_in_file
++from ipapython import ipautil, config
++from ipapython import services as ipaservices
++import os, platform
++
++def convert_java_link(foo, topdir, filepaths):
++    cwd = os.getcwd()
++    os.chdir(topdir)
++    for filepath in filepaths:
++        # All this shouldn't happen because java system upgrade should properly
++        # move files and symlinks but if this is a broken link
++        if os.path.islink(filepath):
++            print "    Checking %s ... " % (filepath),
++            if not os.path.exists(filepath):
++                rpath = os.path.realpath(filepath)
++                # .. and it points to jss in /usr/lib
++                if rpath.find('/usr/lib/') != -1  and rpath.find('jss') != -1:
++                    base = os.path.basename(rpath)
++                    bitness = platform.architecture()[0][:2]
++                    # rewrite it to /usr/lib64 for x86_64 platform
++                    if bitness == '64':
++                        npath = "/usr/lib%s/jss/%s" % (bitness, base)
++                        os.unlink(filepath)
++                        os.symlink(npath, filepath)
++                        print "%s -> %s" % (filepath, npath)
++                    else:
++                        print "Ok"
++                else:
++                    print "Ok"
++            else:
++                print "Ok"
++    os.chdir(cwd)
++
++# 0. Init config
++try:
++    config.init_config()
++except IPAConfigError, e:
++    # No configured IPA install, no need to upgrade anything
++    exit(0)
++
++# 1. Convert broken symlinks, if any, in /var/lib/pki-ca
++if os.path.exists('/var/lib/pki-ca/common/lib'):
++    print "Analyzing symlinks in PKI-CA install"
++    os.path.walk('/var/lib/pki-ca/common/lib', convert_java_link, None)
++
++try:
++    print "Found IPA server for domain %s" % (config.config.default_realm)
++    # 1. Make sure Dogtag instance (if exists) has proper OIDs for IPA CA
++    ipa_ca_cfg = "/var/lib/pki-ca/profiles/ca/caIPAserviceCert.cfg"
++    if os.path.exists(ipa_ca_cfg):
++        print "Make sure PKI-CA has Extended Key Usage OIDs for the certificates (Server and Client Authentication)",
++        key = 'policyset.serverCertSet.7.default.params.exKeyUsageOIDs'
++        value = '1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.2'
++        replacevars = {key:value}
++        appendvars = {}
++        old_values = ipautil.config_replace_variables(ipa_ca_cfg, replacevars=replacevars, appendvars=appendvars)
++        ipaservices.restore_context(ipa_ca_cfg)
++        if key in old_values and old_values[key] != value:
++            print
++            print "    WARNING: Previously issued certificate didn't have both Server and Client Authentication usage"
++            print "             Old usage OID(s): %(oids)s" % (old_values[key])
++            print "    Please make sure to revoke old certificates and re-issue them again to add both usages when needed"
++            ipaservices.service('pki-cad').restart()
++        else:
++            print "... ok"
++    print "Converting services setup to systemd"
++    # 2. Upgrade /etc/sysconfig/dirsrv for systemd
++    print "    Upgrade /etc/sysconfig/dirsrv"
++    update_key_val_in_file("/etc/sysconfig/dirsrv", "KRB5_KTNAME", "/etc/dirsrv/ds.keytab")
++    update_key_val_in_file("/etc/sysconfig/dirsrv", "export KRB5_KTNAME", "/etc/dirsrv/ds.keytab")
++    # 3. Upgrade /etc/sysconfig/krb5kdc for systemd
++    print "    Upgrade /etc/sysconfig/krb5kdc"
++    replacevars = {'KRB5REALM':config.config.default_realm}
++    appendvars = {}
++    ipautil.config_replace_variables("/etc/sysconfig/krb5kdc",
++       replacevars=replacevars, appendvars=appendvars)
++    ipaservices.restore_context("/etc/sysconfig/krb5kdc")
++    # 4. Enable DS instances:
++    # when enabling DS instances we'll also do configure /etc/sysconfig/dirsrv.systemd
++    # which comes with 389-ds-base-1.2.10-0.8.a7 on F-16 and later. This is handled in
++    # fedora16 platform code
++    realm = config.config.default_realm.upper().replace('.','-')
++    print "    Re-enable Directory server instances PKI-IPA and %s " % (realm)
++    if os.path.exists('/etc/systemd/system/dirsrv at .service'):
++        os.unlink('/etc/systemd/system/dirsrv at .service')
++    ipaservices.knownservices.dirsrv.enable(realm)
++    ipaservices.knownservices.dirsrv.enable("PKI-IPA")
++    # 4. Enable FreeIPA
++    print "    Re-enable IPA service"
++    ipaservices.knownservices.ipa.enable()
++except:
++    pass
++
++finally:
++    print "Finished."
+diff --git a/ipapython/platform/fedora16.py b/ipapython/platform/fedora16.py
+index 0e476928e45be69e4aa09c5183070924a00b1269..369a1778b512fea6119e8e0f600ffda26739eb30 100644
+--- a/ipapython/platform/fedora16.py
++++ b/ipapython/platform/fedora16.py
+@@ -59,24 +59,24 @@ class Fedora16Service(systemd.SystemdService):
+         super(Fedora16Service, self).__init__(service_name)
+ 
+ # Special handling of directory server service
+-# LimitNOFILE needs to be increased or any value set in the directory for this value will fail
+-# Read /lib/systemd/system/dirsrv at .service for details.
+-# We do modification of LimitNOFILE on service.enable() but we also need to explicitly enable instances
+-# to install proper symlinks as dirsrv.target.wants/ dependencies. Unfortunately, ipa-server-install
+-# does not do explicit dirsrv.enable() because the service startup is handled by ipactl.
++#
++# We need to explicitly enable instances to install proper symlinks as dirsrv.target.wants/
++# dependencies. Standard systemd service class does it on #enable() method call. Unfortunately,
++# ipa-server-install does not do explicit dirsrv.enable() because the service startup is handled by ipactl.
++#
+ # If we wouldn't do this, our instances will not be started as systemd would not have any clue
+ # about instances (PKI-IPA and the domain we serve) at all. Thus, hook into dirsrv.restart().
+ class Fedora16DirectoryService(Fedora16Service):
+     def enable(self, instance_name=""):
+         super(Fedora16DirectoryService, self).enable(instance_name)
+-        srv_etc = os.path.join(self.SYSTEMD_ETC_PATH, self.service_name)
+-        if os.path.exists(srv_etc):
++        dirsrv_systemd = "/etc/sysconfig/dirsrv.systemd"
++        if os.path.exists(dirsrv_systemd):
+             # We need to enable LimitNOFILE=8192 in the dirsrv at .service
+-            # We rely on the fact that [Service] section is the last one
+-            # and if variable is not there, it will be added as the last line
++            # Since 389-ds-base-1.2.10-0.8.a7 the configuration of the service parameters is performed
++            # via /etc/sysconfig/dirsrv.systemd file which is imported by systemd into dirsrv at .service unit
+             replacevars = {'LimitNOFILE':'8192'}
+-            ipautil.config_replace_variables(srv_etc, replacevars=replacevars)
+-            redhat.restore_context(srv_etc)
++            ipautil.inifile_replace_variables(dirsrv_systemd, 'service', replacevars=replacevars)
++            redhat.restore_context(dirsrv_systemd)
+             ipautil.run(["/bin/systemctl", "--system", "daemon-reload"],raiseonerr=False)
+ 
+     def restart(self, instance_name="", capture_output=True):
+diff --git a/ipapython/platform/systemd.py b/ipapython/platform/systemd.py
+index 3f1fe730ebab4c0636f8c9d8d83d956da307b92b..ae06c0227aa59a46b2d4df024fc87577b8bbab29 100644
+--- a/ipapython/platform/systemd.py
++++ b/ipapython/platform/systemd.py
+@@ -137,16 +137,12 @@ class SystemdService(base.PlatformService):
+ 
+         if len(instance_name) > 0 and l > 1:
+             # New instance, we need to do following:
+-            # 1. Copy <service>@.service to /etc/systemd/system/ if it is not there
+-            # 2. Make /etc/systemd/system/<service>.target.wants/ if it is not there
+-            # 3. Link /etc/systemd/system/<service>.target.wants/<service>@<instance_name>.service to
+-            #    /etc/systemd/system/<service>@.service
+-            srv_etc = os.path.join(self.SYSTEMD_ETC_PATH, self.service_name)
++            # 1. Make /etc/systemd/system/<service>.target.wants/ if it is not there
++            # 2. Link /etc/systemd/system/<service>.target.wants/<service>@<instance_name>.service to
++            #    /lib/systemd/system/<service>@.service
+             srv_tgt = os.path.join(self.SYSTEMD_ETC_PATH, self.SYSTEMD_SRV_TARGET % (elements[0]))
+             srv_lnk = os.path.join(srv_tgt, self.service_instance(instance_name))
+             try:
+-                if not ipautil.file_exists(srv_etc):
+-                    shutil.copy(self.lib_path, srv_etc)
+                 if not ipautil.dir_exists(srv_tgt):
+                     os.mkdir(srv_tgt)
+                 if os.path.exists(srv_lnk):
+@@ -156,11 +152,11 @@ class SystemdService(base.PlatformService):
+                     # object does not exist _or_ is a broken link
+                     if not os.path.islink(srv_lnk):
+                         # if it truly does not exist, make a link
+-                        os.symlink(srv_etc, srv_lnk)
++                        os.symlink(self.lib_path, srv_lnk)
+                     else:
+                         # Link exists and it is broken, make new one
+                         os.unlink(srv_lnk)
+-                        os.symlink(srv_etc, srv_lnk)
++                        os.symlink(self.lib_path, srv_lnk)
+                 ipautil.run(["/bin/systemctl", "--system", "daemon-reload"])
+             except:
+                 pass
+@@ -172,7 +168,7 @@ class SystemdService(base.PlatformService):
+         if instance_name != "" and len(elements) > 1:
+             # Remove instance, we need to do following:
+             #  Remove link from /etc/systemd/system/<service>.target.wants/<service>@<instance_name>.service
+-            #  to /etc/systemd/system/<service>@.service
++            #  to /lib/systemd/system/<service>@.service
+             srv_tgt = os.path.join(self.SYSTEMD_ETC_PATH, self.SYSTEMD_SRV_TARGET % (elements[0]))
+             srv_lnk = os.path.join(srv_tgt, self.service_instance(instance_name))
+             try:
+-- 
+1.7.8.3
+
diff --git a/freeipa.spec b/freeipa.spec
index 04d7a82..7f4c417 100644
--- a/freeipa.spec
+++ b/freeipa.spec
@@ -14,14 +14,13 @@ distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
 
 Name:           freeipa
 Version:        2.1.4
-Release:        4%{?dist}
+Release:        5%{?dist}
 Summary:        The Identity, Policy and Audit system
 
 Group:          System Environment/Base
 License:        GPLv3+
 URL:            http://www.freeipa.org/
 Source0:        freeipa-%{version}.tar.gz
-Source1:        freeipa-systemd-upgrade
 Patch0:         freeipa-2.1.4-connection-failure-recovery.patch
 Patch1:         freeipa-2.1.4-fix-pylint-f16.patch
 Patch2:         freeipa-2.1.4-slapi-plugins-use-thread-safe-ldap-library.patch
@@ -29,6 +28,9 @@ Patch3:         freeipa-2.1.4-selinux-web-migration-policy.patch
 Patch4:         freeipa-2.1.4-logging.patch
 Patch5:         freeipa-2.1.4-replication-addentry.patch
 Patch6:         freeipa-2.1.4-replica-install-services.patch
+Patch7:         freeipa-2.1.4-inifiles-support.patch
+Patch8:         freeipa-2.1.4-python-ldap-2.4.6-support.patch
+Patch9:         freeipa-2.1.4-upgrade-systemd.patch
 BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
 
 %if ! %{ONLY_CLIENT}
@@ -80,7 +82,7 @@ Requires: %{name}-python = %{version}-%{release}
 Requires: %{name}-client = %{version}-%{release}
 Requires: %{name}-admintools = %{version}-%{release}
 Requires: %{name}-server-selinux = %{version}-%{release}
-Requires(pre): 389-ds-base >= 1.2.10-0.6.a6
+Requires(pre): 389-ds-base >= 1.2.10-0.8.a7
 Requires: openldap-clients
 Requires: nss
 Requires: nss-tools
@@ -222,7 +224,6 @@ package.
 
 %prep
 %setup -n freeipa-%{version} -q
-cp %{SOURCE1} init/systemd/
 %patch0 -p1
 %patch1 -p1
 %patch2 -p1
@@ -230,6 +231,9 @@ cp %{SOURCE1} init/systemd/
 %patch4 -p1
 %patch5 -p1
 %patch6 -p1
+%patch7 -p1
+%patch8 -p1
+%patch9 -p1
 
 %build
 export CFLAGS="$CFLAGS %{optflags}"
@@ -333,8 +337,9 @@ if [ $1 -gt 1 ] ; then
     # When upgrade is performed from SysV to systemd, ipa.service will be inactive
     # due to https://bugzilla.redhat.com/show_bug.cgi?id=752846
     # FreeIPA existing setup cannot be used without upgrade script
-    /bin/systemctl --quiet is-active ipa.service >/dev/null || \
-        /usr/libexec/freeipa-systemd-upgrade || :
+    # Note also it is now safe to run this script against working FreeIPA install
+    # after it has been migrated to systemd setup
+    /usr/libexec/freeipa-systemd-upgrade || :
     /usr/sbin/ipa-upgradeconfig || :
     /usr/sbin/ipa-ldap-updater --upgrade >/dev/null 2>&1 || :
 fi
@@ -551,6 +556,11 @@ fi
 %ghost %attr(0644,root,apache) %config(noreplace) %{_sysconfdir}/ipa/default.conf
 
 %changelog
+* Wed Feb 01 2012 Alexander Bokovoy <abokovoy at redhat.com> - 2.1.4-5
+- Force to use 389-ds 1.2.10-0.8.a7 or above
+- Improve upgrade script to handle systemd 389-ds change
+- Fix freeipa to work with python-ldap 2.4.6
+
 * Wed Jan 11 2012 Martin Kosek <mkosek at redhat.com> - 2.1.4-4
 - Fix ipa-replica-install crashes
 - Fix ipa-server-install and ipa-dns-install logging


More information about the scm-commits mailing list