From 0fe6d426ed1b124ee156f24b692e0203f0d0f349 Mon Sep 17 00:00:00 2001 From: lars Date: Fri, 3 Nov 2006 14:27:19 +0000 Subject: [PATCH] added mounting and unmounting of config partition moved config partition handling to CryptoBoxSettings implemented environment checks (writeable config, https (off for now)) chown mounted directory after mount to the cryptobox user --- pythonrewrite/bin/CryptoBox.py | 28 ++------ pythonrewrite/bin/CryptoBoxContainer.py | 49 +++++++------ pythonrewrite/bin/CryptoBoxRootActions.py | 27 +++++-- pythonrewrite/bin/CryptoBoxSettings.py | 88 +++++++++++++++++++++++ pythonrewrite/bin/WebInterfaceSites.py | 54 +++++++++++--- 5 files changed, 180 insertions(+), 66 deletions(-) diff --git a/pythonrewrite/bin/CryptoBox.py b/pythonrewrite/bin/CryptoBox.py index 44d51b4..9d245a2 100755 --- a/pythonrewrite/bin/CryptoBox.py +++ b/pythonrewrite/bin/CryptoBox.py @@ -19,6 +19,7 @@ from CryptoBoxExceptions import * import re import os import CryptoBoxTools +import subprocess @@ -75,7 +76,6 @@ class CryptoBox: def __runTestRootPriv(self): """try to run 'super' with 'CryptoBoxRootActions'""" - import subprocess try: devnull = open(os.devnull, "w") except IOError: @@ -130,25 +130,7 @@ class CryptoBoxProps(CryptoBox): self.containers.sort(cmp = lambda x,y: x.getName() < y.getName() and -1 or 1) - def getConfigPartitions(self): - """returns a sequence of found config partitions""" - import subprocess - proc = subprocess.Popen( - shell = False, - stdout = subprocess.PIPE, - args = [ - self.prefs["Programs"]["blkid"], - "-c", os.path.devnull, - "-t", "LABEL=%s" % self.prefs["Main"]["ConfigVolumeLabel"] ]) - (output, error) = proc.communicate() - if output: - return [e.strip().split(":",1)[0] for e in output.splitlines()] - else: - return [] - - def isConfigPartition(self, device): - import subprocess proc = subprocess.Popen( shell = False, stdout = subprocess.PIPE, @@ -270,9 +252,9 @@ class CryptoBoxProps(CryptoBox): '''reads all files in path LangDir and returns a list of basenames from existing hdf files, that should are all available languages''' - languages = [] - for file in os.listdir(self.prefs["Locations"]["LangDir"]): - if file.endswith(".hdf"): languages.append(file.rstrip(".hdf")) + languages = [ f.rstrip(".hdf") + for f in os.listdir(self.prefs["Locations"]["LangDir"]) + if f.endswith(".hdf") ] if len(languages) < 1: self.log.error("No .hdf files found! The website won't render properly.") return languages @@ -280,5 +262,5 @@ class CryptoBoxProps(CryptoBox): if __name__ == "__main__": - cb = CryptoBox() + cb = CryptoBoxProps() diff --git a/pythonrewrite/bin/CryptoBoxContainer.py b/pythonrewrite/bin/CryptoBoxContainer.py index a35210c..58bf850 100755 --- a/pythonrewrite/bin/CryptoBoxContainer.py +++ b/pythonrewrite/bin/CryptoBoxContainer.py @@ -45,7 +45,6 @@ class CryptoBoxContainer: self.device = device self.cbox = cbox self.log = logging.getLogger("CryptoBox") - self.Progs = self.cbox.prefs["Programs"] self.resetObject() @@ -157,8 +156,8 @@ class CryptoBoxContainer: stdout = subprocess.PIPE, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "cryptsetup", "luksAddKey", self.device, @@ -182,7 +181,7 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["cryptsetup"], + self.cbox.prefs["Programs"]["cryptsetup"], "--batch-mode", "luksDelKey", self.device, @@ -228,7 +227,7 @@ class CryptoBoxContainer: stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - args=[self.Progs["blkid"], + args=[self.cbox.prefs["Programs"]["blkid"], "-s", "UUID", "-o", "value", "-c", os.devnull, @@ -267,7 +266,7 @@ class CryptoBoxContainer: stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - args=[self.Progs["blkid"], + args=[self.cbox.prefs["Programs"]["blkid"], "-s", "TYPE", "-o", "value", "-c", os.devnull, @@ -295,7 +294,7 @@ class CryptoBoxContainer: stdout = devnull, stderr = devnull, args = [ - self.Progs["cryptsetup"], + self.cbox.prefs["Programs"]["cryptsetup"], "--batch-mode", "isLuks", self.device]) @@ -332,8 +331,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "cryptsetup", "luksOpen", self.device, @@ -351,8 +350,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "mount", os.path.join(self.__dmDir, self.name), self.__getMountPoint()]) @@ -378,8 +377,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "umount", self.__getMountPoint()]) proc.wait() @@ -394,8 +393,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "cryptsetup", "luksClose", self.name, @@ -429,8 +428,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "mount", self.device, self.__getMountPoint()]) @@ -456,8 +455,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "umount", self.__getMountPoint()]) proc.wait() @@ -483,7 +482,7 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["mkfs-data"], + self.cbox.prefs["Programs"]["mkfs-data"], self.device]) proc.wait() if proc.returncode != 0: @@ -513,8 +512,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "cryptsetup", "luksFormat", self.device, @@ -534,8 +533,8 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["super"], - self.Progs["CryptoBoxRootActions"], + self.cbox.prefs["Programs"]["super"], + self.cbox.prefs["Programs"]["CryptoBoxRootActions"], "cryptsetup", "luksOpen", self.device, @@ -554,7 +553,7 @@ class CryptoBoxContainer: stdout = devnull, stderr = subprocess.PIPE, args = [ - self.Progs["mkfs-data"], + self.cbox.prefs["Programs"]["mkfs-data"], os.path.join(self.__dmDir, self.name)]) proc.wait() "remove the mapping - for every exit status" diff --git a/pythonrewrite/bin/CryptoBoxRootActions.py b/pythonrewrite/bin/CryptoBoxRootActions.py index e1dc92c..77fe7f6 100755 --- a/pythonrewrite/bin/CryptoBoxRootActions.py +++ b/pythonrewrite/bin/CryptoBoxRootActions.py @@ -78,7 +78,7 @@ def call_plugin(args): proc = subprocess.Popen( shell = False, args = args) - proc.communicate() + proc.wait() return proc.returncode == 0 @@ -169,7 +169,7 @@ def run_cryptsetup(args): proc = subprocess.Popen( shell = False, args = cs_args) - proc.communicate() + proc.wait() ## chown the devmapper block device to the cryptobox user if (proc.returncode == 0) and (action == "luksOpen"): os.chown(os.path.join(os.path.sep, "dev", "mapper", destination), os.getuid(), os.getgid()) @@ -214,10 +214,23 @@ def run_mount(args): proc = subprocess.Popen( shell = False, args = [allowedProgs["mount"], device, destination]) - proc.communicate() - # restore previous real uid - os.setuid(savedUID) - return proc.returncode == 0 + proc.wait() + ## return in case of an error + if proc.returncode != 0: + return False + ## chown the mounted directory - otherwise it will not be writeable for + ## the cryptobox user (at least for the configuration partition this is + ## absolutely necessary) TODO: check if this is valid for data, too + (trustUserName, trustUID, groupsOfTrustUser) = getUserInfo(savedUID) + try: + os.chown(destination, trustUID, groupsOfTrustUser[0]) + except OSError, errMsg: + sys.stderr.write("could not chown the mount destination (%s) to the specified user (%d/%d): %s\n" % (destination, trustUID, groupsOfTrustUser[0], errMsg)) + sys.stderr.write("UID: %d\n" % (os.geteuid(),)) + return False + ## BEWARE: it would be nice, if we could restore the previous uid (not euid) but + ## this would also override the euid (see 'man 2 setuid') - any ideas? + return True def run_umount(args): @@ -242,7 +255,7 @@ def run_umount(args): proc = subprocess.Popen( shell = False, args = [allowedProgs["umount"], "-l", destination]) - proc.communicate() + proc.wait() # restore previous real uid os.setuid(savedUID) return proc.returncode == 0 diff --git a/pythonrewrite/bin/CryptoBoxSettings.py b/pythonrewrite/bin/CryptoBoxSettings.py index 222cd57..d8cd681 100644 --- a/pythonrewrite/bin/CryptoBoxSettings.py +++ b/pythonrewrite/bin/CryptoBoxSettings.py @@ -5,6 +5,7 @@ except: raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'validate'! Try 'apt-get install python-formencode'.") import os import CryptoBoxExceptions +import subprocess try: import configobj ## needed for reading and writing of the config file except: @@ -65,6 +66,92 @@ class CryptoBoxSettings: return ok + def isWriteable(self): + return os.access(self.prefs["Locations"]["SettingsDir"], os.W_OK) + + + def getActivePartition(self): + settings_dir = self.prefs["Locations"]["SettingsDir"] + if not os.path.ismount(settings_dir): return None + for line in file("/proc/mounts"): + fields = line.split(" ") + mount_dir = fields[1] + try: + if os.path.samefile(mount_dir, settings_dir): return fields[0] + except OSError: + pass + ## no matching entry found + return None + + + def mountPartition(self): + if self.isWriteable(): + self.log.warn("mountConfigPartition: configuration is already writeable - mounting anyway") + if self.getActivePartition(): + self.log.warn("mountConfigPartition: configuration partition already mounted - not mounting again") + return False + confPartitions = self.getAvailablePartitions() + if not confPartitions: + self.log.error("no configuration partitions found - you have to create it first") + return False + partition = confPartitions[0] + proc = subprocess.Popen( + shell = False, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + args = [ + self.prefs["Programs"]["super"], + self.prefs["Programs"]["CryptoBoxRootActions"], + "mount", + partition, + self.prefs["Locations"]["SettingsDir"]]) + (stdout, stderr) = proc.communicate() + if proc.returncode != 0: + self.log.error("failed to mount the configuration partition: %s" % partition) + self.log.error("output of mount: %s" % (stderr,)) + return False + self.log.info("configuration partition mounted: %s" % partition) + return True + + + def umountPartition(self): + if not self.getActivePartition(): + self.log.warn("umountConfigPartition: no configuration partition mounted") + return False + proc = subprocess.Popen( + shell = False, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + args = [ + self.prefs["Programs"]["super"], + self.prefs["Programs"]["CryptoBoxRootActions"], + "umount", + self.prefs["Locations"]["SettingsDir"]]) + (stdout, stderr) = proc.communicate() + if proc.returncode != 0: + self.log.error("failed to unmount the configuration partition") + self.log.error("output of mount: %s" % (stderr,)) + return False + self.log.info("configuration partition unmounted") + return True + + + def getAvailablePartitions(self): + """returns a sequence of found config partitions""" + proc = subprocess.Popen( + shell = False, + stdout = subprocess.PIPE, + args = [ + self.prefs["Programs"]["blkid"], + "-c", os.path.devnull, + "-t", "LABEL=%s" % self.prefs["Main"]["ConfigVolumeLabel"] ]) + (output, error) = proc.communicate() + if output: + return [e.strip().split(":",1)[0] for e in output.splitlines()] + else: + return [] + + def __getitem__(self, key): """redirect all requests to the 'prefs' attribute""" return self.prefs[key] @@ -282,6 +369,7 @@ Language = string(min=1, default="en") cryptsetup = fileExecutable(default="/sbin/cryptsetup") mkfs-data = fileExecutable(default="/sbin/mkfs.ext3") blkid = fileExecutable(default="/sbin/blkid") +blockdev = fileExecutable(default="/sbin/blockdev") mount = fileExecutable(default="/bin/mount") umount = fileExecutable(default="/bin/umount") super = fileExecutable(default="/usr/bin/super") diff --git a/pythonrewrite/bin/WebInterfaceSites.py b/pythonrewrite/bin/WebInterfaceSites.py index d13f9c0..0e8cde1 100755 --- a/pythonrewrite/bin/WebInterfaceSites.py +++ b/pythonrewrite/bin/WebInterfaceSites.py @@ -5,6 +5,16 @@ import Plugins from CryptoBoxExceptions import * import cherrypy import types +import os + +try: + import neo_cgi, neo_util, neo_cs +except ImportError: + errorMsg = "Could not import clearsilver module. Try 'apt-get install python-clearsilver'." + self.log.error(errorMsg) + sys.stderr.write(errorMsg) + raise ImportError, errorMsg + class WebInterfacePlugins: @@ -115,6 +125,7 @@ class WebInterfaceSites: def index(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) + self.__checkEnvironment() ## do not forget the language! param_dict = {"weblang":weblang} ## render "disks" plugin by default @@ -124,6 +135,7 @@ class WebInterfaceSites: def return_plugin_action(self, plugin): def handler(self, **args): self.__resetDataset() + self.__checkEnvironment() args_orig = dict(args) try: self.__setWebLang(args["weblang"]) @@ -180,6 +192,7 @@ class WebInterfaceSites: def test(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) + self.__checkEnvironment() return "test passed" @@ -197,6 +210,36 @@ class WebInterfaceSites: ##################### input checker ########################## + + def __checkEnvironment(self): + """here we should place all interesting checks to inform the user of problems + + examples are: non-https, readonly-config, ... + """ + if not self.cbox.prefs.isWriteable(): + self.dataset["Data.EnvironmentWarning"] = "ReadOnlyConfig" + # TODO: turn this on soon (add "not") - for now it is annoying + if self.__checkHTTPS(): + self.dataset["Data.EnvironmentWarning"] = "NoSSL" + + + def __checkHTTPS(self): + ## check the request scheme + if cherrypy.request.scheme == "https": return True + ## check an environment setting - this is quite common behind proxies + try: + if os.environ["HTTPS"]: return True + except KeyError: + pass + ## check http header TODO (check pound for the name) + try: + if cherrypy.request.headers["TODO"]: return True + except KeyError: + pass + ## the connection seems to be unencrypted + return False + + def __setWebLang(self, value): guess = value availLangs = self.cbox.getAvailableLanguages() @@ -229,7 +272,6 @@ class WebInterfaceSites: """guess the preferred language of the user (as sent by the browser) take the first language, that is part of 'availLangs' """ - import cherrypy try: pref_lang_header = cherrypy.request.headers["Accept-Language"] except KeyError: @@ -277,7 +319,6 @@ class WebInterfaceSites: def __getLanguageData(self, web_lang="en"): - import neo_cgi, neo_util, os default_lang = "en" conf_lang = self.prefs["WebSettings"]["Language"] hdf = neo_util.HDF() @@ -302,15 +343,6 @@ class WebInterfaceSites: def __render(self, renderInfo, plugin=None): '''renders from clearsilver templates and returns the resulting html ''' - import os, types - try: - import neo_cgi, neo_util, neo_cs - except ImportError: - errorMsg = "Could not import clearsilver module. Try 'apt-get install python-clearsilver'." - self.log.error(errorMsg) - sys.stderr.write(errorMsg) - raise ImportError, errorMsg - ## is renderInfo a string (filename of the template) or a dictionary? if type(renderInfo) == types.DictType: template = renderInfo["template"]