#!/usr/bin/python2.4 import os, sys import cryptobox.web.sites from cryptobox.core.exceptions import * from optparse import OptionParser try: import cherrypy except: print "Could not import the cherrypy module! Try 'apt-get install python-cherrypy'." sys.exit(1) # TODO: change this for the release version SERVER_ENVIRONMENT = "development" 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)} }) 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""" import resource # Resource usage information. maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] if (maxfd == resource.RLIM_INFINITY): maxfd = 1024 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): 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(): version = "%prog" + cryptobox.core.main.VERSION parser = OptionParser(version=version) parser.set_defaults(conffile="/etc/cryptobox/webserver.conf", pidfile="/var/run/cryptobox/webserver.pid", background=False, datadir="/usr/share/cryptobox/html", logfile="/var/log/cryptobox/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 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() ## initialize the webserver class (before forking to get some error messages) cbw = CryptoBoxWebserver(options) ## run the webserver as a daemon process if options.background: fork_to_background() ## write pid file write_pid_file(options.pidfile) ## 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)