Exemplo n.º 1
0
def run():
    """ Runs the Peekaboo daemon. """
    arg_parser = ArgumentParser(
        description=
        'Peekaboo Extended Email Attachment Behavior Observation Owl')
    arg_parser.add_argument('-c',
                            '--config',
                            action='store',
                            help='The configuration file for Peekaboo.')
    arg_parser.add_argument(
        '-d',
        '--debug',
        action='store_true',
        help=
        "Run Peekaboo in debug mode regardless of what's specified in the configuration."
    )
    arg_parser.add_argument(
        '-D',
        '--daemon',
        action='store_true',
        help=
        'Run Peekaboo in daemon mode (suppresses the logo to be written to STDOUT).'
    )
    args = arg_parser.parse_args()

    print('Starting Peekaboo %s.' % __version__)
    if not args.daemon:
        print(PEEKABOO_OWL)

    # Check if CLI arguments override the configuration
    log_level = None
    if args.debug:
        log_level = logging.DEBUG

    try:
        config = PeekabooConfig(config_file=args.config, log_level=log_level)
        logger.debug(config)
    except PeekabooConfigException as error:
        logging.critical(error)
        sys.exit(1)

    # find localisation in our package directory
    locale_domain = 'peekaboo'
    locale_dir = os.path.join(os.path.dirname(__file__), 'locale')
    languages = None
    if config.report_locale:
        logger.debug('Looking for translations for preconfigured locale "%s"',
                     config.report_locale)
        languages = [config.report_locale]
        if not gettext.find(locale_domain, locale_dir, languages):
            logger.warning('Translation file not found - falling back to '
                           'system configuration.')
            languages = None

    logger.debug('Installing report message translations')
    translation = gettext.translation(locale_domain,
                                      locale_dir,
                                      languages,
                                      fallback=True)
    # python2's gettext needs to be told explicitly to return unicode strings
    loc_kwargs = {}
    if sys.version_info[0] < 3:
        loc_kwargs = {'unicode': True}
    translation.install(loc_kwargs)

    # establish a connection to the database
    try:
        db_con = PeekabooDatabase(
            db_url=config.db_url,
            instance_id=config.cluster_instance_id,
            stale_in_flight_threshold=config.cluster_stale_in_flight_threshold,
            log_level=config.db_log_level)
    except PeekabooDatabaseError as error:
        logging.critical(error)
        sys.exit(1)
    except SQLAlchemyError as dberr:
        logger.critical(
            'Failed to establish a connection to the database '
            'at %s: %s', config.db_url, dberr)
        sys.exit(1)

    # Import debug module if we are in debug mode
    debugger = None
    if config.use_debug_module:
        from peekaboo.debug import PeekabooDebugger
        debugger = PeekabooDebugger()
        debugger.start()

    # initialize the daemon infrastructure such as PID file and dropping
    # privileges, automatically cleans up after itself when going out of scope
    daemon_infrastructure = PeekabooDaemonInfrastructure(
        config.pid_file, config.sock_file, config.user, config.group)
    daemon_infrastructure.init()

    systemd = SystemdNotifier()

    # clear all our in flight samples and all instances' stale in flight
    # samples
    db_con.clear_in_flight_samples()
    db_con.clear_stale_in_flight_samples()

    # a cluster duplicate interval of 0 disables the handler thread which is
    # what we want if we don't have an instance_id and therefore are alone
    cldup_check_interval = 0
    if config.cluster_instance_id > 0:
        cldup_check_interval = config.cluster_duplicate_check_interval
        if cldup_check_interval < 5:
            cldup_check_interval = 5
            logger.warning(
                "Raising excessively low cluster duplicate check "
                "interval to %d seconds.", cldup_check_interval)

    # workers of the job queue need the ruleset configuration to create the
    # ruleset engine with it
    try:
        ruleset_config = PeekabooConfigParser(config.ruleset_config)
    except PeekabooConfigException as error:
        logging.critical(error)
        sys.exit(1)

    # verify the ruleset configuration by spawning a ruleset engine and having
    # it verify it
    try:
        engine = RulesetEngine(ruleset_config, db_con)
    except (KeyError, ValueError, PeekabooConfigException) as error:
        logging.critical('Ruleset configuration error: %s', error)
        sys.exit(1)
    except PeekabooRulesetConfigError as error:
        logging.critical(error)
        sys.exit(1)

    job_queue = JobQueue(worker_count=config.worker_count,
                         ruleset_config=ruleset_config,
                         db_con=db_con,
                         cluster_duplicate_check_interval=cldup_check_interval)

    if config.cuckoo_mode == "embed":
        cuckoo = CuckooEmbed(job_queue, config.cuckoo_exec,
                             config.cuckoo_submit, config.cuckoo_storage,
                             config.interpreter)
    # otherwise it's the new API method and default
    else:
        cuckoo = CuckooApi(job_queue, config.cuckoo_url,
                           config.cuckoo_api_token,
                           config.cuckoo_poll_interval)

    sig_handler = SignalHandler()
    sig_handler.register_listener(cuckoo)

    # Factory producing almost identical samples providing them with global
    # config values and references to other objects they need, such as cuckoo,
    # database connection and connection map.
    sample_factory = SampleFactory(cuckoo, config.sample_base_dir,
                                   config.job_hash_regex,
                                   config.keep_mail_data,
                                   config.processing_info_dir)

    # We only want to accept 2 * worker_count connections.
    try:
        server = PeekabooServer(sock_file=config.sock_file,
                                job_queue=job_queue,
                                sample_factory=sample_factory,
                                request_queue_size=config.worker_count * 2)
    except Exception as error:
        logger.critical('Failed to start Peekaboo Server: %s', error)
        job_queue.shut_down()
        if debugger is not None:
            debugger.shut_down()
        sys.exit(1)

    exit_code = 1
    try:
        systemd.notify("READY=1")
        # If this dies Peekaboo dies, since this is the main thread. (legacy)
        exit_code = cuckoo.do()
    except Exception as error:
        logger.critical('Main thread aborted: %s', error)
    finally:
        server.shutdown()
        job_queue.shut_down()
        try:
            db_con.clear_in_flight_samples()
            db_con.clear_stale_in_flight_samples()
        except PeekabooDatabaseError as dberr:
            logger.error(dberr)

        if debugger is not None:
            debugger.shut_down()

    sys.exit(exit_code)
Exemplo n.º 2
0
def run():
    """ Runs the Peekaboo daemon. """
    arg_parser = ArgumentParser(
        description='Peekaboo Extended Email Attachment Behavior Observation Owl'
    )
    arg_parser.add_argument(
        '-c', '--config',
        action='store',
        help='The configuration file for Peekaboo.'
    )
    arg_parser.add_argument(
        '-d', '--debug',
        action='store_true',
        help="Run Peekaboo in debug mode regardless of what's specified in the configuration."
    )
    arg_parser.add_argument(
        '-D', '--daemon',
        action='store_true',
        help='Run Peekaboo in daemon mode (suppresses the logo to be written to STDOUT).'
    )
    args = arg_parser.parse_args()

    print('Starting Peekaboo %s.' % __version__)
    if not args.daemon:
        print(PEEKABOO_OWL)

    # Check if CLI arguments override the configuration
    log_level = None
    if args.debug:
        log_level = logging.DEBUG

    try:
        config = PeekabooConfig(config_file=args.config, log_level=log_level)
        logger.debug(config)
    except PeekabooConfigException as error:
        logging.critical(error)
        sys.exit(1)

    # find localisation in our package directory
    locale_domain = 'peekaboo'
    locale_dir = os.path.join(os.path.dirname(__file__), 'locale')
    languages = None
    if config.report_locale:
        logger.debug('Looking for translations for preconfigured locale "%s"',
                     config.report_locale)
        languages = [config.report_locale]
        if not gettext.find(locale_domain, locale_dir, languages):
            logger.warning('Translation file not found - falling back to '
                           'system configuration.')
            languages = None

    logger.debug('Installing report message translations')
    translation = gettext.translation(locale_domain, locale_dir, languages,
                                      fallback=True)
    translation.install()

    # establish a connection to the database
    try:
        db_con = PeekabooDatabase(
            db_url=config.db_url, instance_id=config.cluster_instance_id,
            stale_in_flight_threshold=config.cluster_stale_in_flight_threshold,
            log_level=config.db_log_level)
    except PeekabooDatabaseError as error:
        logging.critical(error)
        sys.exit(1)
    except SQLAlchemyError as dberr:
        logger.critical('Failed to establish a connection to the database '
                        'at %s: %s', config.db_url, dberr)
        sys.exit(1)

    # initialize the daemon infrastructure such as PID file and dropping
    # privileges, automatically cleans up after itself when going out of scope
    daemon_infrastructure = PeekabooDaemonInfrastructure(
        config.pid_file, config.sock_file, config.user, config.group)
    daemon_infrastructure.init()

    # clear all our in flight samples and all instances' stale in flight
    # samples
    db_con.clear_in_flight_samples()
    db_con.clear_stale_in_flight_samples()

    # a cluster duplicate interval of 0 disables the handler thread which is
    # what we want if we don't have an instance_id and therefore are alone
    cldup_check_interval = 0
    if config.cluster_instance_id > 0:
        cldup_check_interval = config.cluster_duplicate_check_interval
        if cldup_check_interval < 5:
            cldup_check_interval = 5
            logger.warning("Raising excessively low cluster duplicate check "
                           "interval to %d seconds.",
                           cldup_check_interval)

    sig_handler = SignalHandler()

    # read in the analyzer and ruleset configuration and start the job queue
    try:
        ruleset_config = PeekabooConfigParser(config.ruleset_config)
        analyzer_config = PeekabooAnalyzerConfig(config.analyzer_config)
        job_queue = JobQueue(
            worker_count=config.worker_count, ruleset_config=ruleset_config,
            db_con=db_con, analyzer_config=analyzer_config,
            cluster_duplicate_check_interval=cldup_check_interval)
        sig_handler.register_listener(job_queue)
        job_queue.start()
    except PeekabooConfigException as error:
        logging.critical(error)
        sys.exit(1)

    # Factory producing almost identical samples providing them with global
    # config values and references to other objects they need, such as database
    # connection and connection map.
    sample_factory = SampleFactory(
        config.sample_base_dir, config.job_hash_regex,
        config.keep_mail_data, config.processing_info_dir)

    # We only want to accept 2 * worker_count connections.
    try:
        server = PeekabooServer(
            sock_file=config.sock_file, job_queue=job_queue,
            sample_factory=sample_factory,
            request_queue_size=config.worker_count * 2,
            sock_group=config.sock_group,
            sock_mode=config.sock_mode)
    except Exception as error:
        logger.critical('Failed to start Peekaboo Server: %s', error)
        job_queue.shut_down()
        job_queue.close_down()
        sys.exit(1)

    # abort startup if shutdown was requested meanwhile
    if sig_handler.shutdown_requested:
        sys.exit(0)

    sig_handler.register_listener(server)
    SystemdNotifier().notify("READY=1")
    server.serve()

    # trigger shutdowns of other components (if not already ongoing triggered
    # by e.g. the signal handler), server will already be shut down at this
    # point signaled by the fact that serve() above returned
    job_queue.shut_down()

    # close down components after they've shut down
    job_queue.close_down()

    # do a final cleanup pass through the database
    try:
        db_con.clear_in_flight_samples()
        db_con.clear_stale_in_flight_samples()
    except PeekabooDatabaseError as dberr:
        logger.error(dberr)

    sys.exit(0)