# # Copyright 02006-02007 sense.lab e.V. # # This file is part of the CryptoBox. # # The CryptoBox 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; either version 2 of the License, or # (at your option) any later version. # # The CryptoBox 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 the CryptoBox; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # ''' This is the web interface for a fileserver managing encrypted filesystems. ''' __revision__ = "$Id$" import sys import cryptobox.core.container as cbxContainer from cryptobox.core.exceptions import CBEnvironmentError, CBConfigUndefinedError import cryptobox.core.blockdevice as blockdevice import os import subprocess import threading class CryptoBox: '''this class rules them all! put things like logging, conf and other stuff in here, that might be used by more classes, it will be passed on to them''' def __init__(self, config_file=None): import cryptobox.core.settings as cbxSettings self.log = self.__get_startup_logger() self.prefs = cbxSettings.CryptoBoxSettings(config_file) self.__run_tests() self.__containers = [] self.__busy_devices = {} self.__busy_devices_sema = threading.BoundedSemaphore() self.reread_container_list() def setup(self): """Initialize the cryptobox. """ self.log.info("Starting up the CryptoBox ...") def cleanup(self): """Umount all containers and shutdown everything safely. """ self.log.info("Shutting down the CryptoBox ...") ## umount all containers self.log.info("Umounting all volumes ...") self.reread_container_list() for cont in self.get_container_list(): if cont.is_mounted(): cont.umount() ## save all settings self.log.info("Storing local settings ...") ## problems with storing are logged automatically self.prefs.write() # TODO: improve the configuration partition handling [a]: how? self.prefs.umount_partition() ## shutdown logging as the last step try: self.log.info("Turning off logging ...") self.log.close() except AttributeError: ## there should be 'close' action - but it may fail silently pass def __get_startup_logger(self): """Initialize the configured logging facility of the CryptoBox. 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 """ import logging ## 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 def __run_tests(self): """Do some initial tests. """ self.__run_test_root_priv() def __run_test_root_priv(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 (" + prog_rootactions + ") via 'super' - maybe you did not add the " + "appropriate line to '/etc/super.tab'?") def reread_container_list(self): """Reinitialize the list of available containers. This should be called whenever the available containers may have changed. E.g.: after partitioning and after device addition/removal """ self.log.debug("rereading container list") self.__containers = [] blockdevice.CACHE.reset() for device in blockdevice.Blockdevices().get_storage_devices(): if self.is_device_allowed(device) and not self.is_config_partition(device): self.__containers.append(cbxContainer.CryptoBoxContainer(device, self)) ## sort by container name self.__containers.sort(cmp = lambda x, y: x.get_name() < y.get_name() and -1 or 1) def get_device_busy_state(self, device): """Return whether a device is currently marked as busy or not. The busy flag can be turned off manually (recommended) or the timeout can expire. """ import time self.__busy_devices_sema.acquire() ## not marked as busy if not self.__busy_devices.has_key(device): self.__busy_devices_sema.release() return False ## timer is expired if time.time() > self.__busy_devices[device]: del self.__busy_devices[device] self.__busy_devices_sema.release() return False self.__busy_devices_sema.release() return True def set_device_busy_state(self, device, new_state, timeout=300): """Mark a device as busy. This is especially useful during formatting, as this may take a long time. """ import time self.__busy_devices_sema.acquire() self.log.debug("Turn busy flag %s: %s" % (new_state and "on" or "off", device)) if new_state: self.__busy_devices[device] = time.time() + timeout else: if self.__busy_devices.has_key(device): del self.__busy_devices[device] self.log.debug("Current busy flags: %s" % str(self.__busy_devices)) self.__busy_devices_sema.release() def is_config_partition(self, device): """Check if a given partition contains configuration informations. The check is done by comparing the label of the filesystem with a string. """ return device.label == self.prefs["Main"]["ConfigVolumeLabel"] def is_device_allowed(self, device): """check if a device is white-listed for being used as cryptobox containers also check, if the device is readable and writeable for the current user """ import types allowed = self.prefs["Main"]["AllowedDevices"] if type(allowed) == types.StringType: allowed = [allowed] for devnode in device.devnodes: if [ a_dev for a_dev in allowed if devnode.startswith(a_dev) ]: if os.access(devnode, os.R_OK | os.W_OK): self.log.debug("Adding valid device: %s" % devnode) ## move the device to the first position device.devnodes.remove(devnode) device.devnodes.insert(0, devnode) return True else: self.log.debug("Skipping device without read and write" \ + "permissions: %s" % device.name) self.log.debug("Skipping unusable device: %s" % device.name) return False def get_container_list(self, filter_type=None, filter_name=None): "retrieve the list of all containers of this cryptobox" try: result = self.__containers[:] if filter_type != None: if filter_type in range(len(cbxContainer.CONTAINERTYPES)): return [e for e in self.__containers if e.get_type() == filter_type] else: self.log.info("invalid filter_type (%d)" % filter_type) result.clear() if filter_name != None: result = [e for e in self.__containers if e.get_name() == filter_name] return result except AttributeError: return [] def get_container(self, device): """retrieve the container element for a device "device" can be a name (e.g. "dm-0") or a blockdevice object """ if isinstance(device, str): devicename = device else: devicename = device.name all = [e for e in self.get_container_list() if e.device.name == devicename] if all: return all[0] else: return None def send_event_notification(self, event, event_infos): """call all available scripts in the event directory with some event information""" event_dir = self.prefs["Locations"]["EventDir"] for fname in os.listdir(event_dir): real_fname = os.path.join(event_dir, fname) if os.path.isfile(real_fname) and os.access(real_fname, os.X_OK): cmd_args = [ self.prefs["Programs"]["super"], self.prefs["Programs"]["CryptoBoxRootActions"], "event", real_fname, event] cmd_args.extend(event_infos) proc = subprocess.Popen( shell = False, stdout = subprocess.PIPE, stderr = subprocess.PIPE, args = cmd_args) (stdout, stderr) = proc.communicate() if proc.returncode != 0: self.log.warn( "an event script (%s) failed (exitcode=%d) to handle an event (%s): %s" % (real_fname, proc.returncode, event, stderr.strip())) else: self.log.info("event handler (%s) finished successfully: %s" % (real_fname, event)) if __name__ == "__main__": CryptoBox()