better permission handling of startup script

minor bugfixes
update of CryptoBoxWebserver manpage
This commit is contained in:
lars 2006-12-19 14:02:30 +00:00
parent 949f78958b
commit 9ec9475015
13 changed files with 200 additions and 87 deletions

View file

@ -50,7 +50,7 @@ allowedProgs = {
## this line is necessary to run unittests - otherwise these tests are too strict
# TODO: check this before every release!
OVERRIDE_FILECHECK = True
OVERRIDE_FILECHECK = False
DEV_TYPES = { "pipe":1, "char":2, "dir":4, "block":6, "file":8, "link":10, "socket":12}
EVENT_MARKER = '_event_scripts_'

View file

@ -67,7 +67,7 @@ except:
SERVER_ENVIRONMENT = "production"
class CryptoBoxWebserver:
'''this class starts the cherryp webserver and serves the single sites'''
'''this class starts the cherrypy webserver and serves the single sites'''
def __init__(self, opts):
self.opts = opts
@ -77,20 +77,27 @@ class CryptoBoxWebserver:
sys.exit(1)
## store the absolute path as we will chdir later (for daemons)
self.conffile = os.path.realpath(opts.conffile)
## drop privileges to run the cryptobox without root permissions
self.drop_privileges_temporarily()
## initialize site class
try:
cherrypy.root = cryptobox.web.sites.WebInterfaceSites(self.conffile)
except (CBConfigError,CBEnvironmentError), errMsg:
self.website = cherrypy.root
except (CBConfigError,CBEnvironmentError), err_msg:
sys.stderr.write("Error: the CryptoBox is misconfigured - please fix it!\n")
sys.stderr.write("%s\n" % str(errMsg))
sys.stderr.write("%s\n" % str(err_msg))
sys.exit(1)
## restore privileges, as we need them to connect to a low socket (<1024)
self.restore_privileges()
## 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": {
@ -101,49 +108,102 @@ class CryptoBoxWebserver:
"staticFilter.file": os.path.realpath(os.path.join(opts.datadir, 'favicon.ico'))}
})
self.define_exit_handlers(cherrypy.root)
def define_exit_handlers(self, cbw):
import atexit
import signal
## define exit handler for normal termination (via sys.exit)
def exit_handler():
cbw.cleanup()
try:
os.remove(self.opts.pidfile)
except OSError:
pass
atexit.register(exit_handler)
## catch kill signal
def kill_signal_handler(signum, frame):
cbw.cbox.log.info("Kill signal handler called: %d" % signum)
sys.exit(1)
signal.signal(signal.SIGTERM, kill_signal_handler)
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_uid, pw_gid = 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_uid in entry[3] ]
return (pw_uid, pw_gid, additional_groups)
def drop_privileges_temporarily(self):
"""Temporarily drop privileges.
"""
if self.opts.user is None:
return
(pw_uid, pw_gid, additional_groups) = self.get_user_info()
try:
os.setegid(pw_gid)
os.seteuid(pw_uid)
except OSError, err_msg:
sys.stderr.write("Failed to drop privileges temporarily: %s\n" % err_msg)
def restore_privileges(self):
"""Restore previously temporarily dropped privileges.
"""
if self.opts.user is None:
return
try:
os.setegid(os.getgid())
os.seteuid(os.getuid())
except OSError, err_msg:
sys.stderr.write("Failed to restore privileges: %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:
os.setgroups(additional_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):
cherrypy.server.start()
try:
cherrypy.server.start(initOnly=True)
self.drop_privileges_permanently()
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, errMsg:
except OSError, err_msg:
sys.stderr.write("Error: failed to fork cryptobox daemon process!\n")
sys.stderr.write("%s\n" % errMsg)
sys.stderr.write("%s\n" % err_msg)
sys.exit(1)
if pid == 0: # the first child
os.setsid()
try:
pid = os.fork()
except OSError, errMsg:
except OSError, err_msg:
sys.stderr.write("Error: failed to fork second cryptobox daemon process!\n")
sys.stderr.write("%s\n" % errMsg)
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
@ -163,16 +223,17 @@ def close_open_files():
## 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 = 1024
maxfd = 2
for fd in range(0, maxfd):
try:
os.close(fd)
## 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)
os.dup2(0, 2) # standard error (2)
def write_pid_file(pid_file):
@ -183,14 +244,15 @@ def write_pid_file(pid_file):
pidf = open(pid_file,"w")
pidf.write(str(os.getpid()))
pidf.close()
except (IOError, OSError), errMsg:
except (IOError, OSError), err_msg:
sys.stderr.write(
"Warning: failed to write pid file (%s): %s\n" % (pid_file, errMsg))
"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",
@ -200,7 +262,8 @@ def parseOptions():
logfile="/var/log/cryptobox-server/webserver.log",
port="8080",
host="",
verbose=True)
verbose=True,
user=None)
parser.add_option("-c", "--config", dest="conffile",
help="read configuration from FILE", metavar="FILE")
parser.add_option("","--pidfile", dest="pidfile",
@ -217,6 +280,8 @@ def parseOptions():
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")
(options, args) = parser.parse_args()
## we do not expect any remaining arguments
if len(args) != 0:
@ -232,29 +297,66 @@ def parseOptions():
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
return options
if __name__ == "__main__":
## process arguments
options = parseOptions()
## set umask to 022 (aka 755) - octal value
os.umask(022)
## run the webserver as a daemon process
if options.background: fork_to_background()
## write pid file
write_pid_file(options.pidfile)
## initialize the webserver class (before forking to get some error messages)
cbw = CryptoBoxWebserver(options)
## close open files to allow background execution
if options.background: close_open_files()
## fork to background before cbw.start() - otherwise we lose the socket
if options.background:
fork_to_background()
## start the webserver
try:
cbw.start()
except CBError, errMsg:
except CBError, err_msg:
sys.stderr.write("Failed to start the CryptoBox webserver!\n")
sys.stderr.write("%s\n" % str(errMsg))
sys.stderr.write("%s\n" % str(err_msg))
sys.stderr.write("Check the log file for details.\n")
sys.exit(1)
sys.exit(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_APPEND)
## startup went fine - fork is done - now we may write the pid file
## write pid file
write_pid_file(options.pidfile)
def exit_handler(signum, sigframe):
cbw.website.cbox.log.info("Shutting down ...")
cbw.website.cleanup()
try:
os.remove(options.pidfile)
except OSError:
pass
os._exit(0)
## the signal handler gets called by a kill signal (usually in background mode)
import signal
signal.signal(signal.SIGTERM, exit_handler)
## this exit handler gets called by KeyboardInterrupt and similar ones (foreground)
import atexit
atexit.register(exit_handler, None, None)
## this will never exit - one of the above exit handlers will get triggered
cherrypy.server.block()

4
debian/changelog vendored
View file

@ -1,8 +1,8 @@
cryptobox (0.3.0.1-1) unstable; urgency=low
cryptobox (0.3.1-1) unstable; urgency=low
* new upstream release
-- Lars Kruse <devel@sumpfralle.de> Mon, 18 Dec 2006 09:07:32 +0100
-- Lars Kruse <devel@sumpfralle.de> Tue, 19 Dec 2006 12:34:57 +0100
cryptobox (0.3.0-1) unstable; urgency=low

View file

@ -9,7 +9,6 @@ NO_START=1
RUNAS=cryptobox
# listening port
# for now please use a port above 1024
PORT=8080
# some more server options (rarely necessary)

View file

@ -37,7 +37,7 @@ test -x "$DAEMON" || DAEMON=/usr/bin/CryptoBoxWebserver
PYTHON_EXEC=/usr/bin/python
PIDFILE=/var/run/cryptobox-server/webserver.pid
DESC="CryptoBox Daemon (webinterface)"
OPTIONS="-B --pidfile=$PIDFILE --config=$CONF_FILE --logfile=$LOGFILE --host=$HOST --port=$PORT $SERVER_OPTS"
OPTIONS="-B --pidfile=$PIDFILE --config=$CONF_FILE --logfile=$LOGFILE --host=$HOST --port=$PORT --user=$RUNAS $SERVER_OPTS"
# check if the package is installed
test -e "$DAEMON" || exit 0
@ -51,13 +51,13 @@ case "$1" in
PIDDIR=$(dirname "$PIDFILE")
if [ -d "$PIDDIR" ]
then mkdir -p "$PIDDIR"
# necessary: the cryptobox server needs the permission to remove the pid file
chown $RUNAS:root "$PIDDIR"
chmod 755 "$PIDDIR"
fi
log_daemon_msg "Starting $DESC"
if start-stop-daemon \
--chuid $RUNAS: --quiet --start \
--user $RUNAS --pidfile "$PIDFILE" \
--quiet --start --user $RUNAS --pidfile "$PIDFILE" \
--startas "$PYTHON_EXEC" -- "$DAEMON" $OPTIONS
then log_end_msg 0
else log_end_msg 1
@ -82,7 +82,7 @@ case "$1" in
;;
reload | force-reload | restart )
"$0" stop
sleep 1
sleep 3
"$0" start
;;
status )

View file

@ -33,6 +33,7 @@ umount_all()
remove_stuff()
{
#TODO: remove old log files too (created by logrotate)
test -e "$LOG_FILE" && rm "$LOG_FILE"
test -e "$WEBLOG_FILE" && rm "$WEBLOG_FILE"
test -e "$PID_DIR" && rm -r "$PID_DIR"

View file

@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: CryptoBox-Server 0.3\n"
"Report-Msgid-Bugs-To: translate@cryptobox.org\n"
"POT-Creation-Date: 2006-11-28 05:03+0100\n"
"POT-Creation-Date: 2006-12-19 12:35+0100\n"
"PO-Revision-Date: 2006-12-18 16:38+0100\n"
"Last-Translator: Lars Kruse <lars@systemausfall.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -12,10 +12,6 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Pootle 0.10.1\n"
#: Name
msgid "English"
msgstr "Deutsch"
#: Title.Top
msgid "The CryptoBox"
msgstr "Die CryptoBox"
@ -57,20 +53,36 @@ msgid "The CryptoBox is a project of"
msgstr "Die CryptoBox ist ein Projekt von"
#: Text.ContainerName
msgid "Container's name"
msgid "Volume's name"
msgstr "Name des Datenträgers"
#: Button.HelpForForm
msgid "Get help"
msgstr "Hilfe"
#: Button.EnableHelp
msgid "Enable help"
msgstr "Hilfe aktivieren"
#: Button.DisableHelp
msgid "Disable help"
msgstr "Hilfe deaktivieren"
#: AdviceMessage.VolumeIsBusy.Title
msgid "Disk is busy"
msgstr "Datenträger ist beschäftigt"
#: AdviceMessage.VolumeIsBusy.Text
msgid "This disk is currently busy. Please wait for a moment."
msgstr "Der Datenträger ist noch beschäftigt. Warte bitte einen Moment."
#: AdviceMessage.VolumeIsBusy.Link.Text
msgid "Show all disks"
msgstr "Zeige alle Datenträger"
#: WarningMessage.AccessDenied.Title
msgid "Invalid access credentials"
msgstr "Ungültige Zugangsdaten"
#: WarningMessage.AccessDenied.Text
msgid "Sorry - you are not allowed to do this!"
msgstr "Sorry - du darfst dies nicht tun!"
msgid "Sorry - you did not enter the right credentials! Maybe you should try the default setting: username=>'admin' / password=>'admin'."
msgstr "Tut mir Leid, du hast nicht die richtigen Zugangsdaten eingegeben! (Versuch es mit \"admin\" als Benutzername und als Passwort und ändere das Passwort bald.)"
#: WarningMessage.EmptyPassword.Title
msgid "Missing password"
@ -105,16 +117,16 @@ msgid "The device you have chosen is invalid!"
msgstr "Der ausgewählte Datenträger kann nicht verwendet werden."
#: WarningMessage.VolumeMayNotBeMounted.Title
msgid "The container is mounted"
msgstr "Der Datenträger ist geöffnet."
msgid "The volume is open"
msgstr "Der Datenträger ist geöffnet"
#: WarningMessage.VolumeMayNotBeMounted.Text
msgid "This action is not available while the container is active. Please turn it off first."
msgid "This action is not available while the volume is active. Please close it first."
msgstr "Diese Aktion kann nicht durchgeführt werden, solange der Datenträger geöffnet ist. Bitte schließe ihn zuvor."
#: WarningMessage.VolumeMayNotBeMounted.Link.Text
msgid "Deactivate volume"
msgstr "Schließe den Datenträger"
msgid "Close volume"
msgstr "Schließe Datenträger"
#: WarningMessage.InvalidAction.Title
msgid "Invalid request"
@ -208,14 +220,3 @@ msgstr "Diese Aktion kann nicht durchgeführt werden, solange der Datenträger g
msgid "Close volume"
msgstr "Schließe Datenträger"
#~ msgid "volume_mount"
#~ msgstr "volume_mount"
#~ msgid "logs"
#~ msgstr "logs"
#~ msgid "partition"
#~ msgstr "partition"
#~ msgid "https"
#~ msgstr "https"

View file

@ -14,26 +14,30 @@ simple access to your data.
The following options control the behaviour of the CryptoBoxWebserver:
.TP
\fB\-c\fR, \fB\-\-config\fR=\fBFILE\fR
Uses the named configuration file.
Use the specified configuration file.
.TP
\fB\-p\fR, \fB\-\-port\fR=\fBPORT\fR
Specifiy a port to listen to.
Specify a port to listen to. The default port is 8080.
.TP
\fB\-\-host\fR=\fBHOST\fR
Specifiy the interface to listen to by providing a resolvable name or an ip. The server
Specify the interface to listen to by providing a resolvable name or an ip. The server
listens to all interfaces by default.
.TP
\fB\-B\fR
Run the webserver in the background. Otherwise the terminal will stay attached to the
process.
Run the webserver in the background. By default the process will stay attached to the
terminal.
.TP
\fB\-u\fR, \fB\-\-user\fR=\fBUSER\fR
Run with the permissions of the given user after connecting to the port. You may use a
uid or a name.
.TP
\fB\-q\fR
Quiet output - only errors will get reported.
.TP
\fB\-\-pidfile\fR=\fFILE\fR
\fB\-\-pidfile\fR=\fBFILE\fR
Specify a pid file for the webserver.
.TP
\fB\-\-datadir\fR=\fDIRECTORY\fR
\fB\-\-datadir\fR=\fBDIRECTORY\fR
Specify the location of the data directory of the webserver. The default location is
\fI/usr/share/cryptobox/www-data\fR.
.TP

View file

@ -10,5 +10,5 @@ __all__ = ['core', 'web', 'plugins', 'tests']
__revision__ = "$Id$"
__version__ = "0.3.0.1"
__version__ = "0.3.1"

View file

@ -574,7 +574,7 @@ class CryptoBoxContainer:
def __umount_plain(self):
"umount a plaintext partition"
if not self.is_mounted():
self.cbox.log.info("trying to umount while volume (%s) is mounted" % \
self.cbox.log.info("trying to umount while volume (%s) is not mounted" % \
self.get_device())
return
self.cbox.send_event_notification("preumount", self.__get_event_args())

View file

@ -66,12 +66,13 @@ class CryptoBox:
self.log.info("Umounting all volumes ...")
self.reread_container_list()
for cont in self.get_container_list():
cont.umount()
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:
if self.prefs.get_active_partition():
self.prefs.umount_partition()
## shutdown logging as the last step
try:

View file

@ -601,6 +601,9 @@ class MiscConfigFile:
def save(self):
"""Save a configuration file to disk.
"""
## overriding of ro-files is not necessary (e.g. samba-include.conf)
if os.path.exists(self.filename) and not os.access(self.filename, os.W_OK):
return True
save_dir = os.path.dirname(self.filename)
## create the directory, if necessary
if not os.path.isdir(save_dir):

View file

@ -98,8 +98,10 @@ class WebInterfaceSites:
def cleanup(self):
"""Shutdown the webinterface safely.
"""
self.cbox.log.info("Shutting down webinterface ...")
for plugin in self.__plugin_manager.get_plugins():
if plugin:
self.cbox.log.info("Cleaning up plugin '%s' ..." % plugin.get_name())
plugin.cleanup()
self.cbox.cleanup()