def export_kafka(service, cdr): try: tangelo.log("sending kafka to %s %s" % (service['recipientUrl'], service['recipientIndex'])) client = KafkaClient(hosts=service['recipientUrl']) topic = client.topics[service['recipientIndex']] producer = topic.get_producer() producer.produce(cdr) except Exception as e: tangelo.log_error("error sending via kafka to %s" % recipientUrl,e) return False return True
def export_kafka(service, cdr): try: tangelo.log("sending kafka to %s %s" % (service['recipientUrl'], service['recipientIndex'])) client = KafkaClient(hosts=service['recipientUrl']) topic = client.topics[service['recipientIndex']] producer = topic.get_producer() producer.produce(cdr) except Exception as e: tangelo.log_error("error sending via kafka to %s" % recipientUrl, e) return False return True
def htaccess(self, htfile, reqpath): changed = False if htfile is None: if reqpath in self.security: del self.security[reqpath] cfg = self.app.config[reqpath] for a in AuthUpdate.allowed_auth_types: key = "tools.auth_%s.on" % (a) if key in cfg: cfg[key] = False self.app.merge({reqpath: cfg}) changed = True else: # Get the mtime of the htfile. ht_mtime = os.stat(htfile).st_mtime if (reqpath not in self.security or ht_mtime > self.security[reqpath]): # We have either a new .htaccess file, or one that has # been modified list the last request to this path. htspec = AuthUpdate.parse_htaccess(htfile) if htspec["msg"] is not None: tangelo.log_error( "TANGELO", "[AuthUpdate] Could not register %s: %s" % (reqpath, htspec["msg"])) return changed, htspec["msg"] # Create an auth config tool using the values in the htspec. toolname = "tools.auth_%s." % (htspec["auth_type"]) passdict = ( lambda realm, username: htspec["userpass"].get(username)) # TODO(choudhury): replace "deadbeef" with a nonce created # randomly in the __init__() method. auth_conf = { toolname + "on": True, toolname + "realm": htspec["realm"], toolname + "get_ha1": passdict, toolname + "key": "deadbeef" } self.app.merge({reqpath: auth_conf}) # Store the mtime in the security table. self.security[reqpath] = ht_mtime changed = True return changed, None
def restPut(route, postDict): try: post_buffer = json.dumps(postDict) url = 'http://' + StrongLoopHostname + ':' + StrongLoopPort + '/api/' + route res = httpSession.put(url, data=post_buffer, headers={'content-type': 'application/json'}) res.raise_for_status() ret_val = lambda: None ret_val.__dict__ = json.loads(res.text) return ret_val 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 putting data", e) return -1
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 {}
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 {}
def addBrowsePathData(team_id, domain_id, trail_id, url, userEmail): if UseRestAPI: ts = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') added_data = restPost('DatawakeData', dict(id=0, ts=ts, url=url, domainId=domain_id, trailId=trail_id, useremail=userEmail, teamId=team_id)) try: ret_val = added_data.id return ret_val except Exception as e: tangelo.log_error("error adding Browse Path Data", e) return -1 else: sql = "INSERT INTO datawake_data (url,userEmail,team_id,domain_id,trail_id) VALUES (%s,%s,%s,%s,%s) " params = [url, userEmail, team_id, domain_id, trail_id] lastId = dbCommitSQL(sql, params) return lastId
def restPut(route, postDict): try: post_buffer = json.dumps(postDict) url = 'http://' + StrongLoopHostname + ':' + StrongLoopPort + '/api/' + route res = httpSession.put(url, data=post_buffer, headers={'content-type': 'application/json'}) res.raise_for_status() ret_val = lambda: None ret_val.__dict__ = json.loads(res.text) return ret_val 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 putting data", e) return -1
def htaccess(self, htfile, reqpath): changed = False if htfile is None: if reqpath in self.security: del self.security[reqpath] cfg = self.app.config[reqpath] for a in AuthUpdate.allowed_auth_types: key = "tools.auth_%s.on" % (a) if key in cfg: cfg[key] = False self.app.merge({reqpath: cfg}) changed = True else: # Get the mtime of the htfile. ht_mtime = os.stat(htfile).st_mtime if (reqpath not in self.security or ht_mtime > self.security[reqpath]): # We have either a new .htaccess file, or one that has # been modified list the last request to this path. htspec = AuthUpdate.parse_htaccess(htfile) if htspec["msg"] is not None: tangelo.log_error("TANGELO", "[AuthUpdate] Could not register %s: %s" % (reqpath, htspec["msg"])) return changed, htspec["msg"] # Create an auth config tool using the values in the htspec. toolname = "tools.auth_%s." % (htspec["auth_type"]) passdict = ( lambda realm, username: htspec["userpass"].get(username)) # TODO(choudhury): replace "deadbeef" with a nonce created # randomly in the __init__() method. auth_conf = {toolname + "on": True, toolname + "realm": htspec["realm"], toolname + "get_ha1": passdict, toolname + "key": "deadbeef"} self.app.merge({reqpath: auth_conf}) # Store the mtime in the security table. self.security[reqpath] = ht_mtime changed = True return changed, None
def export_es(service, cdr, domain_name): try: protocol = 'http' cred = '' if service['recipientProtocol']: protocol = service['recipientProtocol'] if service['credentials']: cred = service['credentials'] + '@' es_url = '%s://%s%s' % (protocol, cred, service['recipientUrl']) tangelo.log("sending ES at %s" % (es_url)) tangelo.log("index: %s"%service['recipientIndex']) tangelo.log("doc_type: %s"%domain_name) tangelo.log("cdr: %s"%cdr) es = Elasticsearch(es_url) res = es.index(index=service['recipientIndex'], doc_type=domain_name, body=cdr) return res['created'] except Exception as e: tangelo.log(e) tangelo.log_error("error sending via ES to %s" % service['recipientUrl'],e) return False return True
def export_rest(service, domain_id, domain_name, cdr): try: protocol = 'http' if service['recipientProtocol']: protocol = service['recipientProtocol'] url = '%s://%s' % (protocol, service['recipientUrl']) user = '' password = '' if service['credentials']: creds = service['credentials'].split(':') user = creds[0] password = creds[1] r = requests.put(url, data=cdr, auth=(user, password)) tangelo.log('Sending page via REST put to: %s' % r.url) if r.status == 200: return True except Exception as e: tangelo.log_error("error sending via REST to: %s " % url, e) return False return False
def log_traceback(tag, code, *msgs): if not msgs: raise TypeError("log_traceback() takes at least 3 arguments (2 given)") tangelo.log_error(tag, "Error code: %s" % (code)) for msg in msgs[:-1]: tangelo.log_error(tag, msg) tangelo.log_error(tag, "%s:\n%s" % (msgs[-1], traceback.format_exc()))
def addBrowsePathData(team_id, domain_id, trail_id, url, userEmail): if UseRestAPI: ts = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S') added_data = restPost( 'DatawakeData', dict(id=0, ts=ts, url=url, domainId=domain_id, trailId=trail_id, useremail=userEmail, teamId=team_id)) try: ret_val = added_data.id return ret_val except Exception as e: tangelo.log_error("error adding Browse Path Data", e) return -1 else: sql = "INSERT INTO datawake_data (url,userEmail,team_id,domain_id,trail_id) VALUES (%s,%s,%s,%s,%s) " params = [url, userEmail, team_id, domain_id, trail_id] lastId = dbCommitSQL(sql, params) return lastId
def export_es(service, cdr, domain_name): try: protocol = 'http' cred = '' if service['recipientProtocol']: protocol = service['recipientProtocol'] if service['credentials']: cred = service['credentials'] + '@' es_url = '%s://%s%s' % (protocol, cred, service['recipientUrl']) tangelo.log("sending ES at %s" % (es_url)) tangelo.log("index: %s" % service['recipientIndex']) tangelo.log("doc_type: %s" % domain_name) tangelo.log("cdr: %s" % cdr) es = Elasticsearch(es_url) res = es.index(index=service['recipientIndex'], doc_type=domain_name, body=cdr) return res['created'] except Exception as e: tangelo.log(e) tangelo.log_error( "error sending via ES to %s" % service['recipientUrl'], e) return False return True
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()
def die(signum, frame): tangelo.log_error("TANGELO", "Received quit signal. Exiting immediately.") os.kill(os.getpid(), signal.SIGKILL)
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()