Branched r1161 trunk for 0.3.5 release

This commit is contained in:
frisco 2009-06-25 02:11:30 +00:00
parent d0ed91ffa8
commit 1d1139428b
802 changed files with 135155 additions and 0 deletions

View file

@ -0,0 +1,555 @@
#!/usr/bin/env python
#
# Copyright 2007 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
#
"""module for executing some programs or scripts that need root privileges
Syntax:
check
- return exitcode zero if basic checks succeeded
program PROGRAM_NAME [ARGS]
- call the program (must be defined in "allowedProgs" below)
event EVENT_SCRIPT [ARGS]
- call an event script
plugin PLUGIN_NAME [ARGS]
- call a root_action script of a plugin
Exitcodes:
0 - execution was ok
1 - the executed program or action returned a failure exitcode
100 - improper calling or misconfiguration
of CryptoBoxRootAction (wrong arguments, wrong uid)
101 - failed to execute the given program - maybe it does not exist?
For more detailed information take a look at the manpage:
"man CryptoBoxRootActions"
"""
__revision__ = "$Id"
import os
import sys
import subprocess
import pwd
import grp
import types
allowedProgs = {
"sfdisk": "/sbin/sfdisk",
"cryptsetup": "/sbin/cryptsetup",
"mount": "/bin/mount",
"umount": "/bin/umount",
"blkid": "/sbin/blkid",
"pvdisplay": "/sbin/pvdisplay",
}
## this line is necessary for running unittests or playing around with a local
## svn working copy - otherwise the security checks would be too strict
OVERRIDE_FILECHECK = False
DEV_TYPES = { "pipe":1, "char":2, "dir":4, "block":6, "file":8, "link":10, "socket":12}
EVENT_MARKER = '_event_scripts_'
## use this string as device name if you want to mount a ramdisk
MAGIC_TMPFS = "_tmpfs_"
def checkIfFileIsSafe(fname):
"""check if the file and its parents are only writeable for root"""
## the override setting may be turned off temporarily to allow unittests
if OVERRIDE_FILECHECK:
return True
## if the calling user id is 0 (root), then we do not have to check this,
## as root would be allowed to do this anyway
## this eases testing with a not-installed working copy in a uml environment
if getCallingUserInfo()[1] == 0:
return True
props = os.stat(fname)
## check if it is owned by non-root
if props.st_uid != 0: return False
## check group-write permission if gid is not zero
if (props.st_gid != 0) and (props.st_mode % 32 / 16 > 0): return False
## check if it is world-writeable
if props.st_mode % 4 / 2 > 0: return False
## are we at root-level (directory-wise)? If yes, then we are ok ...
if fname == os.path.sep: return True
## check if the parent directory is ok - recursively :)
return checkIfFileIsSafe(os.path.dirname(os.path.abspath(fname)))
def checkIfPluginIsValid(plugin):
import imp
try:
x = imp.load_source("cbox_plugin", plugin)
except (SyntaxError, IOError):
return False
try:
if getattr(x, "PLUGIN_TYPE") == "cryptobox":
return True
else:
return False
except AttributeError:
return False
def checkIfEventScriptIsValid(plugin):
event_dir = os.path.dirname(plugin)
if os.path.exists(os.path.join(event_dir, EVENT_MARKER)):
return True
else:
return False
def call_plugin(args):
"""check if the plugin may be called - and do it finally ..."""
plugin = os.path.abspath(args[0])
del args[0]
## check existence and if it is executable
if not os.access(plugin, os.X_OK):
raise Exception, "could not find executable plugin (%s)" % plugin
## check if the plugin (and its parents) are only writeable for root
## this can be overridden by OVERRIDE_FILECHECK
if not checkIfFileIsSafe(plugin):
raise Exception, "the plugin (%s) is not safe - check its " % plugin \
+ "(and its parents') permissions"
## check if the plugin is a python program, that is marked as a cryptobox plugin
if not checkIfPluginIsValid(plugin):
raise Exception, "the plugin (%s) is not a correctly marked python script" % plugin
args.insert(0, plugin)
proc = subprocess.Popen(
shell = False,
args = args)
proc.wait()
return proc.returncode == 0
def call_event(args):
"""check if the event script may be called - and do it finally ..."""
event = os.path.abspath(args[0])
del args[0]
## check existence and if it is executable
if not os.access(event, os.X_OK):
raise Exception, "could not find executable event script (%s)" % event
## check if the script is valid (the marker file must be in the same directory)
if not checkIfEventScriptIsValid(event):
raise Exception, "the event script (%s) does not reside in" % event \
+ "a directory with the marker file (%s) - this " % EVENT_MARKER \
+ "is not allowed due to abuse prevention"
## check if the event (and its parents) are only writeable for root
if not checkIfFileIsSafe(event):
raise Exception, "the event (%s) is not safe - check its " % event \
+ "(and its parents') permissions"
args.insert(0, event)
proc = subprocess.Popen(
shell = False,
args = args)
proc.wait()
return proc.returncode == 0
def isWriteable(path, force_dev_type=None):
"""check if the calling user (not root!) has write access to the device/file
the real (not the effective) user id is used for the check
additionally the permissions of the default groups of the real uid are checked
it is sufficient, if the device/dir is owned by us
this check works nicely together with "super", as it changes (by default) only
the effective uid (not the real uid)
"""
## first check, if the device/file exists
if not os.path.exists(path):
sys.stderr.write("%s does not exist!\n" % path)
return False
## check the type of the path - if necessary
if (not force_dev_type is None) and \
(force_dev_type != os.stat(path).st_mode % 65536 / 4096):
sys.stderr.write("%s does not have the numeric type '%d'!\n" \
% (path, force_dev_type))
return False
## retrieve the information for the real user id
(trustUserName, trustUID, groupsOfTrustUser) = getCallingUserInfo()
## are we called by the root user? this would be ok
if trustUID == 0:
return True
## is the path owned by us?
if os.stat(path)[4] == trustUID:
return True
## set the default groups of the caller for the check (restore them later)
savedGroups = os.getgroups()
os.setgroups(groupsOfTrustUser)
## check permissions
result = os.access(path, os.W_OK) and os.access(path, os.R_OK)
## reset the groups of this process
os.setgroups(savedGroups)
return result
def run_cryptsetup(args):
"""execute cryptsetup as root
@args: list of arguments - they will be treated accordingly to the first element
of this list (the action)"""
if not args: raise "WrongArguments", "no action for cryptsetup supplied"
if type(args) != types.ListType:
raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
try:
action = args[0]
del args[0]
device = None
cmd_args = []
if action == "luksFormat":
device = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
elif action == "luksUUID":
device = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
elif action == "luksOpen":
if len(args) < 2: raise "WrongArguments", "missing arguments"
device = args[0]; del args[0]
destination = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
cmd_args.append(destination)
elif action == "luksClose":
if len(args) < 1: raise "WrongArguments", "missing arguments"
destination = args[0]; del args[0]
# maybe add a check for the mapped device's permissions?
# dmsetup deps self.device
cmd_args.append(action)
cmd_args.append(destination)
elif action == "luksAddKey":
device = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
elif action == "luksDelKey":
if len(args) < 2: raise "WrongArguments", "missing arguments"
device = args[0]; del args[0]
slot = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
cmd_args.append(slot)
elif action == "isLuks":
device = args[0]; del args[0]
cmd_args.append(action)
cmd_args.append(device)
else: raise "WrongArguments", "invalid action supplied: %s" % (action, )
# check if a device was defined - and check it
if (not device is None) and (not isWriteable(device, DEV_TYPES["block"])):
raise "WrongArguments", "%s is not a writeable block device" % (device, )
cs_args = [allowedProgs["cryptsetup"]]
cs_args.extend(args)
cs_args.extend(cmd_args)
except (TypeError, IndexError):
raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
# execute cryptsetup with the given parameters
proc = subprocess.Popen(
shell = False,
args = cs_args)
proc.wait()
## chown the devmapper block device to the cryptobox user
calling_user = getCallingUserInfo()
if (proc.returncode == 0) and (action == "luksOpen"):
os.chown(os.path.join(os.path.sep, "dev", "mapper", destination),
calling_user[1], calling_user[2][0])
return proc.returncode == 0
def getFSType(device):
"""get the filesystem type of a device"""
proc = subprocess.Popen(
shell = False,
stdout = subprocess.PIPE,
args = [ allowedProgs["blkid"],
"-s", "TYPE",
"-o", "value",
"-c", os.devnull,
"-w", os.devnull,
device])
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
return None
return stdout.strip()
def run_mount(args):
"""execute mount
"""
if not args: raise "WrongArguments", "no destination for mount supplied"
if type(args) != types.ListType:
raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
try:
device = args[0]
del args[0]
destination = args[0]
del args[0]
## shall we mount a ramdisk?
is_tmpfs = (device == MAGIC_TMPFS)
# check permissions for the device
if (not is_tmpfs) and (not isWriteable(device, DEV_TYPES["block"])):
raise "WrongArguments", "%s is not a writeable block device" % (device, )
## check permissions for the mountpoint
if not isWriteable(destination, DEV_TYPES["dir"]):
raise "WrongArguments", "the mountpoint (%s) is not writeable" \
% (destination, )
# check for additional (not allowed) arguments
if len(args) != 0:
raise "WrongArguments", "too many arguments for 'mount': %s" % (args, )
except TypeError:
raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
# execute mount with the given parameters
# first overwrite the real uid, as 'mount' wants this to be zero (root)
savedUID = os.getuid()
os.setuid(os.geteuid())
## we have to change the permissions of the mounted directory - otherwise it will
## not be writeable for the cryptobox user
## for 'vfat' we have to do this during mount
## for ext2/3 we have to do it afterward
## first: get the user/group of the target
(trustUserName, trustUID, groupsOfTrustUser) = getUserInfo(savedUID)
trustGID = groupsOfTrustUser[0]
if is_tmpfs:
fsType = "tmpfs"
else:
fsType = getFSType(device)
## define arguments
if fsType == "vfat":
## add the "uid/gid" arguments to the mount call
mount_args = [ allowedProgs["mount"],
"-o", "uid=%d,gid=%d,umask=0000" % (trustUID, trustGID),
device,
destination ]
## use fuse (ntfs-3g) for ntfs
elif fsType == "ntfs":
mount_args = [ allowedProgs["mount"],
"-t", "ntfs-3g",
device,
destination ]
elif is_tmpfs:
mount_args = [ allowedProgs["mount"],
"-t", "tmpfs",
"cryptobox-tmpfs", destination ]
else:
## all other filesystem types will be handled after mount
mount_args = [ allowedProgs["mount"], device, destination ]
# execute mount
proc = subprocess.Popen(
shell = False,
args = mount_args)
proc.wait()
## return in case of an error
if proc.returncode != 0:
return False
## for vfat: we are done
if fsType == "vfat": return True
## for all other filesystem types: chown the mount directory
try:
os.chown(destination, trustUID, groupsOfTrustUser[0])
except OSError, errMsg:
sys.stderr.write("could not chown the mount destination (%s) " % destination \
+ "to the specified user (%d/%d): " % (trustUID, groupsOfTrustUser[0]) \
+ "%s/n" % str(errMsg))
sys.stderr.write("UID: %d\n" % (os.geteuid(),))
return False
## BEWARE: it would be nice, if we could restore the previous uid (not euid) but
## this would also override the euid (see 'man 2 setuid') - any ideas?
return True
def run_umount(args):
"""execute mount
"""
if not args: raise "WrongArguments", "no mountpoint for umount supplied"
if type(args) != types.ListType:
raise "WrongArguments", "invalid arguments supplied"
try:
destination = args[0]
del args[0]
# check permissions for the destination
if not isWriteable(os.path.dirname(destination), DEV_TYPES["dir"]):
raise "WrongArguments", "the parent of the mountpoint " \
+ "(%s) is not writeable" % (destination, )
if len(args) != 0: raise "WrongArguments", "umount does not allow arguments"
except TypeError:
raise "WrongArguments", "invalid arguments supplied"
# execute umount with the given parameters
# first overwrite the real uid, as 'umount' wants this to be zero (root)
savedUID = os.getuid()
os.setuid(os.geteuid())
# execute umount (with the parameter '-l' - lazy umount)
proc = subprocess.Popen(
shell = False,
args = [allowedProgs["umount"], "-l", destination])
proc.wait()
# restore previous real uid
os.setuid(savedUID)
return proc.returncode == 0
def run_pvdisplay(args):
"""execute pvdisplay to check for physical LVM devices
"""
if len(args) > 0:
raise "WrongArguments", "no arguments may be supplied for 'pvdisplay'"
## call pvdisplay with the parameter "--colon"
proc = subprocess.Popen(
shell = False,
args = [ allowedProgs["pvdisplay"], "--colon" ])
proc.wait()
return proc.returncode == 0
def getCallingUserInfo():
"""return information about the user that was calling this program via "super"
@user: (uid or name)
@return: tuple of (name, uid, (groups))
"""
## are we called via 'super'?
if ("SUPERCMD" in os.environ) and ("ORIG_USER" in os.environ):
## return the user that was calling super
return getUserInfo(os.environ["ORIG_USER"])
else:
## return the current user
return getUserInfo(os.getuid())
def getUserInfo(user):
"""return information about the specified user
@user: (uid or name)
@return: tuple of (name, uid, (groups))
"""
if (user is None) or (user == ""):
raise "KeyError", "no user supplied"
## if a KeyError is raised again in the following lines, then the supplied
## user was invalid
if type(user) is int:
# 'user' is a uid
userinfo = pwd.getpwuid(user)
elif type(user) is str:
# 'user' is a name
userinfo = pwd.getpwnam(user)
u_groups = [one_group.gr_gid
for one_group in grp.getgrall()
if userinfo.pw_name in one_group.gr_mem]
if not userinfo.pw_gid in u_groups:
## put in front of the list
u_groups.insert(0,userinfo.pw_gid)
return (userinfo.pw_name, userinfo.pw_uid, u_groups)
# **************** main **********************
# prevent import
if __name__ == "__main__":
## do we have root privileges (effective uid is zero)?
if os.geteuid() != 0:
sys.stderr.write("the effective uid is not zero - you should use " \
+ "'super' to call this script (%s)" % sys.argv[0])
sys.exit(100)
## remove program name
args = sys.argv[1:]
# do not allow to use root permissions (real uid may not be zero)
#if os.getuid() == 0:
# sys.stderr.write("the uid of the caller is zero (root) - this is not allowed\n")
# sys.exit(100)
## check if there were arguments
if (len(args) == 0):
sys.stderr.write("No arguments supplied\n")
sys.exit(100)
## did the user call the "check" action?
if (len(args) == 1) and (args[0].lower() == "check"):
# exit silently
sys.exit(0)
## all of the following actions require at least two arguments
if len(args) < 2:
sys.stderr.write("No program/plugin/event specified for execution\n")
sys.exit(100)
## call a plugin root_action script
if args[0].lower() == "plugin":
del args[0]
try:
isOK = call_plugin(args)
except Exception, errMsg:
sys.stderr.write("Execution of plugin '%s' failed: %s\n" \
% (args[0], errMsg))
sys.exit(100)
if isOK:
sys.exit(0)
else:
sys.exit(1)
## call an event script
if args[0].lower() == "event":
del args[0]
try:
isOK = call_event(args)
except Exception, errMsg:
sys.stderr.write("Execution of event '%s' failed: %s\n" \
% (args[0], errMsg))
sys.exit(100)
if isOK:
sys.exit(0)
else:
sys.exit(1)
## call one of the allowed programs
if args[0].lower() == "program":
del args[0]
progRequest = args[0]
del args[0]
if not progRequest in allowedProgs.keys():
sys.stderr.write("Invalid program requested: %s\n" % progRequest)
sys.exit(100)
if progRequest == "cryptsetup": runner = run_cryptsetup
elif progRequest == "mount": runner = run_mount
elif progRequest == "umount": runner = run_umount
elif progRequest == "pvdisplay": runner = run_pvdisplay
else:
sys.stderr.write("The interface for this program (%s) is " \
+ "not yet implemented!\n" % progRequest)
sys.exit(100)
try:
if runner(args):
sys.exit(0)
else:
sys.exit(1)
except OSError, errstr:
sys.stderr.write("Execution failed: %s\n" % errstr)
sys.exit(101)
except "WrongArguments", errstr:
sys.stderr.write("Invalid arguments: %s\n" % errstr)
sys.exit(100)

View file

@ -0,0 +1,401 @@
#!/usr/bin/env python
#
# The daemon script to run the CryptoBox webserver.
#
# run the script with "--help" to see all possible paramters
#
#
# 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
#
__revision__ = "$Id"
REMOVE_ENV_SETTINGS = [ "LANG", "LC", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
"LC_MESSAGES", "LC_NUMERIC", "BASH_ENV", "SHELLOPTS" ]
import os, sys
import signal, atexit
import cryptobox.web.sites
from cryptobox.core.exceptions import *
from optparse import OptionParser
## check python version
(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")
sys.stderr.write("Current version is: %s\n" % sys.version)
sys.exit(1)
## check cherrypy dependency
try:
import cherrypy
except:
sys.stderr.write("Could not import the cherrypy module!\n")
sys.stderr.write("Try 'apt-get install python-cherrypy'.\n")
sys.exit(1)
## check clearsilver dependency
try:
import neo_cgi, neo_util
except:
sys.stderr.write("Could not import the clearsilver module!\n")
sys.stderr.write("Try 'apt-get install python-clearsilver'.\n")
sys.exit(1)
## check configobj dependency
try:
import configobj, validate
except:
sys.stderr.write("Could not import the configobj or validate module!\n")
sys.stderr.write("Try 'apt-get install python-configobj'.\n")
sys.exit(1)
SERVER_ENVIRONMENT = "production"
class CryptoBoxWebserver:
'''this class starts the cherrypy webserver and serves the single sites'''
def __init__(self, opts):
"""Configure cherrypy and check the location of the configuration file
"""
self.opts = opts
## check conffile
if not os.access(opts.conffile, os.R_OK) or not os.path.isfile(opts.conffile):
sys.stderr.write("Error: could not read configuration file (%s)\n" % opts.conffile)
sys.exit(1)
## store the absolute path as we will chdir later (for daemons)
self.conffile = os.path.realpath(opts.conffile)
## expose static content and set options
## beware:
cherrypy.config.update({
"global": {
"server.socket_port" : int(opts.port),
"server.socket_host" : opts.host,
"server.log_to_screen" : not opts.background and opts.verbose,
"server.log_tracebacks" : opts.verbose,
"server.log_request_headers": opts.verbose,
"server.environment": SERVER_ENVIRONMENT,
"server.log_file" : opts.logfile },
"/cryptobox-misc": {
"staticFilter.on" : True,
"staticFilter.dir": os.path.realpath(opts.datadir)},
"/favicon.ico": {
"staticFilter.on" : True,
"staticFilter.file": os.path.realpath(os.path.join(opts.datadir, 'favicon.ico'))}
})
def bootup_cryptobox(self):
## initialize site class
try:
cherrypy.root = cryptobox.web.sites.WebInterfaceSites(self.conffile)
self.website = cherrypy.root
except (CBConfigError,CBEnvironmentError), err_msg:
sys.stderr.write("Error: the CryptoBox is misconfigured - please fix it!\n")
raise
def get_user_info(self):
"""Retrieve the uid, gid and additional groups of the given user
"""
import pwd, grp
user_entry = pwd.getpwuid(self.opts.user)
## get the new uid and gid
pw_name, pw_uid, pw_gid = user_entry[0], user_entry[2], user_entry[3]
## change the owner of the webserver log file
try:
os.chown(self.opts.logfile, pw_uid, pw_gid)
except OSError:
## fail silently
pass
## calculate additional groups of the given user
additional_groups = [ entry[2]
for entry in grp.getgrall()
if pw_name in entry[3] ] + [ pw_gid ]
return (pw_uid, pw_gid, additional_groups)
def change_groups(self):
"""Change the groups of the current process to the ones of the given user
we have to do this before we call cherrypy.server.start(), as it somehow
remembers the current setting for any thread it will create later
"""
if self.opts.user is None:
return
(pw_uid, pw_gid, additional_groups) = self.get_user_info()
try:
os.setgroups(additional_groups)
except OSError, err_msg:
sys.stderr.write("Failed to change the groups: %s\n" % err_msg)
def drop_privileges_permanently(self):
"""Drop all privileges of the current process and acquire the privileges of the
given user instead.
"""
if self.opts.user is None:
return
(pw_uid, pw_gid, additional_groups) = self.get_user_info()
try:
## setgroups happened before (see 'change_groups')
os.setregid(pw_gid, pw_gid)
os.setreuid(pw_uid, pw_uid)
except OSError, err_msg:
sys.stderr.write("Failed to drop privileges permanently: %s\n" % err_msg)
def start(self):
try:
## first: change the groups (cherrypy.server.start stores the
## current setting for creating new threads later)
self.change_groups()
cherrypy.server.start(initOnly=True)
self.drop_privileges_permanently()
## this must be done with dropped privileges - otherwise there is
## at least a problem with 'blkid' - see bug #139
self.bootup_cryptobox()
cherrypy.server.wait_for_http_ready()
except cherrypy._cperror.NotReady, err_msg:
sys.stderr.write("Failed to start CryptoBox: %s\n" % err_msg)
sys.exit(1)
except Exception, err_msg:
if err_msg == "(98, 'Address already in use')":
sys.stderr.write("Failed to start CryptoBox: %s\n" % err_msg)
sys.exit(1)
else:
raise
def fork_to_background():
## this is just copy'n'pasted from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731
## check the original for exhaustive comments
try:
pid = os.fork()
except OSError, err_msg:
sys.stderr.write("Error: failed to fork cryptobox daemon process!\n")
sys.stderr.write("%s\n" % err_msg)
sys.exit(1)
if pid == 0: # the first child
os.setsid()
try:
pid = os.fork()
except OSError, err_msg:
sys.stderr.write("Error: failed to fork second cryptobox daemon process!\n")
sys.stderr.write("%s\n" % err_msg)
sys.exit(1)
if pid == 0: # the second child
## we do not change the directory - otherwise there seems to be a race condition with the python interpreter loading this script file
#os.chdir(os.path.sep)
os.umask(0)
else:
os._exit(0)
else:
os._exit(0)
def close_open_files():
"""this is only necessary if we want to go into background
we will only close stdin, stdout and stderr
"""
import resource # Resource usage information.
## use the following lines to close all open files (including the log file)
# maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
# if (maxfd == resource.RLIM_INFINITY):
# maxfd = 1024
maxfd = 2
for fd in range(0, maxfd):
try:
## close all except for stderr - we will redirect it later
if fd != 2:
os.close(fd)
except OSError: # ERROR, fd wasn't open to begin with (ignored)
pass
os.open(os.devnull, os.O_RDWR) # standard input (0)
os.dup2(0, 1) # standard output (1)
def write_pid_file(pid_file, pid=None):
"""write the process ID of the cryptonas daemon to a file
call this function with the second parameter (e.g. pid=0) to check, if the
given location is writeable
@param pid_file: the path of the pid file to be written
@type pid_file: string
@param pid: use a specific PID instead of the PID of the current process
@type pid: int
"""
if pid is None:
## use the PID of the current process
pid = os.getpid()
if os.path.exists(pid_file):
sys.stderr.write(
"Warning: pid file (%s) already exists - overwriting ...\n" % pid_file)
try:
pidf = open(pid_file,"w")
pidf.write(str(pid))
pidf.close()
except (IOError, OSError), err_msg:
sys.stderr.write(
"Warning: failed to write pid file (%s): %s\n" % (pid_file, err_msg))
## it is just a warning - no need to break
def parseOptions():
import cryptobox
import pwd
version = "%prog" + cryptobox.__version__
parser = OptionParser(version=version)
parser.set_defaults(conffile="/etc/cryptobox-server/cryptobox.conf",
pidfile="/var/run/cryptobox-server/webserver.pid",
background=False,
datadir="/usr/share/cryptobox-server/www-data",
logfile="/var/log/cryptobox-server/webserver.log",
port="8080",
host="",
verbose=True,
profile_file=False,
user=None)
parser.add_option("-c", "--config", dest="conffile",
help="read configuration from FILE", metavar="FILE")
parser.add_option("","--pidfile", dest="pidfile",
help="write process id to FILE", metavar="FILE")
parser.add_option("-B","", dest="background", action="store_true",
help="run webserver in background (as daemon)")
parser.add_option("-q","", dest="verbose", action="store_false",
help="output only errors")
parser.add_option("","--datadir", dest="datadir", metavar="DIR",
help="set data directory to DIR")
parser.add_option("-p","--port", dest="port", metavar="PORT",
help="listen on PORT")
parser.add_option("-l","--logfile", dest="logfile", metavar="FILE",
help="write webserver log to FILE")
parser.add_option("","--host", dest="host", metavar="HOST",
help="attach to HOST")
parser.add_option("-u","--user", dest="user", metavar="USER",
help="change to USER after starting the webserver")
parser.add_option("","--profile", dest="profile_file", metavar="PROFILE_FILE",
help="enable profiling and store results in PROFILE_FILE")
(options, args) = parser.parse_args()
## we do not expect any remaining arguments
if len(args) != 0:
parser.error("unknown argument: %s" % str(args[0]))
if not ((not os.path.exists(options.logfile) \
and os.access(os.path.dirname(options.logfile), os.W_OK)) \
or os.access(options.logfile, os.W_OK)):
parser.error("could not write to logfile (%s)" % options.logfile)
if not os.path.isdir(options.datadir) or not os.access(options.datadir,os.X_OK):
parser.error("could not access the data directory (%s)" % options.datadir)
try:
if (int(options.port) < 0) or (int(options.port) > 65535):
parser.error("invalid port number: %s" % str(options.port))
except ValueError:
parser.error("invalid port specified (%s) - it must be a number" % (options.port))
if options.user:
try:
try:
## check for the user given as uid
uid = pwd.getpwuid(int(options.user))[2]
except ValueError:
## check for the user given as name
uid = pwd.getpwnam(options.user)[2]
except KeyError:
## invalid user specified
parser.error("invalid user specified (%s)" % options.user)
## we will use the uid
options.user = uid
if options.profile_file:
options.profile_file = os.path.abspath(options.profile_file)
try:
import profile
except ImportError:
parser.error("profiling requires the python module 'profile' - debian users should run 'apt-get install python-profiler'")
return options
def clean_environment(settings_list):
"""Remove some environment settings with side effects (e.g. LANG)
Useful, as some plugins depend on the output of other commands - localized
output would be quite ugly for them ...
"""
for one_setting in settings_list:
os.unsetenv(one_setting)
if __name__ == "__main__":
## process arguments
options = parseOptions()
## set umask to 022 (aka 755) - octal value
os.umask(022)
## initialize the webserver class
cbw = CryptoBoxWebserver(options)
## remove some environment settings
clean_environment(REMOVE_ENV_SETTINGS)
## fork to background before cbw.start() - otherwise we lose the socket
if options.background:
fork_to_background()
## define the default exit handler
def exit_handler(signum, sigframe):
if hasattr(cbw, "website"):
## are we already up?
cbw.website.cbox.log.info("Shutting down ...")
cbw.website.cleanup()
cherrypy.server.stop()
try:
os.remove(options.pidfile)
except OSError:
pass
os._exit(0)
## the signal handler gets called by a kill signal (usually in background mode)
signal.signal(signal.SIGTERM, exit_handler)
## this exit handler gets called by KeyboardInterrupt and similar ones (foreground)
atexit.register(exit_handler, None, None)
## start the webserver
try:
if options.profile_file:
import profile
profile.run('cbw.start()', options.profile_file)
else:
cbw.start()
except CBError, err_msg:
sys.stderr.write("Failed to start the CryptoBox webserver!\n")
sys.stderr.write("%s\n" % str(err_msg))
sys.stderr.write("Check the log file for details.\n")
cherrypy.server.stop()
sys.exit(1)
## test if we can write to the PID file
## this _must_ be done before forking, since a potential error would be
## silent (due to the closed files - e.g. STDERR)
write_pid_file(options.pidfile, 0)
## redirect stderr to the webserver's logfile
if options.background:
## replace stdin and stdout by /dev/null
close_open_files()
## replace stderr by the webserver logfile
os.close(2)
os.open(options.logfile, os.O_WRONLY | os.O_APPEND)
## startup went fine - fork is done - now we may write the pid file
write_pid_file(options.pidfile)
## this will never exit - one of the above exit handlers will get triggered
cherrypy.server.block()

View file

@ -0,0 +1,100 @@
[Main]
# comma separated list of possible prefixes for accesible devices
# beware: .e.g "/dev/hd" grants access to _all_ harddisks
AllowedDevices = /dev/ubdb
# use separate config partition? (1=yes / 0=no)
UseConfigPartition = 1
# the default name prefix of not unnamed containers
DefaultVolumePrefix = "Disk"
# which cipher should cryptsetup-luks use?
#TODO: uml does not support this module - DefaultCipher = aes-cbc-essiv:sha256
DefaultCipher = aes-plain
# label of the configuration partition (you should never change this)
ConfigVolumeLabel = cbox_config
# which plugins should be disabled? (comma seperated list)
#DisabledPlugins = network, shutdown, partition
[Locations]
# where should we mount volumes?
# this directory must be writeable by the cryptobox user (see above)
#MountParentDir = /var/cache/cryptobox/mnt
MountParentDir = ../ttt/mnt
# settings directory: contains name database and plugin configuration
#SettingsDir = /var/cache/cryptobox/settings
SettingsDir = ../ttt/settings
# where are the clearsilver templates?
#TemplateDir = /usr/share/cryptobox/templates
TemplateDir = ../templates
# path to documentation files
#DocDir = /usr/share/doc/cryptobox/www-data
DocDir = ../doc/html
# path to the plugin directory
#PluginDir = /usr/share/cryptobox/plugins
PluginDir = ../plugins
# path to the hook directory (e.g. containing some scripts)
#HookDir = /etc/cryptobox/hooks.d
EventDir = ../event-scripts
[Log]
# possible values are "debug", "info", "warn" and "error" or numbers from
# 0 (debug) to 7 (error)
Level = debug
# where to write the log messages to?
# possible values are: file
# syslog support will be added later
Destination = file
# depending on the choosen destination (see above) you may select
# details. Possible values for the different destinations are:
# file: $FILENAME
# syslog: $LOG_FACILITY
#Details = /var/log/cryptobox.log
Details = ./cryptobox.log
[WebSettings]
# URL of default stylesheet
Stylesheet = cryptobox-misc/cryptobox.css
# default language
Languages = en, de, sl, fr
[Programs]
cryptsetup = /sbin/cryptsetup
mkfs = /sbin/mkfs
blkid = /sbin/blkid
blockdev = /sbin/blockdev
mount = /bin/mount
umount = /bin/umount
nice = /usr/bin/nice
super = /usr/bin/super
# this is the "program" name as defined in /etc/super.tab
# "CryptoBoxRootActionsLocal" (in /etc/super.tab) should point to the
# CryptoBoxRootActions.py file in your local working directory. This avoids
# conflicts with a locally (apt-)installed CryptoBoxRootActions.py file
CryptoBoxRootActions = CryptoBoxRootActionsLocal
[PluginSettings]
# plugin specific settings
# the section names _must_ be the same as the names of the plugins
## change the default network interface for the plugin "network"
#[[network]]
#interface = eth0

View file

@ -0,0 +1 @@
cryptobox-unittests.conf

35
staging-v0.3.5/bin/do_pylint.sh Executable file
View file

@ -0,0 +1,35 @@
#!/bin/sh
#
# set some environmental variables for pylint and run it
#
PROJ_DIR=$(dirname "$0")/..
PROJ_DIR=$(cd "$PROJ_DIR"; pwd)
PYLINTRC=$PROJ_DIR/src/pylintrc
PYTHONPATH=$PROJ_DIR/src
check_for_filename()
{
# maybe the argument is a file instead of a module name
if echo "$1" | grep -q "\.py$" && test -e "$1"
then local FILE_DIR=$(dirname "$1")
local MODULE=$(basename "${1%.py}")
ARGS="${ARGS} ${MODULE}"
PYTHONPATH="${PYTHONPATH}:${FILE_DIR}"
else ARGS="${ARGS} ${1}"
fi
}
while test $# -gt 0
do check_for_filename "$1"
shift
done
export PYTHONPATH
export PYLINTRC
[ ! -x /usr/bin/pylint ] && echo >&2 "please run \"apt-get install pylint\" first" && exit 1
pylint $ARGS

View file

@ -0,0 +1,112 @@
#!/bin/sh
#do_unittests.sh
#
# run this script _before_ you do a commit and fix errors before uploading
#
# preparations:
# - add the following lines to /etc/super.tab:
# :global_options relative_path=y
# CryptoBoxRootActionsLocal ./CryptoBoxRootActions cryptobox
#
GETOPT="getopt"
BASE_DIR=$(cd "$(dirname $0)/.."; pwd)
export PYTHONPATH=$BASE_DIR/src
disable_filecheck()
{
sed -i "s/^OVERRIDE_FILECHECK = .*$/OVERRIDE_FILECHECK = True/" "$BASE_DIR/bin/CryptoBoxRootActions"
}
enable_filecheck()
{
sed -i "s/^OVERRIDE_FILECHECK = .*$/OVERRIDE_FILECHECK = False/" "$BASE_DIR/bin/CryptoBoxRootActions"
}
run_tests()
{
# chdir to 'bin' - all config settings depend on this
cd "${BASE_DIR}/bin"
disable_filecheck
if test -n "$files"
then # do the specified tests
##The use of "eval", plus not using double quotes for
##$a below are
##required because getopt already puts argument names in
##single-quotes, and the script will not work with both sets
##of quotes.
for a in $files
do eval testoob -v $a
done
else # do all tests
for a in ${BASE_DIR}/src/cryptobox/tests/test.*.py
do testoob -v "$a"
done
fi
enable_filecheck
return 0
}
usage()
{
echo -e "Usage:"
echo -e "\tbin/do_unittests.sh --clobber=foo [ files... ]"
echo -e "\t"
echo -e "\t(Where \"files\", if given, are absolute pathnames)"
echo -e "\t(Example: bin/do_unittests.sh --clobber=sdX)"
echo -e "***Warning: All data on /dev/foo will be DESTROYED!"
echo -e "\t"
exit 1
}
##### main() section #####
##Declare global variables with empty contents
CNAS_UTEST_CLOBBER=""
files=""
clobber_dev_arg=""
##Use the getopt command line utility to simplify parsing
getopt_str=`${GETOPT} -o c: --long clobber: \
-n do_unittests.sh -- "$@"`
#echo $getopt_str
##Assign the tokens to an array
getopt_arr=($getopt_str)
##Note: Output of getopt in this script should look like:
## --clobber 'sdX' -- file1 file2 file3 ...
##Read the following code with that in mind.
##If there is exactly one option, and it has an argument
if [ "${getopt_arr[0]}" == "--clobber" ] \
&& [ "${getopt_arr[2]}" == "--" ]; then
clobber_dev_arg="${getopt_arr[1]}"
##Strip any single quotes from the device name
clobber_dev_arg="${clobber_dev_arg//\'/}"
#echo ${clobber_dev_arg}
##Grab the part of $getopt_str that follows "--"
files="`expr match \"${getopt_str}\" '.*--\(.*\)'`"
#echo $files
if [ -b "/dev/${clobber_dev_arg}" ]; then
export CNAS_UTEST_CLOBBER="${clobber_dev_arg}"
##Run the tests using testoob...
run_tests
else
echo "Error: /dev/${clobber_dev_arg} is not a valid block device"
exit 2
fi
else
usage
fi

View file

@ -0,0 +1,58 @@
#!/bin/sh
#
# example start script to run a local cryptobox webserver
#
# we set some parameters to make it possible to run it without an
# existing cryptobox installation
#
# change your local settings in "cryptobox-local.conf" - if this file
# does not exist, then "cryptobox.conf" is used
#
# the environment variable PORT may override the default (8080)
#
# BEWARE: the super.tab entry must be named "CryptoBoxRootActionsLocal" instead of
# "CryptoBoxRootActions" (useful for development)
#
BIN_DIR=$(dirname "$0")
BIN_DIR=$(cd "$BIN_DIR"; pwd)
disable_filecheck()
{
sed -i "s/^OVERRIDE_FILECHECK = .*$/OVERRIDE_FILECHECK = True/" "$BIN_DIR/CryptoBoxRootActions"
}
enable_filecheck()
{
sed -i "s/^OVERRIDE_FILECHECK = .*$/OVERRIDE_FILECHECK = False/" "$BIN_DIR/CryptoBoxRootActions"
}
## add the local python directory to the search path
export PYTHONPATH="$BIN_DIR/../src"
## disable ssl detection
#export HTTPS=1
PREFERRED_CONF_FILE=$BIN_DIR/cryptobox-local.conf
FALLBACK_CONF_FILE=$BIN_DIR/cryptobox-unittests.conf
## determine the configuration file
CONFIG_FILE=$FALLBACK_CONF_FILE
test -r "$PREFERRED_CONF_FILE" && CONFIG_FILE=$PREFERRED_CONF_FILE
echo "used config: $CONFIG_FILE"
## create necessary directories
mkdir -p "$BIN_DIR/../ttt/mnt"
mkdir -p "$BIN_DIR/../ttt/settings"
cd "$BIN_DIR"
# disable strict security checks of CryptoBoxRootActions
disable_filecheck
## run the webserver
"$BIN_DIR/CryptoBoxWebserver" --config="$CONFIG_FILE" --pidfile=/tmp/cryptoboxwebserver.pid --logfile=/tmp/cryptoboxwebserver.log --port=${PORT:-8080} --datadir="$BIN_DIR/../www-data" "$@"
# enable strict security checks of CryptoBoxRootActions again
enable_filecheck

42
staging-v0.3.5/bin/uml-setup.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/sh
#uml-setup.sh
#
#This file does some setup required for running the unit tests under
#User Mode Linux (UML). If you are using a different test setup, it
#does not apply.
PROJ_DIR=$(dirname "$0")/..
PROJ_DIR=$(cd "$PROJ_DIR"; pwd)
ROOT_IMG=$PROJ_DIR/bin/cryptobox.img
TEST_IMG=$PROJ_DIR/bin/test.img
TEST_SIZE=128
MEM_SIZE=128M
if test ! -e "$ROOT_IMG"
then echo "Could not find the cryptobox system image ($ROOT_IMG)"
echo " see stuff/uml-howto.txt for information on how to build a system image"
echo " store (or link) the result as '$ROOT_IMG'"
exit 1
fi
# Preparations:
# echo "tun" >>/etc/modules
# follow the instructions in /usr/share/doc/uml-utilities/README.Debian
# add your user to the group 'uml-net'
#
/sbin/ifconfig tap0 &>/dev/null || { echo "tap0 is not configured - read /usr/share/doc/uml-utilities/README.Debian for hints"; exit 1; }
if [ ! -e "$TEST_IMG" ]
then echo "Creating testing image file ..."
dd if=/dev/zero of="$TEST_IMG" bs=1M count=$TEST_SIZE
fi
if [ ! -w "$ROOT_IMG" ]; then
echo "Make sure \"${ROOT_IMG}\" exists and is writeable"
exit 1;
fi
# "aio=2.4" is necessary, as otherwise sfdiks hangs at "nanosleep({3,0})"
linux ubd0="$ROOT_IMG" ubd1="$TEST_IMG" con=xterm hostfs=$PROJ_DIR fakehd eth0=daemon mem=$MEM_SIZE aio=2.4