summaryrefslogtreecommitdiffstats
path: root/server/config
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2009-11-19 19:40:12 +0100
committerStephen Gallagher <sgallagh@redhat.com>2009-11-20 11:18:48 -0500
commit78988f8fbc3a4289d41b09dd099df860d0b1427e (patch)
tree4c12110307fa558ed99869df452f555bfe23801b /server/config
parent287c0086512f806ae1800e144c0b0680867928ba (diff)
Change the upgrade script to use ipachangeconf
With this patch, the upgrade script we use for changing the config files is able to keep ordering and comments. Fixes: #249
Diffstat (limited to 'server/config')
-rw-r--r--server/config/upgrade_config.py383
1 files changed, 383 insertions, 0 deletions
diff --git a/server/config/upgrade_config.py b/server/config/upgrade_config.py
new file mode 100644
index 000000000..71af6daab
--- /dev/null
+++ b/server/config/upgrade_config.py
@@ -0,0 +1,383 @@
+#!/usr/bin/python
+#coding=utf-8
+
+# SSSD
+#
+# upgrade_config.py
+#
+# Copyright (C) Jakub Hrozek <jhrozek@redhat.com> 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 <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+import shutil
+import traceback
+from optparse import OptionParser
+
+from ipachangeconf import openLocked
+from ipachangeconf import SSSDChangeConf
+
+class SSSDConfigFile(SSSDChangeConf):
+ def __init__(self, filename):
+ SSSDChangeConf.__init__(self)
+ self.filename = filename
+
+ f = openLocked(self.filename, 0600)
+ self.opts = self.parse(f)
+ f.close()
+
+ def _backup_file(self, file_name):
+ " Copy the file we operate on to a backup location "
+ shutil.copy(file_name, file_name + self.backup_suffix)
+ # make sure we don't leak data, force permissions on the backup
+ os.chmod(file_name + self.backup_suffix, 0600)
+
+ def get_version(self):
+ ver = self.get_option_index('sssd', 'config_file_version')[1]
+ if not ver:
+ return 1
+ try:
+ return int(ver['value'])
+ except ValueError:
+ raise SyntaxError, 'config_file_version not an integer'
+
+ def rename_opts(self, parent_name, rename_kw, type='option'):
+ for new_name, old_name in rename_kw.items():
+ index, item = self.get_option_index(parent_name, old_name, type)
+ if item:
+ item['name'] = new_name
+
+ def _do_v2_changes(self):
+ # remove Data Provider
+ srvlist = self.get_option_index('sssd', 'services')[1]
+ if srvlist:
+ services = [ srv.strip() for srv in srvlist['value'].split(',') ]
+ if 'dp' in services:
+ services.remove('dp')
+ srvlist['value'] = ", ".join([srv for srv in services])
+ self.delete_option('section', 'dp')
+
+ def _update_option(self, to_section_name, from_section_name, opts):
+ to_section = [ s for s in self.sections() if s['name'].strip() == to_section_name ]
+ from_section = [ s for s in self.sections() if s['name'].strip() == from_section_name ]
+
+ if len(to_section) > 0 and len(from_section) > 0:
+ vals = to_section[0]['value']
+ for o in [one_opt for one_opt in from_section[0]['value'] if one_opt['name'] in opts]:
+ updated = False
+ for v in vals:
+ if v['type'] == 'empty':
+ continue
+ # if already in list, just update
+ if o['name'] == v['name']:
+ o['value'] = v['value']
+ updated = True
+ # not in list, add there
+ if not updated:
+ vals.insert(0, { 'name' : o['name'], 'type' : o['type'], 'value' : o['value'] })
+
+ def _migrate_enumerate(self, domain):
+ " Enumerate was special as it turned into bool from (0,1,2,3) enum "
+ enum = self.findOpts(domain, 'option', 'enumerate')[1]
+ if enum:
+ if enum['value'].upper() not in ['TRUE', 'FALSE']:
+ try:
+ enum['value'] = int(enum['value'])
+ except ValueError:
+ raise ValueError('Cannot convert value %s in domain %s' % (enum['value'], domain['name']))
+
+ if enum['value'] == 0:
+ enum['value'] = 'FALSE'
+ elif enum['value'] > 0:
+ enum['value'] = 'TRUE'
+ else:
+ raise ValueError('Cannot convert value %s in domain %s' % (enum['value'], domain['name']))
+
+ def _migrate_domain(self, domain):
+ # rename the section
+ domain['name'] = domain['name'].strip().replace('domains', 'domain')
+
+ # 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_principal' : '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(domain['value'])
+ self.rename_opts(domain['name'], generic_kw)
+ self.rename_opts(domain['name'], proxy_kw)
+ self.rename_opts(domain['name'], ldap_kw)
+ self.rename_opts(domain['name'], krb5_kw)
+
+ # configuration files before 0.5.0 did not enforce provider= in local domains
+ # it did special-case by domain name (LOCAL)
+ prv = self.findOpts(domain['value'], 'option', 'id_provider')[1]
+ if not prv and domain['name'] == 'domain/LOCAL':
+ prv = { 'type' : 'option',
+ 'name' : 'id_provider',
+ 'value' : 'local',
+ }
+ domain['value'].insert(0, prv)
+ # if domain was local, update with parameters from [user_defaults]
+ if prv['value'] == 'local':
+ self._update_option(domain['name'], 'user_defaults', user_defaults_kw.values())
+ self.delete_option('section', 'user_defaults')
+ self.rename_opts(domain['name'], user_defaults_kw)
+
+ def _migrate_domains(self):
+ for domain in [ s for s in self.sections() if s['name'].startswith("domains/") ]:
+ self._migrate_domain(domain)
+
+ def _update_if_exists(self, opt, to_name, from_section, from_name):
+ index, item = self.get_option_index(from_section, from_name)
+ if item:
+ item['name'] = to_name
+ opt.append(item)
+
+ def _migrate_services(self):
+ # [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',
+ }
+
+ # rename services sections
+ names_kw = { 'nss' : 'services/nss',
+ 'pam' : 'services/pam',
+ 'dp' : 'services/dp',
+ }
+ self.rename_opts(None, names_kw, 'section')
+
+ # [sssd] - monitor service
+ sssd_kw = [
+ { 'type' : 'option',
+ 'name' : 'config_file_version',
+ 'value' : '2',
+ 'action': 'set',
+ }
+ ]
+ self._update_if_exists(sssd_kw, 'domains',
+ 'domains', 'domains')
+ self._update_if_exists(sssd_kw, 'services',
+ 'services', 'activeServices')
+ self._update_if_exists(sssd_kw, 'sbus_timeout',
+ 'services/monitor', 'sbusTimeout')
+ self._update_if_exists(sssd_kw, 're_expression',
+ 'names', 're-expression')
+ self._update_if_exists(sssd_kw, 're_expression',
+ 'names', 'full-name-format')
+ self.add_section('sssd', sssd_kw)
+ # update from general services section and monitor
+ self._update_option('sssd', 'services', service_kw.values())
+ self._update_option('sssd', 'services/monitor', service_kw.values())
+
+ # [nss] - Name service
+ 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._update_option('nss', 'services', service_kw.values())
+ self.rename_opts('nss', nss_kw)
+
+ # [pam] - Authentication service
+ pam_kw = {}
+ pam_kw.update(service_kw)
+ self._update_option('pam', 'services', service_kw.values())
+ self.rename_opts('pam', pam_kw)
+
+ # remove obsolete sections
+ self.delete_option('section', 'services')
+ self.delete_option('section', 'names')
+ self.delete_option('section', 'domains')
+ self.delete_option('section', 'services/monitor')
+
+ def v2_changes(self, out_file_name, backup=True):
+ # read in the old file, make backup if needed
+ if backup:
+ self._backup_file(self.filename)
+
+ self._do_v2_changes()
+
+ # all done, write the file
+ of = open(out_file_name, "wb")
+ output = self.dump(self.opts)
+ of.write(output)
+ of.close()
+ # make sure it has the right permissions too
+ os.chmod(out_file_name, 0600)
+
+ def upgrade_v2(self, out_file_name, backup=True):
+ # read in the old file, make backup if needed
+ if backup:
+ self._backup_file(self.filename)
+
+ # do the migration to v2 format
+ # do the upgrade
+ self._migrate_services()
+ self._migrate_domains()
+ # also include any changes in the v2 format
+ self._do_v2_changes()
+
+ # all done, write the file
+ of = open(out_file_name, "wb")
+ output = self.dump(self.opts)
+ of.write(output)
+ of.close()
+ # make sure it has the right permissions too
+ os.chmod(out_file_name, 0600)
+
+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 SyntaxError:
+ verbose(traceback.format_exc(), options.verbose)
+ print >>sys.stderr, "Cannot parse config file %s" % options.filename
+ return 1
+
+ # make sure we keep strict settings when creating new files
+ os.umask(0077)
+
+ version = config.get_version()
+ if version == 2:
+ verbose("Looks like v2, only checking changes", options.verbose)
+ try:
+ config.v2_changes(options.outfile, options.backup)
+ except Exception, e:
+ print "ERROR: %s" % e
+ verbose(traceback.format_exc(), options.verbose)
+ return 1
+ elif version == 1:
+ verbose("Looks like v1, performing full upgrade", options.verbose)
+ try:
+ config.upgrade_v2(options.outfile, options.backup)
+ except Exception, e:
+ print "ERROR: %s" % e
+ verbose(traceback.format_exc(), options.verbose)
+ return 1
+ else:
+ print >>sys.stderr, "Can only upgrade from v1 to v2, file %s looks like version %d" % (options.filename, config.get_version())
+ return 1
+
+ return 0
+
+if __name__ == "__main__":
+ ret = main()
+ sys.exit(ret)
+