From 3c02938a2643fdc8ff83d81400334172f0743823 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Fri, 25 Sep 2009 00:32:50 +0200 Subject: script to upgrade config to v2 --- contrib/sssd.spec.in | 6 + server/Makefile.am | 3 + server/upgrade/upgrade_config.py | 352 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 server/upgrade/upgrade_config.py diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in index 786b2e9e0..87ee5a9d6 100644 --- a/contrib/sssd.spec.in +++ b/contrib/sssd.spec.in @@ -18,6 +18,7 @@ BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) Requires: libldb = 0.9.3 Requires: libtdb >= 1.1.3 +Requires(post): python Requires(preun): initscripts chkconfig Requires(postun): /sbin/service @@ -132,6 +133,8 @@ rm -rf $RPM_BUILD_ROOT %post /sbin/ldconfig /sbin/chkconfig --add %{servicename} +# a one-time upgrade from confdb v1 to v2 +python %{_libexecdir}/%{servicename}/upgrade_config.py %preun if [ $1 = 0 ]; then @@ -146,6 +149,9 @@ if [ $1 -ge 1 ] ; then fi %changelog +* Fri Sep 25 2009 Stephen Gallagher - 0.6.0-0 +- Convert to new config file format + * Wed Sep 02 2009 Stephen Gallagher - 0.5.0-0 - New upstream release 0.5.0 diff --git a/server/Makefile.am b/server/Makefile.am index 41eeefb42..a5555204f 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -57,6 +57,9 @@ sssdlibexec_PROGRAMS = \ $(sssd_pk) \ $(sssd_info) +dist_sssdlibexec_SCRIPTS = \ + upgrade/upgrade_config.py + if HAVE_CHECK non_interactive_check_based_tests = \ sysdb-tests \ diff --git a/server/upgrade/upgrade_config.py b/server/upgrade/upgrade_config.py new file mode 100644 index 000000000..412fad534 --- /dev/null +++ b/server/upgrade/upgrade_config.py @@ -0,0 +1,352 @@ +#!/usr/bin/python +#coding=utf-8 + +# SSSD +# +# upgrade_config.py +# +# Copyright (C) Jakub Hrozek 2009 +# +# 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; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys +import shutil +import traceback +from ConfigParser import RawConfigParser +from optparse import OptionParser + +class SSSDConfigParser(RawConfigParser): + def raw_set(self, section, option): + " set without interpolation " + pass + + def raw_get(self, section, option): + " get without interpolation " + return self._sections[section].get(option) + + def _write_section(self, section, fp): + fp.write("[%s]\n" % section) + for (key, value) in sorted(self._sections[section].items()): + if key != "__name__": + fp.write("%s = %s\n" % + (key, str(value).replace('\n', '\n\t'))) + fp.write("\n") + + def write(self, fp): + """ + SSSD Config file uses a logical order of sections + ConfigParser does not allow sorting the sections, so + we hackishly sort them here.. + """ + # Write SSSD first + if "sssd" in self._sections: + self._write_section("sssd", fp) + if (self.has_option('sssd', 'domains')): + active_domains = [s.strip() for s in self.get('sssd','domains').split(',')] + else: + #There were no active domains configured + active_domains = [] + del self._sections["sssd"] + # Write the other services + for service in [ s for s in self._sections if not s.startswith('domain/') ]: + self._write_section(service, fp) + del self._sections[service] + + # Write the domains in the order that is specified in domains = + for dom in active_domains: + self._write_section('domain/%s' % dom, fp) + del self._sections['domain/%s' % dom] + + # Write inactive domains + for section in sorted(self._sections): + self._write_section(section, fp) + +class SSSDConfigFile(object): + def __init__(self, file_name): + self.file_name = file_name + self._config = SSSDConfigParser() + self._new_config = SSSDConfigParser() + self._config.read(file_name) + + def get_version(self): + " Guess if we are looking at v1 config file " + if not self._config.has_section('sssd'): + return 1 + if not self._config.has_option('sssd', 'config_file_version'): + return 1 + return self._config.getint('sssd', 'config_file_version') + + def _backup_file(self): + " Copy the file we operate on to a backup location " + shutil.copy(self.file_name, self.file_name+".bak") + + def _migrate_if_exists(self, to_section, to_option, from_section, from_option): + """ + Move value of parameter from one section to another, renaming the parameter + """ + if self._config.has_section(from_section) and \ + self._config.has_option(from_section, from_option): + self._new_config.set(to_section, to_option, + self._config.get(from_section, from_option)) + + def _migrate_kw(self, to_section, from_section, new_old_dict): + """ + Move value of parameter from one section to another according to + mapping in ``new_old_dict`` + """ + for new, old in new_old_dict.items(): + self._migrate_if_exists(to_section, new, from_section, old) + + def _migrate_enumerate(self, to_section, from_section): + " Enumerate was special as it turned into bool from (0,1,2,3) enum " + if self._config.has_section(from_section) and \ + self._config.has_option(from_section, 'enumerate'): + enumvalue = self._config.get(from_section, 'enumerate') + if enumvalue.upper() in ['TRUE', 'FALSE']: + self._new_config.set(to_section, 'enumerate', enumvalue) + else: + try: + enumvalue = int(enumvalue) + except ValueError: + raise ValueError('Cannot convert value %s in domain %s' % (enumvalue, from_section)) + + if enumvalue == 0: + self._new_config.set(to_section, 'enumerate', 'FALSE') + elif enumvalue > 0: + self._new_config.set(to_section, 'enumerate', 'TRUE') + else: + raise ValueError('Cannot convert value %s in domain %s' % (enumvalue, from_section)) + + def _migrate_domain(self, domain): + new_domsec = 'domain/%s' % domain + old_domsec = 'domains/%s' % domain + self._new_config.add_section(new_domsec) + + # Generic options - new:old + generic_kw = { 'min_id' : 'minID', + 'max_id': 'maxID', + 'timeout': 'timeout', + 'magic_private_groups' : 'magicPrivateGroups', + 'cache_credentials' : 'cache-credentials', + 'id_provider' : 'provider', + 'auth_provider' : 'auth-module', + 'access_provider' : 'access-module', + 'chpass_provider' : 'chpass-module', + 'use_fully_qualified_names' : 'useFullyQualifiedNames', + } + # Proxy options + proxy_kw = { 'proxy_pam_target' : 'pam-target', + 'proxy_lib_name' : 'libName', + } + # LDAP options - new:old + ldap_kw = { 'ldap_uri' : 'ldapUri', + 'ldap_schema' : 'ldapSchema', + 'ldap_default_bind_dn' : 'defaultBindDn', + 'ldap_default_authtok_type' : 'defaultAuthtokType', + 'ldap_default_authtok' : 'defaultAuthtok', + 'ldap_user_search_base' : 'userSearchBase', + 'ldap_user_search_scope' : 'userSearchScope', + 'ldap_user_search_filter' : 'userSearchFilter', + 'ldap_user_object_class' : 'userObjectClass', + 'ldap_user_name' : 'userName', + 'ldap_user_pwd' : 'userPassword', + 'ldap_user_uid_number' : 'userUidNumber', + 'ldap_user_gid_number' : 'userGidNumber', + 'ldap_user_gecos' : 'userGecos', + 'ldap_user_home_directory' : 'userHomeDirectory', + 'ldap_user_shell' : 'userShell', + 'ldap_user_uuid' : 'userUUID', + 'ldap_user_principal' : 'userPrincipal', + 'ldap_force_upper_case_realm' : 'force_upper_case_realm', + 'ldap_user_fullname' : 'userFullname', + 'ldap_user_member_of' : 'userMemberOf', + 'ldap_user_modify_timestamp' : 'modifyTimestamp', + 'ldap_group_search_base' : 'groupSearchBase', + 'ldap_group_search_scope' : 'groupSearchScope', + 'ldap_group_search_filter' : 'groupSearchFilter', + 'ldap_group_object_class' : 'groupObjectClass', + 'ldap_group_name' : 'groupName', + 'ldap_group_pwd' : 'userPassword', + 'ldap_group_gid_number' : 'groupGidNumber', + 'ldap_group_member' : 'groupMember', + 'ldap_group_uuid' : 'groupUUID', + 'ldap_group_modify_timestamp' : 'modifyTimestamp', + 'ldap_network_timeout' : 'network_timeout', + 'ldap_offline_timeout' : 'offline_timeout', + 'ldap_enumeration_refresh_timeout' : 'enumeration_refresh_timeout', + 'ldap_stale_time' : 'stale_time', + 'ldap_opt_timeout' : 'opt_timeout', + 'ldap_tls_reqcert' : 'tls_reqcert', + } + krb5_kw = { 'krb5_kdcip' : 'krb5KDCIP', + 'krb5_realm' : 'krb5REALM', + 'krb5_try_simple_upn' : 'krb5try_simple_upn', + 'krb5_changepw_principle' : 'krb5changepw_principle', + 'krb5_ccachedir' : 'krb5ccache_dir', + 'krb5_auth_timeout' : 'krb5auth_timeout', + 'krb5_ccname_template' : 'krb5ccname_template', + } + user_defaults_kw = { 'default_shell' : 'defaultShell', + 'base_directory' : 'baseDirectory', + } + + self._migrate_enumerate(new_domsec, old_domsec) + self._migrate_kw(new_domsec, old_domsec, generic_kw) + self._migrate_kw(new_domsec, old_domsec, proxy_kw) + self._migrate_kw(new_domsec, old_domsec, ldap_kw) + self._migrate_kw(new_domsec, old_domsec, krb5_kw) + + # if domain was local, update with parameters from [user_defaults] + if self._new_config.get(new_domsec, 'id_provider') == 'local': + self._migrate_kw(new_domsec, 'user_defaults', user_defaults_kw) + + + def _migrate_domains(self): + for domain in [ s.replace('domains/','') for s in self._config.sections() if s.startswith("domains/") ]: + domain = domain.strip() + self._migrate_domain(domain) + + def upgrade_v2(self, out_file_name, backup=True): + """ + Upgrade the config file to V2 format and write the result into + ``out_file_name```. + """ + if backup: + self._backup_file() + + # [service] - options common to all services, no section as in v1 + service_kw = { 'reconnection_retries' : 'reconnection_retries', + 'debug_level' : 'debug-level', + 'debug_timestamps' : 'debug-timestamps', + 'command' : 'command', + 'timeout' : 'timeout', + } + + # [sssd] - monitor service + self._new_config.add_section('sssd') + self._new_config.set('sssd', 'config_file_version', '2') + self._migrate_if_exists('sssd', 'domains', + 'domains', 'domains') + self._migrate_if_exists('sssd', 'services', + 'services', 'activeServices') + self._migrate_if_exists('sssd', 'sbus_timeout', + 'services/monitor', 'sbusTimeout') + self._migrate_if_exists('sssd', 're_expression', + 'names', 're-expression') + self._migrate_if_exists('sssd', 're_expression', + 'names', 'full-name-format') + self._migrate_kw('sssd', 'services', service_kw) + self._migrate_kw('sssd', 'services/monitor', service_kw) + + # [nss] - Name service + self._new_config.add_section('nss') + nss_kw = { 'enum_cache_timeout' : 'EnumCacheTimeout', + 'entry_cache_timeout' : 'EntryCacheTimeout', + 'entry_cache_nowait_timeout' : 'EntryCacheNoWaitRefreshTimeout', + 'entry_negative_timeout ' : 'EntryNegativeTimeout', + 'filter_users' : 'filterUsers', + 'filter_groups' : 'filterGroups', + 'filter_users_in_groups' : 'filterUsersInGroups', + } + nss_kw.update(service_kw) + self._migrate_kw('nss', 'services', service_kw) + self._migrate_kw('nss', 'services/nss', nss_kw) + + # [pam] - Authentication service + self._new_config.add_section('pam') + pam_kw = {} + pam_kw.update(service_kw) + self._migrate_kw('pam', 'services', service_kw) + self._migrate_kw('pam', 'services/pam', pam_kw) + + # [dp] - Data provider + self._new_config.add_section('dp') + provider_kw = {'sbus_timeout' : 'sbusTimeout', + } + provider_kw.update(service_kw) + self._migrate_kw('dp', 'services', service_kw) + self._migrate_kw('dp', 'services/dp', provider_kw) + + # Migrate domains + self._migrate_domains() + + # all done, write the file + of = open(out_file_name, "wb") + self._new_config.write(of) + +def parse_options(): + parser = OptionParser() + parser.add_option("-f", "--file", + dest="filename", default="/etc/sssd/sssd.conf", + help="Set input file to FILE", metavar="FILE") + parser.add_option("-o", "--outfile", + dest="outfile", default=None, + help="Set output file to OUTFILE", metavar="OUTFILE") + parser.add_option("", "--no-backup", action="store_false", + dest="backup", default=True, + help="""Do not provide backup file after conversion. +The script copies the original file with the suffix .bak +by default""") + parser.add_option("-v", "--verbose", action="store_true", + dest="verbose", default=False, + help="Be verbose") + (options, args) = parser.parse_args() + if len(args) > 0: + print >>sys.stderr, "Stray arguments: %s" % ' '.join([a for a in args]) + return None + + # do the conversion in place by default + if not options.outfile: + options.outfile = options.filename + + return options + +def verbose(msg, verbose): + if verbose: + print msg + +def main(): + options = parse_options() + if not options: + print >>sys.stderr, "Cannot parse options" + return 1 + + try: + config = SSSDConfigFile(options.filename) + except SSSDConfigParser.ParsingError: + print >>sys.stderr, "Cannot parse config file %s" % options.filename + return 1 + + if config.get_version() == 2: + #Config file is already at the correct version. No upgrade needed + print >>sys.stderr, "Config file is v2. No upgrade required." + return 0 + + if config.get_version() != 1: + print >>sys.stderr, "Can only upgrade from v1 to v2, file %s looks like version %d" % (options.filename, config.get_version()) + return 1 + + try: + config.upgrade_v2(options.outfile, options.backup) + except Exception, e: + print "ERROR: %s" % e + verbose(traceback.format_exc(), options.verbose) + return 1 + + return 0 + +if __name__ == "__main__": + ret = main() + sys.exit(ret) + -- cgit