#!/usr/bin/env python2.4 ''' This is the web interface for a fileserver managing encrypted filesystems. It was originally written in bash/perl. Now a complete rewrite is in progress. So things might be confusing here. Hopefully not for long. :) ''' # check python version import sys (ver_major, ver_minor, ver_sub, ver_desc, ver_subsub) = sys.version_info if (ver_major < 2) or ((ver_major == 2) and (ver_minor < 4)): sys.stderr.write("You need a python version >= 2.4\nCurrent version is:\n %s\n" % sys.version) sys.exit(1) import CryptoBoxContainer from CryptoBoxExceptions import * import re import os import CryptoBoxTools import subprocess class CryptoBox: '''this class rules them all! put things like logging, conf and oter stuff in here, that might be used by more classes, it will be passed on to them''' VERSION = "0.3~1" def __init__(self, config_file=None): import CryptoBoxSettings self.log = self.__getStartupLogger() self.prefs = CryptoBoxSettings.CryptoBoxSettings(config_file) self.__runTests() def __getStartupLogger(self): import logging '''initialises the logging system use it with: 'self.log.[debug|info|warning|error|critical](logmessage)' all classes should get the logging instance during __init__: self.log = logging.getLogger("CryptoBox") first we output all warnings/errors to stderr as soon as we opened the config file successfully, we redirect debug output to the configured destination''' ## basicConfig(...) needs python >= 2.4 try: log_handler = logging.getLogger("CryptoBox") logging.basicConfig( format='%(asctime)s CryptoBox %(levelname)s: %(message)s', stderr=sys.stderr) log_handler.setLevel(logging.ERROR) log_handler.info("loggingsystem is up'n running") ## from now on everything can be logged via self.log... except: raise CBEnvironmentError("couldn't initialise the loggingsystem. I give up.") return log_handler # 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'""" try: devnull = open(os.devnull, "w") except IOError: raise CBEnvironmentError("could not open %s for writing!" % os.devnull) try: prog_super = self.prefs["Programs"]["super"] except KeyError: raise CBConfigUndefinedError("Programs", "super") try: prog_rootactions = self.prefs["Programs"]["CryptoBoxRootActions"] except KeyError: raise CBConfigUndefinedError("Programs", "CryptoBoxRootActions") try: proc = subprocess.Popen( shell = False, stdout = devnull, stderr = devnull, args = [prog_super, prog_rootactions, "check"]) except OSError: raise CBEnvironmentError("failed to execute 'super' (%s)" % self.prefs["Programs"]["super"]) proc.wait() if proc.returncode != 0: raise CBEnvironmentError("failed to call CryptoBoxRootActions (%s) via 'super' - maybe you did not add the appropriate line to /etc/super.tab?" % prog_rootactions) # this method just demonstrates inheritance effects - may be removed def cbx_inheritance_test(self, string="you lucky widow"): self.log.info(string) # RFC: why should CryptoBoxProps inherit CryptoBox? [l] # RFC: shouldn't we move all useful functions of CryptoBoxProps to CryptoBox? [l] class CryptoBoxProps(CryptoBox): '''Get and set the properties of a CryptoBox This class contains all available devices that may be accessed. All properties of the cryptobox can be accessed by this class. ''' def __init__(self, config_file=None): '''read config and fill class variables''' CryptoBox.__init__(self, config_file) self.reReadContainerList() def reReadContainerList(self): self.log.debug("rereading container list") self.containers = [] for device in CryptoBoxTools.getAvailablePartitions(): if self.isDeviceAllowed(device) and not self.isConfigPartition(device): self.containers.append(CryptoBoxContainer.CryptoBoxContainer(device, self)) ## sort by container name self.containers.sort(cmp = lambda x,y: x.getName() < y.getName() and -1 or 1) def isConfigPartition(self, device): proc = subprocess.Popen( shell = False, stdout = subprocess.PIPE, args = [ self.prefs["Programs"]["blkid"], "-c", os.path.devnull, "-o", "value", "-s", "LABEL", device]) (output, error) = proc.communicate() return output.strip() == self.prefs["Main"]["ConfigVolumeLabel"] def isDeviceAllowed(self, devicename): "check if a device is white-listed for being used as cryptobox containers" import types allowed = self.prefs["Main"]["AllowedDevices"] if type(allowed) == types.StringType: allowed = [allowed] for a_dev in allowed: "remove double dots and so on ..." real_device = os.path.realpath(devicename) if a_dev and re.search('^' + a_dev, real_device): return True return False def getLogData(self, lines=None, maxSize=None): """get the most recent log entries of the cryptobox the maximum number and size of these entries can be limited by 'lines' and 'maxSize' """ # return nothing if the currently selected log output is not a file try: if self.prefs["Log"]["Destination"].upper() != "FILE": return [] log_file = self.prefs["Log"]["Details"] except KeyError: self.log.error("could not evaluate one of the following config settings: [Log]->Destination or [Log]->Details") return [] try: fd = open(log_file, "r") if maxSize: fd.seek(-maxSize, 2) # seek relative to the end of the file content = fd.readlines() fd.close() except IOError: self.log.warn("failed to read the log file (%s)" % log_file) return [] if lines: content = content[-lines:] content.reverse() return content def getContainerList(self, filterType=None, filterName=None): "retrieve the list of all containers of this cryptobox" try: result = self.containers[:] if filterType != None: if filterType in range(len(CryptoBoxContainer.Types)): return [e for e in self.containers if e.getType() == filterType] else: self.log.info("invalid filterType (%d)" % filterType) result.clear() if filterName != None: result = [e for e in self.containers if e.getName() == filterName] return result except AttributeError: return [] def getContainer(self, device): "retrieve the container element for this device" all = [e for e in self.getContainerList() if e.device == device] if all: return all[0] else: return None def setNameForUUID(self, uuid, name): "assign a name to a uuid in the ContainerNameDatabase" used_uuid = self.getUUIDForName(name) "first remove potential conflicting uuid/name combination" if used_uuid: ## remember the container which name was overriden for e in self.containers: if e.getName() == name: forcedRename = e break del self.prefs.nameDB[used_uuid] self.prefs.nameDB[uuid] = name self.prefs.nameDB.write() ## rename the container that lost its name (necessary while we use cherrypy) if used_uuid: ## this is surely not the best way to regenerate the name dev = e.getDevice() old_index = self.containers.index(e) self.containers.remove(e) self.containers.insert(old_index, CryptoBoxContainer.CryptoBoxContainer(dev,self)) ## there should be no reason for any failure return True def getNameForUUID(self, uuid): "get the name belonging to a specified key (usually the UUID of a fs)" try: return self.prefs.nameDB[uuid] except KeyError: return None def getUUIDForName(self, name): """ get the key belonging to a value in the ContainerNameDatabase this is the reverse action of 'getNameForUUID' """ for key in self.prefs.nameDB.keys(): if self.prefs.nameDB[key] == name: return key "the uuid was not found" return None def removeUUID(self, uuid): if uuid in self.prefs.nameDB.keys(): del self.prefs.nameDB[uuid] return True else: return False def getAvailableLanguages(self): '''reads all files in path LangDir and returns a list of basenames from existing hdf files, that should are all available languages''' 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 if __name__ == "__main__": cb = CryptoBoxProps()