445dd16
From aab181f0a952a56034278b13b1fdc224132d66f4 Mon Sep 17 00:00:00 2001
445dd16
From: Rob Crittenden <rcritten@redhat.com>
445dd16
Date: Tue, 7 Feb 2012 15:26:00 -0500
445dd16
Subject: [PATCH] Add tool to add memberOf to replication attribute exclusion
445dd16
 list
445dd16
445dd16
We were creating replication agreements without excluding memberOf
445dd16
which could cause unnecessary replication traffic.
445dd16
445dd16
https://fedorahosted.org/freeipa/ticket/2218
445dd16
---
445dd16
 install/tools/Makefile.am          |    1 +
445dd16
 install/tools/ipa-fixreplica       |  129 ++++++++++++++++++++++++++++++++++++
445dd16
 install/tools/man/Makefile.am      |    1 +
445dd16
 install/tools/man/ipa-fixreplica.1 |   51 ++++++++++++++
445dd16
 ipaserver/install/replication.py   |   29 +++++----
445dd16
 6 files changed, 203 insertions(+), 12 deletions(-)
445dd16
 create mode 100755 install/tools/ipa-fixreplica
445dd16
 create mode 100644 install/tools/man/ipa-fixreplica.1
445dd16
445dd16
diff --git a/install/tools/Makefile.am b/install/tools/Makefile.am
445dd16
index 7e071af..cf4d7fd 100644
445dd16
--- a/install/tools/Makefile.am
445dd16
+++ b/install/tools/Makefile.am
445dd16
@@ -12,6 +12,7 @@ sbin_SCRIPTS =			\
445dd16
 	ipa-replica-install	\
445dd16
 	ipa-replica-prepare	\
445dd16
 	ipa-replica-manage	\
445dd16
+	ipa-fixreplica		\
445dd16
 	ipa-csreplica-manage	\
445dd16
  	ipa-server-certinstall  \
445dd16
 	ipactl			\
445dd16
diff --git a/install/tools/ipa-fixreplica b/install/tools/ipa-fixreplica
445dd16
new file mode 100755
445dd16
index 0000000..e444853
445dd16
--- /dev/null
445dd16
+++ b/install/tools/ipa-fixreplica
445dd16
@@ -0,0 +1,129 @@
445dd16
+#!/usr/bin/python
445dd16
+# Authors: Rob Crittenden <rcritten@redhat.com>
445dd16
+#
445dd16
+# Copyright (C) 2012  Red Hat
445dd16
+# see file 'COPYING' for use and warranty information
445dd16
+#
445dd16
+# This program is free software; you can redistribute it and/or modify
445dd16
+# it under the terms of the GNU General Public License as published by
445dd16
+# the Free Software Foundation, either version 3 of the License, or
445dd16
+# (at your option) any later version.
445dd16
+#
445dd16
+# This program is distributed in the hope that it will be useful,
445dd16
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
445dd16
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
445dd16
+# GNU General Public License for more details.
445dd16
+#
445dd16
+# You should have received a copy of the GNU General Public License
445dd16
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
445dd16
+#
445dd16
+
445dd16
+import sys
445dd16
+import os
445dd16
+import pwd
445dd16
+try:
445dd16
+    from optparse import OptionParser
445dd16
+    from ipapython import ipautil, config
445dd16
+    from ipaserver import ipaldap
445dd16
+    from ipaserver.install import installutils, replication
445dd16
+    from ipaserver.plugins.ldap2 import ldap2
445dd16
+    from ipalib import api, errors
445dd16
+except ImportError:
445dd16
+    print >> sys.stderr, """\
445dd16
+There was a problem importing one of the required Python modules. The
445dd16
+error was:
445dd16
+
445dd16
+    %s
445dd16
+""" % sys.exc_value
445dd16
+    sys.exit(1)
445dd16
+
445dd16
+def parse_options():
445dd16
+    usage = "%prog [options]\n"
445dd16
+    parser = OptionParser(usage=usage, formatter=config.IPAFormatter())
445dd16
+
445dd16
+    parser.add_option("-t", "--test", action="store_true", dest="test",
445dd16
+                      help="Run in test mode, no changes are applied")
445dd16
+    parser.add_option("-d", "--debug", action="store_true", dest="debug",
445dd16
+                      help="Display debugging information about the update(s)")
445dd16
+    parser.add_option("-y", dest="password",
445dd16
+                      help="File containing the Directory Manager password")
445dd16
+
445dd16
+    options, args = parser.parse_args()
445dd16
+
445dd16
+    return options, args
445dd16
+
445dd16
+def get_dirman_password():
445dd16
+    """Prompt the user for the Directory Manager password and verify its
445dd16
+       correctness.
445dd16
+    """
445dd16
+    password = installutils.read_password("Directory Manager", confirm=False, validate=False)
445dd16
+
445dd16
+    return password
445dd16
+
445dd16
+def main():
445dd16
+    retval = 0
445dd16
+
445dd16
+    options, args = parse_options()
445dd16
+
445dd16
+    if os.getegid() == 0:
445dd16
+     installutils.standard_logging_setup("/var/log/ipaserver-fixreplica.log", options.debug, filemode='a')
445dd16
+
445dd16
+    api.bootstrap(context='cli', debug=options.debug)
445dd16
+    api.finalize()
445dd16
+
445dd16
+    conn = None
445dd16
+    dirman_password = ""
445dd16
+    if os.getegid() == 0:
445dd16
+        conn = ipaldap.IPAdmin(api.env.host, ldapi=True, realm=api.env.realm)
445dd16
+        conn.do_external_bind(pwd.getpwuid(os.geteuid()).pw_name)
445dd16
+    else:
445dd16
+        if options.password:
445dd16
+            pw = ipautil.template_file(options.password, [])
445dd16
+            dirman_password = pw.strip()
445dd16
+        else:
445dd16
+            dirman_password = get_dirman_password()
445dd16
+            if dirman_password is None:
445dd16
+                sys.exit("\nDirectory Manager password required")
445dd16
+
445dd16
+    repl = replication.ReplicationManager(api.env.realm, api.env.host,
445dd16
+                                          dirman_password, conn=conn)
445dd16
+    entries = repl.find_replication_agreements()
445dd16
+    print "Found %d agreement(s)" % len(entries)
445dd16
+    for replica in entries:
445dd16
+        print "%s: " % replica.description
445dd16
+        if 'memberof' not in replica.nsDS5ReplicatedAttributeList:
445dd16
+            print "    Attribute list needs updating"
445dd16
+            current = replica.toDict()
445dd16
+            replica.setValue('nsDS5ReplicatedAttributeList',
445dd16
+                replica.nsDS5ReplicatedAttributeList + ' memberof')
445dd16
+            if not options.test:
445dd16
+                try:
445dd16
+                    repl.conn.updateEntry(replica.dn, current, replica.toDict())
445dd16
+                    print "    Updated"
445dd16
+                except Exception, e:
445dd16
+                    print "Error caught updating replica: %s" % str(e)
445dd16
+                    retval = 1
445dd16
+            else:
445dd16
+                print "    Test mode, not updating"
445dd16
+                retval = 2
445dd16
+        else:
445dd16
+            print "    Attribute list ok"
445dd16
+
445dd16
+    return retval
445dd16
+
445dd16
+try:
445dd16
+    if __name__ == "__main__":
445dd16
+        sys.exit(main())
445dd16
+except RuntimeError, e:
445dd16
+    print "%s" % e
445dd16
+    sys.exit(1)
445dd16
+except SystemExit, e:
445dd16
+    sys.exit(e)
445dd16
+except KeyboardInterrupt, e:
445dd16
+    sys.exit(1)
445dd16
+except config.IPAConfigError, e:
445dd16
+    print "IPA replica not configured."
445dd16
+    sys.exit(0)
445dd16
+except errors.LDAPError, e:
445dd16
+    print "An error occurred while performing operations: %s" % e
445dd16
+    sys.exit(1)
445dd16
diff --git a/install/tools/man/Makefile.am b/install/tools/man/Makefile.am
445dd16
index 43dc57f..fd219f1 100644
445dd16
--- a/install/tools/man/Makefile.am
445dd16
+++ b/install/tools/man/Makefile.am
445dd16
@@ -6,6 +6,7 @@ NULL=
445dd16
445dd16
 man1_MANS = 				\
445dd16
 	ipa-replica-conncheck.1		\
445dd16
+	ipa-fixreplica.1		\
445dd16
 	ipa-replica-install.1		\
445dd16
 	ipa-replica-manage.1		\
445dd16
 	ipa-csreplica-manage.1		\
445dd16
diff --git a/install/tools/man/ipa-fixreplica.1 b/install/tools/man/ipa-fixreplica.1
445dd16
new file mode 100644
445dd16
index 0000000..9af72cc
445dd16
--- /dev/null
445dd16
+++ b/install/tools/man/ipa-fixreplica.1
445dd16
@@ -0,0 +1,51 @@
445dd16
+.\" A man page for ipa-fixreplica
445dd16
+.\" Copyright (C) 2012 Red Hat, Inc.
445dd16
+.\"
445dd16
+.\" This program is free software; you can redistribute it and/or modify
445dd16
+.\" it under the terms of the GNU General Public License as published by
445dd16
+.\" the Free Software Foundation, either version 3 of the License, or
445dd16
+.\" (at your option) any later version.
445dd16
+.\"
445dd16
+.\" This program is distributed in the hope that it will be useful, but
445dd16
+.\" WITHOUT ANY WARRANTY; without even the implied warranty of
445dd16
+.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
445dd16
+.\" General Public License for more details.
445dd16
+.\"
445dd16
+.\" You should have received a copy of the GNU General Public License
445dd16
+.\" along with this program.  If not, see <http://www.gnu.org/licenses/>.
445dd16
+.\"
445dd16
+.\" Author: Rob Crittenden <rcritten@redhat.com>
445dd16
+.\"
445dd16
+.TH "ipa-fixreplica" "1" "Jan 6 2012" "FreeIPA" "FreeIPA Manual Pages"
445dd16
+.SH "NAME"
445dd16
+ipa\-fixreplica \- Add memberOf as excluded attribute to replication agreements
445dd16
+.SH "SYNOPSIS"
445dd16
+ipa\-fixreplica [options]
445dd16
+.SH "DESCRIPTION"
445dd16
+The memberOf attribute was not properly added to some replication agreements
445dd16
+causing unnecessary replication. This tool will update all replication
445dd16
+agreements for the current host and add memberOf to the exclusion list.
445dd16
+
445dd16
+When executed as root no password is required, otherwise it will prompt
445dd16
+for the Directory Manager password.
445dd16
+
445dd16
+This is executed when the package is updated in rpm so should not need
445dd16
+to be run by an end-user. It is not harmful to run multiple times.
445dd16
+
445dd16
+This needs to be run on all replicas to be truly effective.
445dd16
+.SH "OPTIONS"
445dd16
+.TP
445dd16
+\fB\-d\fR, \fB\-\-debug\fR
445dd16
+Enable debug logging when more verbose output is needed
445dd16
+.TP
445dd16
+\fB\-t\fR, \fB\-\-test\fR
445dd16
+Run in test mode, no changes are applied
445dd16
+.TP
445dd16
+\fB\-y\fR \fIfile\fR
445dd16
+File containing the Directory Manager password
445dd16
+.SH "EXIT STATUS"
445dd16
+0 if the command was successful or IPA is not configured
445dd16
+
445dd16
+1 if an error occurred
445dd16
+
445dd16
+2 If during test mode it determines that there are agreements that need updating
445dd16
diff --git a/ipaserver/install/replication.py b/ipaserver/install/replication.py
445dd16
index 8c8502e..79c0fe7 100644
445dd16
--- a/ipaserver/install/replication.py
445dd16
+++ b/ipaserver/install/replication.py
445dd16
@@ -99,7 +99,7 @@ def enable_replication_version_checking(hostname, realm, dirman_passwd):
445dd16
 class ReplicationManager(object):
445dd16
     """Manage replication agreements between DS servers, and sync
445dd16
     agreements with Windows servers"""
445dd16
-    def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False):
445dd16
+    def __init__(self, realm, hostname, dirman_passwd, port=PORT, starttls=False, conn=None):
445dd16
         self.hostname = hostname
445dd16
         self.port = port
445dd16
         self.dirman_passwd = dirman_passwd
445dd16
@@ -108,18 +108,23 @@ class ReplicationManager(object):
445dd16
         tmp = util.realm_to_suffix(realm)
445dd16
         self.suffix = str(DN(tmp)).lower()
445dd16
445dd16
-        # If we are passed a password we'll use it as the DM password
445dd16
-        # otherwise we'll do a GSSAPI bind.
445dd16
-        if starttls:
445dd16
-            self.conn = ipaldap.IPAdmin(hostname, port=port)
445dd16
-            ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, CACERT)
445dd16
-            self.conn.start_tls_s()
445dd16
-        else:
445dd16
-            self.conn = ipaldap.IPAdmin(hostname, port=port, cacert=CACERT)
445dd16
-        if dirman_passwd:
445dd16
-            self.conn.do_simple_bind(bindpw=dirman_passwd)
445dd16
+        # The caller is allowed to pass in an existing IPAdmin connection.
445dd16
+        # Open a new one if not provided
445dd16
+        if conn is None:
445dd16
+            # If we are passed a password we'll use it as the DM password
445dd16
+            # otherwise we'll do a GSSAPI bind.
445dd16
+            if starttls:
445dd16
+                self.conn = ipaldap.IPAdmin(hostname, port=port)
445dd16
+                ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, CACERT)
445dd16
+                self.conn.start_tls_s()
445dd16
+            else:
445dd16
+                self.conn = ipaldap.IPAdmin(hostname, port=port, cacert=CACERT)
445dd16
+            if dirman_passwd:
445dd16
+                self.conn.do_simple_bind(bindpw=dirman_passwd)
445dd16
+            else:
445dd16
+                self.conn.do_sasl_gssapi_bind()
445dd16
         else:
445dd16
-            self.conn.do_sasl_gssapi_bind()
445dd16
+            self.conn = conn
445dd16
445dd16
         self.repl_man_passwd = dirman_passwd
445dd16
445dd16
--
445dd16
1.7.6