added unittest for CryptoBoxLogger and CryptoBoxProps

fixed bug in devicename check
minor improvements
This commit is contained in:
lars 2006-08-18 14:00:49 +00:00
parent 07a63dbd9c
commit a0ce824eb2
3 changed files with 221 additions and 42 deletions

View file

@ -14,6 +14,7 @@ import re
import os import os
import sys import sys
import types import types
import unittest
CONF_LOCATIONS = [ CONF_LOCATIONS = [
@ -32,9 +33,10 @@ class CryptoBoxProps:
def __init__(self, conf_file=None): def __init__(self, conf_file=None):
'''read config and fill class variables''' '''read config and fill class variables'''
if os.geteuid() != 0: "enable it again - or remove the priv-dropping"
sys.stderr.write("You need to be root to run this program!\n") #if os.geteuid() != 0:
sys.exit(1) # sys.stderr.write("You need to be root to run this program!\n")
# sys.exit(1)
if conf_file == None: if conf_file == None:
for f in CONF_LOCATIONS: for f in CONF_LOCATIONS:
if os.path.exists(os.path.expanduser(f)): if os.path.exists(os.path.expanduser(f)):
@ -53,18 +55,18 @@ class CryptoBoxProps:
self.cbxPrefs["Main"]["DataDir"], self.cbxPrefs["Main"]["DataDir"],
self.cbxPrefs["Main"]["NameDatabase"]) self.cbxPrefs["Main"]["NameDatabase"])
if os.path.exists(nameDB_file): if os.path.exists(nameDB_file):
self.nameDB = configobj.ConfigObj(nameDB_file, create_empty=True)
else:
self.nameDB = configobj.ConfigObj(nameDB_file) self.nameDB = configobj.ConfigObj(nameDB_file)
else:
self.nameDB = configobj.ConfigObj(nameDB_file, create_empty=True)
except SyntaxError: except SyntaxError:
sys.stderr.write("Error during parsing of name database file (%s).\n" % (nameDB_file, )) sys.stderr.write("Error during parsing of name database file (%s).\n" % (nameDB_file, ))
sys.exit(1) sys.exit(1)
self.__cboxUID = int(self.cbxPrefs["System"]["User"]) self.__cboxUID = int(self.cbxPrefs["System"]["User"])
self.__cboxGID = int(self.cbxPrefs["System"]["Group"])
self.debug = CryptoBoxLogger.CryptoBoxLogger( self.debug = CryptoBoxLogger.CryptoBoxLogger(
self.cbxPrefs["Log"]["Level"], self.cbxPrefs["Log"]["Level"],
self.cbxPrefs["Log"]["Facility"], self.cbxPrefs["Log"]["Facility"],
self.cbxPrefs["Log"]["Destination"], self.cbxPrefs["Log"]["Destination"])
self.__cboxUID)
self.dropPrivileges() self.dropPrivileges()
self.debugMessage = self.debug.printMessage self.debugMessage = self.debug.printMessage
self.containers = [] self.containers = []
@ -79,7 +81,9 @@ class CryptoBoxProps:
allowed = self.cbxPrefs["Main"]["AllowedDevices"] allowed = self.cbxPrefs["Main"]["AllowedDevices"]
if type(allowed) == types.StringType: allowed = [allowed] if type(allowed) == types.StringType: allowed = [allowed]
for a_dev in allowed: for a_dev in allowed:
if a_dev and re.search('^' + a_dev, devicename): return True "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 return False
@ -129,16 +133,20 @@ class CryptoBoxProps:
def dropPrivileges(self): def dropPrivileges(self):
"change the effective uid to 'User' specified in section 'System'" "change the effective uid to 'User' specified in section 'System'"
if os.getuid() != os.geteuid(): "enable it again - or remove the priv-dropping"
raise "PrivilegeManager", "we already dropped privileges" #if os.getuid() != os.geteuid():
# raise "PrivilegeManager", "we already dropped privileges"
os.seteuid(self.__cboxUID) os.seteuid(self.__cboxUID)
os.setegid(self.__cboxGID)
def risePrivileges(self): def risePrivileges(self):
"regain superuser privileges temporarily - call dropPrivileges afterwards!" "regain superuser privileges temporarily - call dropPrivileges afterwards!"
if os.getuid() == os.geteuid(): "enable it again - or remove the priv-dropping"
raise "PrivilegeManager", "we already have superuser privileges" #if os.getuid() == os.geteuid():
# raise "PrivilegeManager", "we already have superuser privileges"
os.seteuid(os.getuid()) os.seteuid(os.getuid())
os.setegid(os.getgid())
""" ************ internal stuff starts here *********** """ """ ************ internal stuff starts here *********** """
@ -238,3 +246,66 @@ class CryptoBoxProps:
return [] return []
# *************** test class *********************
class CryptoBoxPropsTest(unittest.TestCase):
configFile = "/tmp/cbox-test.conf"
nameDBFile = "/tmp/cryptobox_names.db"
logFile = "/tmp/cryptobox.log"
configContent = """
[Main]
AllowedDevices = /dev/loop
DefaultVolumePrefix = "Data "
DataDir = /tmp
NameDatabase = cryptobox_names.db
[System]
User = 1000
Group = 1000
MountParentDir = /tmp/mnt
DefaultCipher = aes-cbc-essiv:sha256
[Log]
Level = debug
Facility = file
Destination = /tmp/cryptobox.log
"""
def setUp(self):
if not os.path.exists("/tmp/mnt"): os.mkdir("/tmp/mnt")
fd = open(self.configFile, "w")
fd.write(self.configContent)
fd.close()
def tearDown(self):
if os.path.exists("/tmp/mnt"): os.rmdir("/tmp/mnt")
if os.path.exists(self.configFile): os.remove(self.configFile)
if os.path.exists(self.logFile): os.remove(self.logFile)
if os.path.exists(self.nameDBFile): os.remove(self.nameDBFile)
def testConfigFile(self):
self.assertRaises(KeyError, CryptoBoxProps, "/not/existing/path")
CryptoBoxProps(self.configFile)
self.assertTrue(os.path.exists(self.nameDBFile))
self.assertTrue(os.path.exists(self.logFile))
def testDeviceCheck(self):
cb = CryptoBoxProps(self.configFile)
self.assertTrue(cb.isDeviceAllowed("/dev/loop"))
self.assertTrue(cb.isDeviceAllowed("/dev/loop1"))
self.assertTrue(cb.isDeviceAllowed("/dev/loop/urgd"))
self.assertFalse(cb.isDeviceAllowed("/dev/hda"))
self.assertFalse(cb.isDeviceAllowed("/dev/loopa/../hda"))
self.assertTrue(cb.isDeviceAllowed("/dev/usb/../loop1"))
self.assertFalse(cb.isDeviceAllowed("/"))
"a lot of tests are still missing - how can we provide a prepared environment?"
# ********************* run unittest ****************************
if __name__ == "__main__":
unittest.main()

View file

@ -3,48 +3,69 @@ manages logging events of the CryptoBox
''' '''
import sys import sys
import os import os
import unittest
class CryptoBoxLogger: class CryptoBoxLogger:
''' '''
handles concrete logging events and prints them e.g. to a logfile handles logging events and prints them e.g. to a logfile
''' '''
DebugLevels = {"debug":0, "info":3, "warn":6, "error":9} DebugLevels = {"debug":0, "info":3, "warn":6, "error":9}
DebugFacilities = {"file":0} DebugFacilities = {"file":0}
def __init__(self, level, facility, name=None, user=None): def __init__(self, level, facility, destination=None):
"""create a CryptoBoxLogger object and connect it to an output facility
level: an integer (0..9) or string ('debug', 'info', 'warn' or 'error')
facility: for now only 'file'
destination: e.g. the name of the logfile or syslog facility
"""
try: try:
facility = int(facility) try:
except ValueError: facility = int(facility)
facility = self.DebugFacilities[facility] except Exception:
try:
facility = self.DebugFacilities[facility]
except KeyError:
raise "LoggerError"
if (facility != 0): raise "LoggerError"
except "LoggerError":
errorMsg = "Invalid debug facility: %s" % facility
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
try: try:
level = int(level) try:
except ValueError: level = int(level)
level = self.DebugLevels[level] except Exception:
try:
level = self.DebugLevels[level]
except KeyError:
raise "LoggerError"
if (level < 0) or (level > 9): raise "LoggerError"
except "LoggerError":
errorMsg = "Invalid debug level: %s" % level
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
self.debug_level = level self.debug_level = level
if facility == self.DebugFacilities["file"]: if facility == self.DebugFacilities["file"]:
self.logFunc = self.message2file self.logFunc = self.message2file
if name is not None: if destination is not None:
self.logFile = name self.logFile = destination
else: else:
self.logFile = '/var/log/cryptobox.log' self.logFile = '/var/log/cryptobox.log'
try: try:
fsock = open(self.logFile, "a") fsock = open(self.logFile, "a")
fsock.close() fsock.close()
# TODO: check before, if this file was owned
# by someone else than the cryptobox user - in this case,
# we may not chown it - very baaaad!
"change ownership of log file"
if user != None:
os.chown(self.logFile, user, os.getegid())
return
except IOError: except IOError:
sys.stderr.write("Unable to open logfile (%s) for writing.\n" % (self.logFile, )) errorMsg ="Unable to open logfile (%s) for writing." % (self.logFile,)
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
else: else:
sys.stderr.write("Invalid logging facility: %d.\n" % (facility, )) errorMsg = "Invalid logging facility: %d." % (facility, )
"we will only arrive here, if an error occoured" sys.stderr.write(errorMsg + "\n")
sys.stderr.write("Sorry - bye, bye!\n") raise "LoggerError", errorMsg
sys.exit(1)
def printMessage(self, msg_level, text): def printMessage(self, msg_level, text):
@ -53,7 +74,20 @@ class CryptoBoxLogger:
try: try:
msg_level = int(msg_level) msg_level = int(msg_level)
except ValueError: except ValueError:
msg_level = self.DebugLevels[msg_level] try:
msg_level = self.DebugLevels[msg_level]
except KeyError:
errorMsg = "Invalid debug level: %s" % msg_level
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
if (msg_level < 0) or (msg_level > 9):
errorMsg = "Invalid debug level: %s" % msg_level
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
if text is None:
errorMsg = "Empty debug message - this is not allowed"
sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
if msg_level >= self.debug_level: if msg_level >= self.debug_level:
self.logFunc("[CryptoBox] - %s\n" % (text, )) self.logFunc("[CryptoBox] - %s\n" % (text, ))
@ -66,12 +100,85 @@ class CryptoBoxLogger:
log_sock.close() log_sock.close()
return return
except IOError: except IOError:
sys.stderr.write( errorMsg = "Unable to write messages to logfile (%s)." % (self.logFile, )
"Unable to write messages to logfile (%s).\n" % (self.logFile, )) sys.stderr.write(errorMsg + "\n")
raise "LoggerError", errorMsg
except IOError: except IOError:
sys.stderr.write("Unable to open logfile (%s) for writing.\n" % (self.logFile, )) errorMsg = "Unable to open logfile (%s) for writing." % (self.logFile, )
"we will only arrive here, if an error occoured" sys.stderr.write(errorMsg + "\n")
sys.stderr.write("Sorry - bye, bye!\n") raise "LoggerError", errorMsg
sys.exit(1)
# ********************* test class **********************
class CryptoBoxLoggerTest(unittest.TestCase):
logFile = "/tmp/cbox-test.log"
def setUp(self):
if os.path.exists(self.logFile): os.remove(self.logFile)
def tearDown(self):
if os.path.exists(self.logFile): os.remove(self.logFile)
def testInit(self):
try:
CryptoBoxLogger(0, 0)
except "LoggerError":
CryptoBoxLogger(0, 0, self.logFile)
os.remove(self.logFile)
CryptoBoxLogger("info", "file", self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, "invalid", 0, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 0, "invalid", self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 10, 0, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, -1, 0, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 0, 10, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 0, -1, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, None, 0, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 0, None, self.logFile)
self.assertRaises("LoggerError", CryptoBoxLogger, 0, 0, "/no/existing/path")
os.remove(self.logFile)
def testMessage(self):
cb = CryptoBoxLogger(3, 0, self.logFile)
content1 = self.readFile()
self.assertEquals(content1, "")
cb.printMessage(3, "Ausgabe")
content2 = self.readFile()
self.assertNotEqual(content1, content2)
self.assertRaises("LoggerError", cb.printMessage, 10, "Ausgabe")
self.assertEquals(content2, self.readFile())
self.assertRaises("LoggerError", cb.printMessage, -1, "Ausgabe")
self.assertEquals(content2, self.readFile())
self.assertRaises("LoggerError", cb.printMessage, "invalid", "Ausgabe")
self.assertEquals(content2, self.readFile())
self.assertRaises("LoggerError", cb.printMessage, 3, None)
self.assertEquals(content2, self.readFile())
cb.printMessage(2, "Ausgabe")
self.assertEquals(content2, self.readFile())
cb.printMessage(4, "Ausgabe")
self.assertNotEqual(content2, self.readFile())
os.remove(self.logFile)
def readFile(self):
fd = None
try:
fd = open(self.logFile, "r")
text = fd.read()
fd.close()
except IOError:
if fd is not None: fd.close()
text = None
return text
# *************** unit testing *********************
if __name__ == "__main__":
try:
devnull = open(os.devnull, "w")
sys.stderr = devnull
except IOError:
pass
unittest.main()

View file

@ -19,8 +19,9 @@ NameDatabase = cryptobox_names.db
[System] [System]
# most actions of the cryptobox are not executed as root - choose a limited # most actions of the cryptobox are not executed as root - choose a limited
# user here - for now only numeric ids are allowed # user and group here - for now only numeric ids are allowed
User = 1000 User = 1000
Group = 1000
# where should we mount volumes? # where should we mount volumes?
# this directory must be writeable by the cryptobox user (see above) # this directory must be writeable by the cryptobox user (see above)