From 491d16899f8bfc7a25d275ab1ca89d5f6282c4eb Mon Sep 17 00:00:00 2001 From: lars Date: Wed, 11 Oct 2006 15:51:28 +0000 Subject: [PATCH] plugin configuration file added setting "NameDatabase" replaced by "SettingsDir" storing of local settings implemented (CryptoBoxSettings.write()) --- pythonrewrite/bin/CryptoBox.py | 6 + pythonrewrite/bin/CryptoBoxContainer.py | 2 +- pythonrewrite/bin/CryptoBoxPlugin.py | 49 ++++++ pythonrewrite/bin/CryptoBoxRootActions.py | 8 + pythonrewrite/bin/CryptoBoxSettings.py | 155 +++++++++++++++++- pythonrewrite/bin/CryptoBoxWebserver.py | 1 + pythonrewrite/bin/Plugins.py | 2 +- pythonrewrite/bin/WebInterfaceDataset.py | 12 +- pythonrewrite/bin/WebInterfaceSites.py | 15 +- pythonrewrite/bin/cryptobox.conf | 8 +- pythonrewrite/bin/cryptoboxwebserver.conf | 1 + pythonrewrite/bin/test.complete.CryptoBox.py | 2 +- pythonrewrite/bin/uml-setup.sh | 22 +++ pythonrewrite/bin/unittests.CryptoBox.py | 6 +- .../bin/unittests.CryptoBoxWebserver.py | 2 + 15 files changed, 267 insertions(+), 24 deletions(-) create mode 100755 pythonrewrite/bin/uml-setup.sh diff --git a/pythonrewrite/bin/CryptoBox.py b/pythonrewrite/bin/CryptoBox.py index 3c9532b..44d51b4 100755 --- a/pythonrewrite/bin/CryptoBox.py +++ b/pythonrewrite/bin/CryptoBox.py @@ -64,8 +64,14 @@ class CryptoBox: # do some initial checks def __runTests(self): + self.__runTestUID() self.__runTestRootPriv() + + def __runTestUID(self): + if os.geteuid() == 0: + raise CBEnvironmentError("you may not run the cryptobox as root") + def __runTestRootPriv(self): """try to run 'super' with 'CryptoBoxRootActions'""" diff --git a/pythonrewrite/bin/CryptoBoxContainer.py b/pythonrewrite/bin/CryptoBoxContainer.py index e0c7c41..078614a 100755 --- a/pythonrewrite/bin/CryptoBoxContainer.py +++ b/pythonrewrite/bin/CryptoBoxContainer.py @@ -104,7 +104,7 @@ class CryptoBoxContainer: if self.type == self.Types["luks"]: self.mount = self.__mountLuks self.umount = self.__umountLuks - if self.type == self.Types["plain"]: + elif self.type == self.Types["plain"]: self.mount = self.__mountPlain self.umount = self.__umountPlain diff --git a/pythonrewrite/bin/CryptoBoxPlugin.py b/pythonrewrite/bin/CryptoBoxPlugin.py index 7f83b32..599439f 100644 --- a/pythonrewrite/bin/CryptoBoxPlugin.py +++ b/pythonrewrite/bin/CryptoBoxPlugin.py @@ -14,6 +14,12 @@ class CryptoBoxPlugin: ## does this plugin require admin authentification? requestAuth = False + ## is this plugin enabled by default? + enabled = True + + ## default rank (0..100) of the plugin in listings (lower value means higher priority) + rank = 80 + def __init__(self, cbox, pluginDir): self.cbox = cbox self.hdf = {} @@ -69,3 +75,46 @@ class CryptoBoxPlugin: for (key, value) in self.hdf.items(): hdf.setValue(key, str(value)) + + def isAuthRequired(self): + """check if this plugin requires authentication + first step: check plugin configuration + second step: check default value of plugin""" + try: + if self.cbox.prefs.pluginConf[self.getName()]["requestAuth"] is None: + return self.requestAuth + if self.cbox.prefs.pluginConf[self.getName()]["requestAuth"]: + return True + else: + return False + except KeyError: + return self.requestAuth + + + def isEnabled(self): + """check if this plugin is enabled + first step: check plugin configuration + second step: check default value of plugin""" + import types + try: + if self.cbox.prefs.pluginConf[self.getName()]["enabled"] is None: + return self.enabled + if self.cbox.prefs.pluginConf[self.getName()]["enabled"]: + return True + else: + return False + except KeyError: + return self.enabled + + + def getRank(self): + """check the rank of this plugin + first step: check plugin configuration + second step: check default value of plugin""" + try: + if self.cbox.prefs.pluginConf[self.getName()]["rank"] is None: + return self.rank + return int(self.cbox.prefs.pluginConf[self.getName()]["rank"]) + except KeyError, TypeError: + return self.rank + diff --git a/pythonrewrite/bin/CryptoBoxRootActions.py b/pythonrewrite/bin/CryptoBoxRootActions.py index bdd3bef..e1dc92c 100755 --- a/pythonrewrite/bin/CryptoBoxRootActions.py +++ b/pythonrewrite/bin/CryptoBoxRootActions.py @@ -170,6 +170,9 @@ def run_cryptsetup(args): shell = False, args = cs_args) proc.communicate() + ## 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()) return proc.returncode == 0 @@ -282,6 +285,11 @@ if __name__ == "__main__": if os.getuid() == 0: sys.stderr.write("the uid of the caller is zero (root) - this is not allowed\n") sys.exit(100) + + # check if there were arguments + if (len(args) == 0): + sys.stderr.write("No arguments supplied\n") + sys.exit(100) # did the user call the "check" action? if (len(args) == 1) and (args[0].lower() == "check"): diff --git a/pythonrewrite/bin/CryptoBoxSettings.py b/pythonrewrite/bin/CryptoBoxSettings.py index 4e82058..0d2af22 100644 --- a/pythonrewrite/bin/CryptoBoxSettings.py +++ b/pythonrewrite/bin/CryptoBoxSettings.py @@ -8,12 +8,17 @@ except: raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'configobj'! Try 'apt-get install python-configobj'.") + class CryptoBoxSettings: CONF_LOCATIONS = [ "./cryptobox.conf", "~/.cryptobox.conf", "/etc/cryptobox/cryptobox.conf"] + + NAMEDB_FILE = "cryptobox_names.db" + PLUGINCONF_FILE = "cryptobox_plugins.conf" + USERDB_FILE = "cryptobox_users.db" def __init__(self, config_file=None): @@ -25,8 +30,38 @@ class CryptoBoxSettings: self.__configureLogHandler() self.__checkUnknownPreferences() self.nameDB = self.__getNameDatabase() + self.pluginConf = self.__getPluginConfig() + self.userDB = self.__getUserDB() + self.misc_files = self.__getMiscFiles() + def write(self): + """ + write all local setting files including the content of the "misc" subdirectory + """ + ok = True + try: + self.nameDB.write() + except IOError: + self.log.warn("could not save the name database") + ok = False + try: + self.pluginConf.write() + except IOError: + self.log.warn("could not save the plugin configuration") + ok = False + try: + self.userDB.write() + except IOError: + self.log.warn("could not save the user database") + ok = False + for misc_file in self.misc_files: + if not misc_file.save(): + self.log.warn("could not save a misc setting file (%s)" % misc_file.filename) + ok = False + return ok + + def __getitem__(self, key): """redirect all requests to the 'prefs' attribute""" return self.prefs[key] @@ -92,12 +127,12 @@ class CryptoBoxSettings: def __getNameDatabase(self): try: try: - nameDB_file = self.prefs["Locations"]["NameDatabase"] + nameDB_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.NAMEDB_FILE) except KeyError: - raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "NameDatabase") + raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir") except SyntaxError: - raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "NameDatabase", nameDB_file, "failed to interprete the filename of the name database correctly") - ## create nameDB is necessary + raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", nameDB_file, "failed to interprete the filename of the name database correctly (%s)" % nameDB_file) + ## create nameDB if necessary if os.path.exists(nameDB_file): nameDB = configobj.ConfigObj(nameDB_file) else: @@ -108,6 +143,63 @@ class CryptoBoxSettings: return nameDB + def __getPluginConfig(self): + import StringIO + plugin_rules = StringIO.StringIO(self.pluginValidationSpec) + try: + try: + pluginConf_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.PLUGINCONF_FILE) + except KeyError: + raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir") + except SyntaxError: + raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", pluginConf_file, "failed to interprete the filename of the plugin config file correctly (%s)" % pluginConf_file) + ## create pluginConf_file if necessary + if os.path.exists(pluginConf_file): + pluginConf = configobj.ConfigObj(pluginConf_file, configspec=plugin_rules) + else: + pluginConf = configobj.ConfigObj(pluginConf_file, configspec=plugin_rules, create_empty=True) + ## validate and convert values according to the spec + pluginConf.validate(validate.Validator()) + ## check if pluginConf_file file was created successfully? + if not os.path.exists(pluginConf_file): + raise CryptoBoxExceptions.CBEnvironmentError("failed to create plugin configuration file (%s)" % pluginConf_file) + return pluginConf + + + def __getUserDB(self): + import StringIO, sha + userDB_rules = StringIO.StringIO(self.userDatabaseSpec) + try: + try: + userDB_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.USERDB_FILE) + except KeyError: + raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir") + except SyntaxError: + raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", userDB_file, "failed to interprete the filename of the users database file correctly (%s)" % userDB_file) + ## create userDB_file if necessary + if os.path.exists(userDB_file): + userDB = configobj.ConfigObj(userDB_file, configspec=userDB_rules) + else: + userDB = configobj.ConfigObj(userDB_file, configspec=userDB_rules, create_empty=True) + ## validate and set default value for "admin" user + userDB.validate(validate.Validator()) + ## check if userDB file was created successfully? + if not os.path.exists(userDB_file): + raise CryptoBoxExceptions.CBEnvironmentError("failed to create user database file (%s)" % userDB_file) + ## define password hash function - never use "sha" directly - SPOT + userDB.getDigest = lambda password: sha.new(password).hexdigest() + return userDB + + + def __getMiscFiles(self): + misc_dir = os.path.join(self.prefs["Locations"]["SettingsDir"], "misc") + if (not os.path.isdir(misc_dir)) or (not os.access(misc_dir, os.X_OK)): + return [] + return [MiscConfigFile(os.path.join(misc_dir, f), self.log) + for f in os.listdir(misc_dir) + if os.path.isfile(os.path.join(misc_dir, f))] + + def __getConfigFileName(self, config_file): # search for the configuration file import types @@ -168,7 +260,7 @@ ConfigVolumeLabel = string(min=1,default="cbox_config") [Locations] MountParentDir = directoryExists(default="/var/cache/cryptobox/mnt") -NameDatabase = fileWriteable(default="/var/cache/cryptobox/volumen_names.db") +SettingsDir = directoryExists(default="/var/cache/cryptobox/settings") TemplateDir = directoryExists(default="/usr/share/cryptobox/template") LangDir = directoryExists(default="/usr/share/cryptobox/lang") DocDir = directoryExists(default="/usr/share/doc/cryptobox/html") @@ -194,6 +286,17 @@ super = fileExecutable(default="/usr/bin/super") CryptoBoxRootActions = string(min=1) """ + pluginValidationSpec = """ +[__many__] +enabled = boolean(default=None) +requestAuth = boolean(default=None) +rank = integer(default=None) + """ + + userDatabaseSpec = """ +[admins] +admin = string(default=d033e22ae348aeb5660fc2140aec35850c4da997) + """ class CryptoBoxSettingsValidator(validate.Validator): @@ -234,4 +337,46 @@ class CryptoBoxSettingsValidator(validate.Validator): return file_path raise validate.VdtValueError("%s (directory does not exist)" % value) return file_path + + + +class MiscConfigFile: + + maxSize = 20480 + + def __init__(self, filename, logger): + self.filename = filename + self.log = logger + self.load() + + def load(self): + fd = open(self.filename, "rb") + ## limit the maximum size + self.content = fd.read(self.maxSize) + if fd.tell() == self.maxSize: + self.log.warn("file in misc settings directory (%s) is bigger than allowed (%s)" % (self.filename, self.maxSize)) + fd.close() + + + def save(self): + save_dir = os.path.dirname(self.filename) + ## create the directory, if necessary + if not os.path.isdir(save_dir): + try: + os.mkdir(save_dir) + except IOError: + return False + ## save the content of the file + try: + fd = open(self.filename, "wb") + except IOError: + return False + try: + fd.write(self.content) + fd.close() + return True + except IOError: + fd.close() + return False + diff --git a/pythonrewrite/bin/CryptoBoxWebserver.py b/pythonrewrite/bin/CryptoBoxWebserver.py index 3b17ee6..b841262 100755 --- a/pythonrewrite/bin/CryptoBoxWebserver.py +++ b/pythonrewrite/bin/CryptoBoxWebserver.py @@ -1,6 +1,7 @@ #!/usr/bin/env python2.4 import os import WebInterfaceSites +import sys try: import cherrypy diff --git a/pythonrewrite/bin/Plugins.py b/pythonrewrite/bin/Plugins.py index 021923d..97a7e83 100644 --- a/pythonrewrite/bin/Plugins.py +++ b/pythonrewrite/bin/Plugins.py @@ -60,7 +60,7 @@ class PluginManager: if __name__ == "__main__": - x = PluginManager(None, None, "../plugins") + x = PluginManager(None, "../plugins") for a in x.getPlugins(): if not a is None: print "Plugin: %s" % a.getName() diff --git a/pythonrewrite/bin/WebInterfaceDataset.py b/pythonrewrite/bin/WebInterfaceDataset.py index f60319e..581c6ce 100644 --- a/pythonrewrite/bin/WebInterfaceDataset.py +++ b/pythonrewrite/bin/WebInterfaceDataset.py @@ -14,7 +14,12 @@ class WebInterfaceDataset(dict): self.cbox = cbox self.__setConfigValues() self.__setCryptoBoxState() - self.__setPluginList(plugins) + self.plugins = plugins + self.setPluginData() + + + def setPluginData(self): + self.__setPluginList(self.plugins) def setCurrentDiskState(self, device): @@ -63,6 +68,7 @@ class WebInterfaceDataset(dict): self["Settings.Stylesheet"] = self.prefs["WebSettings"]["Stylesheet"] self["Settings.Language"] = self.prefs["WebSettings"]["Language"] self["Settings.PluginDir"] = self.prefs["Locations"]["PluginDir"] + self["Settings.SettingsDir"] = self.prefs["Locations"]["SettingsDir"] def __setCryptoBoxState(self): @@ -85,8 +91,10 @@ class WebInterfaceDataset(dict): lang_data = p.getLanguageData() entryName = "Settings.PluginList." + p.getName() self[entryName] = p.getName() - self[entryName + ".Rank"] = lang_data.getValue("Rank", "100") self[entryName + ".Link"] = lang_data.getValue("Link", p.getName()) + self[entryName + ".Rank"] = p.getRank() + self[entryName + ".RequestAuth"] = p.isAuthRequired() and "1" or "0" + self[entryName + ".Enabled"] = p.isEnabled() and "1" or "0" for a in p.pluginCapabilities: self[entryName + ".Types." + a] = "1" diff --git a/pythonrewrite/bin/WebInterfaceSites.py b/pythonrewrite/bin/WebInterfaceSites.py index 9242b18..78fd975 100755 --- a/pythonrewrite/bin/WebInterfaceSites.py +++ b/pythonrewrite/bin/WebInterfaceSites.py @@ -5,9 +5,6 @@ import Plugins from CryptoBoxExceptions import * import cherrypy -# TODO: for now the admin access is defined statically -authDict = {"test": "tester"} - class WebInterfacePlugins: @@ -63,7 +60,7 @@ class WebInterfaceSites: ## this is a function decorator to check authentication ## it has to be defined before any page definition requiring authentification - def __requestAuth(self, authDict): + def __requestAuth(self=None): def check_credentials(site): def _inner_wrapper(self, *args, **kargs): import base64 @@ -81,8 +78,9 @@ class WebInterfaceSites: except AttributeError: ## no cherrypy response header defined pass + authDict = self.cbox.prefs.userDB["admins"] if user in authDict.keys(): - if password == authDict[user]: + if self.cbox.prefs.userDB.getDigest(password) == authDict[user]: ## ok: return the choosen page self.cbox.log.info("access granted for: %s" % user) return site(self, *args, **kargs) @@ -203,20 +201,21 @@ class WebInterfaceSites: self.dataset.setCurrentDiskState(plugin.device) if not nextTemplate: nextTemplate = "show_volume" else: + self.dataset.setPluginData() if not nextTemplate: nextTemplate = "form_system" ## save the currently active plugin name self.dataset["Data.ActivePlugin"] = plugin.getName() return self.__render(nextTemplate, plugin) ## apply authentication? - if plugin.requestAuth: - return lambda **args: self.__requestAuth(authDict)(handler)(self, **args) + if plugin.isAuthRequired(): + return lambda **args: self.__requestAuth()(handler)(self, **args) else: return lambda **args: handler(self, **args) ## test authentication @cherrypy.expose - @__requestAuth(None, authDict) + @__requestAuth def test(self, weblang=""): self.__resetDataset() self.__setWebLang(weblang) diff --git a/pythonrewrite/bin/cryptobox.conf b/pythonrewrite/bin/cryptobox.conf index 9d13953..42c1af5 100644 --- a/pythonrewrite/bin/cryptobox.conf +++ b/pythonrewrite/bin/cryptobox.conf @@ -2,7 +2,7 @@ # comma separated list of possible prefixes for accesible devices # beware: .e.g "/dev/hd" grants access to _all_ harddisks -AllowedDevices = /dev/loop, /dev/sda +AllowedDevices = /dev/loop # the default name prefix of not unnamed containers @@ -20,9 +20,9 @@ ConfigVolumeLabel = cbox_config # this directory must be writeable by the cryptobox user (see above) MountParentDir = /var/cache/cryptobox/mnt -# the name-database file - inside of DataDir -#NameDatabase = /var/cache/cryptobox/cryptobox_names.db -NameDatabase = cryptobox_names.db +# settings directory: contains name database and plugin configuration +#SettingsDir = /var/cache/cryptobox/settings +SettingsDir = . # where are the clearsilver templates? #TemplateDir = /usr/share/cryptobox/templates diff --git a/pythonrewrite/bin/cryptoboxwebserver.conf b/pythonrewrite/bin/cryptoboxwebserver.conf index 9b86133..b8e1e6a 100644 --- a/pythonrewrite/bin/cryptoboxwebserver.conf +++ b/pythonrewrite/bin/cryptoboxwebserver.conf @@ -3,6 +3,7 @@ server.socketPort = 8080 #server.environment = "production" server.environment = "development" server.logToScreen = True +server.log_tracebacks = True server.threadPool = 1 server.reverseDNS = False server.logFile = "cryptoboxwebserver.log" diff --git a/pythonrewrite/bin/test.complete.CryptoBox.py b/pythonrewrite/bin/test.complete.CryptoBox.py index 081e5b0..db5300d 100755 --- a/pythonrewrite/bin/test.complete.CryptoBox.py +++ b/pythonrewrite/bin/test.complete.CryptoBox.py @@ -24,7 +24,7 @@ def main(): for e in cb.getContainerList(): print "\t\t%d\t\t%s - %s - %d" % (cb.getContainerList().index(e), e.getDevice(), e.getName(), e.getType()) - if not cb.getContainerList(): + if not cb.getContainerList() or len(cb.getContainerList()) < 1: print "no loop devices found for testing" sys.exit(1) diff --git a/pythonrewrite/bin/uml-setup.sh b/pythonrewrite/bin/uml-setup.sh new file mode 100755 index 0000000..f299de7 --- /dev/null +++ b/pythonrewrite/bin/uml-setup.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +ROOT_IMG=~/devel-stuff/devel-chroots/cryptobox.img +TEST_IMG=test.img +TEST_SIZE=256 + +# Preparations: +# echo "tun" >>/etc/modules +# follow the instructions in /usr/share/doc/uml-utilities/README.Debian +# add your user to the group 'uml-net' +# + +/sbin/ifconfig tap0 &>/dev/null || { echo "tap0 is not configured - read /usr/share/doc/uml-utilities/README.Debian for hints"; exit 1; } + + +if [ ! -e "$TEST_IMG" ] + then echo "Creating testing image file ..." + dd if=/dev/zero of="$TEST_IMG" bs=1M count=$TEST_SIZE + fi + +linux ubd0="$ROOT_IMG" ubd1="$TEST_IMG" con=xterm hostfs=../ fakehd eth0=daemon + diff --git a/pythonrewrite/bin/unittests.CryptoBox.py b/pythonrewrite/bin/unittests.CryptoBox.py index 29ddd00..e597df0 100755 --- a/pythonrewrite/bin/unittests.CryptoBox.py +++ b/pythonrewrite/bin/unittests.CryptoBox.py @@ -33,6 +33,8 @@ class CryptoBoxPropsConfigTests(unittest.TestCase): "configFileOK" : "cbox-test_ok.conf", "configFileBroken" : "cbox-test_broken.conf", "nameDBFile" : "cryptobox_names.db", + "pluginConf" : "cryptobox_plugins.conf", + "userDB" : "cryptobox_users.db", "logFile" : "cryptobox.log", "tmpdir" : "cryptobox-mnt" } tmpdirname = "" @@ -43,7 +45,7 @@ AllowedDevices = /dev/loop DefaultVolumePrefix = "Data " DefaultCipher = aes-cbc-essiv:sha256 [Locations] -NameDatabase = %s/cryptobox_names.db +SettingsDir = %s MountParentDir = %s TemplateDir = ../templates LangDir = ../lang @@ -105,7 +107,7 @@ CryptoBoxRootActions = CryptoBoxRootActions def testBrokenConfigs(self): """Check various broken configurations""" - self.writeConfig("NameDatabase", "#out", filename=self.filenames["configFileBroken"]) + self.writeConfig("SettingsDir", "#out", filename=self.filenames["configFileBroken"]) self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"]) self.writeConfig("Level", "Level = ho", filename=self.filenames["configFileBroken"]) self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"]) diff --git a/pythonrewrite/bin/unittests.CryptoBoxWebserver.py b/pythonrewrite/bin/unittests.CryptoBoxWebserver.py index 14adc80..f3d0576 100755 --- a/pythonrewrite/bin/unittests.CryptoBoxWebserver.py +++ b/pythonrewrite/bin/unittests.CryptoBoxWebserver.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python2.4 + import unittest import twill import cherrypy