cryptonas/src/cryptobox/core/main.py
lars e38d566088 create log directory during startup if it does not exist (debian)
skip creation of settings file if the location is not writeable during startup
add basic support for profiling
fixed is_device_allowed problem (was to strict)
2007-01-06 11:40:38 +00:00

295 lines
9.2 KiB
Python

#
# Copyright 2006 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 re
import os
import cryptobox.core.tools as cbxTools
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()
if self.prefs.get_active_partition():
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 = []
for device in cbxTools.get_available_partitions():
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.
"""
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
stderr = 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 is_device_allowed(self, devicename):
"check if a device is white-listed for being used as cryptobox containers"
import types
devicename = os.path.abspath(devicename)
allowed = self.prefs["Main"]["AllowedDevices"]
if type(allowed) == types.StringType:
allowed = [allowed]
for a_dev in allowed:
if not a_dev:
continue
## double dots are not allowed (e.g. /dev/ide/../sda)
if re.search("/\.\./", devicename):
continue
## it is not possible to check for 'realpath' - that does not work
## for the cryptobox as /dev/ is bind-mounted (real hda-name is /opt/...)
if re.search('^%s' % a_dev, devicename):
return True
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 this device"
all = [e for e in self.get_container_list() if e.device == device]
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()