plugin configuration file added

setting "NameDatabase" replaced by "SettingsDir"
storing of local settings implemented (CryptoBoxSettings.write())
This commit is contained in:
lars 2006-10-11 15:51:28 +00:00
parent 11c2873934
commit 491d16899f
15 changed files with 267 additions and 24 deletions

View file

@ -64,9 +64,15 @@ class CryptoBox:
# do some initial checks # do some initial checks
def __runTests(self): def __runTests(self):
self.__runTestUID()
self.__runTestRootPriv() self.__runTestRootPriv()
def __runTestUID(self):
if os.geteuid() == 0:
raise CBEnvironmentError("you may not run the cryptobox as root")
def __runTestRootPriv(self): def __runTestRootPriv(self):
"""try to run 'super' with 'CryptoBoxRootActions'""" """try to run 'super' with 'CryptoBoxRootActions'"""
import subprocess import subprocess

View file

@ -104,7 +104,7 @@ class CryptoBoxContainer:
if self.type == self.Types["luks"]: if self.type == self.Types["luks"]:
self.mount = self.__mountLuks self.mount = self.__mountLuks
self.umount = self.__umountLuks self.umount = self.__umountLuks
if self.type == self.Types["plain"]: elif self.type == self.Types["plain"]:
self.mount = self.__mountPlain self.mount = self.__mountPlain
self.umount = self.__umountPlain self.umount = self.__umountPlain

View file

@ -14,6 +14,12 @@ class CryptoBoxPlugin:
## does this plugin require admin authentification? ## does this plugin require admin authentification?
requestAuth = False 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): def __init__(self, cbox, pluginDir):
self.cbox = cbox self.cbox = cbox
self.hdf = {} self.hdf = {}
@ -69,3 +75,46 @@ class CryptoBoxPlugin:
for (key, value) in self.hdf.items(): for (key, value) in self.hdf.items():
hdf.setValue(key, str(value)) 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

View file

@ -170,6 +170,9 @@ def run_cryptsetup(args):
shell = False, shell = False,
args = cs_args) args = cs_args)
proc.communicate() 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 return proc.returncode == 0
@ -283,6 +286,11 @@ if __name__ == "__main__":
sys.stderr.write("the uid of the caller is zero (root) - this is not allowed\n") sys.stderr.write("the uid of the caller is zero (root) - this is not allowed\n")
sys.exit(100) 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? # did the user call the "check" action?
if (len(args) == 1) and (args[0].lower() == "check"): if (len(args) == 1) and (args[0].lower() == "check"):
# exit silently # exit silently

View file

@ -8,6 +8,7 @@ except:
raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'configobj'! Try 'apt-get install python-configobj'.") raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'configobj'! Try 'apt-get install python-configobj'.")
class CryptoBoxSettings: class CryptoBoxSettings:
CONF_LOCATIONS = [ CONF_LOCATIONS = [
@ -15,6 +16,10 @@ class CryptoBoxSettings:
"~/.cryptobox.conf", "~/.cryptobox.conf",
"/etc/cryptobox/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): def __init__(self, config_file=None):
self.log = logging.getLogger("CryptoBox") self.log = logging.getLogger("CryptoBox")
@ -25,6 +30,36 @@ class CryptoBoxSettings:
self.__configureLogHandler() self.__configureLogHandler()
self.__checkUnknownPreferences() self.__checkUnknownPreferences()
self.nameDB = self.__getNameDatabase() 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): def __getitem__(self, key):
@ -92,12 +127,12 @@ class CryptoBoxSettings:
def __getNameDatabase(self): def __getNameDatabase(self):
try: try:
try: try:
nameDB_file = self.prefs["Locations"]["NameDatabase"] nameDB_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.NAMEDB_FILE)
except KeyError: except KeyError:
raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "NameDatabase") raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir")
except SyntaxError: except SyntaxError:
raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "NameDatabase", nameDB_file, "failed to interprete the filename of the name database correctly") raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", nameDB_file, "failed to interprete the filename of the name database correctly (%s)" % nameDB_file)
## create nameDB is necessary ## create nameDB if necessary
if os.path.exists(nameDB_file): if os.path.exists(nameDB_file):
nameDB = configobj.ConfigObj(nameDB_file) nameDB = configobj.ConfigObj(nameDB_file)
else: else:
@ -108,6 +143,63 @@ class CryptoBoxSettings:
return nameDB 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): def __getConfigFileName(self, config_file):
# search for the configuration file # search for the configuration file
import types import types
@ -168,7 +260,7 @@ ConfigVolumeLabel = string(min=1,default="cbox_config")
[Locations] [Locations]
MountParentDir = directoryExists(default="/var/cache/cryptobox/mnt") 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") TemplateDir = directoryExists(default="/usr/share/cryptobox/template")
LangDir = directoryExists(default="/usr/share/cryptobox/lang") LangDir = directoryExists(default="/usr/share/cryptobox/lang")
DocDir = directoryExists(default="/usr/share/doc/cryptobox/html") DocDir = directoryExists(default="/usr/share/doc/cryptobox/html")
@ -194,6 +286,17 @@ super = fileExecutable(default="/usr/bin/super")
CryptoBoxRootActions = string(min=1) 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): class CryptoBoxSettingsValidator(validate.Validator):
@ -235,3 +338,45 @@ class CryptoBoxSettingsValidator(validate.Validator):
raise validate.VdtValueError("%s (directory does not exist)" % value) raise validate.VdtValueError("%s (directory does not exist)" % value)
return file_path 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

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python2.4 #!/usr/bin/env python2.4
import os import os
import WebInterfaceSites import WebInterfaceSites
import sys
try: try:
import cherrypy import cherrypy

View file

@ -60,7 +60,7 @@ class PluginManager:
if __name__ == "__main__": if __name__ == "__main__":
x = PluginManager(None, None, "../plugins") x = PluginManager(None, "../plugins")
for a in x.getPlugins(): for a in x.getPlugins():
if not a is None: if not a is None:
print "Plugin: %s" % a.getName() print "Plugin: %s" % a.getName()

View file

@ -14,7 +14,12 @@ class WebInterfaceDataset(dict):
self.cbox = cbox self.cbox = cbox
self.__setConfigValues() self.__setConfigValues()
self.__setCryptoBoxState() self.__setCryptoBoxState()
self.__setPluginList(plugins) self.plugins = plugins
self.setPluginData()
def setPluginData(self):
self.__setPluginList(self.plugins)
def setCurrentDiskState(self, device): def setCurrentDiskState(self, device):
@ -63,6 +68,7 @@ class WebInterfaceDataset(dict):
self["Settings.Stylesheet"] = self.prefs["WebSettings"]["Stylesheet"] self["Settings.Stylesheet"] = self.prefs["WebSettings"]["Stylesheet"]
self["Settings.Language"] = self.prefs["WebSettings"]["Language"] self["Settings.Language"] = self.prefs["WebSettings"]["Language"]
self["Settings.PluginDir"] = self.prefs["Locations"]["PluginDir"] self["Settings.PluginDir"] = self.prefs["Locations"]["PluginDir"]
self["Settings.SettingsDir"] = self.prefs["Locations"]["SettingsDir"]
def __setCryptoBoxState(self): def __setCryptoBoxState(self):
@ -85,8 +91,10 @@ class WebInterfaceDataset(dict):
lang_data = p.getLanguageData() lang_data = p.getLanguageData()
entryName = "Settings.PluginList." + p.getName() entryName = "Settings.PluginList." + p.getName()
self[entryName] = 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 + ".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: for a in p.pluginCapabilities:
self[entryName + ".Types." + a] = "1" self[entryName + ".Types." + a] = "1"

View file

@ -5,9 +5,6 @@ import Plugins
from CryptoBoxExceptions import * from CryptoBoxExceptions import *
import cherrypy import cherrypy
# TODO: for now the admin access is defined statically
authDict = {"test": "tester"}
class WebInterfacePlugins: class WebInterfacePlugins:
@ -63,7 +60,7 @@ class WebInterfaceSites:
## this is a function decorator to check authentication ## this is a function decorator to check authentication
## it has to be defined before any page definition requiring authentification ## it has to be defined before any page definition requiring authentification
def __requestAuth(self, authDict): def __requestAuth(self=None):
def check_credentials(site): def check_credentials(site):
def _inner_wrapper(self, *args, **kargs): def _inner_wrapper(self, *args, **kargs):
import base64 import base64
@ -81,8 +78,9 @@ class WebInterfaceSites:
except AttributeError: except AttributeError:
## no cherrypy response header defined ## no cherrypy response header defined
pass pass
authDict = self.cbox.prefs.userDB["admins"]
if user in authDict.keys(): if user in authDict.keys():
if password == authDict[user]: if self.cbox.prefs.userDB.getDigest(password) == authDict[user]:
## ok: return the choosen page ## ok: return the choosen page
self.cbox.log.info("access granted for: %s" % user) self.cbox.log.info("access granted for: %s" % user)
return site(self, *args, **kargs) return site(self, *args, **kargs)
@ -203,20 +201,21 @@ class WebInterfaceSites:
self.dataset.setCurrentDiskState(plugin.device) self.dataset.setCurrentDiskState(plugin.device)
if not nextTemplate: nextTemplate = "show_volume" if not nextTemplate: nextTemplate = "show_volume"
else: else:
self.dataset.setPluginData()
if not nextTemplate: nextTemplate = "form_system" if not nextTemplate: nextTemplate = "form_system"
## save the currently active plugin name ## save the currently active plugin name
self.dataset["Data.ActivePlugin"] = plugin.getName() self.dataset["Data.ActivePlugin"] = plugin.getName()
return self.__render(nextTemplate, plugin) return self.__render(nextTemplate, plugin)
## apply authentication? ## apply authentication?
if plugin.requestAuth: if plugin.isAuthRequired():
return lambda **args: self.__requestAuth(authDict)(handler)(self, **args) return lambda **args: self.__requestAuth()(handler)(self, **args)
else: else:
return lambda **args: handler(self, **args) return lambda **args: handler(self, **args)
## test authentication ## test authentication
@cherrypy.expose @cherrypy.expose
@__requestAuth(None, authDict) @__requestAuth
def test(self, weblang=""): def test(self, weblang=""):
self.__resetDataset() self.__resetDataset()
self.__setWebLang(weblang) self.__setWebLang(weblang)

View file

@ -2,7 +2,7 @@
# comma separated list of possible prefixes for accesible devices # comma separated list of possible prefixes for accesible devices
# beware: .e.g "/dev/hd" grants access to _all_ harddisks # 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 # 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) # this directory must be writeable by the cryptobox user (see above)
MountParentDir = /var/cache/cryptobox/mnt MountParentDir = /var/cache/cryptobox/mnt
# the name-database file - inside of DataDir # settings directory: contains name database and plugin configuration
#NameDatabase = /var/cache/cryptobox/cryptobox_names.db #SettingsDir = /var/cache/cryptobox/settings
NameDatabase = cryptobox_names.db SettingsDir = .
# where are the clearsilver templates? # where are the clearsilver templates?
#TemplateDir = /usr/share/cryptobox/templates #TemplateDir = /usr/share/cryptobox/templates

View file

@ -3,6 +3,7 @@ server.socketPort = 8080
#server.environment = "production" #server.environment = "production"
server.environment = "development" server.environment = "development"
server.logToScreen = True server.logToScreen = True
server.log_tracebacks = True
server.threadPool = 1 server.threadPool = 1
server.reverseDNS = False server.reverseDNS = False
server.logFile = "cryptoboxwebserver.log" server.logFile = "cryptoboxwebserver.log"

View file

@ -24,7 +24,7 @@ def main():
for e in cb.getContainerList(): for e in cb.getContainerList():
print "\t\t%d\t\t%s - %s - %d" % (cb.getContainerList().index(e), e.getDevice(), e.getName(), e.getType()) 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" print "no loop devices found for testing"
sys.exit(1) sys.exit(1)

22
pythonrewrite/bin/uml-setup.sh Executable file
View file

@ -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

View file

@ -33,6 +33,8 @@ class CryptoBoxPropsConfigTests(unittest.TestCase):
"configFileOK" : "cbox-test_ok.conf", "configFileOK" : "cbox-test_ok.conf",
"configFileBroken" : "cbox-test_broken.conf", "configFileBroken" : "cbox-test_broken.conf",
"nameDBFile" : "cryptobox_names.db", "nameDBFile" : "cryptobox_names.db",
"pluginConf" : "cryptobox_plugins.conf",
"userDB" : "cryptobox_users.db",
"logFile" : "cryptobox.log", "logFile" : "cryptobox.log",
"tmpdir" : "cryptobox-mnt" } "tmpdir" : "cryptobox-mnt" }
tmpdirname = "" tmpdirname = ""
@ -43,7 +45,7 @@ AllowedDevices = /dev/loop
DefaultVolumePrefix = "Data " DefaultVolumePrefix = "Data "
DefaultCipher = aes-cbc-essiv:sha256 DefaultCipher = aes-cbc-essiv:sha256
[Locations] [Locations]
NameDatabase = %s/cryptobox_names.db SettingsDir = %s
MountParentDir = %s MountParentDir = %s
TemplateDir = ../templates TemplateDir = ../templates
LangDir = ../lang LangDir = ../lang
@ -105,7 +107,7 @@ CryptoBoxRootActions = CryptoBoxRootActions
def testBrokenConfigs(self): def testBrokenConfigs(self):
"""Check various broken configurations""" """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.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
self.writeConfig("Level", "Level = ho", filename=self.filenames["configFileBroken"]) self.writeConfig("Level", "Level = ho", filename=self.filenames["configFileBroken"])
self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"]) self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])

View file

@ -1,3 +1,5 @@
#!/usr/bin/env python2.4
import unittest import unittest
import twill import twill
import cherrypy import cherrypy