[Freeipa-devel] [PATCH] Initial replication setup

Karl MacMillan kmacmill at redhat.com
Tue Nov 20 16:34:56 UTC 2007


# HG changeset patch
# User "Karl MacMillan <kmacmill at redhat.com>"
# Date 1195576491 18000
# Node ID c3d5826785f3be0354bee8d7c977c7103bc4b38a
# Parent  8ac4557bef2bcee046498f5e4088169130598148
Initial replication setup.

This add replication setup through two new commands: ipa-replica-prepare
and ipa-replica-install. The procedure is to run ipa-replica-prepare
on an existing master. This will collect information about the realm
and the current master and create a file storing all of the information.
After copying that file to the new replica, ipa-replica-install is
run (with -r to create a read-only replica).

This is the initial patch - remaining features:
- ssl for replication.
- automatic configuration of mesh topology for
  master (or a simpler way to replicate multiple
  masters.
- tool for view / configuring current replication.

diff -r 8ac4557bef2b -r c3d5826785f3 ipa-python/ipautil.py
--- a/ipa-python/ipautil.py	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-python/ipautil.py	Tue Nov 20 11:34:51 2007 -0500
@@ -25,6 +25,7 @@ import subprocess
 import subprocess
 import os
 import stat
+import socket
 
 from string import lower
 import re
@@ -35,7 +36,6 @@ def realm_to_suffix(realm_name):
     s = realm_name.split(".")
     terms = ["dc=" + x.lower() for x in s]
     return ",".join(terms)
-
 
 def template_str(txt, vars):
     return string.Template(txt).substitute(vars)
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/freeipa-server.spec
--- a/ipa-server/freeipa-server.spec	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/freeipa-server.spec	Tue Nov 20 11:34:51 2007 -0500
@@ -71,6 +71,8 @@ rm -rf %{buildroot}
 %files
 %defattr(-,root,root,-)
 %{_sbindir}/ipa-server-install
+%{_sbindir}/ipa-replica-install
+%{_sbindir}/ipa-replica-prepare
 %{_sbindir}/ipa_kpasswd
 %{_sbindir}/ipa-webgui
 %attr(755,root,root) %{_initrddir}/ipa-kpasswd
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/freeipa-server.spec.in
--- a/ipa-server/freeipa-server.spec.in	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/freeipa-server.spec.in	Tue Nov 20 11:34:51 2007 -0500
@@ -71,6 +71,8 @@ rm -rf %{buildroot}
 %files
 %defattr(-,root,root,-)
 %{_sbindir}/ipa-server-install
+%{_sbindir}/ipa-replica-install
+%{_sbindir}/ipa-replica-prepare
 %{_sbindir}/ipa_kpasswd
 %{_sbindir}/ipa-webgui
 %attr(755,root,root) %{_initrddir}/ipa-kpasswd
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipa-install/Makefile.am
--- a/ipa-server/ipa-install/Makefile.am	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipa-install/Makefile.am	Tue Nov 20 11:34:51 2007 -0500
@@ -6,6 +6,8 @@ SUBDIRS =			\
 
 sbin_SCRIPTS =			\
 	ipa-server-install	\
+	ipa-replica-install	\
+	ipa-replica-prepare	\
 	$(NULL)
 
 appdir = $(IPA_DATA_DIR)
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipa-install/ipa-replica-install
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-server/ipa-install/ipa-replica-install	Tue Nov 20 11:34:51 2007 -0500
@@ -0,0 +1,137 @@
+#! /usr/bin/python -E
+# Authors: Karl MacMillan <kmacmillan at 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 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 sys
+sys.path.append("/usr/share/ipa")
+
+import tempfile
+from ConfigParser import SafeConfigParser
+
+from ipa import ipautil
+
+from ipaserver import dsinstance, replication, installutils, krbinstance, service
+from ipaserver import httpinstance, webguiinstance, radiusinstance, ntpinstance
+
+class ReplicaConfig:
+    def __init__(self):
+        self.realm_name = ""
+        self.master_host_name = ""
+        self.dirman_password = ""
+        self.ds_user = ""
+        self.host_name = ""
+        self.repl_password = ""
+        self.dir = ""
+
+def parse_options():
+    from optparse import OptionParser
+    parser = OptionParser()
+    parser.add_option("-r", "--read-only", dest="master", action="store_false",
+                      default=True, help="create read-only replica - default is master")
+
+    options, args = parser.parse_args()
+
+    if len(args) != 1:
+        parser.error("you must provide a file generated by ipa-replica-prepare")
+
+    return options, args[0]
+
+def expand_info(filename):
+    top_dir = tempfile.mkdtemp("ipa")
+    dir = top_dir + "/realm_info"
+    ipautil.run(["tar", "xfz", filename, "-C", top_dir])
+
+    return top_dir, dir
+
+def read_info(dir, rconfig):
+    filename = dir + "/realm_info"
+    fd = open(filename)
+    config = SafeConfigParser()
+    config.readfp(fd)
+
+    rconfig.realm_name = config.get("realm", "realm_name")
+    rconfig.master_host_name = config.get("realm", "master_host_name")
+    rconfig.dirman_password = config.get("realm", "dirman_password")
+    rconfig.ds_user = config.get("realm", "ds_user")
+
+def get_host_name():
+    hostname = installutils.get_fqdn()
+    try:
+        installutils.verify_fqdn(hostname)
+    except RuntimeError, e:
+        logging.error(str(e))
+        sys.exit(1)
+
+    return hostname
+
+def install_ds(config):
+    dsinstance.check_existing_installation()
+    dsinstance.check_ports()
+
+    ds = dsinstance.DsInstance()
+    ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.dirman_password)
+
+def install_krb(config):
+    krb = krbinstance.KrbInstance()
+    ldappwd_filename = config.dir + "/ldappwd"
+    krb.create_replica(config.ds_user, config.realm_name, config.host_name,
+                       config.dirman_password, ldappwd_filename)
+
+def install_http(config):
+    http = httpinstance.HTTPInstance()
+    http.create_instance(config.realm_name, config.host_name)
+
+def main():
+    options, filename = parse_options()
+    top_dir, dir = expand_info(filename)
+    
+    config = ReplicaConfig()
+    read_info(dir, config)
+    config.host_name = get_host_name()
+    config.repl_password = "box"
+    config.dir = dir
+
+    install_ds(config)
+    
+    repl = replication.ReplicationManager(config.host_name, config.dirman_password)
+    repl.setup_replication(config.master_host_name, config.realm_name, options.master)
+
+    install_krb(config)
+    install_http(config)
+    
+    # Create a Web Gui instance
+    webgui = webguiinstance.WebGuiInstance()
+    webgui.create_instance()
+
+    # Create a radius instance
+    radius = radiusinstance.RadiusInstance()
+    # FIXME: ldap_server should be derived, not hardcoded to localhost, also should it be a URL?
+    radius.create_instance(config.realm_name, config.host_name, 'localhost')
+
+    # Configure ntpd
+    ntp = ntpinstance.NTPInstance()
+    ntp.create_instance()
+
+
+    service.restart("dirsrv")
+    service.restart("krb5kdc")
+    
+main()
+    
+    
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipa-install/ipa-replica-prepare
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-server/ipa-install/ipa-replica-prepare	Tue Nov 20 11:34:51 2007 -0500
@@ -0,0 +1,119 @@
+#! /usr/bin/python -E
+# Authors: Karl MacMillan <kmacmillan at 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 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 sys
+sys.path.append("/usr/share/ipa")
+
+import logging, tempfile, shutil, os, pwd
+from ConfigParser import SafeConfigParser
+import krbV
+
+from ipa import ipautil
+from ipaserver import dsinstance
+from ipaserver import installutils
+
+certutil = "/usr/bin/certutil"
+
+def get_host_name():
+    hostname = installutils.get_fqdn()
+    try:
+        installutils.verify_fqdn(hostname)
+    except RuntimeError, e:
+        logging.error(str(e))
+        sys.exit(1)
+
+    return hostname
+
+def get_realm_name():
+    c = krbV.default_context()
+    return c.default_realm
+
+def get_dirman_password():
+    return installutils.read_password("Directory Manager")
+
+def check_ipa_configuration(realm_name):
+    config_dir = dsinstance.config_dirname(realm_name)
+    if not ipautil.dir_exists(config_dir):
+        logging.error("could not find directory instance: %s" % config_dir)
+        sys.exit(1)
+
+def create_certdb(ds_dir, dir):
+    # copy the passwd, noise, and pin files
+    shutil.copyfile(ds_dir + "/pwdfile.txt", dir + "/pwdfile.txt")
+    shutil.copyfile(ds_dir + "/noise.txt", dir + "/noise.txt")
+    shutil.copyfile(ds_dir + "/pin.txt", dir + "/pin.txt")
+
+    # create a new cert db
+    ipautil.run([certutil, "-N", "-d", dir, "-f", dir + "/pwdfile.txt"])
+
+    # Add the CA cert
+    ipautil.run([certutil, "-A", "-d", dir, "-n", "CA certificate", "-t", "CT,CT", "-a", "-i",
+                 ds_dir + "/cacert.asc"])
+
+def get_ds_user(ds_dir):
+    uid = os.stat(ds_dir).st_uid
+    user = pwd.getpwuid(uid)[0]
+
+    return user
+
+def copy_files(realm_name, dir):
+    shutil.copy("/var/kerberos/krb5kdc/ldappwd", dir + "/ldappwd")
+    
+    
+def save_config(dir, realm_name, host_name, dirman_password, ds_user):
+    config = SafeConfigParser()
+    config.add_section("realm")
+    config.set("realm", "realm_name", realm_name)
+    config.set("realm", "master_host_name", host_name)
+    config.set("realm", "dirman_password", dirman_password)
+    config.set("realm", "ds_user", ds_user)
+    fd = open(dir + "/realm_info", "w")
+    config.write(fd)
+    
+
+def main():
+    realm_name = get_realm_name()
+    host_name = get_host_name()
+    dirman_password = get_dirman_password()
+    ds_dir = dsinstance.config_dirname(realm_name)
+    ds_user = get_ds_user(ds_dir)
+
+    check_ipa_configuration(realm_name)
+
+    top_dir = tempfile.mkdtemp("ipa")
+    dir = top_dir + "/realm_info"
+    os.mkdir(dir, 0700)
+
+    create_certdb(ds_dir, dir)
+
+    copy_files(realm_name, dir)
+
+    save_config(dir, realm_name, host_name, dirman_password, ds_user)
+
+    ipautil.run(["/bin/tar", "cfz", "replica-info-" + realm_name, "-C", top_dir, "realm_info"])
+
+    shutil.rmtree(dir)
+
+main()    
+    
+
+
+    
+    
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipa-install/ipa-server-install
--- a/ipa-server/ipa-install/ipa-server-install	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipa-install/ipa-server-install	Tue Nov 20 11:34:51 2007 -0500
@@ -34,7 +34,6 @@ import errno
 import errno
 import logging
 import pwd
-import getpass
 import subprocess
 import signal
 import shutil
@@ -51,8 +50,9 @@ import ipaserver.webguiinstance
 import ipaserver.webguiinstance
 
 from ipaserver import service
-
-from ipa.ipautil import run
+from ipaserver.installutils import *
+
+from ipa.ipautil import *
 
 def parse_options():
     parser = OptionParser(version=VERSION)
@@ -86,39 +86,6 @@ def parse_options():
 
     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='ipaserver-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 erase_ds_instance_data(serverid):
-    try:
-        shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid)
-    except:
-        pass
-    try:
-        shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid)
-    except:
-        pass
-    try:
-        shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid)
-    except:
-        pass
-
 def signal_handler(signum, frame):
     global ds
     print "\nCleaning up..."
@@ -126,58 +93,8 @@ def signal_handler(signum, frame):
         print "Removing configuration for %s instance" % ds.serverid
         ds.stop()
         if ds.serverid:
-            erase_ds_instance_data (ds.serverid)
+            ipaserver.dsinstance.erase_ds_instance_data (ds.serverid)
     sys.exit(1)
-
-def check_existing_installation():
-    dirs = glob.glob("/etc/dirsrv/slapd-*")
-    if not dirs:
-        return
-    print ""
-    print "An existing Directory Server has been detected."
-    yesno = raw_input("Do you wish to remove it and create a new one? [no]: ")
-    if not yesno or yesno.lower()[0] != "y":
-        sys.exit(1)
-
-    try:
-        run(["/sbin/service", "dirsrv", "stop"])
-    except:
-        pass
-    for d in dirs:
-        serverid = os.path.basename(d).split("slapd-", 1)[1]
-        if serverid:
-            erase_ds_instance_data(serverid)
-
-def check_ports():
-    ds_unsecure = port_available(389)
-    ds_secure = port_available(636)
-    if not ds_unsecure or not ds_secure:
-        print "IPA requires ports 389 and 636 for the Directory Server."
-        print "These are currently in use:"
-        if not ds_unsecure:
-            print "\t389"
-        if not ds_secure:
-            print "\t636"
-        sys.exit(1)
-
-def get_fqdn():
-    fqdn = ""
-    try:
-        fqdn = socket.getfqdn()
-    except:
-        try:
-            fqdn = socket.gethostname()
-        except:
-            fqdn = ""
-    return fqdn
-   
-def verify_fqdn(host_name):
-    is_ok = True
-    if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain":
-        print "Invalid hostname: " + host_name
-        print "This host name can't be used as a hostname for an IPA Server"
-        is_ok = False
-    return is_ok
 
 def read_host_name(host_default):
     host_ok = False
@@ -198,7 +115,9 @@ def read_host_name(host_default):
             host_name = host_default
         else:
             host_name = host_input
-        if not verify_fqdn(host_name):
+        try:
+            verify_fqdn(host_name)
+        except:
             host_name = ""
             continue
         else:
@@ -256,36 +175,6 @@ def read_ip_address(host_name):
 
         return ip
 
-def port_available(port):
-    """Try to bind to a port on the wildcard host
-       Return 1 if the port is available
-       Return 0 if the port is in use
-    """
-    rv = 1
-
-    try:
-        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        s.bind(('', port))
-        s.shutdown(0)
-        s.close()
-    except socket.error, e:
-        if e[0] == errno.EADDRINUSE:
-            rv = 0
-
-    if rv:
-        try:
-            s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-            s.bind(('', port))
-            s.shutdown(0)
-            s.close()
-        except socket.error, e:
-            if e[0] == errno.EADDRINUSE:
-                rv = 0
-
-    return rv
-
 def read_ds_user():
     print "The server must run as a specific user in a specific group."
     print "It is strongly recommended that this user should have no privileges"
@@ -333,23 +222,6 @@ def read_realm_name(domain_name):
             realm_name = upper_dom
     return realm_name
 
-def read_password(user):
-    correct = False
-    pwd = ""
-    while not correct:
-        pwd = getpass.getpass(user + " password: ")
-        if not pwd:
-            continue
-        pwd_confirm = getpass.getpass("Password (confirm): ")
-        if pwd != pwd_confirm:
-            print "Password mismatch!"
-            print ""
-        else:
-            correct = True
-        #TODO: check validity/length
-    print ""
-    return pwd
-
 def read_dm_password():
     print "Certain directory server operations require an administrative user."
     print "This user is referred to as the Directory Manager and has full access"
@@ -405,11 +277,11 @@ def main():
     print "To accept the default shown in brackets, press the Enter key."
     print ""
 
-    check_existing_installation()
-    check_ports()
+    ipaserver.dsinstance.check_existing_installation()
+    ipaserver.dsinstance.check_ports()
 
     options = parse_options()
-    logging_setup(options)
+    standard_logging_setup("ipaserver-install.log", options.debug)
 
     ds_user = ""
     realm_name = ""
@@ -439,10 +311,13 @@ def main():
         host_default = get_fqdn()
         
     if options.unattended:
-        if not verify_fqdn(host_default):
-            return "-Fatal Error-"
-        else:
-            host_name = host_default
+        try:
+            verify_fqdn(host_default)
+        except RuntimeError, e:
+            logging.error(str(e) + "\n")
+            return "-Fatal Error-"
+
+        host_name = host_default
     else:
         host_name = read_host_name(host_default)
     
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/Makefile.am
--- a/ipa-server/ipaserver/Makefile.am	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipaserver/Makefile.am	Tue Nov 20 11:34:51 2007 -0500
@@ -12,6 +12,8 @@ app_PYTHON = 			\
 	radiusinstance.py	\
 	webguiinstance.py	\
 	service.py		\
+	installutils.py		\
+	replication.py		\
 	$(NULL)
 
 EXTRA_DIST =			\
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/dsinstance.py
--- a/ipa-server/ipaserver/dsinstance.py	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipaserver/dsinstance.py	Tue Nov 20 11:34:51 2007 -0500
@@ -24,9 +24,13 @@ import shutil
 import shutil
 import logging
 import pwd
+import glob
+import sys
 
 from ipa.ipautil import *
 import service
+
+import installutils
 
 SERVER_ROOT_64 = "/usr/lib64/dirsrv"
 SERVER_ROOT_32 = "/usr/lib/dirsrv"
@@ -34,9 +38,6 @@ def ldap_mod(fd, dn, pwd):
 def ldap_mod(fd, dn, pwd):
     args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", "-D", dn, "-w", pwd, "-f", fd.name]
     run(args)
-
-    text = fd.read()
-    print text
 
 def realm_to_suffix(realm_name):
     s = realm_name.split(".")
@@ -48,6 +49,61 @@ def find_server_root():
         return SERVER_ROOT_64
     else:
         return SERVER_ROOT_32
+
+def realm_to_serverid(realm_name):
+    return "-".join(realm_name.split("."))
+
+def config_dirname(realm_name):
+    return "/etc/dirsrv/slapd-" + realm_to_serverid(realm_name) + "/"
+
+def schema_dirname(realm_name):
+    return config_dirname(realm_name) + "/schema/"
+
+def erase_ds_instance_data(serverid):
+    try:
+        shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid)
+    except:
+        pass
+    try:
+        shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid)
+    except:
+        pass
+    try:
+        shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid)
+    except:
+        pass
+
+def check_existing_installation():
+    dirs = glob.glob("/etc/dirsrv/slapd-*")
+    if not dirs:
+        return
+    print ""
+    print "An existing Directory Server has been detected."
+    yesno = raw_input("Do you wish to remove it and create a new one? [no]: ")
+    if not yesno or yesno.lower()[0] != "y":
+        sys.exit(1)
+
+    try:
+        run(["/sbin/service", "dirsrv", "stop"])
+    except:
+        pass
+    for d in dirs:
+        serverid = os.path.basename(d).split("slapd-", 1)[1]
+        if serverid:
+            erase_ds_instance_data(serverid)
+
+def check_ports():
+    ds_unsecure = installutils.port_available(389)
+    ds_secure = installutils.port_available(636)
+    if not ds_unsecure or not ds_secure:
+        print "IPA requires ports 389 and 636 for the Directory Server."
+        print "These are currently in use:"
+        if not ds_unsecure:
+            print "\t389"
+        if not ds_secure:
+            print "\t636"
+        sys.exit(1)
+
 
 INF_TEMPLATE = """
 [General]
@@ -75,7 +131,7 @@ class DsInstance(service.Service):
     def create_instance(self, ds_user, realm_name, host_name, dm_password):
         self.ds_user = ds_user
         self.realm_name = realm_name.upper()
-        self.serverid = "-".join(self.realm_name.split("."))
+        self.serverid = realm_to_serverid(self.realm_name)
         self.suffix = realm_to_suffix(self.realm_name)
         self.host_name = host_name
         self.dm_password = dm_password
@@ -107,14 +163,6 @@ class DsInstance(service.Service):
         self.chkconfig_on()
 
         self.done_creation()
-
-    def config_dirname(self):
-        if not self.serverid:
-            raise RuntimeError("serverid not set")
-        return "/etc/dirsrv/slapd-" + self.serverid + "/"
-
-    def schema_dirname(self):
-        return self.config_dirname() + "/schema/"
 
     def __setup_sub_dict(self):
         server_root = find_server_root()
@@ -165,13 +213,13 @@ class DsInstance(service.Service):
     def __add_default_schemas(self):
         self.step("adding default schema")
         shutil.copyfile(SHARE_DIR + "60kerberos.ldif",
-                        self.schema_dirname() + "60kerberos.ldif")
+                        schema_dirname(self.realm_name) + "60kerberos.ldif")
         shutil.copyfile(SHARE_DIR + "60samba.ldif",
-                        self.schema_dirname() + "60samba.ldif")
+                        schema_dirname(self.realm_name) + "60samba.ldif")
         shutil.copyfile(SHARE_DIR + "60radius.ldif",
-                        self.schema_dirname() + "60radius.ldif")
+                        schema_dirname(self.realm_name) + "60radius.ldif")
         shutil.copyfile(SHARE_DIR + "60ipaconfig.ldif",
-                        self.schema_dirname() + "60ipaconfig.ldif")
+                        schema_dirname(self.realm_name) + "60ipaconfig.ldif")
 
     def __add_memberof_module(self):
         self.step("enabling memboerof plugin")
@@ -235,7 +283,7 @@ class DsInstance(service.Service):
 
     def __enable_ssl(self):
         self.step("configuring ssl for ds instance")
-        dirname = self.config_dirname()
+        dirname = config_dirname(self.realm_name)
         args = ["/usr/share/ipa/ipa-server-setupssl", self.dm_password,
                 dirname, self.host_name]
         try:
@@ -273,7 +321,7 @@ class DsInstance(service.Service):
 
     def __certmap_conf(self):
         self.step("configuring certmap.conf")
-        dirname = self.config_dirname()
+        dirname = config_dirname(self.realm_name)
         certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict)
         certmap_fd = open(dirname+"certmap.conf", "w+")
         certmap_fd.write(certmap_conf)
@@ -281,7 +329,7 @@ class DsInstance(service.Service):
 
     def change_admin_password(self, password):
         logging.debug("Changing admin password")
-        dirname = self.config_dirname()
+        dirname = config_dirname(self.realm_name)
         if dir_exists("/usr/lib64/mozldap"):
             app = "/usr/lib64/mozldap/ldappasswd"
         else:
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/installutils.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ipa-server/ipaserver/installutils.py	Tue Nov 20 11:34:51 2007 -0500
@@ -0,0 +1,108 @@
+# Authors: Simo Sorce <ssorce at 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 logging
+import socket
+import errno
+import getpass
+
+def get_fqdn():
+    fqdn = ""
+    try:
+        fqdn = socket.getfqdn()
+    except:
+        try:
+            fqdn = socket.gethostname()
+        except:
+            fqdn = ""
+    return fqdn
+   
+def verify_fqdn(host_name):
+    if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain":
+        raise RuntimeError("Invalid hostname: " + host_name)
+
+def port_available(port):
+    """Try to bind to a port on the wildcard host
+       Return 1 if the port is available
+       Return 0 if the port is in use
+    """
+    rv = 1
+
+    try:
+        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s.bind(('', port))
+        s.shutdown(0)
+        s.close()
+    except socket.error, e:
+        if e[0] == errno.EADDRINUSE:
+            rv = 0
+
+    if rv:
+        try:
+            s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            s.bind(('', port))
+            s.shutdown(0)
+            s.close()
+        except socket.error, e:
+            if e[0] == errno.EADDRINUSE:
+                rv = 0
+
+    return rv
+
+def standard_logging_setup(log_filename, debug=False):
+    # Always log everything (i.e., DEBUG) to the log
+    # file.
+    logging.basicConfig(level=logging.DEBUG,
+                        format='%(asctime)s %(levelname)s %(message)s',
+                        filename=log_filename,
+                        filemode='w')
+
+    console = logging.StreamHandler()
+    # If the debug option is set, also log debug messages to the console
+    if 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 read_password(user):
+    correct = False
+    pwd = ""
+    while not correct:
+        pwd = getpass.getpass(user + " password: ")
+        if not pwd:
+            continue
+        if len(pwd) < 8:
+            print "Password must be at least 8 characters long"
+            continue
+        pwd_confirm = getpass.getpass("Password (confirm): ")
+        if pwd != pwd_confirm:
+            print "Password mismatch!"
+            print ""
+        else:
+            correct = True
+    print ""
+    return pwd
+
+
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/ipaldap.py
--- a/ipa-server/ipaserver/ipaldap.py	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipaserver/ipaldap.py	Tue Nov 20 11:34:51 2007 -0500
@@ -176,25 +176,90 @@ def wrapper(f,name):
             return f(*args, **kargs)
     return inner
 
+class LDIFConn(ldif.LDIFParser):
+    def __init__(
+        self,
+        input_file,
+        ignored_attr_types=None,max_entries=0,process_url_schemes=None
+    ):
+        """
+        See LDIFParser.__init__()
+        
+        Additional Parameters:
+        all_records
+        List instance for storing parsed records
+        """
+        self.dndict = {} # maps dn to Entry
+        self.dnlist = [] # contains entries in order read
+        myfile = input_file
+        if isinstance(input_file,str) or isinstance(input_file,unicode):
+            myfile = open(input_file, "r")
+        ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes)
+        self.parse()
+        if isinstance(input_file,str) or isinstance(input_file,unicode):
+            myfile.close()
+
+    def handle(self,dn,entry):
+        """
+        Append single record to dictionary of all records.
+        """
+        if not dn:
+            dn = ''
+        newentry = Entry((dn, entry))
+        self.dndict[IPAdmin.normalizeDN(dn)] = newentry
+        self.dnlist.append(newentry)
+
+    def get(self,dn):
+        ndn = IPAdmin.normalizeDN(dn)
+        return self.dndict.get(ndn, Entry(None))
+
 class IPAdmin(SimpleLDAPObject):
     CFGSUFFIX = "o=NetscapeRoot"
     DEFAULT_USER_ID = "nobody"
+
+    def getDseAttr(self,attrname):
+        conffile = self.confdir + '/dse.ldif'
+        dseldif = LDIFConn(conffile)
+        cnconfig = dseldif.get("cn=config")
+        if cnconfig:
+            return cnconfig.getValue(attrname)
+        return None
     
     def __initPart2(self):
         if self.binddn and len(self.binddn) and not hasattr(self,'sroot'):
             try:
                 ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)',
-                                    [ 'nsslapd-instancedir', 'nsslapd-errorlog' ])
+                                    [ 'nsslapd-instancedir', 'nsslapd-errorlog',
+                                      'nsslapd-certdir', 'nsslapd-schemadir' ])
+                self.errlog = ent.getValue('nsslapd-errorlog')
+                self.confdir = None
+                if self.isLocal:
+                    self.confdir = ent.getValue('nsslapd-certdir')
+                    if not self.confdir or not os.access(self.confdir + '/dse.ldif', os.R_OK):
+                        self.confdir = ent.getValue('nsslapd-schemadir')
+                        if self.confdir:
+                            self.confdir = os.path.dirname(self.confdir)
                 instdir = ent.getValue('nsslapd-instancedir')
-                self.sroot, self.inst = re.match(r'(.*)[\/]slapd-(\w+)$', instdir).groups()
-                self.errlog = ent.getValue('nsslapd-errorlog')
-            except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR,
-                    ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND)):
+                if not instdir:
+                    # get instance name from errorlog
+                    self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)/errors', self.errlog).group(2)
+                    if self.confdir:
+                        instdir = self.getDseAttr('nsslapd-instancedir')
+                    else:
+                        if self.isLocal:
+                            print instdir
+                            self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups()
+                            instdir = re.match(r'(.*/slapd-.*)/errors', self.errlog).group(1)
+                            #self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups()
+                ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config',
+                                    ldap.SCOPE_BASE, '(objectclass=*)',
+                                    [ 'nsslapd-directory' ])
+                self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory'))
+            except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR):
                 pass # usually means 
-#                print "ignored exception"
             except ldap.LDAPError, e:
                 print "caught exception ", e
-                raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
+                raise
 
     def __localinit__(self):
         """If a CA certificate is provided then it is assumed that we are
@@ -209,7 +274,7 @@ class IPAdmin(SimpleLDAPObject):
         else:
             SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
 
-    def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None,debug=None):
+    def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None):
         """We just set our instance variables and wrap the methods - the real
            work is done in __localinit__ and __initPart2 - these are separated
            out this way so that we can call them from places other than
@@ -223,7 +288,7 @@ class IPAdmin(SimpleLDAPObject):
             ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
 
         self.__wrapmethods()
-        self.port = port or 389
+        self.port = port
         self.host = host
         self.cacert = cacert
         self.bindcert = bindcert
@@ -272,6 +337,12 @@ class IPAdmin(SimpleLDAPObject):
             self.principal = principal
         self.proxydn = None
 
+    def do_simple_bind(self, binddn="cn=directory manager", bindpw=""):
+        self.binddn = binddn
+        self.bindpwd = bindpw
+        self.simple_bind_s(binddn, bindpw)
+        self.__initPart2()
+
     def getEntry(self,*args):
         """This wraps the search function.  It is common to just get one entry"""
 
@@ -283,8 +354,9 @@ class IPAdmin(SimpleLDAPObject):
         try:
             res = self.search(*args)
             type, obj = self.result(res)
-
-    #        res = self.search_ext(args[0], args[1], filterstr=args[2], attrlist=args[3], serverctrls=sctrl)
+        except ldap.NO_SUCH_OBJECT:
+            raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND,
+                    "no such entry for " + str(args))            
         except ldap.LDAPError, e:
             raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
 
@@ -538,7 +610,7 @@ class IPAdmin(SimpleLDAPObject):
                 print "Export task %s for file %s completed successfully" % (cn,file)
         return rc
 
-    def waitForEntry(self, dn, timeout=7200, attr='', quiet=False):
+    def waitForEntry(self, dn, timeout=7200, attr='', quiet=True):
         scope = ldap.SCOPE_BASE
         filter = "(objectclass=*)"
         attrlist = []
@@ -560,7 +632,8 @@ class IPAdmin(SimpleLDAPObject):
                 entry = self.getEntry(dn, scope, filter, attrlist)
             except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
                 pass # found entry, but no attr
-            except ldap.NO_SUCH_OBJECT: pass # no entry yet
+            except ldap.NO_SUCH_OBJECT:
+                pass # no entry yet
             except ldap.LDAPError, e: # badness
                 print "\nError reading entry", dn, e
                 break
@@ -574,7 +647,7 @@ class IPAdmin(SimpleLDAPObject):
             print "\nwaitForEntry timeout for %s for %s" % (self,dn)
         elif entry and not quiet:
             print "\nThe waited for entry is:", entry
-        else:
+        elif not entry:
             print "\nError: could not read entry %s from %s" % (dn,self)
 
         return entry
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/krbinstance.py
--- a/ipa-server/ipaserver/krbinstance.py	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipaserver/krbinstance.py	Tue Nov 20 11:34:51 2007 -0500
@@ -32,16 +32,21 @@ import pwd
 import pwd
 import socket
 import time
+import shutil
 
 import service
 from ipa.ipautil import *
+from ipa import ipaerror
+
+import ipaldap
 
 import ldap
 from ldap import LDAPError
 from ldap import ldapobject
 
-from pyasn1.type import univ
+from pyasn1.type import univ, namedtype
 import pyasn1.codec.ber.encoder
+import pyasn1.codec.ber.decoder
 import struct
 import base64
 
@@ -88,18 +93,18 @@ class KrbInstance(service.Service):
         self.kdc_password = None
         self.sub_dict = None
 
-    def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password):
+    def __common_setup(self, ds_user, realm_name, host_name, admin_password):
         self.ds_user = ds_user
-        self.fqdn = host_name
-        self.ip = socket.gethostbyname(host_name)
+        self.fqdn = host_name        
         self.realm = realm_name.upper()
         self.host = host_name.split(".")[0]
-        self.domain = host_to_domain(host_name)
-        self.admin_password = admin_password
-        self.master_password = master_password
-        
+        self.ip = socket.gethostbyname(host_name)
+        self.domain = host_to_domain(host_name)        
 	self.suffix = realm_to_suffix(self.realm)
         self.kdc_password = generate_kdc_password()
+        self.admin_password = admin_password
+
+        self.__setup_sub_dict()
 
         try:
             self.stop()
@@ -107,22 +112,7 @@ class KrbInstance(service.Service):
             # It could have been not running
             pass
 
-        self.start_creation(10, "Configuring Kerberos KDC")
-
-	self.__configure_kdc_account_password()
-
-        self.__setup_sub_dict()
-
-        self.__configure_ldap()
-
-        self.__create_instance()
-
-        self.__create_ds_keytab()
-
-        self.__export_kadmin_changepw_keytab()
-
-        self.__add_pwd_extop_module()
-
+    def __common_post_setup(self):
         try:
             self.step("starting the KDC")
             self.start()
@@ -138,8 +128,46 @@ class KrbInstance(service.Service):
         self.step("starting ipa-kpasswd")
         service.start("ipa-kpasswd")
 
+
+    def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password):
+        self.master_password = master_password
+
+        self.__common_setup(ds_user, realm_name, host_name, admin_password)
+
+        self.start_creation(10, "Configuring Kerberos KDC")
+        
+	self.__configure_kdc_account_password()
+        self.__configure_ldap()
+        self.__create_instance()
+        self.__create_ds_keytab()
+        self.__export_kadmin_changepw_keytab()
+        self.__add_pwd_extop_module()
+
+        self.__common_post_setup()
+
         self.done_creation()
 
+
+    def create_replica(self, ds_user, realm_name, host_name, admin_password, ldap_passwd_filename):
+        
+        self.__common_setup(ds_user, realm_name, host_name, admin_password)
+
+        self.start_creation(8, "Configuring Kerberos KDC")
+        self.__copy_ldap_passwd(ldap_passwd_filename)
+        self.__write_stash_from_ds()
+        self.__create_instance(replica=True)
+        self.__create_ds_keytab()
+        self.__export_kadmin_changepw_keytab()
+
+        self.__common_post_setup()
+
+        self.done_creation()
+
+
+    def __copy_ldap_passwd(self, filename):
+        shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd")
+        
+        
     def __configure_kdc_account_password(self):
         self.step("setting KDC account password")
         hexpwd = ''
@@ -160,6 +188,7 @@ class KrbInstance(service.Service):
 
     def __configure_ldap(self):
         self.step("adding kerberos configuration to the directory")
+
         # we need to remove any existing SASL mappings in the directory as otherwise they
         # they may conflict. There is no way to define the order they are used in atm.
         try:
@@ -192,7 +221,7 @@ class KrbInstance(service.Service):
             logging.critical("Failed to load default-aci.ldif: %s" % str(e))
         aci_fd.close()
 
-    def __create_instance(self):
+    def __create_instance(self, replica=False):
         self.step("configuring KDC")
         kdc_conf = template_file(SHARE_DIR+"kdc.conf.template", self.sub_dict)
         kdc_fd = open("/var/kerberos/krb5kdc/kdc.conf", "w+")
@@ -220,12 +249,36 @@ class KrbInstance(service.Service):
         krb_fd.write(krb_realm)
         krb_fd.close()
 
-        #populate the directory with the realm structure
-        args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"]
-        try:
-            run(args)
-        except subprocess.CalledProcessError, e:
-            print "Failed to populate the realm structure in kerberos", e
+        if not replica:
+            #populate the directory with the realm structure
+            args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"]
+            try:
+                run(args)
+            except subprocess.CalledProcessError, e:
+                print "Failed to populate the realm structure in kerberos", e
+
+    def __write_stash_from_ds(self):
+        self.step("writing stash file from DS")
+        try:
+            conn = ipaldap.IPAdmin(self.fqdn)
+            conn.do_simple_bind(bindpw=self.admin_password)
+            entry = conn.getEntry("cn=%s, cn=kerberos, %s" % (self.realm, self.suffix), ldap.SCOPE_SUBTREE)
+        except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e:
+            logging.critical("Could not find master key in DS")
+            raise e
+
+        krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey)
+        keytype = int(krbMKey[0][1][0])
+        keydata = str(krbMKey[0][1][1])
+
+        format = '=hi%ss' % len(keydata)
+        s = struct.pack(format, keytype, len(keydata), keydata)
+        try:
+            fd = open("/var/kerberos/krb5kdc/.k5."+self.realm, "w")
+            fd.write(s)
+        except os.error, e:
+            logging.critical("failed to write stash file")
+            raise e
 
     #add the password extop module
     def __add_pwd_extop_module(self):
diff -r 8ac4557bef2b -r c3d5826785f3 ipa-server/ipaserver/radiusinstance.py
--- a/ipa-server/ipaserver/radiusinstance.py	Mon Nov 19 19:34:27 2007 -0500
+++ b/ipa-server/ipaserver/radiusinstance.py	Tue Nov 20 11:34:51 2007 -0500
@@ -25,6 +25,7 @@ import logging
 import logging
 import pwd
 import time
+import sys
 from ipa.ipautil import *
 
 import service
@@ -149,7 +150,7 @@ class RadiusInstance(service.Service):
             retry += 1
             if retry > 15:
                 print "Error timed out waiting for kadmin to finish operations\n"
-                os.exit()
+                sys.exit(1)
                 
         try:
             pent = pwd.getpwnam(RADIUS_USER)




More information about the Freeipa-devel mailing list