cryptonas/src/cryptobox/core/main.py

305 lines
11 KiB
Python

#
# 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("CryptoNAS")
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("CryptoNAS")
logging.basicConfig(
format = '%(asctime)s CryptoNAS %(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
## if "device" is a string, then turn it into a blockdevice object
if type(device) == types.StringType:
device = blockdevice.get_blockdevice(device)
if device is None:
return False
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"]
try:
event_scripts = os.listdir(event_dir)
except OSError:
self.log.warn("event handler can't access dir: (%s)" % event_dir)
return
for fname in event_scripts:
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()