Beispiel #1
0
def get_ssl_hostnames(config):
	"""
	Retrieve the SSL hosts that are specified within the configuration. This
	also ensures that the settings appear to be valid by ensuring that the
	necessary files are defined and readable.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`smoke_zephyr.configuration.Configuration`
	:return: The specified SSH hosts.
	:rtype: list
	"""
	ssl_hostnames = []
	for entry, ssl_host in enumerate(config.get_if_exists('server.ssl_hosts', [])):
		hostname = ssl_host.get('host')
		if hostname is None:
			logger.critical("setting server.ssl_hosts[{0}] host not specified".format(entry))
			raise errors.KingPhisherError("invalid ssl host configuration #{0}, host must be specified".format(entry + 1))
		ssl_certfile = ssl_host.get('ssl_cert')
		if ssl_certfile is None:
			logger.critical("setting server.ssl_hosts[{0}] cert file not specified".format(entry))
			raise errors.KingPhisherError("invalid ssl host configuration #{0}, missing cert file".format(entry + 1))
		if not os.access(ssl_certfile, os.R_OK):
			logger.critical("setting server.ssl_hosts[{0}] file '{1}' not found".format(entry, ssl_certfile))
			raise errors.KingPhisherError("invalid ssl host configuration #{0}, missing cert file".format(entry + 1))
		ssl_keyfile = ssl_host.get('ssl_key')
		if ssl_keyfile is not None and not os.access(ssl_keyfile, os.R_OK):
			logger.critical("setting server.ssl_hosts[{0}] file '{1}' not found".format(entry, ssl_keyfile))
			raise errors.KingPhisherError("invalid ssl host configuration #{0}, missing key file".format(entry + 1))
		ssl_hostnames.append((hostname, ssl_certfile, ssl_keyfile))
	return ssl_hostnames
Beispiel #2
0
def server_from_config(config, handler_klass=None, plugin_manager=None):
	"""
	Build a server from a provided configuration instance. If *handler_klass* is
	specified, then the object must inherit from the corresponding
	KingPhisherServer base class.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`smoke_zephyr.configuration.Configuration`
	:param handler_klass: Alternative handler class to use.
	:type handler_klass: :py:class:`.KingPhisherRequestHandler`
	:param plugin_manager: The server's plugin manager instance.
	:type plugin_manager: :py:class:`~king_phisher.server.plugins.ServerPluginManager`
	:return: A configured server instance.
	:rtype: :py:class:`.KingPhisherServer`
	"""
	handler_klass = (handler_klass or KingPhisherRequestHandler)
	addresses = get_bind_addresses(config)

	if not len(addresses):
		raise errors.KingPhisherError('at least one address to listen on must be specified')

	ssl_certfile = None
	ssl_keyfile = None
	if config.has_option('server.ssl_cert'):
		ssl_certfile = config.get('server.ssl_cert')
		if not os.access(ssl_certfile, os.R_OK):
			logger.critical("setting server.ssl_cert file '{0}' not found".format(ssl_certfile))
			raise errors.KingPhisherError('invalid ssl configuration, missing file')
		logger.info("using default ssl cert file '{0}'".format(ssl_certfile))
		if config.has_option('server.ssl_key'):
			ssl_keyfile = config.get('server.ssl_key')
			if not os.access(ssl_keyfile, os.R_OK):
				logger.critical("setting server.ssl_key file '{0}' not found".format(ssl_keyfile))
				raise errors.KingPhisherError('invalid ssl configuration, missing file')

	if any([address[2] for address in addresses]) and ssl_certfile is None:
		raise errors.KingPhisherError('an ssl certificate must be specified when ssl is enabled')
	if ssl_certfile is None:
		ssl_hostnames = []
	else:
		ssl_hostnames = get_ssl_hostnames(config)

	try:
		server = KingPhisherServer(config, plugin_manager, handler_klass, addresses=addresses, ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile)
	except socket.error as error:
		error_number, error_message = error.args
		error_message = "socket error #{0} ({1})".format((error_number or 'NOT-SET'), error_message)
		if error_number == 98:
			logger.error('failed to bind server to address (socket error #98)')
		logger.error(error_message, exc_info=True)
		raise errors.KingPhisherError(error_message)
	if config.has_option('server.server_header'):
		server.server_version = config.get('server.server_header')
		logger.info("setting the server version to the custom header: '{0}'".format(config.get('server.server_header')))
	for hostname, ssl_certfile, ssl_keyfile in ssl_hostnames:
		logger.info("setting configuration for ssl hostname: {0} with cert: {1}".format(hostname, ssl_certfile))
		server.add_sni_cert(hostname, ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile)

	signals.server_initialized.send(server)
	return server
Beispiel #3
0
def get_bind_addresses(config):
    """
	Retrieve the addresses on which the server should bind to. Each of these
	addresses should be an IP address, port and optionally enable SSL. The
	returned list will contain tuples for each address found in the
	configuration. These tuples will be in the (host, port, use_ssl) format that
	is compatible with AdvancedHTTPServer.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`smoke_zephyr.configuration.Configuration`
	:return: The specified addresses to bind to.
	:rtype: list
	"""
    addresses = []
    # pull the legacy lone address
    if config.has_option('server.address'):
        host = config.get_if_exists('server.address.host', '0.0.0.0')
        port = config.get('server.address.port')
        if not isinstance(port, int) and (0 <= port <= 0xffff):
            logger.critical("can not bind to invalid port: {0!r}".format(port))
            raise errors.KingPhisherError(
                "invalid port configuration for address '{0}'".format(host))
        addresses.append((host, port, config.has_option('server.ssl_cert')))

    # pull the new-style list of addresses
    if not isinstance(config.get_if_exists('server.addresses', []), list):
        logger.critical(
            'the server.addresses configuration is invalid, it must be a list of entries'
        )
        raise errors.KingPhisherError('invalid server.addresses configuration')
    for entry, address in enumerate(
            config.get_if_exists('server.addresses', [])):
        host = address.get('host', '0.0.0.0')
        port = address['port']
        if not (isinstance(port, int) and (0 <= port <= 0xffff)):
            logger.critical(
                "setting server.addresses[{0}] invalid port specified".format(
                    entry))
            raise errors.KingPhisherError(
                "invalid port configuration for address #{0}".format(entry +
                                                                     1))
        addresses.append((host, port, address.get('ssl', False)))

    for host, port, use_ssl in addresses:
        if port in (443, 8443) and not use_ssl:
            logger.warning(
                "running on port {0} without ssl, this is generally unintended behaviour"
                .format(port))
        elif port in (80, 8080) and use_ssl:
            logger.warning(
                "running on port {0} with ssl, this is generally unintended behaviour"
                .format(port))
    return addresses
Beispiel #4
0
def build_king_phisher_server(config, ServerClass=None, HandlerClass=None):
	"""
	Build a server from a provided configuration instance. If *ServerClass* or
	*HandlerClass* is specified, then the object must inherit from the
	corresponding KingPhisherServer base class.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`smoke_zephyr.configuration.Configuration`
	:param ServerClass: Alternative server class to use.
	:type ServerClass: :py:class:`.KingPhisherServer`
	:param HandlerClass: Alternative handler class to use.
	:type HandlerClass: :py:class:`.KingPhisherRequestHandler`
	:return: A configured server instance.
	:rtype: :py:class:`.KingPhisherServer`
	"""
	logger = logging.getLogger('KingPhisher.Server.build')
	ServerClass = (ServerClass or KingPhisherServer)
	HandlerClass = (HandlerClass or KingPhisherRequestHandler)
	# set config defaults
	if not config.has_option('server.secret_id'):
		config.set('server.secret_id', make_uid())
	address = (config.get('server.address.host'), config.get('server.address.port'))
	ssl_certfile = None
	ssl_keyfile = None
	if config.has_option('server.ssl_cert'):
		ssl_certfile = config.get('server.ssl_cert')
		if not os.access(ssl_certfile, os.R_OK):
			logger.critical("setting server.ssl_cert file '{0}' not found".format(ssl_certfile))
			raise errors.KingPhisherError('invalid ssl configuration, missing file')
		if config.has_option('server.ssl_key'):
			ssl_keyfile = config.get('server.ssl_key')
			if not os.access(ssl_keyfile, os.R_OK):
				logger.critical("setting server.ssl_key file '{0}' not found".format(ssl_keyfile))
				raise errors.KingPhisherError('invalid ssl configuration, missing file')
	try:
		server = ServerClass(config, HandlerClass, address=address, ssl_certfile=ssl_certfile, ssl_keyfile=ssl_keyfile)
	except socket.error as error:
		error_number, error_message = error.args
		if error_number == 98:
			logger.critical("failed to bind server to address {0}:{1} (socket error #98)".format(*address))
		raise errors.KingPhisherError("socket error #{0} ({1})".format((error_number or 'NOT-SET'), error_message))
	if config.has_option('server.server_header'):
		server.server_version = config.get('server.server_header')

	if not config.get_if_exists('server.rest_api.token'):
		config.set('server.rest_api.token', rest_api.generate_token())
	if config.get('server.rest_api.enabled'):
		logger.info('rest api initialized with token: ' + config.get('server.rest_api.token'))
	return server
Beispiel #5
0
def get_bind_addresses(config):
    """
	Retrieve the addresses on which the server should bind to. Each of these
	addresses should be an IP address, port and optionally enable SSL. The
	returned list will contain tuples for each address found in the
	configuration. These tuples will be in the (host, port, use_ssl) format that
	is compatible with AdvancedHTTPServer.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`smoke_zephyr.configuration.Configuration`
	:return: The specified addresses to bind to.
	:rtype: list
	"""
    addresses = []
    for entry, address in enumerate(config.get('server.addresses')):
        port = address['port']
        if not (0 <= port <= 0xffff):
            logger.critical(
                "setting server.addresses[{0}] invalid port specified".format(
                    entry))
            raise errors.KingPhisherError(
                "invalid port configuration for address #{0}".format(entry +
                                                                     1))
        addresses.append((address['host'], port, address.get('ssl', False)))

    for host, port, use_ssl in addresses:
        if port in (443, 8443) and not use_ssl:
            logger.warning(
                "running on port {0} without ssl, this is generally unintended behaviour"
                .format(port))
        elif port in (80, 8080) and use_ssl:
            logger.warning(
                "running on port {0} with ssl, this is generally unintended behaviour"
                .format(port))
    return addresses
Beispiel #6
0
def build_king_phisher_server(config, ServerClass=None, HandlerClass=None):
    """
	Build a server from a provided :py:class:`.Configuration`
	instance. If a ServerClass or HandlerClass is specified, then the
	object must inherit from the corresponding KingPhisherServer base
	class.

	:param config: Configuration to retrieve settings from.
	:type config: :py:class:`.Configuration`
	:param ServerClass: Alternative server class to use.
	:type ServerClass: :py:class:`.KingPhisherServer`
	:param HandlerClass: Alternative handler class to use.
	:type HandlerClass: :py:class:`.KingPhisherRequestHandler`
	:return: A configured server instance.
	:rtype: :py:class:`.KingPhisherServer`
	"""
    logger = logging.getLogger('KingPhisher.Server.build')
    ServerClass = (ServerClass or KingPhisherServer)
    HandlerClass = (HandlerClass or KingPhisherRequestHandler)
    # set config defaults
    if not config.has_option('server.secret_id'):
        config.set('server.secret_id', make_uid())
    address = (config.get('server.address.host'),
               config.get('server.address.port'))
    ssl_certfile = None
    ssl_keyfile = None
    if config.has_option('server.ssl_cert'):
        ssl_certfile = config.get('server.ssl_cert')
        ssl_keyfile = config.get_if_exists('server.ssl_key')
    try:
        server = ServerClass(config,
                             HandlerClass,
                             address=address,
                             ssl_certfile=ssl_certfile,
                             ssl_keyfile=ssl_keyfile)
    except socket.error as error:
        error_number, error_message = error.args
        if error_number == 98:
            logger.critical(
                "failed to bind server to address {0}:{1} (socket error #98)".
                format(*address))
        raise errors.KingPhisherError("socket error #{0} ({1})".format(
            (error_number or 'NOT-SET'), error_message))
    if config.has_option('server.server_header'):
        server.server_version = config.get('server.server_header')
    return server
Beispiel #7
0
    def enable(self, name):
        """
		Enable a plugin by it's name. This will create a new instance of the
		plugin modules "Plugin" class, passing it the arguments defined in
		:py:attr:`.plugin_init_args`. A reference to the plugin instance is kept
		in :py:attr:`.enabled_plugins`. After the instance is created, the
		plugins :py:meth:`~.PluginBase.initialize` method is called.

		:param str name: The name of the plugin to enable.
		:return: The newly created instance.
		:rtype: :py:class:`.PluginBase`
		"""
        self._lock.acquire()
        klass = self.loaded_plugins[name]
        if not klass.is_compatible:
            raise errors.KingPhisherError('this plugin is incompatible')
        inst = klass(*self.plugin_init_args)
        if not inst.initialize():
            self._lock.release()
            return
        self.enabled_plugins[name] = inst
        self._lock.release()
        return inst
Beispiel #8
0
def download_geolite2_city_db(dest, license=None, date=None):
    """
	Download the GeoLite2 database and save it to disk.

	.. versionchanged:: 1.16.0
		Added the *license* and *date* parameters.

	:param str dest: The file path to save the database to.
	:param str license: The MaxMind license key to use to download the database.
	:param date: The date for which to download the database.
	:type date: :py:class:`datetime.date`
	"""
    params = {
        'edition_id': 'GeoLite2-City',
        'license_key': license,
        'suffix': 'tar.gz'
    }
    if date is not None:
        if license is None:
            raise errors.KingPhisherError(
                'can not request a specific date when no license is specified')
        params['date'] = date.strftime('%Y%m%d')
    try:
        if license is None:
            logger.info(
                'no license was specified, downloading the default GeoLite2 database'
            )
            response = requests.get(DB_DOWNLOAD_URL, stream=True)
        else:
            response = requests.get(
                'https://download.maxmind.com/app/geoip_download',
                params=params,
                stream=True)
    except requests.ConnectionError:
        logger.error(
            'geoip database download failed (could not connect to the server)')
        raise errors.KingPhisherResourceError(
            'could not download the geoip database') from None
    except requests.RequestException:
        logger.error('geoip database download failed', exc_info=True)
        raise errors.KingPhisherResourceError(
            'could not download the geoip database') from None
    tmp_file = tempfile.mkstemp()
    os.close(tmp_file[0])
    tmp_file = tmp_file[1]
    try:
        with open(tmp_file, 'wb') as file_h:
            for chunk in response.iter_content(chunk_size=4096):
                file_h.write(chunk)
                file_h.flush()
        tar_file = tarfile.open(tmp_file, mode='r:gz')
        member = next((name for name in tar_file.getnames()
                       if name.endswith('GeoLite2-City.mmdb')), None)
        if member is None:
            raise errors.KingPhisherResourceError(
                'could not find the GeoLite2-City.mmdb file in the archive')
        with open(dest, 'wb') as file_h:
            shutil.copyfileobj(tar_file.extractfile(member), file_h)
    finally:
        os.remove(tmp_file)
    os.chmod(dest, 0o644)
    return os.stat(dest).st_size