cryptonas/bin/CryptoBoxWebserver

242 lines
8.2 KiB
Python
Executable File

#!/usr/bin/env python2.4
#
# 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"
import os, sys
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)
# TODO: change this for the release version [development|production]
SERVER_ENVIRONMENT = "production"
class CryptoBoxWebserver:
'''this class starts the cherryp webserver and serves the single sites'''
def __init__(self, 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)
## initialize site class
try:
cherrypy.root = cryptobox.web.sites.WebInterfaceSites(self.conffile)
except (CBConfigError,CBEnvironmentError), errMsg:
sys.stderr.write("Error: the CryptoBox is misconfigured - please fix it!\n")
sys.stderr.write("%s\n" % str(errMsg))
sys.exit(1)
#expose static content:
#I currently have no idea how to cleanly extract the stylesheet path from
#the config object without an extra cryptobox.core.main.CryptoBoxProps instance.
#perhaps put config handling into a separate class in CryptoBox.py?
#
# the following manual mapping is necessary, as we may not use relative
# paths in the config file
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.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 start(self):
cherrypy.server.start()
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:
sys.stderr.write("Error: failed to fork cryptobox daemon process!\n")
sys.stderr.write("%s\n" % errMsg)
sys.exit(1)
if pid == 0: # the first child
os.setsid()
try:
pid = os.fork()
except OSError, errMsg:
sys.stderr.write("Error: failed to fork second cryptobox daemon process!\n")
sys.stderr.write("%s\n" % errMsg)
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:
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):
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(os.getpid()))
pidf.close()
except (IOError, OSError), errMsg:
sys.stderr.write(
"Warning: failed to write pid file (%s): %s\n" % (pid_file, errMsg))
## it is just a warning - no need to break
def parseOptions():
import cryptobox
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)
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")
(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))
return options
if __name__ == "__main__":
## process arguments
options = parseOptions()
## 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()
## start the webserver
try:
cbw.start()
except CBError, errMsg:
sys.stderr.write("Failed to start the CryptoBox webserver!\n")
sys.stderr.write("%s\n" % str(errMsg))
sys.stderr.write("Check the log file for details.\n")
sys.exit(1)
sys.exit(0)