2006-08-23 15:27:25 +02:00
#!/usr/bin/env python2.4
2006-08-16 13:07:57 +02:00
'''
2006-08-17 12:38:05 +02:00
This is the web interface for a fileserver managing encrypted filesystems .
2006-08-16 20:17:16 +02:00
2006-08-16 13:07:57 +02:00
It was originally written in bash / perl . Now a complete rewrite is in
progress . So things might be confusing here . Hopefully not for long .
: )
'''
2006-08-16 20:17:16 +02:00
2006-08-23 15:27:25 +02:00
# 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 \n Current version is: \n %s \n " % sys . version )
sys . exit ( 1 )
2006-08-16 13:07:57 +02:00
import CryptoBoxContainer
2006-08-20 20:23:35 +02:00
import types
2006-08-16 20:17:16 +02:00
import re
import os
2006-08-18 16:00:49 +02:00
import unittest
2006-08-22 08:55:07 +02:00
import logging
2006-08-23 15:27:25 +02:00
import subprocess
2006-08-17 12:38:05 +02:00
2006-08-21 09:41:03 +02:00
CONF_LOCATIONS = [
" ./cryptobox.conf " ,
" ~/.cryptobox.conf " ,
" /etc/cryptobox/cryptobox.conf " ]
2006-08-16 09:17:44 +02:00
2006-08-20 18:33:52 +02:00
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 '''
2006-08-21 09:41:03 +02:00
def __init__ ( self , config_file = None ) :
2006-08-20 20:23:35 +02:00
self . __initLogging ( )
2006-08-21 09:41:03 +02:00
self . __initPreferences ( config_file )
2006-08-23 15:27:25 +02:00
self . __runTests ( )
2006-08-21 09:41:03 +02:00
2006-08-20 18:33:52 +02:00
2006-08-20 20:23:35 +02:00
def __initLogging ( self ) :
import logging
2006-08-20 18:33:52 +02:00
''' initialises the logging system
use it with : ' self.log.[debug|info|warning|error|critical](logmessage) '
2006-08-22 08:55:07 +02:00
all classes should get the logging instance during __init__ :
self . log = logging . getLogger ( " CryptoBox " )
2006-08-20 18:33:52 +02:00
2006-08-22 08:55:07 +02:00
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 '''
2006-08-20 18:33:52 +02:00
## basicConfig(...) needs python >= 2.4
2006-08-20 20:23:35 +02:00
try :
2006-08-22 08:55:07 +02:00
self . log = logging . getLogger ( " CryptoBox " )
2006-08-24 09:36:47 +02:00
logging . basicConfig (
format = ' %(asctime)s %(module)s %(levelname)s %(message)s ' ,
stderr = sys . stderr )
self . log . setLevel ( logging . ERROR )
2006-08-20 20:23:35 +02:00
self . log . info ( " loggingsystem is up ' n running " )
## from now on everything can be logged via self.log...
except :
2006-08-24 16:41:11 +02:00
sys . errorExit ( " SystemError " , " Couldn ' t initialise the loggingsystem. I give up. " )
2006-08-20 20:23:35 +02:00
2006-08-21 09:41:03 +02:00
def __initPreferences ( self , config_file ) :
2006-08-20 20:23:35 +02:00
try :
2006-08-21 09:41:03 +02:00
import configobj ## needed for reading and writing of the config file
2006-08-20 20:23:35 +02:00
except :
2006-08-24 16:41:11 +02:00
self . errorExit ( " SystemError " , " Couldn ' t import ' configobj ' ! Try ' apt-get install python-configobj ' . " )
2006-08-21 09:41:03 +02:00
# search for the configuration file
if config_file is None :
# no config file was specified - we will look for it in the ususal locations
conf_file_list = [ os . path . expanduser ( f )
for f in CONF_LOCATIONS
if os . path . exists ( os . path . expanduser ( f ) ) ]
if not conf_file_list :
# no possible config file found in the usual locations
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " No configuration file found - sorry! " )
2006-08-21 09:41:03 +02:00
config_file = conf_file_list [ 0 ]
else :
# a config file was specified (e.g. via command line)
2006-08-24 09:36:47 +02:00
if type ( config_file ) != types . StringType :
self . errorExit ( " ConfigError " , " Invalid config file specified: %s " % config_file )
2006-08-21 09:41:03 +02:00
if not os . path . exists ( config_file ) :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " Could not find the specified configuration file ( %s ) " % config_file )
2006-08-20 20:23:35 +02:00
try :
2006-08-21 10:09:05 +02:00
self . cbxPrefs = configobj . ConfigObj ( config_file )
2006-08-23 20:19:37 +02:00
if self . cbxPrefs :
self . log . info ( " found config: %s " % self . cbxPrefs . items ( ) )
else :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " failed to load the config file: %s " % config_file )
except IOError :
self . errorExit ( " ConfigError " , " unable to open the config file: %s " % config_file )
2006-08-20 20:23:35 +02:00
try :
2006-08-24 09:36:47 +02:00
try :
nameDB_file = os . path . join (
self . cbxPrefs [ " Main " ] [ " DataDir " ] ,
self . cbxPrefs [ " Main " ] [ " NameDatabase " ] )
except KeyError :
self . errorExit ( " ConfigError " , " could not find one of these configuration settings: [Main]->DataDir and [Main]->NameDatabase - please check your config file( %s ) " % config_file )
2006-08-20 20:23:35 +02:00
if os . path . exists ( nameDB_file ) :
self . nameDB = configobj . ConfigObj ( nameDB_file )
else :
self . nameDB = configobj . ConfigObj ( nameDB_file , create_empty = True )
except SyntaxError :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " Error during parsing of name database file ( %s ). \n " % ( nameDB_file , ) )
2006-08-21 09:41:03 +02:00
# TODO: check if nameDB file was created successfully?
2006-08-22 08:55:07 +02:00
# get the loglevel
try :
log_level = self . cbxPrefs [ " Log " ] [ " Level " ] . upper ( )
2006-08-22 10:00:32 +02:00
log_level_avail = [ " DEBUG " , " INFO " , " WARN " , " ERROR " ]
if not log_level in log_level_avail :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " invalid log level: %s is not in %s " % ( self . cbxPrefs [ " Log " ] [ " Level " ] , log_level_avail ) )
2006-08-22 08:55:07 +02:00
except TypeError :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " invalid log level: %s " % self . cbxPrefs [ " Log " ] [ " Level " ] )
2006-08-23 15:27:25 +02:00
try :
2006-08-24 09:36:47 +02:00
try :
new_handler = logging . FileHandler ( self . cbxPrefs [ " Log " ] [ " Details " ] )
except KeyError :
self . errorExit ( " ConfigError " , " could not find a configuration setting: [Log]->Details - please check your config file( %s ) " % config_file )
new_handler . setFormatter ( logging . Formatter ( ' %(asctime)s %(module)s %(levelname)s : %(message)s ' ) )
2006-08-23 15:27:25 +02:00
self . log . addHandler ( new_handler )
2006-08-24 09:36:47 +02:00
# do not call parent's handlers
self . log . propagate = False
self . log . setLevel ( getattr ( logging , log_level ) )
2006-08-22 08:55:07 +02:00
except IOError :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " could not open logfile: %s " % self . cbxPrefs [ " Log " ] [ " Details " ] )
2006-08-20 18:33:52 +02:00
2006-08-21 09:41:03 +02:00
2006-08-23 15:27:25 +02:00
# do some initial checks
def __runTests ( self ) :
try :
devnull = open ( os . devnull , " w " )
except IOError :
2006-08-24 09:36:47 +02:00
self . errorExit ( " SystemError " , " Could not open %s for writing! " % os . devnull )
2006-08-23 18:44:14 +02:00
try :
proc = subprocess . Popen (
shell = False ,
stdout = devnull ,
stderr = devnull ,
args = [ self . cbxPrefs [ " Programs " ] [ " super " ] ,
self . cbxPrefs [ " Programs " ] [ " CryptoBoxRootActions " ] ,
" check " ] )
except OSError :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " could not find: %s " % self . cbxPrefs [ " Programs " ] [ " super " ] )
2006-08-23 15:27:25 +02:00
proc . wait ( )
if proc . returncode != 0 :
2006-08-24 09:36:47 +02:00
self . errorExit ( " ConfigError " , " Could not call CryptoBoxRootActions by ' super ' - maybe you did not add the appropriate line to /etc/super.tab? " )
2006-08-23 15:27:25 +02:00
# this method just demonstrates inheritance effects - may be removed
2006-08-23 20:19:37 +02:00
def cbx_inheritance_test ( self , string = " you lucky widow " ) :
self . log . info ( string )
2006-08-24 09:36:47 +02:00
def errorExitMod ( self , title , msg , code = 1 ) :
""" output an error message and quit by raising an exception
this function should be used if this class was used as a module instead
of as a standalone program
"""
self . log . error ( msg )
raise title , msg
def errorExitProg ( self , title , msg , code = 1 ) :
""" output an error message and quit by calling sys.exit
this function should be used if this class was used as a standalone
program
"""
self . log . error ( msg )
sys . exit ( code )
2006-08-16 13:07:57 +02:00
2006-08-20 18:33:52 +02:00
2006-08-21 09:41:03 +02:00
# RFC: why should CryptoBoxProps inherit CryptoBox? [l]
# RFC: shouldn't we move all useful functions of CryptoBoxProps to CryptoBox? [l]
2006-08-20 18:33:52 +02:00
class CryptoBoxProps ( CryptoBox ) :
2006-08-16 13:07:57 +02:00
''' Get and set the properties of a CryptoBox
2006-08-17 12:38:05 +02:00
This class contains all available devices that may be accessed .
All properties of the cryptobox can be accessed by this class .
2006-08-16 13:07:57 +02:00
'''
2006-08-16 09:17:44 +02:00
2006-08-24 09:36:47 +02:00
def __init__ ( self , config_file = None ) :
2006-08-16 13:07:57 +02:00
''' read config and fill class variables '''
2006-08-24 09:36:47 +02:00
CryptoBox . __init__ ( self , config_file )
2006-08-20 18:33:52 +02:00
#self.cbx_inheritance_test()
2006-08-20 20:23:35 +02:00
#print self.cbxPrefs.items()
####
2006-08-16 20:17:16 +02:00
self . containers = [ ]
for device in self . __getAvailablePartitions ( ) :
if self . isDeviceAllowed ( device ) :
2006-08-17 12:38:05 +02:00
self . containers . append ( CryptoBoxContainer . CryptoBoxContainer ( device , self ) )
2006-08-16 20:17:16 +02:00
2006-08-24 09:36:47 +02:00
2006-08-16 20:17:16 +02:00
def isDeviceAllowed ( self , devicename ) :
" check if a device is white-listed for being used as cryptobox containers "
2006-08-17 12:38:05 +02:00
allowed = self . cbxPrefs [ " Main " ] [ " AllowedDevices " ]
if type ( allowed ) == types . StringType : allowed = [ allowed ]
for a_dev in allowed :
2006-08-18 16:00:49 +02:00
" remove double dots and so on ... "
real_device = os . path . realpath ( devicename )
if a_dev and re . search ( ' ^ ' + a_dev , real_device ) : return True
2006-08-16 20:17:16 +02:00
return False
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 :
2006-08-21 12:10:40 +02:00
self . log . info ( " invalid filterType ( %d ) " % filterType )
2006-08-16 20:17:16 +02:00
result . clear ( )
if filterName != None :
result = [ e for e in self . containers if e . getName ( ) == filterName ]
return result
except AttributeError :
return [ ]
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 "
2006-08-17 12:38:05 +02:00
if used_uuid : del self . nameDB [ used_uuid ]
self . nameDB [ uuid ] = name
self . nameDB . write ( )
2006-08-16 20:17:16 +02:00
def getNameForUUID ( self , uuid ) :
" get the name belonging to a specified key (usually the UUID of a fs) "
try :
2006-08-17 12:38:05 +02:00
return self . nameDB [ uuid ]
2006-08-16 20:17:16 +02:00
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 ' """
2006-08-17 12:38:05 +02:00
for key in self . nameDB . keys ( ) :
if self . nameDB [ key ] == name : return key
2006-08-16 20:17:16 +02:00
" the uuid was not found "
return None
""" ************ internal stuff starts here *********** """
def __getAvailablePartitions ( self ) :
" retrieve a list of all available containers "
2006-08-16 09:17:44 +02:00
ret_list = [ ]
try :
2006-08-16 20:17:16 +02:00
" the following reads all lines of /proc/partitions and adds the mentioned devices "
2006-08-16 09:17:44 +02:00
fpart = open ( " /proc/partitions " , " r " )
try :
line = fpart . readline ( )
while line :
p_details = line . split ( )
if ( len ( p_details ) == 4 ) :
2006-08-16 20:17:16 +02:00
" the following code prevents double entries like /dev/hda and /dev/hda1 "
2006-08-16 09:17:44 +02:00
( p_major , p_minor , p_size , p_device ) = p_details
if re . search ( ' ^[0-9]*$ ' , p_major ) and re . search ( ' ^[0-9]*$ ' , p_minor ) :
p_parent = re . sub ( ' [1-9]?[0-9]$ ' , ' ' , p_device )
if p_parent == p_device :
if [ e for e in ret_list if re . search ( ' ^ ' + p_parent + ' [1-9]?[0-9]$ ' , e ) ] :
" major partition - its children are already in the list "
pass
else :
" major partition - but there are no children for now "
ret_list . append ( p_device )
else :
" minor partition - remove parent if necessary "
if p_parent in ret_list : ret_list . remove ( p_parent )
ret_list . append ( p_device )
line = fpart . readline ( )
finally :
fpart . close ( )
2006-08-16 20:17:16 +02:00
return [ self . __getAbsoluteDeviceName ( e ) for e in ret_list ]
2006-08-16 09:17:44 +02:00
except IOError :
2006-08-21 12:10:40 +02:00
self . log . warning ( " Could not read /proc/partitions " )
2006-08-16 09:17:44 +02:00
return [ ]
2006-08-16 20:17:16 +02:00
def __getAbsoluteDeviceName ( self , shortname ) :
""" returns the absolute file name of a device (e.g.: " hda1 " -> " /dev/hda1 " )
this does also work for device mapper devices
if the result is non - unique , one arbitrary value is returned """
if re . search ( ' ^/ ' , shortname ) : return shortname
default = os . path . join ( " /dev " , shortname )
if os . path . exists ( default ) : return default
result = self . __findMajorMinorOfDevice ( shortname )
" if no valid major/minor was found -> exit "
if not result : return default
( major , minor ) = result
" for device-mapper devices (major == 254) ... "
if major == 254 :
result = self . __findMajorMinorDeviceName ( " /dev/mapper " , major , minor )
if result : return result [ 0 ]
" now check all files in /dev "
result = self . __findMajorMinorDeviceName ( " /dev " , major , minor )
if result : return result [ 0 ]
return default
def __findMajorMinorOfDevice ( self , device ) :
" return the major/minor numbers of a block device by querying /sys/block/?/dev "
if not os . path . exists ( os . path . join ( " /sys/block " , device ) ) : return None
blockdev_info_file = os . path . join ( os . path . join ( " /sys/block " , device ) , " dev " )
2006-08-16 09:17:44 +02:00
try :
2006-08-16 20:17:16 +02:00
f_blockdev_info = open ( blockdev_info_file , " r " )
blockdev_info = f_blockdev_info . read ( )
f_blockdev_info . close ( )
( str_major , str_minor ) = blockdev_info . split ( " : " )
" numeric conversion "
try :
major = int ( str_major )
minor = int ( str_minor )
return ( major , minor )
except ValueError :
" unknown device numbers -> stop guessing "
return None
except IOError :
pass
2006-08-16 09:17:44 +02:00
2006-08-16 20:17:16 +02:00
def __findMajorMinorDeviceName ( self , dir , major , minor ) :
" returns the names of devices with the specified major and minor number "
collected = [ ]
try :
subdirs = [ os . path . join ( dir , e ) for e in os . listdir ( dir ) if ( not os . path . islink ( os . path . join ( dir , e ) ) ) and os . path . isdir ( os . path . join ( dir , e ) ) ]
" do a recursive call to parse the directory tree "
for dirs in subdirs :
collected . extend ( self . __findMajorMinorDeviceName ( dirs , major , minor ) )
" filter all device inodes in this directory "
collected . extend ( [ os . path . realpath ( os . path . join ( dir , e ) ) for e in os . listdir ( dir ) if ( os . major ( os . stat ( os . path . join ( dir , e ) ) . st_rdev ) == major ) and ( os . minor ( os . stat ( os . path . join ( dir , e ) ) . st_rdev ) == minor ) ] )
result = [ ]
for e in collected :
if e not in result : result . append ( e )
return collected
except OSError :
return [ ]
2006-08-24 09:36:47 +02:00
# choose an exit function
if __name__ == " __main__ " :
CryptoBox . errorExit = CryptoBox . errorExitProg
else :
CryptoBox . errorExit = CryptoBox . errorExitMod
2006-08-16 20:17:16 +02:00
2006-08-18 16:00:49 +02:00
if __name__ == " __main__ " :
2006-08-22 06:29:53 +02:00
cb = CryptoBox ( )
2006-08-18 16:00:49 +02:00