#!/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)