Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
    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
Esempio n. 4
0
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
Esempio n. 5
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 {}
Esempio n. 6
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 {}
Esempio n. 7
0
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
Esempio n. 8
0
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
Esempio n. 9
0
    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
Esempio n. 10
0
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
Esempio n. 11
0
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
Esempio n. 12
0
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
Esempio n. 13
0
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()))
Esempio n. 14
0
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()))
Esempio n. 15
0
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
Esempio n. 16
0
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
Esempio n. 17
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()
Esempio n. 18
0
def die(signum, frame):
    tangelo.log_error("TANGELO", "Received quit signal.  Exiting immediately.")
    os.kill(os.getpid(), signal.SIGKILL)
Esempio n. 19
0
def die(signum, frame):
    tangelo.log_error("TANGELO", "Received quit signal.  Exiting immediately.")
    os.kill(os.getpid(), signal.SIGKILL)
Esempio n. 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="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()