Example #1
0
def delete(key=None):
    # TODO(choudhury): shut down a vtkweb process by key after a given timeout.

    processes = tangelo.plugin_store()["processes"]

    if key is None:
        tangelo.http_status(400, "Required Argument Missing")
        return {"error": "'key' argument is required"}

    # Check for the key in the process table.
    if key not in processes:
        tangelo.http_status(400, "Key Not Found")
        return {"error": "Key %s not in process table" % (key)}

    # Terminate the process.
    tangelo.log_info("VTKWEB", "Shutting down process %s" % (key))
    proc = processes[key]
    proc["process"].terminate()
    proc["process"].wait()
    tangelo.log_info("VTKWEB", "Process terminated")

    # Remove the process entry from the table.
    del processes[key]

    return {"key": key}
Example #2
0
    def unload(self, plugin_name):
        tangelo.log_info("PLUGIN", "Unloading plugin '%s'" % (plugin_name))

        plugin = self.plugins[plugin_name]

        if plugin.module is not None:
            tangelo.log_info("PLUGIN",
                             "\t...removing module %s" % (plugin.module))
            del sys.modules[plugin.module]

        for app_path in plugin.apps:
            tangelo.log_info("PLUGIN",
                             "\t...unmounting app at %s" % (app_path))
            del cherrypy.tree.apps[app_path]

        if "teardown" in dir(plugin.control):
            tangelo.log_info("PLUGIN", "\t...running teardown")
            try:
                plugin.control.teardown(
                    cherrypy.config["plugin-config"][plugin.path],
                    cherrypy.config["plugin-store"][plugin.path])
            except:
                tangelo.log_warning("PLUGIN", "Could not run teardown:\n%s",
                                    (traceback.format_exc()))

        del self.plugins[plugin_name]
        tangelo.log_info("plugin %s unloaded" % (plugin_name))
Example #3
0
 def closed(self, code, reason=None):
     # TODO(choudhury): figure out if recovery, etc. is possible if the
     # socket is closed for some reason.
     tangelo.log_info(
         "VTKWEB",
         "websocket at %s:%d/%s closed with code %d (%s)" % (
             hostname, port, key, code, reason
         )
     )
Example #4
0
def teardown(config, store):
    if reactor.running:
        tangelo.log_info("VTKWEB", "Shutting down Twisted reactor")
        threads.blockingCallFromThread(reactor, reactor.stop)

    if "processes" in store:
        tangelo.log_info("VTKWEB", "Terminating VTKWeb processes")
        for p in store["processes"].values():
            p["process"].terminate()
            p["process"].wait()
Example #5
0
def initialize():
    global vtkpython
    global weblauncher

    # Get the module config.
    config = tangelo.plugin_config()

    # Raise an error if there's no vtkpython executable.
    vtkpython = config.get("vtkpython", None)
    if not vtkpython:
        msg = "No 'vtkpython' option specified in configuration plugin"
        tangelo.log_warning("VTKWEB", "[initialization] fatal error: %s" % (msg))

        # Construct a run() function that will mask the restful API and just
        # inform the caller about the configuration problem.
        def run():
            tangelo.http_status(400, "Bad Configuration")
            return {"error": msg}

        sys.modules[__name__].__dict__["run"] = run
        return

    vtkpython = tangelo.util.expandpath(vtkpython)
    tangelo.log("VTKWEB", "[initialization] Using vtkpython executable %s" % (vtkpython))

    # Use the "web launcher" included with the plugin.
    weblauncher = os.path.realpath("%s/../include/vtkweb-launcher.py" % (os.path.dirname(__file__)))

    # Initialize a table of VTKWeb processes.
    if tangelo.plugin_store().get("processes") is None:
        tangelo.plugin_store()["processes"] = {}

    # Check to see if a reactor is running already.
    if twisted.internet.reactor.running:
        threads = [t for t in threading.enumerate() if t.name == "tangelo-vtkweb-plugin"]
        if len(threads) > 0:
            tangelo.log_warning(
                "VTKWEB", "[initialization] A reactor started by a previous loading of this plugin is already running"
            )
        else:
            tangelo.log_warning(
                "VTKWEB", "[initialization] A reactor started by someone other than this plugin is already running"
            )
    else:
        # Start the Twisted reactor, but in a separate thread so it doesn't
        # block the CherryPy main loop.  Mark the thread as "daemon" so that
        # when Tangelo's main thread exits, the reactor thread will be killed
        # immediately.
        reactor = threading.Thread(
            target=twisted.internet.reactor.run, kwargs={"installSignalHandlers": False}, name="tangelo-vtkweb-plugin"
        )
        reactor.daemon = True
        reactor.start()

        tangelo.log_info("VTKWEB", "[initialization] Starting Twisted reactor")
Example #6
0
        def __init__(self, *pargs, **kwargs):
            ws4py.websocket.WebSocket.__init__(self, *pargs, **kwargs)

            scheme = "ws"
            if cherrypy.config.get("server.ssl_private_key"):
                scheme = "wss"
            url = "%s://%s:%d/ws" % (scheme, hostname, port)

            tangelo.log_info("VTKWEB", "websocket created at %s:%d/%s (proxy to %s)" % (hostname, port, key, url))

            self.client = VTKWebSocketAB(url, self)
Example #7
0
def initialize():
    global vtkpython
    global weblauncher

    # Get the module config.
    config = tangelo.plugin_config()

    # Raise an error if there's no vtkpython executable.
    vtkpython = config.get("vtkpython", None)
    if not vtkpython:
        msg = "No 'vtkpython' option specified in configuration plugin"
        tangelo.log_warning("VTKWEB", "[initialization] fatal error: %s" % (msg))

        # Construct a run() function that will mask the restful API and just
        # inform the caller about the configuration problem.
        def run():
            tangelo.http_status(400, "Bad Configuration")
            return {"error": msg}

        sys.modules[__name__].__dict__["run"] = run
        return

    vtkpython = tangelo.util.expandpath(vtkpython)
    tangelo.log("VTKWEB", "[initialization] Using vtkpython executable %s" % (vtkpython))

    # Use the "web launcher" included with the plugin.
    weblauncher = os.path.realpath("%s/../include/vtkweb-launcher.py" % (os.path.dirname(__file__)))

    # Initialize a table of VTKWeb processes.
    if tangelo.plugin_store().get("processes") is None:
        tangelo.plugin_store()["processes"] = {}

    # Check to see if a reactor is running already.
    if twisted.internet.reactor.running:
        threads = [t for t in threading.enumerate() if t.name == "tangelo-vtkweb-plugin"]
        if len(threads) > 0:
            tangelo.log_warning("VTKWEB", "[initialization] A reactor started by a previous loading of this plugin is already running")
        else:
            tangelo.log_warning("VTKWEB", "[initialization] A reactor started by someone other than this plugin is already running")
    else:
        # Start the Twisted reactor, but in a separate thread so it doesn't
        # block the CherryPy main loop.  Mark the thread as "daemon" so that
        # when Tangelo's main thread exits, the reactor thread will be killed
        # immediately.
        reactor = threading.Thread(
            target=twisted.internet.reactor.run,
            kwargs={"installSignalHandlers": False},
            name="tangelo-vtkweb-plugin"
        )
        reactor.daemon = True
        reactor.start()

        tangelo.log_info("VTKWEB", "[initialization] Starting Twisted reactor")
Example #8
0
        def __init__(self, *pargs, **kwargs):
            ws4py.websocket.WebSocket.__init__(self, *pargs, **kwargs)

            scheme = "ws"
            if cherrypy.config.get("server.ssl_private_key"):
                scheme = "wss"
            url = "%s://%s:%d/ws" % (scheme, hostname, port)

            tangelo.log_info(
                "VTKWEB", "websocket created at %s:%d/%s (proxy to %s)" %
                (hostname, port, key, url))

            self.client = VTKWebSocketAB(url, self)
Example #9
0
def restGet(route, query_string=''):
    tangelo.log_info("Table: %s, query string: %s"%(route,query_string))
    try:
        url = 'http://' + StrongLoopHostname + ':' + StrongLoopPort + '/api/' + route
        if len(query_string) > 0:
            # query_string = urllib.quote_plus(query_string)
            url += '?' + query_string
        res = httpSession.get(url)
        res.raise_for_status()
        return json.loads(res.text)
    except requests.exceptions.HTTPError as e:
        tangelo.log_error("URL: %s Error Message: %s"%(url, res.text))
        return {}
    except Exception as e:
        tangelo.log_error("Error getting data",e)
        return {}
Example #10
0
def restGet(route, query_string=''):
    tangelo.log_info("Table: %s, query string: %s" % (route, query_string))
    try:
        url = 'http://' + StrongLoopHostname + ':' + StrongLoopPort + '/api/' + route
        if len(query_string) > 0:
            # query_string = urllib.quote_plus(query_string)
            url += '?' + query_string
        res = httpSession.get(url)
        res.raise_for_status()
        return json.loads(res.text)
    except requests.exceptions.HTTPError as e:
        tangelo.log_error("URL: %s Error Message: %s" % (url, res.text))
        return {}
    except Exception as e:
        tangelo.log_error("Error getting data", e)
        return {}
Example #11
0
def watch_module_cache_get(cache, module):
    """
    When we ask to fetch a module with optional config file, check time stamps
    and dependencies to determine if it should be reloaded or not.

    :param cache: the cache object that stores whether to check for config
                  files and which files have been loaded.
    :param module: the path of the module to load.
    :returns: the loaded module.
    """
    imp.acquire_lock()
    try:
        if not hasattr(cache, "timestamps"):
            cache.timestamps = {}
        mtime = os.path.getmtime(module)
        mtime = latest_submodule_time(module, mtime)
        if getattr(cache, "config", False):
            config_file = module[:-2] + "yaml"
            if os.path.exists(config_file):
                # Our timestamp is the latest time of the config file or the
                # module.
                mtime = max(mtime, os.path.getmtime(config_file))
            # If we have a config file and the timestamp is more recent than
            # the recorded timestamp, remove the config file from the list of
            # loaded files so that it will get loaded again.
            if config_file in cache.config_files and mtime > cache.timestamps.get(module, 0):
                del cache.config_files[config_file]
                tangelo.log("WATCH", "Asking to reload config file %s" % config_file)
        # If the timestamp is more recent than the recorded value, remove the
        # the module from our records so that it will be loaded again.
        if module in cache.modules and mtime > cache.timestamps.get(module, 0):
            del cache.modules[module]
            tangelo.log("WATCH", "Asking to reload module %s" % module)
        if module not in cache.timestamps:
            tangelo.log_info("WATCH", "Monitoring module %s" % module)
        reload_recent_submodules(module, mtime)
        cache.timestamps[module] = mtime
        service = tangelo_module_cache_get(cache, module)
        # Update our time based on all the modules that we may have just
        # imported.  The times can change from before because python files are
        # compiled, for instance.
        mtime = latest_submodule_time(module, mtime)
        cache.timestamps[module] = mtime
    finally:
        imp.release_lock()
    return service
Example #12
0
def watch_import(name, globals=None, *args, **kwargs):
    """
    When a module is asked to be imported, check if we have previously imported
    it.  If so, check if the time stamp of it, a companion yaml file, or any
    modules it imports have changed.  If so, reimport the module.

    :params: see __builtin__.__import__
    """
    # Don"t monitor builtin modules.  types seem special, so don"t monitor it
    # either.
    monitor = not imp.is_builtin(name) and name not in ("types", )
    # Don"t monitor modules if we don"t know where they came from
    monitor = monitor and isinstance(globals, dict) and globals.get("__name__")
    if not monitor:
        return builtin_import(name, globals, *args, **kwargs)
    # This will be the dotted module name except for service modules where it
    # will be the absolute file path.
    parent = globals["__name__"]
    key = parent + "." + name
    module_reload_changed(key)
    try:
        module = builtin_import(name, globals, *args, **kwargs)
    except ImportError:
        raise
    if getattr(module, "__file__", None):
        if key not in WatchList:
            tangelo.log_info("WATCH", "Monitoring import %s from %s" % (name, parent))
        imp.acquire_lock()
        try:
            if key not in WatchList:
                filemtime = module_getmtime(module.__file__) or 0
                filemtime = latest_submodule_time(key, filemtime)
                WatchList[key] = {
                    "time": filemtime
                }
            WatchList[key].update({
                "parent": parent,
                "name": name,
                "file": module.__file__
            })
        finally:
            imp.release_lock()
    return module
Example #13
0
    def unload(self, plugin_name):
        tangelo.log_info("PLUGIN", "Unloading plugin '%s'" % (plugin_name))

        plugin = self.plugins[plugin_name]

        if plugin.module is not None:
            tangelo.log_info("PLUGIN", "\t...removing module %s" % (plugin.module))
            del sys.modules[plugin.module]

        for app_path in plugin.apps:
            tangelo.log_info("PLUGIN", "\t...unmounting app at %s" % (app_path))
            del cherrypy.tree.apps[app_path]

        if "teardown" in dir(plugin.control):
            tangelo.log_info("PLUGIN", "\t...running teardown")
            try:
                plugin.control.teardown(
                    cherrypy.config["plugin-config"][plugin.path], cherrypy.config["plugin-store"][plugin.path]
                )
            except:
                tangelo.log_warning("PLUGIN", "Could not run teardown:\n%s", (traceback.format_exc()))

        del self.plugins[plugin_name]
        tangelo.log_info("plugin %s unloaded" % (plugin_name))
Example #14
0
def shutdown(signum, frame):
    tangelo.log_info("TANGELO", "Received interrupt signal, performing graceful shutdown")

    # Disbale the shutdown handler (i.e., for repeated Ctrl-C etc.) for the
    # "polite" shutdown signals.
    for sig in [signal.SIGINT, signal.SIGTERM]:
        signal.signal(sig, polite)

    # Perform plugin shutdown operations.
    tangelo.log_info("TANGELO", "Shutting down plugins...")
    plugins = cherrypy.config.get("plugins")
    if plugins:
        plugins.unload_all()

    # Perform CherryPy shutdown and exit.
    tangelo.log_info("TANGELO", "Stopping web server")
    cherrypy.engine.stop()
    cherrypy.engine.exit()

    tangelo.log_info("\033[32mTANGELO", "\033[32mBe seeing you.")
Example #15
0
def shutdown(signum, frame):
    tangelo.log_info(
        "TANGELO", "Received interrupt signal, performing graceful shutdown")

    # Disbale the shutdown handler (i.e., for repeated Ctrl-C etc.) for the
    # "polite" shutdown signals.
    for sig in [signal.SIGINT, signal.SIGTERM]:
        signal.signal(sig, polite)

    # Perform plugin shutdown operations.
    tangelo.log_info("TANGELO", "Shutting down plugins...")
    plugins = cherrypy.config.get("plugins")
    if plugins:
        plugins.unload_all()

    # Perform CherryPy shutdown and exit.
    tangelo.log_info("TANGELO", "Stopping web server")
    cherrypy.engine.stop()
    cherrypy.engine.exit()

    tangelo.log_info("\033[32mTANGELO", "\033[32mBe seeing you.")
Example #16
0
def main():
    p = argparse.ArgumentParser(description="Start a Tangelo server.")
    p.add_argument("-c", "--config", type=str, default=None, metavar="FILE", help="specifies configuration file or json string to use")
    p.add_argument("-a", "--access-auth", action="store_const", const=True, default=None, help="enable HTTP authentication (i.e. processing of .htaccess files) (default)")
    p.add_argument("-na", "--no-access-auth", action="store_const", const=True, default=None, help="disable HTTP authentication (i.e. processing of .htaccess files)")
    p.add_argument("-p", "--drop-privileges", action="store_const", const=True, default=None, help="enable privilege drop when started as superuser (default)")
    p.add_argument("-np", "--no-drop-privileges", action="store_const", const=True, default=None, help="disable privilege drop when started as superuser")
    p.add_argument("-s", "--sessions", action="store_const", const=True, default=None, help="enable session tracking (default)")
    p.add_argument("-ns", "--no-sessions", action="store_const", const=True, default=None, help="disable session tracking")
    p.add_argument("--list-dir", action="store_true", default=None, help="enable directory content serving")
    p.add_argument("--no-list-dir", action="store_true", default=None, help="disable directory content serving (default)")
    p.add_argument("--show-py", action="store_true", default=None, help="enable Python service source code serving")
    p.add_argument("--no-show-py", action="store_true", default=None, help="disable Python service source code serving (default)")
    p.add_argument("--hostname", type=str, default=None, metavar="HOSTNAME", help="overrides configured hostname on which to run Tangelo")
    p.add_argument("--port", type=int, default=None, metavar="PORT", help="overrides configured port number on which to run Tangelo")
    p.add_argument("-u", "--user", type=str, default=None, metavar="USERNAME", help="specifies the user to run as when root privileges are dropped")
    p.add_argument("-g", "--group", type=str, default=None, metavar="GROUPNAME", help="specifies the group to run as when root privileges are dropped")
    p.add_argument("-r", "--root", type=str, default=None, metavar="DIR", help="the directory from which Tangelo will serve content")
    p.add_argument("--verbose", "-v", action="append_const", help="display extra information as Tangelo runs", default=[logging.INFO], const=logging.DEBUG - logging.INFO)
    p.add_argument("--quiet", "-q", action="append_const", help="reduce the amount of information displayed", dest="verbose", const=logging.INFO - logging.DEBUG)
    p.add_argument("--version", action="store_true", help="display Tangelo version number")
    p.add_argument("--key", type=str, default=None, metavar="FILE", help="the path to the SSL key.  You must also specify --cert to serve content over https.")
    p.add_argument("--cert", type=str, default=None, metavar="FILE", help="the path to the SSL certificate.  You must also specify --key to serve content over https.")
    p.add_argument("--examples", action="store_true", default=None, help="Serve the Tangelo example applications")
    p.add_argument("--watch", action="store_true", default=None, help="Add the watch plugin (reload python files if they change).")
    args = p.parse_args()

    # If version flag is present, print the version number and exit.
    if args.version:
        print tangelo_version
        return 0

    # Set the verbosity
    log_level = max(sum(args.verbose), 1)
    cherrypy.log.error_log.setLevel(log_level)

    # Make sure user didn't specify conflicting flags.
    if args.access_auth and args.no_access_auth:
        tangelo.log_critical("ERROR", "can't specify both --access-auth (-a) and --no-access-auth (-na) together")
        return 1

    if args.drop_privileges and args.no_drop_privileges:
        tangelo.log_critical("ERROR", "can't specify both --drop-privileges (-p) and --no-drop-privileges (-np) together")
        return 1

    if args.no_sessions and args.sessions:
        tangelo.log_critical("ERROR", "can't specify both --sessions (-s) and --no-sessions (-ns) together")
        return 1

    if args.examples and args.root:
        tangelo.log_critical("ERROR", "can't specify both --examples and --root (-r) together")
        return 1

    if args.examples and args.config:
        tangelo.log_critical("ERROR", "can't specify both --examples and --config (-c) together")
        return 1

    if args.no_list_dir and args.list_dir:
        tangelo.log_critical("ERROR", "can't specify both --list-dir and --no-list-dir together")
        return 1

    if args.no_show_py and args.show_py:
        tangelo.log_critical("ERROR", "can't specify both --show-py and --no-show-py together")
        return 1

    # Report the logging level.
    if log_level > logging.CRITICAL:
        log_level_tag = "NONE"
    elif log_level > logging.ERROR:
        log_level_tag = "CRITICAL"
    elif log_level > logging.WARNING:
        log_level_tag = "ERROR"
    elif log_level > logging.INFO:
        log_level_tag = "WARNING"
    elif log_level > logging.DEBUG:
        log_level_tag = "INFO"
    else:
        log_level_tag = "DEBUG"

    tangelo.log_info("TANGELO", "Logging level: %s (%d)" % (log_level_tag, log_level))

    # Decide if we have a configuration file or not.
    cfg_file = args.config
    if cfg_file is None:
        tangelo.log_info("TANGELO", "No configuration file specified - using command line args and defaults")
    else:
        if os.path.exists(tangelo.util.expandpath(cfg_file)) or not cfg_file.startswith("{"):
            cfg_file = tangelo.util.expandpath(cfg_file)
            tangelo.log("TANGELO", "Using configuration file %s" % (cfg_file))
        else:
            tangelo.log("TANGELO", "Using configuration string")

    # Parse the config file; report errors if any.
    try:
        config = Config(cfg_file)
    except (IOError, ValueError) as e:
        tangelo.log_critical("ERROR", e)
        return 1

    # Type check the config entries.
    if not config.type_check():
        for message in config.errors:
            tangelo.log_critical("TANGELO", message)
        return 1

    # Determine whether to use access auth.
    access_auth = True
    if args.access_auth is None and args.no_access_auth is None:
        if config.access_auth is not None:
            access_auth = config.access_auth
    else:
        access_auth = (args.access_auth is not None) or (not args.no_access_auth)

    tangelo.log_info("TANGELO", "Access authentication %s" % ("enabled" if access_auth else "disabled"))

    # Determine whether to perform privilege drop.
    drop_privileges = True
    if args.drop_privileges is None and args.no_drop_privileges is None:
        if config.drop_privileges is not None:
            drop_privileges = config.drop_privileges
    else:
        drop_privileges = (args.drop_privileges is not None) or (not args.no_drop_privileges)

    # Determine whether to enable sessions.
    sessions = True
    if args.sessions is None and args.no_sessions is None:
        if config.sessions is not None:
            sessions = config.sessions
    else:
        sessions = (args.sessions is not None) or (not args.no_sessions)

    tangelo.log_info("TANGELO", "Sessions %s" % ("enabled" if sessions else "disabled"))

    # Determine whether to serve directory listings by default.
    listdir = False
    if args.list_dir is None and args.no_list_dir is None:
        if config.list_dir is not None:
            listdir = config.list_dir
    else:
        listdir = (args.list_dir is not None) or (not args.no_list_dir)

    cherrypy.config["listdir"] = listdir
    tangelo.log_info("TANGELO", "Directory content serving %s" % ("enabled" if listdir else "disabled"))

    # Determine whether to serve web service Python source code by default.
    showpy = False
    if args.show_py is None and args.no_show_py is None:
        if config.show_py is not None:
            showpy = config.show_py
    else:
        showpy = (args.show_py is not None) or (not args.no_show_py)

    cherrypy.config["showpy"] = showpy
    tangelo.log_info("TANGELO", "Web service source code serving %s" % ("enabled" if showpy else "disabled"))

    # Extract the rest of the arguments, giving priority first to command line
    # arguments, then to the configuration file (if any), and finally to a
    # hard-coded default value.
    hostname = args.hostname or config.hostname or "localhost"
    port = args.port or config.port or 8080
    user = args.user or config.user or "nobody"
    group = args.group or config.group or "nobody"

    tangelo.log_info("TANGELO", "Hostname: %s" % (hostname))
    tangelo.log_info("TANGELO", "Port: %d" % (port))

    tangelo.log_info("TANGELO", "Privilege drop %s" % ("enabled (if necessary)" if drop_privileges else "disabled"))
    if drop_privileges:
        tangelo.log_info("TANGELO", "\tUser: %s" % (user))
        tangelo.log_info("TANGELO", "\tGroup: %s" % (group))

    # HTTPS support
    #
    # Grab the ssl key file.
    ssl_key = args.key or config.key
    if ssl_key is not None:
        ssl_key = tangelo.util.expandpath(ssl_key)

    # Grab the cert file.
    ssl_cert = args.cert or config.cert
    if ssl_cert is not None:
        ssl_cert = tangelo.util.expandpath(ssl_cert)

    # In order to enable HTTPS, *both* the key and cert must be specified.  If
    # only one or the other is specified, this is considered an error, because
    # we don't want to serve what the user is considering sensitive content over
    # HTTP by default.
    if ssl_key is not None and ssl_cert is not None:
        cherrypy.config.update({"server.ssl_module": "pyopenssl",
                                "server.ssl_certificate": ssl_cert,
                                "server.ssl_private_key": ssl_key})
        tangelo.log_info("TANGELO", "HTTPS enabled")
        tangelo.log_info("TANGELO", "\tSSL Cert file: %s" % (ssl_cert))
        tangelo.log_info("TANGELO", "\tSSL Key file: %s" % (ssl_key))
    elif not (ssl_key is None and ssl_cert is None):
        tangelo.log_critical("TANGELO", "error: SSL key or SSL cert missing")
        return 1
    else:
        tangelo.log_info("TANGELO", "HTTPS disabled")

    # We need a web root - use the installed example web directory as a
    # fallback.  This might be found in a few different places, so try them one
    # by one until we find one that exists.
    root = args.root or config.root
    if root:
        root = tangelo.util.expandpath(root)
    elif args.examples:
        # Set the examples web root.
        root = get_web_directory()
        tangelo.log_info("TANGELO", "Looking for example web content path in %s" % (root))
        if not os.path.exists(root):
            tangelo.log_critical("ERROR", "could not find examples package")
            return 1

        # Set the examples plugins.
        config.plugins = [{"name": "config"},
                          {"name": "data"},
                          {"name": "docs"},
                          {"name": "mapping"},
                          {"name": "mongo"},
                          {"name": "stream"},
                          {"name": "tangelo"},
                          {"name": "ui"},
                          {"name": "vis"}]
    else:
        root = tangelo.util.expandpath(".")
    # All that the core does when --watch is includes is to make sure it is in
    # the list of plugins.  If it isn't present, it gets added to the beginning
    # of the list so that other plugins will be monitored.
    if args.watch:
        if config.plugins is None:
            config.plugins = []
        if "watch" not in [plugin.get("name") for plugin in config.plugins]:
            config.plugins.insert(0, {"name": "watch"})

    tangelo.log_info("TANGELO", "Serving content from %s" % (root))

    # Set the web root directory.
    cherrypy.config.update({"webroot": root})

    # Place an empty dict to hold per-module configuration into the global
    # configuration object, and one for persistent per-module storage (the
    # latter can be manipulated by the service).
    cherrypy.config.update({"module-config": {}})
    cherrypy.config.update({"module-store": {}})

    # Analogs of the module storage dicts, but for plugins.
    cherrypy.config.update({"plugin-config": {}})
    cherrypy.config.update({"plugin-store": {}})

    # Create a plugin manager.
    plugins = tangelo.server.Plugins("tangelo.plugin", config=config.plugins, plugin_dir=get_bundled_plugin_directory())

    # Check for any errors - if there are, report them and exit.
    if not plugins.good():
        for message in plugins.errors:
            tangelo.log_critical("PLUGIN", message)
        return 1

    # Save the plugin manager for use later (when unloading plugins during
    # shutdown).
    cherrypy.config.update({"plugins": plugins})

    # Create an instance of the main handler object.
    module_cache = tangelo.util.ModuleCache()
    tangelo_server = tangelo.server.Tangelo(module_cache=module_cache, plugins=plugins)
    rootapp = cherrypy.Application(tangelo_server, "/")

    # Place an AuthUpdate handler in the Tangelo object if access authorization
    # is on.
    tangelo_server.auth_update = tangelo.server.AuthUpdate(app=rootapp)

    # Mount the root application object.
    cherrypy.tree.mount(rootapp, config={"/": {"tools.sessions.on": sessions},
                                         "/favicon.ico": {"tools.staticfile.on": True,
                                                          "tools.staticfile.filename": get_tangelo_ico()}})

    # Set up the global configuration.
    cherrypy.config.update({"environment": "production",
                            "log.screen": True,
                            "server.socket_host": hostname,
                            "server.socket_port": port})

    # Dump in any other global configuration present in the configuration.
    if config.server_settings:
        tangelo.log_info("TANGELO", "User server settings:")
        for setting, value in config.server_settings.iteritems():
            tangelo.util.set_server_setting(setting, value)
            tangelo.log_info("TANGELO", "\t%s -> %s" % (setting, cherrypy.config.get(setting)))

    # Try to drop privileges if requested, since we've bound to whatever port
    # superuser privileges were needed for already.
    if drop_privileges:
        # If we're on windows, don't supply any username/groupname, and just
        # assume we should drop priveleges.
        if platform.system() == "Windows":
            tangelo.log_info("TANGELO", "Performing privilege drop")
            cherrypy.process.plugins.DropPrivileges(cherrypy.engine).subscribe()
        elif os.getuid() == 0:
            tangelo.log_info("TANGELO", "Performing privilege drop")

            # Reaching here means we're on unix, and we are the root user, so go
            # ahead and drop privileges to the requested user/group.
            import grp
            import pwd

            # On some systems, negative uids and gids are allowed.  These can
            # render in Python (in particular, on OS X) as very large unsigned
            # values.  This function first checks to see if the input value is
            # already negative; if so, there's no issue and we return it
            # unchanged.  Otherwise, we treat the argument as a bit
            # representation of a *signed* value, check the sign bit to see if
            # it *should* be a negative number, and then perform the proper
            # arithmetic to turn it into a signed one.
            def to_signed(val):
                # If we already see a negative number, just return it.
                if val < 0:
                    return val

                # Check sign bit, and subtract the unsigned range from the value
                # if it is set.
                return val - 0x100000000 if val & 0x80000000 else val

            # Find the UID and GID for the requested user and group.
            try:
                mode = "user"
                value = user
                uid = to_signed(pwd.getpwnam(user).pw_uid)

                mode = "group"
                value = group
                gid = to_signed(grp.getgrnam(group).gr_gid)
            except KeyError:
                tangelo.log_critical("TANGELO", "no such %s '%s' to drop privileges to" % (mode, value))
                return 1

            # Set the process home directory to be the dropped-down user's.
            os.environ["HOME"] = os.path.expanduser("~%s" % (user))

            # Perform the actual UID/GID change.
            cherrypy.process.plugins.DropPrivileges(cherrypy.engine, uid=uid, gid=gid).subscribe()
        else:
            tangelo.log_info("TANGELO", "Not performing privilege drop (because not running as superuser)")

    # Set up websocket handling.  Use the pass-through subclassed version of the
    # plugin so we can set a priority on it that doesn't conflict with privilege
    # drop.
    tangelo.websocket.WebSocketLowPriorityPlugin(cherrypy.engine).subscribe()
    cherrypy.tools.websocket = ws4py.server.cherrypyserver.WebSocketTool()

    # Replace the stock auth_digest and auth_basic tools with ones that have
    # slightly lower priority (so the AuthUpdate tool can run before them).
    cherrypy.tools.auth_basic = cherrypy.Tool("before_handler", cherrypy.lib.auth_basic.basic_auth, priority=2)
    cherrypy.tools.auth_digest = cherrypy.Tool("before_handler", cherrypy.lib.auth_digest.digest_auth, priority=2)

    # Install signal handlers to allow for proper cleanup/shutdown.
    for sig in [signal.SIGINT, signal.SIGTERM]:
        signal.signal(sig, shutdown)

    # Send SIGQUIT to an immediate, ungraceful shutdown instead.
    if platform.system() != "Windows":
        signal.signal(signal.SIGQUIT, die)

    # Start the CherryPy engine.
    cherrypy.engine.start()
    tangelo.log_info("\033[32mTANGELO", "\033[32mServer is running")
    cherrypy.engine.block()
Example #17
0
def post(*pargs, **query):
    args = query.get("args", "")
    timeout = float(query.get("timeout", 0))

    if len(pargs) == 0:
        tangelo.http_status(400, "Required Argument Missing")
        return {"error": "No program path was specified"}

    program_url = "/" + "/".join(pargs)

    content = analyze_url(program_url).content
    if content is None or content.type != Content.File:
        tangelo.http_status(404, "Not Found")
        return {"error": "Could not find a script at %s" % (program_url)}

    program = content.path

    # Check the user arguments.
    userargs = args.split()
    if "--port" in userargs:
        tangelo.http_status(400, "Illegal Argument")
        return {"error": "You may not specify '--port' among the arguments passed in 'args'"}

    # Obtain an available port.
    port = tangelo.util.get_free_port()

    # Generate a unique key.
    key = tangelo.util.generate_key(processes.keys())

    # Detect http vs. https
    scheme = "ws"
    ssl_key = cherrypy.config.get("server.ssl_private_key")
    ssl_cert = cherrypy.config.get("server.ssl_certificate")

    # Generate command line.
    cmdline = [vtkpython, weblauncher, program, "--port", str(port)] + userargs
    if ssl_key and ssl_cert:
        scheme = "wss"
        cmdline.extend(["--sslKey", ssl_key, "--sslCert", ssl_cert])

    # Launch the requested process.
    tangelo.log_info("VTKWEB", "Starting process: %s" % (" ".join(cmdline)))
    try:
        process = subprocess.Popen(cmdline,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
    except (OSError, IOError) as e:
        tangelo.log_warning("VTKWEB", "Error: could not launch VTKWeb process")
        return {"error": e.strerror}

    # Capture the new process's stdout and stderr streams in
    # non-blocking readers.
    stdout = tangelo.util.NonBlockingReader(process.stdout)
    stderr = tangelo.util.NonBlockingReader(process.stderr)

    # Read from stdout to look for the signal that the process has
    # started properly.
    class FactoryStarted:
        pass

    class Failed:
        pass

    class Timeout:
        pass
    signal = "Starting factory"
    if timeout <= 0:
        timeout = 10
    sleeptime = 0.5
    wait = 0
    saved_lines = []
    try:
        while True:
            lines = stdout.readlines()
            saved_lines += lines
            for line in lines:
                if line == "":
                    # This indicates that stdout has closed without
                    # starting the process.
                    raise Failed()
                elif signal in line:
                    # This means that the server has started.
                    raise FactoryStarted()

            # If neither failure nor success occurred in the last block
            # of lines from stdout, either time out, or try again after
            # a short delay.
            if wait >= timeout:
                raise Timeout()

            wait += sleeptime
            time.sleep(sleeptime)
    except Timeout:
        tangelo.http_status(524, "Timeout")
        return {"error": "Process startup timed out"}
    except Failed:
        tangelo.http_status(500)
        return {"error": "Process did not start up properly",
                "stdout": saved_lines,
                "stderr": stderr.readlines()}
    except FactoryStarted:
        stdout.pushlines(saved_lines)

    # Create a websocket handler path dedicated to this process.
    host = "localhost" if cherrypy.server.socket_host == "0.0.0.0" else cherrypy.server.socket_host
    tangelo.websocket.mount(key, WebSocketRelay(host, port, key), "wamp")

    # Log the new process in the process table, including non-blocking
    # stdout and stderr readers.
    processes[key] = {"port": port,
                      "process": process,
                      "stdout": stdout,
                      "stderr": stderr}

    # Form the websocket URL from the hostname/port used in the
    # request, and the newly generated key.
    url = "%s://%s/ws/%s/ws" % (scheme, cherrypy.request.base.split("//")[1], key)
    return {"key": key,
            "url": url}
def get_url_entities(domain, trail, url):
    results = json.dumps(dict(entities=db.get_entities_on_url(helper.get_org(), domain, trail, url)))
    tangelo.log_info(results)
    return results
Example #19
0
def get_url_entities(domain, trail, url):
    results = json.dumps(dict(entities=db.get_entities_on_url(helper.get_org(), domain, trail, url)))
    tangelo.log_info(results)
    return results
Example #20
0
def main():
    p = argparse.ArgumentParser(description="Start a Tangelo server.")
    p.add_argument("-c",
                   "--config",
                   type=str,
                   default=None,
                   metavar="FILE",
                   help="specifies configuration file to use")
    p.add_argument(
        "-a",
        "--access-auth",
        action="store_const",
        const=True,
        default=None,
        help=
        "enable HTTP authentication (i.e. processing of .htaccess files) (default)"
    )
    p.add_argument(
        "-na",
        "--no-access-auth",
        action="store_const",
        const=True,
        default=None,
        help="disable HTTP authentication (i.e. processing of .htaccess files)"
    )
    p.add_argument(
        "-p",
        "--drop-privileges",
        action="store_const",
        const=True,
        default=None,
        help="enable privilege drop when started as superuser (default)")
    p.add_argument("-np",
                   "--no-drop-privileges",
                   action="store_const",
                   const=True,
                   default=None,
                   help="disable privilege drop when started as superuser")
    p.add_argument("-s",
                   "--sessions",
                   action="store_const",
                   const=True,
                   default=None,
                   help="enable session tracking (default)")
    p.add_argument("-ns",
                   "--no-sessions",
                   action="store_const",
                   const=True,
                   default=None,
                   help="disable session tracking")
    p.add_argument("--list-dir",
                   action="store_true",
                   default=None,
                   help="enable directory content serving")
    p.add_argument("--no-list-dir",
                   action="store_true",
                   default=None,
                   help="disable directory content serving (default)")
    p.add_argument("--show-py",
                   action="store_true",
                   default=None,
                   help="enable Python service source code serving")
    p.add_argument("--no-show-py",
                   action="store_true",
                   default=None,
                   help="disable Python service source code serving (default)")
    p.add_argument(
        "--hostname",
        type=str,
        default=None,
        metavar="HOSTNAME",
        help="overrides configured hostname on which to run Tangelo")
    p.add_argument(
        "--port",
        type=int,
        default=None,
        metavar="PORT",
        help="overrides configured port number on which to run Tangelo")
    p.add_argument(
        "-u",
        "--user",
        type=str,
        default=None,
        metavar="USERNAME",
        help="specifies the user to run as when root privileges are dropped")
    p.add_argument(
        "-g",
        "--group",
        type=str,
        default=None,
        metavar="GROUPNAME",
        help="specifies the group to run as when root privileges are dropped")
    p.add_argument("-r",
                   "--root",
                   type=str,
                   default=None,
                   metavar="DIR",
                   help="the directory from which Tangelo will serve content")
    p.add_argument("--verbose",
                   "-v",
                   action="store_true",
                   help="display extra information as Tangelo starts up")
    p.add_argument("--version",
                   action="store_true",
                   help="display Tangelo version number")
    p.add_argument(
        "--key",
        type=str,
        default=None,
        metavar="FILE",
        help=
        "the path to the SSL key.  You must also specify --cert to serve content over https."
    )
    p.add_argument(
        "--cert",
        type=str,
        default=None,
        metavar="FILE",
        help=
        "the path to the SSL certificate.  You must also specify --key to serve content over https."
    )
    p.add_argument("--examples",
                   action="store_true",
                   default=None,
                   help="Serve the Tangelo example applications")
    args = p.parse_args()

    # If version flag is present, print the version number and exit.
    if args.version:
        print tangelo_version
        return 0

    # Make sure user didn't specify conflicting flags.
    if args.access_auth and args.no_access_auth:
        tangelo.log_error(
            "ERROR",
            "can't specify both --access-auth (-a) and --no-access-auth (-na) together"
        )
        return 1

    if args.drop_privileges and args.no_drop_privileges:
        tangelo.log_error(
            "ERROR",
            "can't specify both --drop-privileges (-p) and --no-drop-privileges (-np) together"
        )
        return 1

    if args.no_sessions and args.sessions:
        tangelo.log_error(
            "ERROR",
            "can't specify both --sessions (-s) and --no-sessions (-ns) together"
        )
        return 1

    if args.examples and args.root:
        tangelo.log_error(
            "ERROR", "can't specify both --examples and --root (-r) together")
        return 1

    if args.examples and args.config:
        tangelo.log_error(
            "ERROR",
            "can't specify both --examples and --config (-c) together")
        return 1

    if args.no_list_dir and args.list_dir:
        tangelo.log_error(
            "ERROR",
            "can't specify both --list-dir and --no-list-dir together")
        sys.exit(1)

    if args.no_show_py and args.show_py:
        tangelo.log_error(
            "ERROR", "can't specify both --show-py and --no-show-py together")
        sys.exit(1)

    # Decide if we have a configuration file or not.
    cfg_file = args.config
    if cfg_file is None:
        tangelo.log(
            "TANGELO",
            "No configuration file specified - using command line args and defaults"
        )
    else:
        cfg_file = tangelo.util.expandpath(cfg_file)
        tangelo.log("TANGELO", "Using configuration file %s" % (cfg_file))

    # Parse the config file; report errors if any.
    try:
        config = Config(cfg_file)
    except (IOError, ValueError) as e:
        tangelo.log_error("ERROR", e)
        return 1

    # Type check the config entries.
    if not config.type_check():
        for message in config.errors:
            tangelo.log_error("TANGELO", message)
        return 1

    # Determine whether to use access auth.
    access_auth = True
    if args.access_auth is None and args.no_access_auth is None:
        if config.access_auth is not None:
            access_auth = config.access_auth
    else:
        access_auth = (args.access_auth
                       is not None) or (not args.no_access_auth)

    tangelo.log(
        "TANGELO", "Access authentication %s" %
        ("enabled" if access_auth else "disabled"))

    # Determine whether to perform privilege drop.
    drop_privileges = True
    if args.drop_privileges is None and args.no_drop_privileges is None:
        if config.drop_privileges is not None:
            drop_privileges = config.drop_privileges
    else:
        drop_privileges = (args.drop_privileges
                           is not None) or (not args.no_drop_privileges)

    # Determine whether to enable sessions.
    sessions = True
    if args.sessions is None and args.no_sessions is None:
        if config.sessions is not None:
            sessions = config.sessions
    else:
        sessions = (args.sessions is not None) or (not args.no_sessions)

    tangelo.log("TANGELO",
                "Sessions %s" % ("enabled" if sessions else "disabled"))

    # Determine whether to serve directory listings by default.
    listdir = False
    if args.list_dir is None and args.no_list_dir is None:
        if config.list_dir is not None:
            listdir = config.list_dir
    else:
        listdir = (args.list_dir is not None) or (not args.no_list_dir)

    cherrypy.config["listdir"] = listdir
    tangelo.log(
        "TANGELO", "Directory content serving %s" %
        ("enabled" if listdir else "disabled"))

    # Determine whether to serve web service Python source code by default.
    showpy = False
    if args.show_py is None and args.no_show_py is None:
        if config.show_py is not None:
            showpy = config.show_py
    else:
        showpy = (args.show_py is not None) or (not args.no_show_py)

    cherrypy.config["showpy"] = showpy
    tangelo.log(
        "TANGELO", "Web service source code serving %s" %
        ("enabled" if showpy else "disabled"))

    # Extract the rest of the arguments, giving priority first to command line
    # arguments, then to the configuration file (if any), and finally to a
    # hard-coded default value.
    hostname = args.hostname or config.hostname or "localhost"
    port = args.port or config.port or 8080
    user = args.user or config.user or "nobody"
    group = args.group or config.group or "nobody"

    tangelo.log("TANGELO", "Hostname: %s" % (hostname))
    tangelo.log("TANGELO", "Port: %d" % (port))

    tangelo.log(
        "TANGELO", "Privilege drop %s" %
        ("enabled (if necessary)" if drop_privileges else "disabled"))
    if drop_privileges:
        tangelo.log("TANGELO", "\tUser: %s" % (user))
        tangelo.log("TANGELO", "\tGroup: %s" % (group))

    # HTTPS support
    #
    # Grab the ssl key file.
    ssl_key = args.key or config.key
    if ssl_key is not None:
        ssl_key = tangelo.util.expandpath(ssl_key)

    # Grab the cert file.
    ssl_cert = args.cert or config.cert
    if ssl_cert is not None:
        ssl_cert = tangelo.util.expandpath(ssl_cert)

    # In order to enable HTTPS, *both* the key and cert must be specified.  If
    # only one or the other is specified, this is considered an error, because
    # we don't want to serve what the user is considering sensitive content over
    # HTTP by default.
    if ssl_key is not None and ssl_cert is not None:
        cherrypy.config.update({
            "server.ssl_module": "pyopenssl",
            "server.ssl_certificate": ssl_cert,
            "server.ssl_private_key": ssl_key
        })
        tangelo.log("TANGELO", "HTTPS enabled")
        tangelo.log("TANGELO", "\tSSL Cert file: %s" % (ssl_cert))
        tangelo.log("TANGELO", "\tSSL Key file: %s" % (ssl_key))
    elif not (ssl_key is None and ssl_cert is None):
        tangelo.log_error("TANGELO", "error: SSL key or SSL cert missing")
        return 1
    else:
        tangelo.log("TANGELO", "HTTPS disabled")

    # We need a web root - use the installed example web directory as a
    # fallback.  This might be found in a few different places, so try them one
    # by one until we find one that exists.
    root = args.root or config.root
    if root:
        root = tangelo.util.expandpath(root)
    elif args.examples:
        # Set the examples web root.
        root = get_web_directory()
        tangelo.log_info("TANGELO",
                         "Looking for example web content path in %s" % (root))
        if not os.path.exists(root):
            tangelo.log_error("ERROR", "could not find examples package")
            return 1

        # Set the examples plugins.
        config.plugins = [{
            "name": "config"
        }, {
            "name": "data"
        }, {
            "name": "docs"
        }, {
            "name": "mapping"
        }, {
            "name": "mongo"
        }, {
            "name": "stream"
        }, {
            "name": "tangelo"
        }, {
            "name": "ui"
        }, {
            "name": "vis"
        }]
    else:
        root = tangelo.util.expandpath(".")

    tangelo.log("TANGELO", "Serving content from %s" % (root))

    # Set the web root directory.
    cherrypy.config.update({"webroot": root})

    # Place an empty dict to hold per-module configuration into the global
    # configuration object, and one for persistent per-module storage (the
    # latter can be manipulated by the service).
    cherrypy.config.update({"module-config": {}})
    cherrypy.config.update({"module-store": {}})

    # Analogs of the module storage dicts, but for plugins.
    cherrypy.config.update({"plugin-config": {}})
    cherrypy.config.update({"plugin-store": {}})

    # Create a plugin manager.
    plugins = tangelo.server.Plugins("tangelo.plugin",
                                     config=config.plugins,
                                     plugin_dir=get_bundled_plugin_directory())

    # Check for any errors - if there are, report them and exit.
    if not plugins.good():
        for message in plugins.errors:
            tangelo.log_error("PLUGIN", message)
        return 1

    # Save the plugin manager for use later (when unloading plugins during
    # shutdown).
    cherrypy.config.update({"plugins": plugins})

    # Create an instance of the main handler object.
    module_cache = tangelo.util.ModuleCache()
    tangelo_server = tangelo.server.Tangelo(module_cache=module_cache,
                                            plugins=plugins)
    rootapp = cherrypy.Application(tangelo_server, "/")

    # Place an AuthUpdate handler in the Tangelo object if access authorization
    # is on.
    tangelo_server.auth_update = tangelo.server.AuthUpdate(app=rootapp)

    # Mount the root application object.
    cherrypy.tree.mount(rootapp,
                        config={
                            "/": {
                                "tools.sessions.on": sessions
                            },
                            "/favicon.ico": {
                                "tools.staticfile.on": True,
                                "tools.staticfile.filename": get_tangelo_ico()
                            }
                        })

    # Set up the global configuration.
    cherrypy.config.update({
        "environment": "production",
        "log.screen": True,
        "server.socket_host": hostname,
        "server.socket_port": port
    })

    # Try to drop privileges if requested, since we've bound to whatever port
    # superuser privileges were needed for already.
    if drop_privileges:
        # If we're on windows, don't supply any username/groupname, and just
        # assume we should drop priveleges.
        if platform.system() == "Windows":
            tangelo.log("TANGELO", "Performing privilege drop")
            cherrypy.process.plugins.DropPrivileges(
                cherrypy.engine).subscribe()
        elif os.getuid() == 0:
            tangelo.log("TANGELO", "Performing privilege drop")

            # Reaching here means we're on unix, and we are the root user, so go
            # ahead and drop privileges to the requested user/group.
            import grp
            import pwd

            # On some systems, negative uids and gids are allowed.  These can
            # render in Python (in particular, on OS X) as very large unsigned
            # values.  This function first checks to see if the input value is
            # already negative; if so, there's no issue and we return it
            # unchanged.  Otherwise, we treat the argument as a bit
            # representation of a *signed* value, check the sign bit to see if
            # it *should* be a negative number, and then perform the proper
            # arithmetic to turn it into a signed one.
            def to_signed(val):
                # If we already see a negative number, just return it.
                if val < 0:
                    return val

                # Check sign bit, and subtract the unsigned range from the value
                # if it is set.
                return val - 0x100000000 if val & 0x80000000 else val

            # Find the UID and GID for the requested user and group.
            try:
                mode = "user"
                value = user
                uid = to_signed(pwd.getpwnam(user).pw_uid)

                mode = "group"
                value = group
                gid = to_signed(grp.getgrnam(group).gr_gid)
            except KeyError:
                tangelo.log_error(
                    "TANGELO",
                    "no such %s '%s' to drop privileges to" % (mode, value))
                return 1

            # Set the process home directory to be the dropped-down user's.
            os.environ["HOME"] = os.path.expanduser("~%s" % (user))

            # Perform the actual UID/GID change.
            cherrypy.process.plugins.DropPrivileges(cherrypy.engine,
                                                    uid=uid,
                                                    gid=gid).subscribe()
        else:
            tangelo.log(
                "TANGELO",
                "Not performing privilege drop (because not running as superuser)"
            )

    # Set up websocket handling.  Use the pass-through subclassed version of the
    # plugin so we can set a priority on it that doesn't conflict with privilege
    # drop.
    tangelo.websocket.WebSocketLowPriorityPlugin(cherrypy.engine).subscribe()
    cherrypy.tools.websocket = ws4py.server.cherrypyserver.WebSocketTool()

    # Replace the stock auth_digest and auth_basic tools with ones that have
    # slightly lower priority (so the AuthUpdate tool can run before them).
    cherrypy.tools.auth_basic = cherrypy.Tool(
        "before_handler", cherrypy.lib.auth_basic.basic_auth, priority=2)
    cherrypy.tools.auth_digest = cherrypy.Tool(
        "before_handler", cherrypy.lib.auth_digest.digest_auth, priority=2)

    # Install signal handlers to allow for proper cleanup/shutdown.
    for sig in [signal.SIGINT, signal.SIGTERM]:
        signal.signal(sig, shutdown)

    # Send SIGQUIT to an immediate, ungraceful shutdown instead.
    if platform.system() != "Windows":
        signal.signal(signal.SIGQUIT, die)

    # Start the CherryPy engine.
    cherrypy.engine.start()
    tangelo.log_success("TANGELO", "Server is running")
    cherrypy.engine.block()
Example #21
0
    def __init__(self, base_package, config, plugin_dir):
        self.base_package = base_package
        self.plugin_dir = plugin_dir

        self.errors = []
        self.plugins = {}

        self.modules = tangelo.util.ModuleCache(config=False)

        # Create a virtual module to hold all plugin python modules.
        package_parts = self.base_package.split(".")
        for pkg_step in range(len(package_parts)):
            package = ".".join(package_parts[:pkg_step + 1])
            if package not in sys.modules:
                sys.modules[package] = imp.new_module(package)
                if not pkg_step:
                    globals()[package] = sys.modules[package]
                else:
                    package_root = ".".join(package_parts[:pkg_step])
                    setattr(sys.modules[package_root], package_parts[pkg_step],
                            sys.modules[package])

        # A null config should be treated as an empty list.
        if config is None:
            config = []

        startlist = []
        # Read through the list of plugin configs, extracting the info and
        # validating as we go.
        for i, entry in enumerate(config):
            if not isinstance(entry, dict):
                self.errors.append(
                    "Configuration for plugin %d is not an associative array" %
                    (i + 1))
                return

            name = entry.get("name")
            if name is None:
                self.errors.append(
                    "Configuration for plugin %d is missing required 'name' property"
                    % (i + 1))
                return

            if name in self.plugins:
                self.errors.append(
                    "Configuration for plugin %d uses duplicate plugin name '%s'"
                    % (i + 1, name))
                return

            # Copy the entry so we don't alter the config object
            self.plugins[name] = entry.copy()
            del self.plugins[name]["name"]
            startlist.append(name)

        # Load all the plugins one by one, bailing out if there are any errors.
        for plugin in startlist:
            conf = self.plugins[plugin]
            if "path" in conf:
                # Extract the plugin path.
                path = os.path.abspath(conf["path"])
            else:
                # Construct the plugin path, given the name of the plugin,
                # and the base path of Tangelo.
                path = os.path.join(self.plugin_dir, plugin)

            if not self.load(plugin, path):
                self.errors.append("Plugin %s failed to load" % (plugin))
                return

            tangelo.log_info("Plugin %s loaded" % (plugin))
Example #22
0
    def __init__(self, base_package, config, plugin_dir):
        self.base_package = base_package
        self.plugin_dir = plugin_dir

        self.errors = []
        self.plugins = {}

        self.modules = tangelo.util.ModuleCache(config=False)

        # Create a virtual module to hold all plugin python modules.
        package_parts = self.base_package.split(".")
        for pkg_step in range(len(package_parts)):
            package = ".".join(package_parts[: pkg_step + 1])
            if package not in sys.modules:
                sys.modules[package] = imp.new_module(package)
                if not pkg_step:
                    globals()[package] = sys.modules[package]
                else:
                    package_root = ".".join(package_parts[:pkg_step])
                    setattr(sys.modules[package_root], package_parts[pkg_step], sys.modules[package])

        # A null config should be treated as an empty list.
        if config is None:
            config = []

        startlist = []
        # Read through the list of plugin configs, extracting the info and
        # validating as we go.
        for i, entry in enumerate(config):
            if not isinstance(entry, dict):
                self.errors.append("Configuration for plugin %d is not an associative array" % (i + 1))
                return

            name = entry.get("name")
            if name is None:
                self.errors.append("Configuration for plugin %d is missing required 'name' property" % (i + 1))
                return

            if name in self.plugins:
                self.errors.append("Configuration for plugin %d uses duplicate plugin name '%s'" % (i + 1, name))
                return

            # Copy the entry so we don't alter the config object
            self.plugins[name] = entry.copy()
            del self.plugins[name]["name"]
            startlist.append(name)

        # Load all the plugins one by one, bailing out if there are any errors.
        for plugin in startlist:
            conf = self.plugins[plugin]
            if "path" in conf:
                # Extract the plugin path.
                path = os.path.abspath(conf["path"])
            else:
                # Construct the plugin path, given the name of the plugin,
                # and the base path of Tangelo.
                path = os.path.join(self.plugin_dir, plugin)

            if not self.load(plugin, path):
                self.errors.append("Plugin %s failed to load" % (plugin))
                return

            tangelo.log_info("Plugin %s loaded" % (plugin))
Example #23
0
    def load(self, plugin_name, path):
        tangelo.log_info("PLUGIN", "Loading plugin %s (from %s)" % (plugin_name, path))

        if not os.path.exists(path):
            self.errors.append("Plugin path %s does not exist" % (path))
            return False

        plugin = Plugins.Plugin(path)

        # Check for a configuration file.
        config_file = os.path.join(path, "config.yaml")
        config = {}
        if os.path.exists(config_file):
            try:
                config = tangelo.util.yaml_safe_load(config_file)
            except TypeError as e:
                self.errors.append("Bad configuration for plugin %s (%s): %s" % (plugin_name, config_file, e))
                return False
            except IOError:
                self.errors.append("Could not open configuration for plugin %s (%s)" % (plugin_name, config_file))
                return False
            except ValueError as e:
                self.errors.append("Error reading configuration for plugin %s (%s): %s" % (plugin_name, config_file, e))
                return False

        # Install the config and an empty dict as the plugin-level
        # config and store.
        cherrypy.config["plugin-config"][path] = config
        cherrypy.config["plugin-store"][path] = {}

        # Check for a "python" directory, and place all modules found
        # there in a virtual submodule of tangelo.plugin.
        python = os.path.join(path, "python")
        if os.path.exists(python):
            tangelo.log_info("PLUGIN", "\t...loading python module content")

            init = os.path.join(python, "__init__.py")
            if not os.path.exists(init):
                self.errors.append("'python' directory of plugin %s is missing __init.py__" % (plugin_name))
                return False
            else:
                module_name = "%s.%s" % (self.base_package, plugin_name)
                plugin.module = module_name
                old_path = sys.path
                sys.path.append(python)
                try:
                    exec('%s = sys.modules[module_name] = self.modules.get(init)' % (module_name))
                except:
                    self.errors.append("Could not import python module content for plugin %s:\n%s" % (plugin_name, traceback.format_exc()))
                    sys.path = old_path
                    return False
                finally:
                    sys.path = old_path

        # Check for any setup that needs to be done, including any apps
        # that need to be mounted.
        control_file = os.path.join(path, "control.py")
        if os.path.exists(control_file):
            tangelo.log_info("PLUGIN", "\t...loading plugin control module")
            try:
                control = self.modules.get(control_file)
                plugin.control = control
            except:
                self.errors.append("Could not import control module for plugin %s:\n%s" % (plugin_name, traceback.format_exc()))
                return False
            else:
                if "setup" in dir(control):
                    tangelo.log_info("PLUGIN", "\t...running plugin setup")
                    try:
                        setup = control.setup(config, cherrypy.config["plugin-store"][path])
                    except:
                        self.errors.append("Could not set up plugin %s:\n%s" % (plugin_name, traceback.format_exc()))
                        return False
                    else:
                        for app in setup.get("apps", []):
                            if len(app) == 2:
                                (app_obj, mountpoint) = app
                                app_config = {}
                            elif len(app) == 3:
                                (app_obj, app_config, mountpoint) = app
                            else:
                                self.errors.append("App mount spec for plugin %s has %d item%s (should be either 2 or 3)" % (plugin_name, len(app), "" if len(app) == 1 else "s"))
                                return False

                            app_path = os.path.join("/plugin", plugin_name, mountpoint)
                            if app_path in cherrypy.tree.apps:
                                self.errors.append("Failed to mount application for plugin %s at %s (app already mounted there)" % (plugin_name, app_path))
                                return False
                            else:
                                tangelo.log_info("PLUGIN", "\t...mounting application at %s" % (app_path))
                                cherrypy.tree.mount(app_obj, app_path, app_config)
                                plugin.apps.append(app_path)

        self.plugins[plugin_name] = plugin
        return True
Example #24
0
def post(*pargs, **query):
    args = query.get("args", "")
    timeout = float(query.get("timeout", 0))

    processes = tangelo.plugin_store()["processes"]

    if len(pargs) == 0:
        tangelo.http_status(400, "Required Argument Missing")
        return {"error": "No program path was specified"}

    program_url = "/" + "/".join(pargs)

    content = analyze_url(program_url).content
    if content is None or content.type != Content.File:
        tangelo.http_status(404, "Not Found")
        return {"error": "Could not find a script at %s" % (program_url)}
    elif content.path is None:
        tangelo.http_status(403, "Restricted")
        return {"error": "The script at %s is access-restricted"}

    program = content.path

    # Check the user arguments.
    userargs = args.split()
    if "--port" in userargs:
        tangelo.http_status(400, "Illegal Argument")
        return {"error": "You may not specify '--port' among the arguments passed in 'args'"}

    # Obtain an available port.
    port = tangelo.util.get_free_port()

    # Generate a unique key.
    key = tangelo.util.generate_key(processes.keys())

    # Detect http vs. https
    scheme = "ws"
    ssl_key = cherrypy.config.get("server.ssl_private_key")
    ssl_cert = cherrypy.config.get("server.ssl_certificate")

    # Generate command line.
    cmdline = [vtkpython, weblauncher, program, "--port", str(port)] + userargs
    if ssl_key and ssl_cert:
        scheme = "wss"
        cmdline.extend(["--sslKey", ssl_key, "--sslCert", ssl_cert])

    # Launch the requested process.
    tangelo.log_info("VTKWEB", "Starting process: %s" % (" ".join(cmdline)))
    try:
        process = subprocess.Popen(cmdline,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
    except (OSError, IOError) as e:
        tangelo.log_warning("VTKWEB", "Error: could not launch VTKWeb process")
        return {"error": e.strerror}

    # Capture the new process's stdout and stderr streams in
    # non-blocking readers.
    stdout = tangelo.util.NonBlockingReader(process.stdout)
    stderr = tangelo.util.NonBlockingReader(process.stderr)

    # Read from stdout to look for the signal that the process has
    # started properly.
    class FactoryStarted:
        pass

    class Failed:
        pass

    class Timeout:
        pass
    signal = "Starting factory"
    if timeout <= 0:
        timeout = 10
    sleeptime = 0.5
    wait = 0
    saved_lines = []
    try:
        while True:
            lines = stdout.readlines()
            saved_lines += lines
            for line in lines:
                if line == "":
                    # This indicates that stdout has closed without
                    # starting the process.
                    raise Failed()
                elif signal in line:
                    # This means that the server has started.
                    raise FactoryStarted()

            # If neither failure nor success occurred in the last block
            # of lines from stdout, either time out, or try again after
            # a short delay.
            if wait >= timeout:
                raise Timeout()

            wait += sleeptime
            time.sleep(sleeptime)
    except Timeout:
        tangelo.http_status(524, "Timeout")
        return {"error": "Process startup timed out"}
    except Failed:
        tangelo.http_status(500)
        return {"error": "Process did not start up properly",
                "stdout": saved_lines,
                "stderr": stderr.readlines()}
    except FactoryStarted:
        stdout.pushlines(saved_lines)

    # Create a websocket handler path dedicated to this process.
    host = "localhost" if cherrypy.server.socket_host == "0.0.0.0" else cherrypy.server.socket_host
    tangelo.websocket.mount(key, WebSocketRelay(host, port, key), "wamp")

    # Log the new process in the process table, including non-blocking
    # stdout and stderr readers.
    processes[key] = {"port": port,
                      "process": process,
                      "stdout": stdout,
                      "stderr": stderr}

    # Form the websocket URL from the hostname/port used in the
    # request, and the newly generated key.
    url = "%s://%s/ws/%s/ws" % (scheme, cherrypy.request.base.split("//")[1], key)
    return {"key": key,
            "url": url}
Example #25
0
def main():
    p = argparse.ArgumentParser(description="Start a Tangelo server.")
    p.add_argument("-c", "--config", type=str, default=None, metavar="FILE", help="specifies configuration file to use")
    p.add_argument("-a", "--access-auth", action="store_const", const=True, default=None, help="enable HTTP authentication (i.e. processing of .htaccess files) (default)")
    p.add_argument("-na", "--no-access-auth", action="store_const", const=True, default=None, help="disable HTTP authentication (i.e. processing of .htaccess files)")
    p.add_argument("-p", "--drop-privileges", action="store_const", const=True, default=None, help="enable privilege drop when started as superuser (default)")
    p.add_argument("-np", "--no-drop-privileges", action="store_const", const=True, default=None, help="disable privilege drop when started as superuser")
    p.add_argument("-s", "--sessions", action="store_const", const=True, default=None, help="enable session tracking (default)")
    p.add_argument("-ns", "--no-sessions", action="store_const", const=True, default=None, help="edisable session tracking")
    p.add_argument("--hostname", type=str, default=None, metavar="HOSTNAME", help="overrides configured hostname on which to run Tangelo")
    p.add_argument("--port", type=int, default=None, metavar="PORT", help="overrides configured port number on which to run Tangelo")
    p.add_argument("-u", "--user", type=str, default=None, metavar="USERNAME", help="specifies the user to run as when root privileges are dropped")
    p.add_argument("-g", "--group", type=str, default=None, metavar="GROUPNAME", help="specifies the group to run as when root privileges are dropped")
    p.add_argument("-r", "--root", type=str, default=None, metavar="DIR", help="the directory from which Tangelo will serve content")
    p.add_argument("--verbose", "-v", action="store_true", help="display extra information as Tangelo starts up")
    p.add_argument("--version", action="store_true", help="display Tangelo version number")
    p.add_argument("--key", type=str, default=None, metavar="FILE", help="the path to the SSL key.  You must also specify --cert to serve content over https.")
    p.add_argument("--cert", type=str, default=None, metavar="FILE", help="the path to the SSL certificate.  You must also specify --key to serve content over https.")
    p.add_argument("--plugin-config", type=str, default=None, metavar="PATH", help="path to plugin configuration file")
    args = p.parse_args()

    # If version flag is present, print the version number and exit.
    if args.version:
        print tangelo_version
        return 0

    # Make sure user didn't specify conflicting flags.
    if args.access_auth and args.no_access_auth:
        tangelo.log_error("ERROR", "can't specify both --access-auth (-a) and --no-access-auth (-na) together")
        return 1

    if args.drop_privileges and args.no_drop_privileges:
        tangelo.log_error("ERROR", "can't specify both --drop-privileges (-p) and --no-drop-privileges (-np) together")
        return 1

    if args.no_sessions and args.sessions:
        tangelo.log_error("ERROR", "can't specify both --sessions (-s) and --no-sessions (-ns) together")
        sys.exit(1)

    # Figure out where this is being called from - that will be useful for a
    # couple of purposes.
    invocation_dir = os.path.abspath(os.path.dirname(os.path.abspath(__file__)) + "/..")

    # Before extracting the other arguments, compute a configuration dictionary.
    # If --no-config was specified, this will be the empty dictionary;
    # otherwise, check the command line arguments for a config file first, then
    # look for one in a sequence of other places.
    config = {}
    cfg_file = args.config
    if cfg_file is None:
        tangelo.log("TANGELO", "No configuration file specified - using command line args and defaults")
    else:
        cfg_file = tangelo.util.expandpath(cfg_file)
        tangelo.log("TANGELO", "Using configuration file %s" % (cfg_file))

    # Get a dict representing the contents of the config file.
    try:
        ok = False
        config = Config(cfg_file)
        ok = True
    except (IOError, TypeError) as e:
        tangelo.log_error("TANGELO", "error: %s" % (e))
    except yaml.YAMLError as e:
        tangelo.log_error("TANGELO", "error while parsing config file: %s" % (e))
    finally:
        if not ok:
            return 1

    # Determine whether to use access auth.
    access_auth = True
    if args.access_auth is None and args.no_access_auth is None:
        if config.access_auth is not None:
            access_auth = config.access_auth
    else:
        access_auth = (args.access_auth is not None) or (not args.no_access_auth)

    tangelo.log("TANGELO", "Access authentication %s" % ("enabled" if access_auth else "disabled"))

    # Determine whether to perform privilege drop.
    drop_privileges = True
    if args.drop_privileges is None and args.no_drop_privileges is None:
        if config.drop_privileges is not None:
            drop_privileges = config.drop_privileges
    else:
        drop_privileges = (args.drop_privileges is not None) or (not args.no_drop_privileges)

    # Determine whether to enable sessions.
    sessions = True
    if args.sessions is None and args.no_sessions is None:
        if config.sessions is not None:
            sessions = config.sessions
    else:
        sessions = (args.sessions is not None) or (not args.no_sessions)

    tangelo.log("TANGELO", "Sessions %s" % ("enabled" if sessions else "disabled"))

    # Extract the rest of the arguments, giving priority first to command line
    # arguments, then to the configuration file (if any), and finally to a
    # hard-coded default value.
    hostname = args.hostname or config.hostname or "localhost"
    port = args.port or config.port or 8080
    user = args.user or config.user or "nobody"
    group = args.group or config.group or "nobody"

    tangelo.log("TANGELO", "Hostname: %s" % (hostname))
    tangelo.log("TANGELO", "Port: %d" % (port))

    tangelo.log("TANGELO", "Privilege drop %s" % ("enabled (if necessary)" if drop_privileges else "disabled"))
    if drop_privileges:
        tangelo.log("TANGELO", "\tUser: %s" % (user))
        tangelo.log("TANGELO", "\tGroup: %s" % (group))

    # HTTPS support
    #
    # Grab the ssl key file.
    ssl_key = args.key or config.key
    if ssl_key is not None:
        ssl_key = tangelo.util.expandpath(ssl_key)

    # Grab the cert file.
    ssl_cert = args.cert or config.cert
    if ssl_cert is not None:
        ssl_cert = tangelo.util.expandpath(ssl_cert)

    # In order to enable HTTPS, *both* the key and cert must be specified.  If
    # only one or the other is specified, this is considered an error, because
    # we don't want to serve what the user is considering sensitive content over
    # HTTP by default.
    if ssl_key is not None and ssl_cert is not None:
        cherrypy.config.update({"server.ssl_module": "pyopenssl",
                                "server.ssl_certificate": ssl_cert,
                                "server.ssl_private_key": ssl_key})
        tangelo.log("TANGELO", "HTTPS enabled")
        tangelo.log("TANGELO", "\tSSL Cert file: %s" % (ssl_cert))
        tangelo.log("TANGELO", "\tSSL Key file: %s" % (ssl_key))
    elif not (ssl_key is None and ssl_cert is None):
        tangelo.log_error("TANGELO", "error: SSL key or SSL cert missing")
        return 1
    else:
        tangelo.log("TANGELO", "HTTPS disabled")

    # We need a web root - use the installed example web directory as a
    # fallback.  This might be found in a few different places, so try them one
    # by one until we find one that exists.
    #
    # TODO(choudhury): shouldn't we *only* check the invocation_dir option?  We
    # shouldn't pick up a stray web directory that happens to be found in /usr
    # if we're invoking tangelo from a totally different location.
    root = args.root or config.root
    if root:
        root = tangelo.util.expandpath(root)
    else:
        default_paths = map(tangelo.util.expandpath, [sys.prefix + "/share/tangelo/web",
                                                      invocation_dir + "/share/tangelo/web",
                                                      "/usr/local/share/tangelo/web"])
        tangelo.log_info("TANGELO", "Looking for default web content path")
        for path in default_paths:
            tangelo.log_info("TANGELO", "Trying %s" % (path))
            if os.path.exists(path):
                root = path
                break

        # TODO(choudhury): by default, should we simply serve from the current
        # directory?  This is how SimpleHTTPServer works, for example.
        if not root:
            tangelo.log_error("TANGELO", "could not find default web root directory")
            return 1

    tangelo.log("TANGELO", "Serving content from %s" % (root))

    # Compute a default plugin configuration if it was not supplied.
    if args.plugin_config is None:
        plugin_cfg_file = None
        default_paths = map(tangelo.util.expandpath, [sys.prefix + "/share/tangelo/plugin/plugin.conf",
                                                      invocation_dir + "/share/tangelo/plugin/plugin.conf",
                                                      "/usr/local/share/tangelo/plugin/plugin.conf"])
        tangelo.log_info("TANGELO", "Looking for default plugin configuration file")
        for path in default_paths:
            tangelo.log_info("TANGELO", "Trying %s" % (path))
            if os.path.exists(path):
                plugin_cfg_file = path
                break
    else:
        plugin_cfg_file = tangelo.util.expandpath(args.plugin_config)

    # Warn if plugin file doesn't exist.
    if plugin_cfg_file is None:
        tangelo.log_warning("TANGELO", "Could not find a default plugin configuration file")
    elif not os.path.exists(plugin_cfg_file):
        tangelo.log_warning("TANGELO", "Plugin configuration file %s does not exist - create it to load plugins at runtime" % (plugin_cfg_file))
    else:
        tangelo.log("TANGELO", "Using plugin configuration file '%s'" % (plugin_cfg_file))

    # Set the web root directory.
    cherrypy.config.update({"webroot": root})

    # Place an empty dict to hold per-module configuration into the global
    # configuration object, and one for persistent per-module storage (the
    # latter can be manipulated by the service).
    cherrypy.config.update({"module-config": {}})
    cherrypy.config.update({"module-store": {}})

    # Analogs of the module storage dicts, but for plugins.
    cherrypy.config.update({"plugin-config": {}})
    cherrypy.config.update({"plugin-store": {}})

    # Create a plugin manager.  It is marked global so that the plugins can be
    # unloaded when Tangelo exits.
    plugins = tangelo.server.Plugins("tangelo.plugin", plugin_cfg_file)
    cherrypy.config.update({"plugins": plugins})

    # Create an instance of the main handler object.
    module_cache = tangelo.util.ModuleCache()
    tangelo_server = tangelo.server.Tangelo(module_cache=module_cache, plugins=plugins)
    rootapp = cherrypy.Application(tangelo_server, "/")

    # Place an AuthUpdate handler in the Tangelo object if access authorization
    # is on.
    tangelo_server.auth_update = tangelo.server.AuthUpdate(app=rootapp)

    # Mount the root application object.
    cherrypy.tree.mount(rootapp, config={"/": {"tools.sessions.on": sessions},
                                         "/favicon.ico": {"tools.staticfile.on": True,
                                                          "tools.staticfile.filename": sys.prefix + "/share/tangelo/tangelo.ico"}})

    # Set up the global configuration.
    try:
        cherrypy.config.update({"environment": "production",
                                "log.screen": True,
                                "server.socket_host": hostname,
                                "server.socket_port": port})
    except IOError as e:
        tangelo.log_error("TANGELO", "problem with config file %s: %s" % (e.filename, e.strerror))
        return 1

    # Try to drop privileges if requested, since we've bound to whatever port
    # superuser privileges were needed for already.
    if drop_privileges:
        # If we're on windows, don't supply any username/groupname, and just
        # assume we should drop priveleges.
        if platform.system() == "Windows":
            tangelo.log("TANGELO", "Performing privilege drop")
            cherrypy.process.plugins.DropPrivileges(cherrypy.engine).subscribe()
        elif os.getuid() == 0:
            tangelo.log("TANGELO", "Performing privilege drop")

            # Reaching here means we're on unix, and we are the root user, so go
            # ahead and drop privileges to the requested user/group.
            import grp
            import pwd

            # On some systems, negative uids and gids are allowed.  These can
            # render in Python (in particular, on OS X) as very large unsigned
            # values.  This function first checks to see if the input value is
            # already negative; if so, there's no issue and we return it
            # unchanged.  Otherwise, we treat the argument as a bit
            # representation of a *signed* value, check the sign bit to see if
            # it *should* be a negative number, and then perform the proper
            # arithmetic to turn it into a signed one.
            def to_signed(val):
                # If we already see a negative number, just return it.
                if val < 0:
                    return val

                # Check sign bit, and subtract the unsigned range from the value
                # if it is set.
                return val - 0x100000000 if val & 0x80000000 else val

            # Find the UID and GID for the requested user and group.
            try:
                mode = "user"
                value = user
                uid = to_signed(pwd.getpwnam(user).pw_uid)

                mode = "group"
                value = group
                gid = to_signed(grp.getgrnam(group).gr_gid)
            except KeyError:
                tangelo.log_error("TANGELO", "no such %s '%s' to drop privileges to" % (mode, value))
                return 1

            # Set the process home directory to be the dropped-down user's.
            os.environ["HOME"] = os.path.expanduser("~%s" % (user))

            # Perform the actual UID/GID change.
            cherrypy.process.plugins.DropPrivileges(cherrypy.engine, uid=uid, gid=gid).subscribe()
        else:
            tangelo.log("TANGELO", "Not performing privilege drop (because not running as superuser)")

    # Set up websocket handling.  Use the pass-through subclassed version of the
    # plugin so we can set a priority on it that doesn't conflict with privilege
    # drop.
    tangelo.websocket.WebSocketLowPriorityPlugin(cherrypy.engine).subscribe()
    cherrypy.tools.websocket = ws4py.server.cherrypyserver.WebSocketTool()

    # Replace the stock auth_digest and auth_basic tools with ones that have
    # slightly lower priority (so the AuthUpdate tool can run before them).
    cherrypy.tools.auth_basic = cherrypy.Tool("before_handler", cherrypy.lib.auth_basic.basic_auth, priority=2)
    cherrypy.tools.auth_digest = cherrypy.Tool("before_handler", cherrypy.lib.auth_digest.digest_auth, priority=2)

    # Install signal handlers to allow for proper cleanup/shutdown.
    for sig in [signal.SIGINT, signal.SIGTERM]:
        signal.signal(sig, shutdown)

    # Send SIGQUIT to an immediate, ungraceful shutdown instead.
    if platform.system() != "Windows":
        signal.signal(signal.SIGQUIT, die)

    # Start the CherryPy engine.
    cherrypy.engine.start()
    tangelo.log_success("TANGELO", "Server is running")
    cherrypy.engine.block()