[Freeipa-devel] [PATCH] 0025/0026 ipa-client-install --hostname not setting HOSTNAME if it is missing from the configuration file

Alexander Bokovoy abokovoy at redhat.com
Wed Oct 12 11:33:33 UTC 2011


Hi,

attached is a small refactoring that is pursuing two goals:
 - fix https://fedorahosted.org/freeipa/ticket/1871 
  and
 - prepare grounds for systemd integration

As ticket 1871 is about cases when HOSTNAME is missing from 
/etc/sysconfig/network, this patch adds support to append 
HOSTNAME=<hostname> when it is missing. However, it does a bit more -- 
it introduces generic replacement/appending tool for key=value style 
configuration files.

For all key=value pairs in replacevars, existing value of key will get 
replaced by a new one.

For all key=value pairs in appendvars, existing value of key will me 
amended to also include new value.

The latter is the case of configuring krb5kdc.

Also, all keys totally missing from the config will be added. Values 
from replacevars and appendvars are merged before doing it so there is 
only single key=value pair afterwards. Obviously, it is the caller 
responsibility to not allow replacevars/appendvars have conflicting 
values.

Patch 0025 introduces the common code and transforms 
ipapython.services.backup_and_replace_hostname()

Patch 0026 uses this new code to change 
ipaserver/install/krb5instance.py to use new code.
-- 
/ Alexander Bokovoy
-------------- next part --------------
>From db1a37bbb58ddd1a99c5498940f2988477392a06 Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 12 Oct 2011 14:15:01 +0300
Subject: [PATCH 1/2] Refactor backup_and_replace_hostname() into a flexible
 config modification tool

backup_and_replace_hostname() was doing three things:
1. Given config file in 'key=value' style, replace value for a specified key (HOSTNAME)
2. Backup original file and install a replacement
3. Restore original security context after editing

We have several more places where parts of the functionality are needed,
thus making two tools in ipapython.ipautil:

    1. config_replace_variables(filepath, replacevars=dict(), appendvars=dict())
       Replaces or appends values to specified keys, adding new key=value pairs if key was absent

    2. backup_config_and_replace_variables(fstore, filepath, replacevars=dict(), appendvars=dict())
       Backups config file and calls config_replace_variables()

A caller must handle security context after using these two tools.

In addition, as before, there is ipapython.services.backup_and_replace_hostname() that uses
these common tools and restores security context after editing.

The code will be used extensively for systemd integration for Fedora 16.

Fixes:
    https://fedorahosted.org/freeipa/ticket/1871
---
 ipapython/ipautil.py         |   90 ++++++++++++++++++++++++++++++++++++++++++
 ipapython/platform/redhat.py |   51 +++--------------------
 2 files changed, 97 insertions(+), 44 deletions(-)

diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 6e037926ce678557d9273be6ff1e9a6c65d0f80e..49fc64cea7c48d7aaf7e44de3c26d395fddbaa28 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -1185,3 +1185,93 @@ def get_ipa_basedn(conn):
 
     return None
 
+def config_replace_variables(filepath, replacevars=dict(), appendvars=dict()):
+    """
+    Take a key=value based configuration file, and write new version 
+    with certain values replaced or appended 
+
+    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('''
+(^
+                        \s*
+        (?P<option>     [^\#;]+?)
+                        (\s*=\s*)
+        (?P<value>      .+?)?
+                        (\s*((\#|;).*)?)?
+$)''', re.VERBOSE)
+    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:
+            for line in f:
+                new_line = line
+                m = pattern.match(line)
+                if m:
+                    option, value = m.group('option', 'value')
+                    if option is not None:
+                        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 new value unless it is already existing in the original one
+                            if 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)
+        # Now add all options from replacevars and appendvars that were not found in the file
+        new_vars = replacevars.copy()
+        new_vars.update(appendvars)
+        newvars_view = new_vars.viewkeys() - old_values.viewkeys()
+        append_view = (appendvars.viewkeys() - replacevars.viewkeys()) - old_values.viewkeys()
+        for item in newvars_view:
+            new_config.write("%s=%s\n" % (item,new_vars[item]))
+        for item in append_view:
+            new_config.write("%s=%s\n" % (item,appendvars[item]))
+        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
+    write new version with certain values replaced or appended 
+
+    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.
+
+    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
+    """
+    # Backup original filepath
+    fstore.backup_file(filepath)
+    old_values = config_replace_variables(filepath, replacevars, appendvars)
+
+    return old_values
diff --git a/ipapython/platform/redhat.py b/ipapython/platform/redhat.py
index 6bf8bf348a4c30c79ad29d8b2b30a088bd28372d..ef4a9c45016ba7761dff04a9181ddda736396beb 100644
--- a/ipapython/platform/redhat.py
+++ b/ipapython/platform/redhat.py
@@ -133,48 +133,11 @@ def restore_context(filepath):
     ipautil.run(["/sbin/restorecon", filepath], raiseonerr=False)
 
 def backup_and_replace_hostname(fstore, statestore, hostname):
-    network_filename = "/etc/sysconfig/network"
-    # Backup original /etc/sysconfig/network
-    fstore.backup_file(network_filename)
-    hostname_pattern = re.compile('''
-(^
-                        \s*
-        (?P<option>     [^\#;]+?)
-                        (\s*=\s*)
-        (?P<value>      .+?)?
-                        (\s*((\#|;).*)?)?
-$)''', re.VERBOSE)
-    temp_filename = None
-    with tempfile.NamedTemporaryFile(delete=False) as new_config:
-        temp_filename = new_config.name
-        with open(network_filename, 'r') as f:
-            for line in f:
-                new_line = line
-                m = hostname_pattern.match(line)
-                if m:
-                    option, value = m.group('option', 'value')
-                    if option is not None and option == 'HOSTNAME':
-                        if value is not None and hostname != value:
-                            new_line = u"HOSTNAME=%s\n" % (hostname)
-                            statestore.backup_state('network', 'hostname', value)
-                new_config.write(new_line)
-        new_config.flush()
-        # Make sure the resulting file is readable by others before installing it
-        os.fchmod(new_config.fileno(), stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
-        os.fchown(new_config.fileno(), 0, 0)
-
-    # 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.
-    ipautil.install_file(temp_filename, network_filename)
-    try:
-        ipautil.run(['/bin/hostname', hostname])
-    except ipautil.CalledProcessError, e:
-        print >>sys.stderr, "Failed to set this machine hostname to %s (%s)." % (hostname, str(e))
-
-    # For SE Linux environments it is important to reset SE labels to the expected ones
-    try:
-        restore_context(network_filename)
-    except ipautil.CalledProcessError, e:
-        print >>sys.stderr, "Failed to set permissions for %s (%s)." % (network_filename, str(e))
+    replacevars = {'HOSTNAME':hostname}
+    old_values = ipautil.backup_config_and_replace_variables(fstore,
+                                                          "/etc/sysconfig/network", 
+                                                          replacevars=replacevars)
+    restore_context("/etc/sysconfig/network")
+    if old_values['HOSTNAME']:
+        statestore.backup_state('network', 'hostname', old_values['HOSTNAME'])
 
-- 
1.7.6.4

-------------- next part --------------
>From b3eafe089c96ee80affaf3a49c15e0f72104990b Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at redhat.com>
Date: Wed, 12 Oct 2011 14:18:21 +0300
Subject: [PATCH 2/2] Write KRB5REALM to /etc/sysconfig/krb5kdc and make use
 of common backup_config_and_replace_variables() tool

systemd service unit for krb5kdc in Fedora 16 uses KRB5REALM variable of /etc/sysconfig/krb5kdc
to start krb5kdc for the default realm. Thus, we need to make sure it is always existing and
pointing to our realm.

Partial fix for:
   https://fedorahosted.org/freeipa/ticket/1192
---
 ipaserver/install/krbinstance.py |   30 +++++++++---------------------
 1 files changed, 9 insertions(+), 21 deletions(-)

diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py
index 513dc5523589341cbcebbe1a80694e7eae6c4308..ad89e87d67c2f581836bd8dbbef530492325e394 100644
--- a/ipaserver/install/krbinstance.py
+++ b/ipaserver/install/krbinstance.py
@@ -352,28 +352,16 @@ class KrbInstance(service.Service):
             min = version.LooseVersion(MIN_KRB5KDC_WITH_WORKERS)
             if ver >= min:
                 workers = True
+        # Write down config file
+        # We write realm and also number of workers (for multi-CPU systems)
+        replacevars = {'KRB5REALM':self.realm}
+        appendvars = {}
         if workers and cpus > 1:
-            #read in memory, find KRB5KDC_ARGS, check/change it, then overwrite file
-            self.fstore.backup_file("/etc/sysconfig/krb5kdc")
-
-            need_w = True
-            fd = open("/etc/sysconfig/krb5kdc", "r")
-            lines = fd.readlines()
-            fd.close()
-            for line in lines:
-                sline = line.strip()
-                if not sline.startswith('KRB5KDC_ARGS'):
-                    continue
-                sline = sline.replace('"', '')
-                if sline.find("-w") != -1:
-                    need_w = False
-
-            if need_w:
-                fd = open("/etc/sysconfig/krb5kdc", "w")
-                for line in lines:
-                    fd.write(line)
-                fd.write('KRB5KDC_ARGS="${KRB5KDC_ARGS} -w %s"\n' % str(cpus))
-                fd.close()
+            appendvars = {'KRB5KDC_ARGS': "-w %s" % str(cpus)}
+        ipautil.backup_config_and_replace_variables(self.fstore, "/etc/sysconfig/krb5kdc",
+                                                    replacevars=replacevars,
+                                                    appendvars=appendvars)
+        ipaservices.restore_context("/etc/sysconfig/krb5kdc")
 
     def __write_stash_from_ds(self):
         try:
-- 
1.7.6.4



More information about the Freeipa-devel mailing list