From aac086582aee79ccf72206faf118e997c623170c Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Mar 27 2008 23:01:38 +0000 Subject: Move sysrestore to ipa-python so it can be used by client scripts too. Change backup format so files are all in a single directory (no dir hierarchies) and use an index file so we can save also ownership and permission info for the restore (and eventually other data later on). --- diff --git a/ipa-python/sysrestore.py b/ipa-python/sysrestore.py new file mode 100644 index 0000000..46233ef --- /dev/null +++ b/ipa-python/sysrestore.py @@ -0,0 +1,319 @@ +# Authors: Mark McLoughlin +# +# 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 only +# +# 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 +# + +# +# This module provides a very simple API which allows +# ipa-xxx-install --uninstall to restore certain +# parts of the system configuration to the way it was +# before ipa-server-install was first run + +import os +import os.path +import errno +import shutil +import logging +import ConfigParser +import random +import string + +from ipa import ipautil + +SYSRESTORE_PATH = "/tmp" +SYSRESTORE_INDEXFILE = "sysrestore.index" +SYSRESTORE_STATEFILE = "sysrestore.state" + +class FileStore: + """Class for handling backup and restore of files""" + + def __init__(self, path = SYSRESTORE_PATH): + """Create a _StoreFiles object, that uses @path as the + base directory. + + The file @path/sysrestore.index is used to store information + about the original location of the saved files. + """ + self._path = path+"/"+SYSRESTORE_INDEXFILE + + self.random = random.Random() + + self.files = {} + self._load() + + def _load(self): + """Load the file list from the index file. @files will + be an empty dictionary if the file doesn't exist. + """ + + logging.debug("Loading Index file from '%s'", self._path) + + self.files = {} + + p = ConfigParser.SafeConfigParser() + p.read(self._path) + + for section in p.sections(): + if section == "files": + for (key, value) in p.items(section): + self.files[key] = value + + + def save(self): + """Save the file list to @_path. If @files is an empty + dict, then @_path should be removed. + """ + logging.debug("Saving Index File to '%s'", self._path) + + if len(self.files) == 0: + logging.debug(" -> no files, removing file") + if os.path.exists(self._path): + os.remove(self._path) + return + + p = ConfigParser.SafeConfigParser() + + p.add_section('files') + for (key, value) in self.files.items(): + p.set('files', key, str(value)) + + f = file(self._path, "w") + p.write(f) + f.close() + + def backup_file(self, path): + """Create a copy of the file at @path - so long as a copy + does not already exist - which will be restored to its + original location by restore_files(). + """ + logging.debug("Backing up system configuration file '%s'", path) + + if not os.path.isabs(path): + raise ValueError("Absolute path required") + + if not os.path.isfile(path): + logging.debug(" -> Not backing up - '%s' doesn't exist", path) + return + + (reldir, file) = os.path.split(path) + + filename = "" + for i in range(8): + h = "%02x" % self.random.randint(0,255) + filename += h + filename += "-"+file + + backup_path = os.path.join(SYSRESTORE_PATH, filename) + if os.path.exists(backup_path): + logging.debug(" -> Not backing up - already have a copy of '%s'", path) + return + + shutil.copy2(path, backup_path) + + stat = os.stat(path) + + self.files[filename] = string.join([str(stat.st_mode),str(stat.st_uid),str(stat.st_gid),path], ',') + self.save() + + def restore_file(self, path): + """Restore the copy of a file at @path to its original + location and delete the copy. + + Returns #True if the file was restored, #False if there + was no backup file to restore + """ + + logging.debug("Restoring system configuration file '%s'", path) + + if not os.path.isabs(path): + raise ValueError("Absolute path required") + + mode = None + uid = None + gid = None + filename = None + + for (key, value) in self.files.items(): + (mode,uid,gid,filepath) = string.split(value, ',', 3) + if (filepath == path): + filename = key + break + + if not filename: + raise ValueError("No such file name in the index") + + backup_path = os.path.join(SYSRESTORE_PATH, filename) + if not os.path.exists(backup_path): + logging.debug(" -> Not restoring - '%s' doesn't exist", backup_path) + return False + + shutil.move(backup_path, path) + os.chown(path, int(uid), int(gid)) + os.chmod(path, int(mode)) + + ipautil.run(["/sbin/restorecon", path]) + + del self.files[filename] + self.save() + + return True + + def restore_all_files(self): + """Restore the files in the inbdex to their original + location and delete the copy. + + Returns #True if the file was restored, #False if there + was no backup file to restore + """ + + if len(self.files) == 0: + return False + + for (filename, value) in self.files.items(): + + (mode,uid,gid,path) = string.split(value, ',', 3) + + backup_path = os.path.join(SYSRESTORE_PATH, filename) + if not os.path.exists(backup_path): + logging.debug(" -> Not restoring - '%s' doesn't exist", backup_path) + + shutil.move(backup_path, path) + os.chown(path, int(uid), int(gid)) + os.chmod(path, int(mode)) + + ipautil.run(["/sbin/restorecon", path]) + + #force file to be deleted + self.files = {} + self.save() + + return True + +class _StateFile: + """A metadata file for recording system state which can + be backed up and later restored. The format is something + like: + + [httpd] + running=True + enabled=False + """ + + def __init__(self, path = SYSRESTORE_PATH): + """Create a _StateFile object, loading from @path. + + The dictionary @modules, a member of the returned object, + is where the state can be modified. @modules is indexed + using a module name to return another dictionary containing + key/value pairs with the saved state of that module. + + The keys in these latter dictionaries are arbitrary strings + and the values may either be strings or booleans. + """ + self._path = path+"/"+SYSRESTORE_STATEFILE + + self.modules = {} + + self._load() + + def _load(self): + """Load the modules from the file @_path. @modules will + be an empty dictionary if the file doesn't exist. + """ + logging.debug("Loading StateFile from '%s'", self._path) + + self.modules = {} + + p = ConfigParser.SafeConfigParser() + p.read(self._path) + + for module in p.sections(): + self.modules[module] = {} + for (key, value) in p.items(module): + if value == str(True): + value = True + elif value == str(False): + value = False + self.modules[module][key] = value + + def save(self): + """Save the modules to @_path. If @modules is an empty + dict, then @_path should be removed. + """ + logging.debug("Saving StateFile to '%s'", self._path) + + for module in self.modules.keys(): + if len(self.modules[module]) == 0: + del self.modules[module] + + if len(self.modules) == 0: + logging.debug(" -> no modules, removing file") + if os.path.exists(self._path): + os.remove(self._path) + return + + p = ConfigParser.SafeConfigParser() + + for module in self.modules.keys(): + p.add_section(module) + for (key, value) in self.modules[module].items(): + p.set(module, key, str(value)) + + f = file(self._path, "w") + p.write(f) + f.close() + +def backup_state(module, key, value): + """Backup an item of system state from @module, identified + by the string @key and with the value @value. @value may be + a string or boolean. + """ + if not (isinstance(value, str) or isinstance(value, bool)): + raise ValueError("Only strings or booleans supported") + + state = _StateFile() + + if not state.modules.has_key(module): + state.modules[module] = {} + + if not state.modules.has_key(key): + state.modules[module][key] = value + + state.save() + +def restore_state(module, key): + """Return the value of an item of system state from @module, + identified by the string @key, and remove it from the backed + up system state. + + If the item doesn't exist, #None will be returned, otherwise + the original string or boolean value is returned. + """ + state = _StateFile() + + if not state.modules.has_key(module): + return None + + if not state.modules[module].has_key(key): + return None + + value = state.modules[module][key] + del state.modules[module][key] + + state.save() + + return value diff --git a/ipa-server/Makefile.am b/ipa-server/Makefile.am index d26aa29..35d3074 100644 --- a/ipa-server/Makefile.am +++ b/ipa-server/Makefile.am @@ -15,12 +15,14 @@ SUBDIRS = \ $(NULL) install-exec-local: - mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/sysrestore + mkdir -p $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore + chmod 700 $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore mkdir -p $(DESTDIR)$(localstatedir)/cache/ipa/sessions chmod 700 $(DESTDIR)$(localstatedir)/cache/ipa/sessions uninstall-local: - -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/sysrestore + -rmdir $(DESTDIR)$(localstatedir)/lib/ipa/sysrestore + -rmdir $(DESTDIR)$(localstatedir)/lib/ipa -rmdir $(DESTDIR)$(localstatedir)/cache/ipa/sessions -rmdir $(DESTDIR)$(localstatedir)/cache/ipa diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install index 8b5c441..1be1d3c 100644 --- a/ipa-server/ipa-install/ipa-server-install +++ b/ipa-server/ipa-install/ipa-server-install @@ -46,9 +46,9 @@ import ipaserver.httpinstance import ipaserver.ntpinstance from ipaserver import service -from ipaserver import sysrestore from ipaserver.installutils import * +from ipa import sysrestore from ipa.ipautil import * def parse_options(): @@ -173,7 +173,7 @@ def read_ip_address(host_name): continue print "Adding ["+ip+" "+host_name+"] to your /etc/hosts file" - sysrestore.backup_file("/etc/hosts") + fstore.backup_file("/etc/hosts") hosts_fd = open('/etc/hosts', 'r+') hosts_fd.seek(0, 2) hosts_fd.write(ip+'\t'+host_name+' '+host_name[:host_name.find('.')]+'\n') @@ -292,14 +292,13 @@ def check_dirsrv(): sys.exit(1) def uninstall(): - ipaserver.ntpinstance.NTPInstance().uninstall() - ipaserver.bindinstance.BindInstance().uninstall() + ipaserver.ntpinstance.NTPInstance(fstore).uninstall() + ipaserver.bindinstance.BindInstance(fstore).uninstall() ipaserver.httpinstance.WebGuiInstance().uninstall() - ipaserver.httpinstance.HTTPInstance().uninstall() - ipaserver.krbinstance.KrbInstance().uninstall() + ipaserver.httpinstance.HTTPInstance(fstore).uninstall() + ipaserver.krbinstance.KrbInstance(fstore).uninstall() ipaserver.dsinstance.DsInstance().uninstall() - sysrestore.restore_file("/etc/hosts") - sysrestore.restore_file("/etc/ipa/ipa.conf") + fstore.restore_all_files() return 0 def main(): @@ -321,6 +320,9 @@ def main(): standard_logging_setup("/var/log/ipaserver-install.log", options.debug) print "\nThe log file for this installation can be found in /var/log/ipaserver-install.log" + global fstore + fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + if options.uninstall: return uninstall() @@ -454,7 +456,7 @@ def main(): # Configure ntpd if options.conf_ntp: - ntp = ipaserver.ntpinstance.NTPInstance() + ntp = ipaserver.ntpinstance.NTPInstance(fstore) ntp.create_instance() # Create a directory server instance @@ -462,11 +464,11 @@ def main(): ds.create_instance(ds_user, realm_name, host_name, domain_name, dm_password) # Create a kerberos instance - krb = ipaserver.krbinstance.KrbInstance() + krb = ipaserver.krbinstance.KrbInstance(fstore) krb.create_instance(ds_user, realm_name, host_name, domain_name, dm_password, master_password) # Create a HTTP instance - http = ipaserver.httpinstance.HTTPInstance() + http = ipaserver.httpinstance.HTTPInstance(fstore) http.create_instance(realm_name, host_name, domain_name) # Create a Web Gui instance @@ -499,7 +501,7 @@ def main(): ds.change_admin_password(admin_password) # Create the config file - sysrestore.backup_file("/etc/ipa/ipa.conf") + fstore.backup_file("/etc/ipa/ipa.conf") fd = open("/etc/ipa/ipa.conf", "w") fd.write("[defaults]\n") fd.write("server=" + host_name + "\n") diff --git a/ipa-server/ipa-server.spec b/ipa-server/ipa-server.spec index 97b0e47..4360924 100755 --- a/ipa-server/ipa-server.spec +++ b/ipa-server/ipa-server.spec @@ -149,8 +149,8 @@ fi %attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so %attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so -%dir %{_localstatedir}/cache/ipa -%dir %{_localstatedir}/cache/ipa/sysrestore +%dir %{_localstatedir}/lib/ipa +%dir %{_localstatedir}/lib/ipa/sysrestore %attr(700,apache,apache) %dir %{_localstatedir}/cache/ipa/sessions %{_mandir}/man8/ipactl.8.gz diff --git a/ipa-server/ipa-server.spec.in b/ipa-server/ipa-server.spec.in index 57813f4..46adec9 100644 --- a/ipa-server/ipa-server.spec.in +++ b/ipa-server/ipa-server.spec.in @@ -149,8 +149,8 @@ fi %attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so %attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so -%dir %{_localstatedir}/cache/ipa -%dir %{_localstatedir}/cache/ipa/sysrestore +%dir %{_localstatedir}/lib/ipa +%dir %{_localstatedir}/lib/ipa/sysrestore %attr(700,apache,apache) %dir %{_localstatedir}/cache/ipa/sessions %{_mandir}/man8/ipactl.8.gz diff --git a/ipa-server/ipaserver/Makefile.am b/ipa-server/ipaserver/Makefile.am index 4a33b95..13029e1 100644 --- a/ipa-server/ipaserver/Makefile.am +++ b/ipa-server/ipaserver/Makefile.am @@ -13,7 +13,6 @@ app_PYTHON = \ installutils.py \ replication.py \ certs.py \ - sysrestore.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-server/ipaserver/bindinstance.py b/ipa-server/ipaserver/bindinstance.py index e9824ef..a331b9d 100644 --- a/ipa-server/ipaserver/bindinstance.py +++ b/ipa-server/ipaserver/bindinstance.py @@ -22,13 +22,14 @@ import tempfile import shutil import os import socket +import logging import service -import sysrestore +from ipa import sysrestore from ipa import ipautil class BindInstance(service.Service): - def __init__(self): + def __init__(self, fstore=None): service.Service.__init__(self, "named") self.fqdn = None self.domain = None @@ -37,6 +38,11 @@ class BindInstance(service.Service): self.realm = None self.sub_dict = None + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + def setup(self, fqdn, ip_address, realm_name, domain_name): self.fqdn = fqdn self.ip_address = ip_address @@ -68,15 +74,25 @@ class BindInstance(service.Service): except: pass - self.__setup_zone() - self.__setup_named_conf() + self.step("Setting up our zone", self.__setup_zone) + self.step("Setting up named.conf", self.__setup_named_conf) + + self.step("restarting named", self.__start) + self.step("configuring named to start on boot", self.__enable) + + self.step("Changing resolve.conf to point to ourselves", self.__setup_resolve_conf) + def __start(self): try: self.backup_state("running", self.is_running()) - self.start() + self.restart() except: print "named service failed to start" + def __enable(self): + self.backup_state("enabled", self.is_running()) + self.chkconfig_on() + def __setup_sub_dict(self): self.sub_dict = dict(FQDN=self.fqdn, IP=self.ip_address, @@ -87,13 +103,13 @@ class BindInstance(service.Service): def __setup_zone(self): self.backup_state("domain", self.domain) zone_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.zone.db.template", self.sub_dict) - sysrestore.backup_file('/var/named/'+self.domain+'.zone.db') + self.fstore.backup_file('/var/named/'+self.domain+'.zone.db') zone_fd = open('/var/named/'+self.domain+'.zone.db', 'w') zone_fd.write(zone_txt) zone_fd.close() def __setup_named_conf(self): - sysrestore.backup_file('/etc/named.conf') + self.fstore.backup_file('/etc/named.conf') named_txt = ipautil.template_file(ipautil.SHARE_DIR + "bind.named.conf.template", self.sub_dict) named_fd = open('/etc/named.conf', 'w') named_fd.seek(0) @@ -101,7 +117,8 @@ class BindInstance(service.Service): named_fd.write(named_txt) named_fd.close() - sysrestore.backup_file('/etc/resolve.conf') + def __setup_resolve_conf(self): + self.fstore.backup_file('/etc/resolve.conf') resolve_txt = "search "+self.domain+"\nnameserver "+self.ip_address+"\n" resolve_fd = open('/etc/resolve.conf', 'w') resolve_fd.seek(0) @@ -111,16 +128,28 @@ class BindInstance(service.Service): def uninstall(self): running = self.restore_state("running") + enabled = self.restore_state("enabled") domain = self.restore_state("domain") if not running is None: self.stop() - if not domain is None: - sysrestore.restore_file(os.path.join ("/var/named/", domain + ".zone.db")) - - sysrestore.restore_file('/etc/named.conf') - sysrestore.restore_file('/etc/resolve.conf') + if not domain is None: + try: + self.fstore.restore_file(os.path.join ("/var/named/", domain + ".zone.db")) + except ValueError, error: + logging.debug(error) + pass + + for f in ["/etc/named.conf", "/etc/resolve.conf"]: + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass + + if not enabled is None and not enabled: + self.chkconfig_off() if not running is None and running: self.start() diff --git a/ipa-server/ipaserver/certs.py b/ipa-server/ipaserver/certs.py index 2ad842c..12fb354 100644 --- a/ipa-server/ipaserver/certs.py +++ b/ipa-server/ipaserver/certs.py @@ -21,10 +21,11 @@ import os, stat, subprocess, re import sha import errno +from ipa import sysrestore from ipa import ipautil class CertDB(object): - def __init__(self, dir): + def __init__(self, dir, fstore=None): self.secdir = dir self.noise_fname = self.secdir + "/noise.txt" @@ -57,7 +58,12 @@ class CertDB(object): mode = os.stat(self.secdir) self.uid = mode[stat.ST_UID] self.gid = mode[stat.ST_GID] - + + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + def set_serial_from_pkcs12(self): """A CA cert was loaded from a PKCS#12 file. Set up our serial file""" @@ -188,7 +194,7 @@ class CertDB(object): return x.group(1) raise RuntimeError("Unable to find serial number") - + def create_server_cert(self, nickname, name, other_certdb=None): cdb = other_certdb if not cdb: @@ -198,7 +204,7 @@ class CertDB(object): self.add_cert(self.certder_fname, nickname) os.unlink(self.certreq_fname) os.unlink(self.certder_fname) - + def create_signing_cert(self, nickname, name, other_certdb=None): cdb = other_certdb if not cdb: @@ -322,7 +328,6 @@ class CertDB(object): server_certs.append((name, flags)) return server_certs - def import_pkcs12(self, pkcs12_fname, passwd_fname=None): args = ["/usr/bin/pk12util", "-d", self.secdir, @@ -369,13 +374,13 @@ class CertDB(object): self.export_ca_cert(False) def backup_files(self): - sysrestore.backup_file(self.noise_fname) - sysrestore.backup_file(self.passwd_fname) - sysrestore.backup_file(self.certdb_fname) - sysrestore.backup_file(self.keydb_fname) - sysrestore.backup_file(self.secmod_fname) - sysrestore.backup_file(self.cacert_fname) - sysrestore.backup_file(self.pk12_fname) - sysrestore.backup_file(self.pin_fname) - sysrestore.backup_file(self.certreq_fname) - sysrestore.backup_file(self.certder_fname) + self.fstore.backup_file(self.noise_fname) + self.fstore.backup_file(self.passwd_fname) + self.fstore.backup_file(self.certdb_fname) + self.fstore.backup_file(self.keydb_fname) + self.fstore.backup_file(self.secmod_fname) + self.fstore.backup_file(self.cacert_fname) + self.fstore.backup_file(self.pk12_fname) + self.fstore.backup_file(self.pin_fname) + self.fstore.backup_file(self.certreq_fname) + self.fstore.backup_file(self.certder_fname) diff --git a/ipa-server/ipaserver/httpinstance.py b/ipa-server/ipaserver/httpinstance.py index da89f6b..a55cf25 100644 --- a/ipa-server/ipaserver/httpinstance.py +++ b/ipa-server/ipaserver/httpinstance.py @@ -29,10 +29,10 @@ import sys import shutil import service -import sysrestore import certs import dsinstance import installutils +from ipa import sysrestore from ipa import ipautil HTTPD_DIR = "/etc/httpd" @@ -52,8 +52,12 @@ class WebGuiInstance(service.SimpleServiceInstance): service.SimpleServiceInstance.__init__(self, "ipa_webgui") class HTTPInstance(service.Service): - def __init__(self): + def __init__(self, fstore = None): service.Service.__init__(self, "httpd") + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') def create_instance(self, realm, fqdn, domain_name, autoconfig=True, pkcs12_info=None): self.fqdn = fqdn @@ -61,7 +65,7 @@ class HTTPInstance(service.Service): self.domain = domain_name self.pkcs12_info = pkcs12_info self.sub_dict = { "REALM" : realm, "FQDN": fqdn, "DOMAIN" : self.domain } - + self.step("disabling mod_ssl in httpd", self.__disable_mod_ssl) self.step("Setting mod_nss port to 443", self.__set_mod_nss_port) self.step("Adding URL rewriting rules", self.__add_include) @@ -109,7 +113,7 @@ class HTTPInstance(service.Service): ipautil.run(["/usr/sbin/setsebool", "-P", "httpd_can_network_connect", "true"]) except: self.print_msg(selinux_warning) - + def __create_http_keytab(self): http_principal = "HTTP/" + self.fqdn + "@" + self.realm installutils.kadmin_addprinc(http_principal) @@ -120,24 +124,24 @@ class HTTPInstance(service.Service): def __configure_http(self): http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa.conf", self.sub_dict) - sysrestore.backup_file("/etc/httpd/conf.d/ipa.conf") + self.fstore.backup_file("/etc/httpd/conf.d/ipa.conf") http_fd = open("/etc/httpd/conf.d/ipa.conf", "w") http_fd.write(http_txt) http_fd.close() http_txt = ipautil.template_file(ipautil.SHARE_DIR + "ipa-rewrite.conf", self.sub_dict) - sysrestore.backup_file("/etc/httpd/conf.d/ipa-rewrite.conf") + self.fstore.backup_file("/etc/httpd/conf.d/ipa-rewrite.conf") http_fd = open("/etc/httpd/conf.d/ipa-rewrite.conf", "w") http_fd.write(http_txt) http_fd.close() def __disable_mod_ssl(self): if os.path.exists(SSL_CONF): - sysrestore.backup_file(SSL_CONF) + self.fstore.backup_file(SSL_CONF) os.unlink(SSL_CONF) def __set_mod_nss_port(self): - sysrestore.backup_file(NSS_CONF) + self.fstore.backup_file(NSS_CONF) if installutils.update_file(NSS_CONF, '8443', '443') != 0: print "Updating port in %s failed." % NSS_CONF @@ -160,7 +164,7 @@ class HTTPInstance(service.Service): prefs_txt = ipautil.template_file(ipautil.SHARE_DIR + "preferences.html.template", self.sub_dict) prefs_fd = open("/usr/share/ipa/html/preferences.html", "w") prefs_fd.write(prefs_txt) - prefs_fd.close() + prefs_fd.close() # The signing cert is generated in __setup_ssl ds_ca = certs.CertDB(dsinstance.config_dirname(dsinstance.realm_to_serverid(self.realm))) @@ -189,7 +193,11 @@ class HTTPInstance(service.Service): self.chkconfig_off() for f in ["/etc/httpd/conf.d/ipa.conf", SSL_CONF, NSS_CONF]: - sysrestore.restore_file(f) + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass sebool_state = self.restore_state("httpd_can_network_connect") if not sebool_state is None: diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index 9d0a257..778b4e2 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -31,8 +31,8 @@ import socket import shutil import service -import sysrestore import installutils +from ipa import sysrestore from ipa import ipautil from ipa import ipaerror @@ -71,9 +71,9 @@ def update_key_val_in_file(filename, key, val): class KpasswdInstance(service.SimpleServiceInstance): def __init__(self): service.SimpleServiceInstance.__init__(self, "ipa_kpasswd") - + class KrbInstance(service.Service): - def __init__(self): + def __init__(self, fstore=None): service.Service.__init__(self, "krb5kdc") self.ds_user = None self.fqdn = None @@ -88,9 +88,14 @@ class KrbInstance(service.Service): self.kpasswd = KpasswdInstance() + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + def __common_setup(self, ds_user, realm_name, host_name, domain_name, admin_password): self.ds_user = ds_user - self.fqdn = host_name + self.fqdn = host_name self.realm = realm_name.upper() self.host = host_name.split(".")[0] self.ip = socket.gethostbyname(host_name) @@ -161,16 +166,16 @@ class KrbInstance(service.Service): self.kpasswd.create_instance() def __copy_ldap_passwd(self, filename): - sysrestore.backup_file("/var/kerberos/krb5kdc/ldappwd") + self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") os.chmod("/var/kerberos/krb5kdc/ldappwd", 0600) - - + + def __configure_kdc_account_password(self): hexpwd = '' for x in self.kdc_password: hexpwd += (hex(ord(x))[2:]) - sysrestore.backup_file("/var/kerberos/krb5kdc/ldappwd") + self.fstore.backup_file("/var/kerberos/krb5kdc/ldappwd") pwd_fd = open("/var/kerberos/krb5kdc/ldappwd", "w") pwd_fd.write("uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix+"#{HEX}"+hexpwd+"\n") pwd_fd.close() @@ -273,7 +278,7 @@ class KrbInstance(service.Service): def __template_file(self, path): template = os.path.join(ipautil.SHARE_DIR, os.path.basename(path) + ".template") conf = ipautil.template_file(template, self.sub_dict) - sysrestore.backup_file(path) + self.fstore.backup_file(path) fd = open(path, "w+") fd.write(conf) fd.close() @@ -347,10 +352,10 @@ class KrbInstance(service.Service): ldap_principal = "ldap/" + self.fqdn + "@" + self.realm installutils.kadmin_addprinc(ldap_principal) - sysrestore.backup_file("/etc/dirsrv/ds.keytab") + self.fstore.backup_file("/etc/dirsrv/ds.keytab") installutils.create_keytab("/etc/dirsrv/ds.keytab", ldap_principal) - sysrestore.backup_file("/etc/sysconfig/dirsrv") + self.fstore.backup_file("/etc/sysconfig/dirsrv") update_key_val_in_file("/etc/sysconfig/dirsrv", "export KRB5_KTNAME", "/etc/dirsrv/ds.keytab") pent = pwd.getpwnam(self.ds_user) os.chown("/etc/dirsrv/ds.keytab", pent.pw_uid, pent.pw_gid) @@ -359,7 +364,7 @@ class KrbInstance(service.Service): host_principal = "host/" + self.fqdn + "@" + self.realm installutils.kadmin_addprinc(host_principal) - sysrestore.backup_file("/etc/krb5.keytab") + self.fstore.backup_file("/etc/krb5.keytab") installutils.create_keytab("/etc/krb5.keytab", host_principal) # Make sure access is strictly reserved to root only for now @@ -369,10 +374,10 @@ class KrbInstance(service.Service): def __export_kadmin_changepw_keytab(self): installutils.kadmin_modprinc("kadmin/changepw", "+requires_preauth") - sysrestore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") + self.fstore.backup_file("/var/kerberos/krb5kdc/kpasswd.keytab") installutils.create_keytab("/var/kerberos/krb5kdc/kpasswd.keytab", "kadmin/changepw") - sysrestore.backup_file("/etc/sysconfig/ipa_kpasswd") + self.fstore.backup_file("/etc/sysconfig/ipa_kpasswd") update_key_val_in_file("/etc/sysconfig/ipa_kpasswd", "export KRB5_KTNAME", "/var/kerberos/krb5kdc/kpasswd.keytab") pent = pwd.getpwnam(self.ds_user) os.chown("/var/kerberos/krb5kdc/kpasswd.keytab", pent.pw_uid, pent.pw_gid) @@ -386,21 +391,15 @@ class KrbInstance(service.Service): if not running is None: self.stop() + for f in ["/var/kerberos/krb5kdc/ldappwd", "/var/kerberos/krb5kdc/kdc.conf", "/etc/krb5.conf"]: + try: + self.fstore.restore_file(f) + except ValueError, error: + logging.debug(error) + pass + if not enabled is None and not enabled: self.chkconfig_off() - for f in ["/var/kerberos/krb5kdc/ldappwd", - "/var/kerberos/krb5kdc/kdc.conf", - "/etc/krb5.conf", - "/usr/share/ipa/html/krb5.ini", - "/usr/share/ipa/html/krb.con", - "/usr/share/ipa/html/krbrealm.con", - "/etc/dirsrv/ds.keytab", - "/etc/sysconfig/dirsrv", - "/etc/krb5.keytab", - "/var/kerberos/krb5kdc/kpasswd.keytab", - "/etc/sysconfig/ipa_kpasswd"]: - sysrestore.restore_file(f) - if not running is None and running: self.start() diff --git a/ipa-server/ipaserver/ntpinstance.py b/ipa-server/ipaserver/ntpinstance.py index 5385881..e2ec606 100644 --- a/ipa-server/ipaserver/ntpinstance.py +++ b/ipa-server/ipaserver/ntpinstance.py @@ -18,15 +18,21 @@ # import shutil +import logging import service -import sysrestore +from ipa import sysrestore from ipa import ipautil class NTPInstance(service.Service): - def __init__(self): + def __init__(self, fstore=None): service.Service.__init__(self, "ntpd") + if fstore: + self.fstore = fstore + else: + self.fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') + def __write_config(self): # The template sets the config to point towards ntp.pool.org, but # they request that software not point towards the default pool. @@ -47,8 +53,8 @@ class NTPInstance(service.Service): ntp_conf = ipautil.template_file(ipautil.SHARE_DIR + "ntp.conf.server.template", sub_dict) ntp_sysconf = ipautil.template_file(ipautil.SHARE_DIR + "ntpd.sysconfig.template", {}) - sysrestore.backup_file("/etc/ntp.conf") - sysrestore.backup_file("/etc/sysconfig/ntpd") + self.fstore.backup_file("/etc/ntp.conf") + self.fstore.backup_file("/etc/sysconfig/ntpd") fd = open("/etc/ntp.conf", "w") fd.write(ntp_conf) @@ -87,10 +93,15 @@ class NTPInstance(service.Service): if not running is None: self.stop() + + try: + self.fstore.restore_file("/etc/ntp.conf") + except ValueError, error: + logging.debug(error) + pass + if not enabled is None and not enabled: self.chkconfig_off() - sysrestore.restore_file("/etc/ntp.conf") - if not running is None and running: self.start() diff --git a/ipa-server/ipaserver/service.py b/ipa-server/ipaserver/service.py index dbdc9a7..f9270f2 100644 --- a/ipa-server/ipaserver/service.py +++ b/ipa-server/ipaserver/service.py @@ -18,7 +18,7 @@ # import logging, sys -import sysrestore +from ipa import sysrestore from ipa import ipautil @@ -30,7 +30,7 @@ def start(service_name): def restart(service_name): ipautil.run(["/sbin/service", service_name, "restart"]) - + def is_running(service_name): ret = True try: @@ -38,7 +38,7 @@ def is_running(service_name): except ipautil.CalledProcessError: ret = False return ret - + def chkconfig_on(service_name): ipautil.run(["/sbin/chkconfig", service_name, "on"]) @@ -70,12 +70,12 @@ def is_enabled(service_name): break return (runlevels[3] and runlevels[4] and runlevels[5]) - + def print_msg(message, output_fd=sys.stdout): logging.debug(message) output_fd.write(message) output_fd.write("\n") - + class Service: def __init__(self, service_name): @@ -85,7 +85,7 @@ class Service: def set_output(self, fd): self.output_fd = fd - + def stop(self): stop(self.service_name) @@ -133,7 +133,7 @@ class Service: self.print_msg(" [%d/%d]: %s" % (step+1, len(self.steps), message)) method() step += 1 - + self.print_msg("done configuring %s." % self.service_name) self.steps = [] diff --git a/ipa-server/ipaserver/sysrestore.py b/ipa-server/ipaserver/sysrestore.py deleted file mode 100644 index 4954b8d..0000000 --- a/ipa-server/ipaserver/sysrestore.py +++ /dev/null @@ -1,253 +0,0 @@ -# Authors: Mark McLoughlin -# -# 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 only -# -# 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 -# - -# -# This module provides a very simple API which allows -# ipa-server-install --uninstall to restore certain -# parts of the system configuration to the way it was -# before ipa-server-install was first run -# - -import os -import os.path -import errno -import shutil -import logging -import ConfigParser - -from ipa import ipautil - -SYSRESTORE_CACHE_PATH = "/var/cache/ipa/sysrestore" -SYSRESTORE_STATEFILE_PATH = "/var/cache/ipa/sysrestore.state" - -def _mktree(basedir, reldir): - """Create the tree of directories specified by @reldir - under the directory @base. - - Caveats: - - @basedir must exist - - @reldir must not be absolute - - @reldir must refer to a directory - """ - (parentdir, subdir) = os.path.split(reldir) - if parentdir: - _mktree(basedir, parentdir) - - absdir = os.path.join(basedir, reldir) - try: - logging.debug("Creating directory '%s'", absdir) - os.mkdir(absdir) - except OSError, err: - if err.errno != errno.EEXIST: - raise err - -def _rmtree(basedir, reldir): - """Delete a tree of directories specified by @reldir - under the directory @base, excluding the @base itself. - Only empty directories will be deleted. - - Caveats: - - @reldir must not be absolute - - @reldir must refer to a directory - """ - absdir = os.path.join(basedir, reldir) - try: - logging.debug("Deleting directory '%s'", absdir) - os.rmdir(absdir) - except OSError, err: - if err.errno == errno.ENOTEMPTY: - logging.debug("Directory '%s' not empty", absdir) - return - else: - raise err - - (parentdir, subdir) = os.path.split(reldir) - if parentdir: - _rmtree(basedir, parentdir) - -def backup_file(path): - """Create a copy of the file at @path - so long as a copy - does not already exist - which will be restored to its - original location by restore_files(). - """ - logging.debug("Backing up system configuration file '%s'", path) - - if not os.path.isabs(path): - raise ValueError("Absolute path required") - - if not os.path.isfile(path): - logging.debug(" -> Not backing up - '%s' doesn't exist", path) - return - - relpath = path[1:] - - backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath) - if os.path.exists(backup_path): - logging.debug(" -> Not backing up - already have a copy of '%s'", path) - return - - (reldir, file) = os.path.split(relpath) - if reldir: - _mktree(SYSRESTORE_CACHE_PATH, reldir) - - shutil.copy2(path, backup_path) - -def restore_file(path): - """Restore the copy of a file at @path to its original - location and delete the copy. - - Returns #True if the file was restored, #False if there - was no backup file to restore - """ - logging.debug("Restoring system configuration file '%s'", path) - - if not os.path.isabs(path): - raise ValueError("Absolute path required") - - relpath = path[1:] - - backup_path = os.path.join(SYSRESTORE_CACHE_PATH, relpath) - if not os.path.exists(backup_path): - logging.debug(" -> Not restoring - '%s' doesn't exist", backup_path) - return False - - shutil.move(backup_path, path) - - ipautil.run(["/sbin/restorecon", path]) - - (reldir, file) = os.path.split(relpath) - if reldir: - _rmtree(SYSRESTORE_CACHE_PATH, reldir) - - return True - -class _StateFile: - """A metadata file for recording system state which can - be backed up and later restored. The format is something - like: - - [httpd] - running=True - enabled=False - """ - - def __init__(self, path = SYSRESTORE_STATEFILE_PATH): - """Create a _StateFile object, loading from @path. - - The dictionary @modules, a member of the returned object, - is where the state can be modified. @modules is indexed - using a module name to return another dictionary containing - key/value pairs with the saved state of that module. - - The keys in these latter dictionaries are arbitrary strings - and the values may either be strings or booleans. - """ - self._path = path - - self.modules = {} - - self._load() - - def _load(self): - """Load the modules from the file @_path. @modules will - be an empty dictionary if the file doesn't exist. - """ - logging.debug("Loading StateFile from '%s'", self._path) - - self.modules = {} - - p = ConfigParser.SafeConfigParser() - p.read(self._path) - - for module in p.sections(): - self.modules[module] = {} - for (key, value) in p.items(module): - if value == str(True): - value = True - elif value == str(False): - value = False - self.modules[module][key] = value - - def save(self): - """Save the modules to @_path. If @modules is an empty - dict, then @_path should be removed. - """ - logging.debug("Saving StateFile to '%s'", self._path) - - for module in self.modules.keys(): - if len(self.modules[module]) == 0: - del self.modules[module] - - if len(self.modules) == 0: - logging.debug(" -> no modules, removing file") - if os.path.exists(self._path): - os.remove(self._path) - return - - p = ConfigParser.SafeConfigParser() - - for module in self.modules.keys(): - p.add_section(module) - for (key, value) in self.modules[module].items(): - p.set(module, key, str(value)) - - f = file(self._path, "w") - p.write(f) - f.close() - -def backup_state(module, key, value): - """Backup an item of system state from @module, identified - by the string @key and with the value @value. @value may be - a string or boolean. - """ - if not (isinstance(value, str) or isinstance(value, bool)): - raise ValueError("Only strings or booleans supported") - - state = _StateFile() - - if not state.modules.has_key(module): - state.modules[module] = {} - - if not state.modules.has_key(key): - state.modules[module][key] = value - - state.save() - -def restore_state(module, key): - """Return the value of an item of system state from @module, - identified by the string @key, and remove it from the backed - up system state. - - If the item doesn't exist, #None will be returned, otherwise - the original string or boolean value is returned. - """ - state = _StateFile() - - if not state.modules.has_key(module): - return None - - if not state.modules[module].has_key(key): - return None - - value = state.modules[module][key] - del state.modules[module][key] - - state.save() - - return value