From 3f5491bd27086d43395855e407776f708df5c93f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 27 Jul 2018 09:58:29 -0400 Subject: [PATCH 01/49] Bump the version to 1.12.0b1 --- king_phisher/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/king_phisher/version.py b/king_phisher/version.py index 6059003c..305d4c2c 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -66,7 +66,7 @@ def get_revision(): version_info = collections.namedtuple('version_info', ('major', 'minor', 'micro'))(1, 12, 0) """A tuple representing the version information in the format ('major', 'minor', 'micro')""" -version_label = 'beta' +version_label = 'beta1' """A version label such as alpha or beta.""" version = "{0}.{1}.{2}".format(version_info.major, version_info.minor, version_info.micro) From b7dc74432c59d20c4b774a0922289a21b04594ea Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 1 Aug 2018 12:02:51 -0400 Subject: [PATCH 02/49] Add documentation regarding client signal flags --- docs/source/client/gobject_signals.rst | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/docs/source/client/gobject_signals.rst b/docs/source/client/gobject_signals.rst index c25b7168..0b1730d2 100644 --- a/docs/source/client/gobject_signals.rst +++ b/docs/source/client/gobject_signals.rst @@ -8,6 +8,42 @@ value to be returned by their handlers as noted. .. _gobject-signals-application-label: +Signal Flags +------------ + +The "Signal flags" attribute of each of the signals describes certain attributes +of their respective signals. See the `GObject Signals`_ documentation for more +information including a detailed description of the signal emission process. + +:SIGNAL_ACTION: + Signals with the ``SIGNAL_ACTION`` flag are safe to be emitted from arbitrary + components within the application. These signals have default handlers which + perform their action and are not stateful. + +:SIGNAL_RUN_FIRST: + Signals with the ``SIGNAL_RUN_FIRST`` flag execute the default handler before + handlers which are connected with either the + :py:meth:`~GObject.Object.connect` or + :py:meth:`~GObject.Object.connect_after` methods. These signals therefor do + not provide connected handlers with an opportunity to block the emission of + the signal from the default handler. + +:SIGNAL_RUN_LAST: + Signals with the ``SIGNAL_RUN_LAST`` flag execute the default function after + handlers which are connected with the :py:meth:`~GObject.Object.connect` + method but before handlers which are connected with the + :py:meth:`~GObject.Object.connect_after` method. This provides connected + handlers with an opportunity to block the default function by halting + emissionof the signal by using the + :py:meth:`~GObject.Object.emit_stop_by_name` method. + +.. note:: + Plugins which connect to signals should use the + :py:meth:`~king_phisher.client.plugins.ClientPlugin.signal_connect` method + which by defaults uses :py:meth:`~GObject.Object.connect` to connect the + signal. Alternatively :py:meth:`~GObject.Object.connect_after` can be used by + setting the **after** keyword argument to ``True``. + Application Signals ------------------- @@ -359,3 +395,5 @@ GObject signal is emitted for consumption by the client. See the section on :flags: ``SIGNAL_RUN_FIRST`` :param str event_type: The type of event, one of either deleted, inserted or updated. :param list objects: The objects from the server. The available attributes depend on the subscription. + +.. _GObject Signals: https://developer.gnome.org/gobject/stable/signal.html From 75bfdd45907c7207baa0632d427dad1bed8a8ece Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 14 Aug 2018 17:10:39 -0400 Subject: [PATCH 03/49] Forward additional GObject signals --- king_phisher/client/gui_utilities.py | 21 +++++++++++++++++---- king_phisher/client/windows/main.py | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/king_phisher/client/gui_utilities.py b/king_phisher/client/gui_utilities.py index 7de15a3b..38990941 100644 --- a/king_phisher/client/gui_utilities.py +++ b/king_phisher/client/gui_utilities.py @@ -710,10 +710,6 @@ def _load_child_proxies(self): self.logger.debug("setting proxied widget {0} via {1}.{2}".format(child.name, dest.widget, dest.method)) method(src_widget, *dest.args, **dest.kwargs) - def destroy(self): - """Destroy the top-level GObject.""" - getattr(self, self.top_gobject).destroy() - @property def parent(self): return self.application.get_active_window() @@ -778,6 +774,23 @@ def objects_save_to_config(self): continue self.config[config_name] = gobject_get_value(gobject, gtype) + # forwarded methods + def destroy(self): + """Call :py:meth:`~Gtk.Widget.destroy` on the top-level GTK Widget.""" + getattr(self, self.top_gobject).destroy() + + def hide(self): + """Call :py:meth:`~Gtk.Widget.hide` on the top-level GTK Widget.""" + getattr(self, self.top_gobject).hide() + + def show(self): + """Call :py:meth:`~Gtk.Widget.show` on the top-level GTK Widget.""" + getattr(self, self.top_gobject).show() + + def show_all(self): + """Call :py:meth:`~Gtk.Widget.show_all` on the top-level GTK Widget.""" + getattr(self, self.top_gobject).show_all() + class FileMonitor(object): """Monitor a file for changes.""" def __init__(self, path, on_changed): diff --git a/king_phisher/client/windows/main.py b/king_phisher/client/windows/main.py index 6d73cacd..0625874b 100755 --- a/king_phisher/client/windows/main.py +++ b/king_phisher/client/windows/main.py @@ -271,7 +271,7 @@ def __init__(self, config, application): self.login_dialog = dialogs.LoginDialog(self.application) self.login_dialog.dialog.connect('response', self.signal_login_dialog_response) - self.login_dialog.dialog.show() + self.login_dialog.show() def signal_notebook_switch_page(self, notebook, current_page, index): #previous_page = notebook.get_nth_page(self.last_page_id) From 501878f55a98734a1f3faa4469bf54a30905fc7a Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 16 Aug 2018 00:29:33 -0400 Subject: [PATCH 04/49] Add a new campaign_expired server signal --- docs/source/change_log.rst | 2 ++ docs/source/server/signals.rst | 43 +++++++++++++++++++--------------- king_phisher/server/server.py | 23 ++++++++++++++++++ king_phisher/server/signals.py | 12 ++++++++++ 4 files changed, 61 insertions(+), 19 deletions(-) diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst index 8d5bda2b..e5ef05aa 100644 --- a/docs/source/change_log.rst +++ b/docs/source/change_log.rst @@ -22,6 +22,8 @@ Version 1.12.0 * Plugins can ship with dedicated documentation in markdown files that will be displayed * The GUI no longer locks up while tasks like downloading plugins are taking place +* Added a ``campaign-expired`` server signal + Version 1.11.0 ^^^^^^^^^^^^^^ diff --git a/docs/source/server/signals.rst b/docs/source/server/signals.rst index 33f9dfb0..bcc82326 100644 --- a/docs/source/server/signals.rst +++ b/docs/source/server/signals.rst @@ -1,3 +1,4 @@ +.. py:currentmodule:: king_phisher.server.signals .. _server-signals-label: Server Signals @@ -17,7 +18,11 @@ particular event occurs. These signals are defined in the Campaign Signals ---------------- -.. autodata:: king_phisher.server.signals.campaign_alert +.. autodata:: campaign_alert + :annotation: + :noindex: + +.. autodata:: campaign_expired :annotation: :noindex: @@ -26,31 +31,31 @@ Campaign Signals Database Signals ---------------- -.. autodata:: king_phisher.server.signals.db_initialized +.. autodata:: db_initialized :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_session_deleted +.. autodata:: db_session_deleted :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_session_inserted +.. autodata:: db_session_inserted :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_session_updated +.. autodata:: db_session_updated :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_table_delete +.. autodata:: db_table_delete :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_table_insert +.. autodata:: db_table_insert :annotation: :noindex: -.. autodata:: king_phisher.server.signals.db_table_update +.. autodata:: db_table_update :annotation: :noindex: @@ -63,43 +68,43 @@ Signals which are emitted for events specific to individual HTTP requests. These signals use the respective instance of :py:class:`~king_phisher.server.server.KingPhisherRequestHandler` as the sender. -.. autodata:: king_phisher.server.signals.credentials_received +.. autodata:: credentials_received :annotation: :noindex: -.. autodata:: king_phisher.server.signals.email_opened +.. autodata:: email_opened :annotation: :noindex: -.. autodata:: king_phisher.server.signals.request_handle +.. autodata:: request_handle :annotation: :noindex: -.. autodata:: king_phisher.server.signals.request_received +.. autodata:: request_received :annotation: :noindex: -.. autodata:: king_phisher.server.signals.response_sent +.. autodata:: response_sent :annotation: :noindex: -.. autodata:: king_phisher.server.signals.rpc_method_call +.. autodata:: rpc_method_call :annotation: :noindex: -.. autodata:: king_phisher.server.signals.rpc_method_called +.. autodata:: rpc_method_called :annotation: :noindex: -.. autodata:: king_phisher.server.signals.rpc_user_logged_in +.. autodata:: rpc_user_logged_in :annotation: :noindex: -.. autodata:: king_phisher.server.signals.rpc_user_logged_out +.. autodata:: rpc_user_logged_out :annotation: :noindex: -.. autodata:: king_phisher.server.signals.visit_received +.. autodata:: visit_received :annotation: :noindex: @@ -111,6 +116,6 @@ Server Signals Signals which are emitted for a :py:class:`~king_phisher.server.server.KingPhisherServer` instance. -.. autodata:: king_phisher.server.signals.server_initialized +.. autodata:: server_initialized :annotation: :noindex: diff --git a/king_phisher/server/server.py b/king_phisher/server/server.py index 0d424992..2c749b55 100644 --- a/king_phisher/server/server.py +++ b/king_phisher/server/server.py @@ -33,6 +33,7 @@ import base64 import binascii import collections +import datetime import json import logging import os @@ -883,6 +884,9 @@ def __init__(self, config, plugin_manager, handler_klass, *args, **kwargs): self.job_manager = smoke_zephyr.job.JobManager(logger_name='KingPhisher.Server.JobManager') """A :py:class:`~smoke_zephyr.job.JobManager` instance for scheduling tasks.""" self.job_manager.start() + maintenance_interval = 900 # 15 minutes + self._maintenance_job = self.job_manager.job_add(self._maintenance, parameters=(maintenance_interval,), seconds=maintenance_interval) + loader = jinja2.FileSystemLoader(config.get('server.web_root')) global_vars = {} if config.has_section('server.page_variables'): @@ -926,6 +930,25 @@ def __init__(self, config, plugin_manager, handler_klass, *args, **kwargs): self.headers[header] = value self.logger.info("including {0} custom http headers".format(len(self.headers))) + def _maintenance(self, interval): + """ + Execute periodic maintenance related tasks. + + :param int interval: The interval of time (in seconds) at which this method is being executed. + """ + self.logger.debug('running periodic maintenance tasks') + now = db_models.current_timestamp() + session = db_manager.Session() + campaigns = session.query(db_models.Campaign).filter( + db_models.Campaign.expiration != None + ).filter( + db_models.Campaign.expiration < now + ).filter( + db_models.Campaign.expiration >= now - datetime.timedelta(seconds=interval) + ) + for campaign in campaigns: + signals.send_safe('campaign-expired', self.server.logger, campaign) + def shutdown(self, *args, **kwargs): """ Request that the server perform any cleanup necessary and then shut diff --git a/king_phisher/server/signals.py b/king_phisher/server/signals.py index c00f0a45..022119bf 100644 --- a/king_phisher/server/signals.py +++ b/king_phisher/server/signals.py @@ -88,6 +88,18 @@ def send_safe(signal, logger, sender, **kwargs): """ ) +campaign_expired = blinker.signal( + 'campaign-expired', + """ + Emitted after a campaign has expired as determined by the + :py:class:`~king_phisher.server.database.models.Campaign.expiration` field. + The server periodically checks for newly expired campaigns at an arbitrary + interval. If a campaign is updated to expire at a time less than the next + check minus the interval, then this signal will not be emitted for the + campaign. + """ +) + # database signals db_initialized = blinker.signal( 'db-initialized', From 1976917b7e7a1272eac6751f7deac0e968b283af Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 17 Aug 2018 08:27:56 -0400 Subject: [PATCH 05/49] Add a second signal for campaign expiration alerts --- docs/source/change_log.rst | 2 +- docs/source/server/signals.rst | 4 ++++ king_phisher/server/server.py | 12 ++++++++++++ king_phisher/server/signals.py | 11 +++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst index e5ef05aa..769eb839 100644 --- a/docs/source/change_log.rst +++ b/docs/source/change_log.rst @@ -22,7 +22,7 @@ Version 1.12.0 * Plugins can ship with dedicated documentation in markdown files that will be displayed * The GUI no longer locks up while tasks like downloading plugins are taking place -* Added a ``campaign-expired`` server signal +* Added ``campaign-alert-expired`` and ``campaign-expired`` server signals Version 1.11.0 ^^^^^^^^^^^^^^ diff --git a/docs/source/server/signals.rst b/docs/source/server/signals.rst index bcc82326..2a2f857a 100644 --- a/docs/source/server/signals.rst +++ b/docs/source/server/signals.rst @@ -22,6 +22,10 @@ Campaign Signals :annotation: :noindex: +.. autodata:: campaign_alert_expired + :annotation: + :noindex: + .. autodata:: campaign_expired :annotation: :noindex: diff --git a/king_phisher/server/server.py b/king_phisher/server/server.py index 2c749b55..5a92d013 100644 --- a/king_phisher/server/server.py +++ b/king_phisher/server/server.py @@ -948,6 +948,18 @@ def _maintenance(self, interval): ) for campaign in campaigns: signals.send_safe('campaign-expired', self.server.logger, campaign) + alert_subscriptions = tuple(subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) + if not alert_subscriptions: + continue + if not signals.campaign_alert.receivers: + self.server.logger.warning('users are subscribed to campaign expiration alerts, and no signal handlers are connected') + continue + for subscription in alert_subscriptions: + results = signals.send_safe('campaign-alert-expired', self.server.logger, alert_subscription=subscription) + if any((result for (_, result) in results)): + continue + self.server.logger.warning("user {0} is subscribed to campaign alerts, and no signal handlers succeeded to send an alert".format(subscription.user.name)) + session.close() def shutdown(self, *args, **kwargs): """ diff --git a/king_phisher/server/signals.py b/king_phisher/server/signals.py index 022119bf..20f84dd5 100644 --- a/king_phisher/server/signals.py +++ b/king_phisher/server/signals.py @@ -88,6 +88,17 @@ def send_safe(signal, logger, sender, **kwargs): """ ) +campaign_alert_expired = blinker.signal( + 'campaign-alert-expired', + """ + Emitted for each user who is subscribed to alerts for a particular campaign + after it has expired. + + :param alert_subscription: The alert subscription. + :type alert_subscription: :py:class:`king_phisher.server.database.models.AlertSubscription` + """ +) + campaign_expired = blinker.signal( 'campaign-expired', """ From 576f5c0c93b107df50cb819a856a41079e4d1d03 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 17 Aug 2018 09:02:59 -0400 Subject: [PATCH 06/49] Add example usages to the tools help output --- tools/certbot_wrapper.py | 29 +++++++++++++++++++++++------ tools/otp_enroll.py | 24 ++++++++++++++++++++---- tools/spf_check.py | 19 +++++++++++++++++-- tools/targets_from_recon_ng.py | 10 ++++++---- 4 files changed, 66 insertions(+), 16 deletions(-) diff --git a/tools/certbot_wrapper.py b/tools/certbot_wrapper.py index 89f1b227..fc5c082f 100755 --- a/tools/certbot_wrapper.py +++ b/tools/certbot_wrapper.py @@ -49,15 +49,32 @@ import smoke_zephyr.utilities LETS_ENCRYPT_LIVE_PATH = '/etc/letsencrypt/live' -PARSER_EPILOG = """ -This tool facilitates issuing certificates with the certbot utility while the -King Phisher server is running. +PARSER_DESCRIPTION = """\ +King Phisher Certbot Wrapper Utility + +This tool facilitates issuing certificates with the certbot Let's Encrypt client +utility while the King Phisher server is running. +""" +PARSER_EPILOG = """\ +Example usage (issuing a certificate for "test.king-phisher.com"): + # standard configuration output + sudo ./certbot_wrapper.py ../server_config.yml test.king-phisher.com + + # use json configuration output (for use with the config file's include + # directive) and then restart the service + sudo ./certbot_wrapper.py \\ + --json-output ../configs/ssl_hosts.json \\ + --restart-service \\ + ../server_config.yml test.king-phisher.com """ -PARSER_EPILOG = PARSER_EPILOG.replace('\n', ' ') -PARSER_EPILOG = PARSER_EPILOG.strip() def main(): - parser = argparse.ArgumentParser(description='King Phisher Certbot Wrapper Utility', conflict_handler='resolve') + parser = argparse.ArgumentParser( + conflict_handler='resolve', + description=PARSER_DESCRIPTION, + epilog=PARSER_EPILOG, + formatter_class=argparse.RawTextHelpFormatter + ) utilities.argp_add_args(parser) parser.add_argument('--certbot', dest='certbot_bin', help='the path to the certbot binary to use') parser.add_argument('--json-output', dest='json_file', help='update a json formatted file with the details') diff --git a/tools/otp_enroll.py b/tools/otp_enroll.py index 11e380b7..b0611c39 100755 --- a/tools/otp_enroll.py +++ b/tools/otp_enroll.py @@ -51,17 +51,33 @@ else: has_qrcode = True -PARSER_EPILOG = """ +PARSER_DESCRIPTION = """\ +King Phisher TOTP Enrollment Utility +""" +PARSER_EPILOG = """\ If --otp is set then it must be a valid base32 encoded secret otherwise a random one will be used. Also note that unless the --force flag is specified, the user that is selected to be managed must already exist within the database, i.e. they should have logged in at least once before. + +Example usage (managing user "alice"): + # set a new OTP secret (enable OTP) for the user, creating them if necessary + ./otp_enroll.py -c ../server_config.yml --force alice set + + # remove the user's OTP secret (disable OTP) + ./otp_enroll.py -c ../server_config.yml alice remove + + # show the user's current OTP secret and provisioning URL + ./otp_enroll.py -c ../server_config.yml alice show """ -PARSER_EPILOG = PARSER_EPILOG.replace('\n', ' ') -PARSER_EPILOG = PARSER_EPILOG.strip() def main(): - parser = argparse.ArgumentParser(description='King Phisher TOTP Enrollment Utility', conflict_handler='resolve') + parser = argparse.ArgumentParser( + conflict_handler='resolve', + description=PARSER_DESCRIPTION, + epilog=PARSER_EPILOG, + formatter_class=argparse.RawTextHelpFormatter + ) utilities.argp_add_args(parser) config_group = parser.add_mutually_exclusive_group(required=True) config_group.add_argument('-c', '--config', dest='server_config', help='the server configuration file') diff --git a/tools/spf_check.py b/tools/spf_check.py index dfaa0f43..6907c734 100755 --- a/tools/spf_check.py +++ b/tools/spf_check.py @@ -41,12 +41,27 @@ import king_phisher.spf as spf import king_phisher.utilities as utilities +PARSER_DESCRIPTION = """\ +King Phisher SPF Check Utility +""" +PARSER_EPILOG = """ +Example usage: + # check if an SMTP server at 1.2.3.4 is authorized to send on behalf of + # king-phisher.com + ./spf_check.py 1.2.3.4 test@king-phisher.com +""" + def main(): - parser = argparse.ArgumentParser(description='King Phisher SPF Check Utility', conflict_handler='resolve') + parser = argparse.ArgumentParser( + conflict_handler='resolve', + description=PARSER_DESCRIPTION, + epilog=PARSER_EPILOG, + formatter_class=argparse.RawTextHelpFormatter + ) utilities.argp_add_args(parser) parser.add_argument('smtp_server_ip', help='the ip address of the sending smtp server') parser.add_argument('target_email', help='the email address that messages are from') - parser.add_argument('--dns-timeout', dest='dns_timeout', default=spf.DEFAULT_DNS_TIMEOUT, type=int, help='the timeout for dns queries') + parser.add_argument('--dns-timeout', dest='dns_timeout', default=spf.DEFAULT_DNS_TIMEOUT, metavar='TIMEOUT', type=int, help='the timeout for dns queries') arguments = parser.parse_args() server_ip = arguments.smtp_server_ip diff --git a/tools/targets_from_recon_ng.py b/tools/targets_from_recon_ng.py index 8380b48a..3fea4e88 100755 --- a/tools/targets_from_recon_ng.py +++ b/tools/targets_from_recon_ng.py @@ -42,18 +42,20 @@ import king_phisher.color as color import king_phisher.version as version -PROG_DESCRIPTION = """King Phisher Recon-ng CSV Converter +PROG_DESCRIPTION = """\ +King Phisher Recon-ng CSV Converter This tool is used to convert the output from the recon-ng reporting/csv module to a CSV file for use with King Phisher. """ -PROG_EPILOG = """The format string uses Python's native .format syntax. +PROG_EPILOG = """\ +The format string uses Python's native .format syntax. Format string examples: - first initial followed by the last name (default) + First initial followed by the last name (default) {first:.1}{last} - first name dot last name + First name dot last name {first}.{last} """ From 553fade64d2a21461e585348521fdf57db7feb55 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 21 Aug 2018 15:33:01 -0400 Subject: [PATCH 07/49] Only render page templates once --- king_phisher/server/server.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/king_phisher/server/server.py b/king_phisher/server/server.py index 0d424992..8c7a7106 100644 --- a/king_phisher/server/server.py +++ b/king_phisher/server/server.py @@ -495,7 +495,7 @@ def respond_file(self, file_path, attachment=False, query=None): template_module = template.make_module(template_vars) except (TypeError, jinja2.TemplateError) as error: self.semaphore_release() - self.server.logger.error("jinja2 template {0} render failed: {1} {2}".format(template.filename, error.__class__.__name__, error.message)) + self.server.logger.error("jinja2 template {0} render failed: {1} {2}".format(template.filename, error.__class__.__name__, getattr(error, 'message', ''))) raise errors.KingPhisherAbortRequestError() require_basic_auth = getattr(template_module, 'require_basic_auth', False) @@ -506,15 +506,9 @@ def respond_file(self, file_path, attachment=False, query=None): self.send_response(401) headers.append(('WWW-Authenticate', "Basic realm=\"{0}\"".format(getattr(template_module, 'basic_auth_realm', 'Authentication Required')))) else: - try: - template_data = template.render(template_vars) - except (TypeError, jinja2.TemplateError) as error: - self.semaphore_release() - self.server.logger.error("jinja2 template {0} render failed: {1} {2}".format(template.filename, error.__class__.__name__, error.message)) - raise errors.KingPhisherAbortRequestError() self.send_response(200) headers.append(('Last-Modified', self.date_time_string(os.stat(template.filename).st_mtime))) - template_data = template_data.encode('utf-8', 'ignore') + template_data = str(template_module).encode('utf-8', 'ignore') if mime_type.startswith('text'): mime_type += '; charset=utf-8' From 411235f35a5e4a232590990878b80a397a72ffa5 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 22 Aug 2018 11:44:47 -0400 Subject: [PATCH 08/49] Add more comment documentation to the server config --- data/server/king_phisher/server_config.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/data/server/king_phisher/server_config.yml b/data/server/king_phisher/server_config.yml index 2323149c..56ad1c01 100644 --- a/data/server/king_phisher/server_config.yml +++ b/data/server/king_phisher/server_config.yml @@ -25,11 +25,16 @@ logging: path: /var/log/king-phisher.log server: - # Bind address information, multiple ports can be + # Bind address information, multiple ports can be specified through additional entries under "addresses" addresses: + # bind to port 80 on all IPv4 interfaces without SSL (http) - host: 0.0.0.0 port: 80 ssl: false + # bind to port 443 on all IPv4 interfaces with SSL (https) + #- host: 0.0.0.0 + # port: 443 + # ssl: true # Server RPC authentication related settings #authentication: From fd6616a4002354a2f66d67e77ac67e2fefb2a936 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 22 Aug 2018 14:18:34 -0400 Subject: [PATCH 09/49] Add the fetch and fromjson Jinja features --- docs/source/change_log.rst | 2 ++ king_phisher/templates.py | 32 ++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst index 8d5bda2b..0b667f34 100644 --- a/docs/source/change_log.rst +++ b/docs/source/change_log.rst @@ -22,6 +22,8 @@ Version 1.12.0 * Plugins can ship with dedicated documentation in markdown files that will be displayed * The GUI no longer locks up while tasks like downloading plugins are taking place +* Added the new ``fetch`` Jinja function and ``fromjson`` Jinja filter + Version 1.11.0 ^^^^^^^^^^^^^^ diff --git a/king_phisher/templates.py b/king_phisher/templates.py index 42de6f94..c4f2e69e 100644 --- a/king_phisher/templates.py +++ b/king_phisher/templates.py @@ -48,6 +48,10 @@ import boltons.strutils import jinja2 +import json +import requests +import requests.exceptions +import requests_file __all__ = ('FindFileSystemLoader', 'TemplateEnvironmentBase', 'MessageTemplateEnvironment') @@ -91,6 +95,8 @@ def __init__(self, loader=None, global_vars=None): self.filters['encode'] = self._filter_encode self.filters['decode'] = self._filter_decode self.filters['hash'] = self._filter_hash + # counter part to https://jinja.readthedocs.io/en/stable/templates.html#tojson + self.filters['fromjson'] = self._filter_json # time filters self.filters['strftime'] = self._filter_strftime @@ -108,14 +114,13 @@ def __init__(self, loader=None, global_vars=None): self.globals['version'] = version.version # global functions - self.globals['random_integer'] = random.randint + self.globals['fetch'] = self._func_fetch self.globals['parse_user_agent'] = ua_parser.parse_user_agent self.globals['password_is_complex'] = utilities.password_is_complex + self.globals['random_integer'] = random.randint # additional globals - if global_vars: - for key, value in global_vars.items(): - self.globals[key] = value + self.globals.update(global_vars or {}) def from_file(self, path, **kwargs): """ @@ -213,6 +218,14 @@ def _filter_hash(self, data, hash_type): hash_obj = hashlib.new(hash_type, data) return hash_obj.digest() + def _filter_json(self, data): + try: + data = json.loads(data) + except json.JSONDecodeError: + self.logger.error('template failed to load json data') + data = None + return data + def _filter_strftime(self, dt, fmt): try: result = dt.strftime(fmt) @@ -229,6 +242,17 @@ def _filter_timedelta(self, dt, *args, **kwargs): result = '' return result + def _func_fetch(self, url, allow_file=False): + session = requests.Session() + if allow_file: + session.mount('file://', requests_file.FileAdapter()) + try: + response = session.get(url) + except requests.exceptions.RequestException: + self.logger.error('template failed to load url: ' + url) + return None + return response.text + class MessageTemplateEnvironment(TemplateEnvironmentBase): """A configured Jinja2 environment for formatting messages.""" MODE_PREVIEW = 0 From 679553e4f5696295469816f6f53811f8b8a8a6af Mon Sep 17 00:00:00 2001 From: True Demon Date: Mon, 27 Aug 2018 16:37:49 -0400 Subject: [PATCH 10/49] Added: Time Selector TopLevel widget --- .../king_phisher/king-phisher-client.ui | 2144 +++++++++-------- king_phisher/client/assistants/campaign.py | 20 +- king_phisher/client/widget/managers.py | 61 + 3 files changed, 1158 insertions(+), 1067 deletions(-) diff --git a/data/client/king_phisher/king-phisher-client.ui b/data/client/king_phisher/king-phisher-client.ui index d658fa5f..875bdf54 100644 --- a/data/client/king_phisher/king-phisher-client.ui +++ b/data/client/king_phisher/king-phisher-client.ui @@ -120,342 +120,324 @@ Spencer McIntyre - + + 450 + 350 False - 750 - 500 - - - + New Campaign + center-on-parent + True + 1 + + + + - + True False + 5 + 10 + 5 vertical - + 10 - + True False center - 3 - 3 - CampaignCompWindow.stack_main - - - False - True - 0 - - - - - True - False + 5 - + True False - vertical - - - True - True - in - - - True - True - - - - - - - - True - True - 1 - - + end + gtk-add + 6 - Select - Select + False + True + 0 - + True False - vertical - - - True - True - never - in - - - - - - True - True - 0 - - - - - + start + place holder + + + + - Compare - Compare + False + True 1 - True + False + True + 0 + + + + + True + False + place holder + True + 45 + + + False True 1 + + intro + Intro + True + False + - - - 650 - 350 - False - 5 - King Phisher Campaigns - dialog - - - - + True False + 5 + 10 + 5 vertical - 5 - - + 10 + + True False - end + center + Basic Settings + + + + + + + False + True + 0 + + + + + True + False + 3 + 10 + True - - Cancel + True - True - True + False + Name - False - False - 1 + 0 + 0 - - Select + True True - True + The name of this campaign. + True + 60 + + False + False + campaign name (REQUIRED) - False - False - 2 + 1 + 0 - - - False - True - end - 1 - - - - - True - False - 5 - + True False - start - Showing 0 of 0 Campaigns - - - + Description - False - True - 0 + 0 + 1 - + True - False - Expired Campaigns + True + Any descriptive notes to store with this campaign. + True + 200 + campaign description (OPTIONAL) - False - True - end - 1 + 1 + 1 - - 20 - 20 + True False - center - center - + Type - False - True - end - 2 + 0 + 2 - - - False - True - 0 - - - - - True - True - + True - True - - - + False + The type of this campaign. + True + True + 1 + 0 + + + True + 20 + campaign type (OPTIONAL) + + + 1 + 2 + - True + False True 1 + + + Basic Settings + True + False + + + + + True + False + 5 + 10 + 5 + vertical + 10 - + True False - 10 + center + Advanced Settings + + + + + + + False + True + 0 + + + + + True + False + 5 + 5 + vertical - + + Subscribe To Event Alerts True True - True - Filtering options for displayed campaigns. - right - - - True - False - Display Criteria - - + False + Receive messages for events regarding this campaign. + start + True False False - 1 + 0 - - New Campaign + + Deny Requests After Credential Submission True True - True - Create a new campaign. - + False + Deny HTTP requests after credentials have been received from the client. + start + True False - False - end - 2 + True + 1 False True - 2 + 1 - - - CampaignSelectionDialog.button_cancel - CampaignSelectionDialog.button_select - - - - 400 - 300 - True - False - vertical - - - True - True - - - True - True - - - - - - - True - True - 0 + Advanced Settings + True + False - + True False 5 - 5 + 10 5 - 5 + vertical + 10 - - Show Passwords + True - True - True - + False + center + Company + + + + False @@ -464,408 +446,358 @@ Spencer McIntyre - - Refresh - True - True - True - - - - False - False - 1 - True - - - - - False - True - 1 - - - - - 400 - 300 - True - False - vertical - - - True - True - vertical - - + True - True + False + Select whether to create a new company, use an existing one or none at all. + 10 - + True False - vertical - - - True - True - in - - - - - - True - True - 0 - - - - - + Company - True - True + False + True + 0 - + True False - vertical + 10 + True - + + Existing True True - in - - - + False + True + CampaignAssistant.radiobutton_company_none + - True + False True 0 - - - - - True - True - - - - - True - True - - - - - True - False - vertical - - - True - True - in + + New + True + True + False + True + CampaignAssistant.radiobutton_company_none + + + + False + True + 1 + + - + + None + True + True + False + True + True + + + + False + True + 2 + - True + False True - 0 + end + 1 - - - - True - True + False + True + 1 - - - True - True - 0 - - - - - True - False - 5 - 5 - 5 - 5 - end - - Refresh + True - True - True - + False + False + 0 + + + True + False + 5 + 5 + 12 + 5 + + + True + False + 10 + + + True + False + Company + + + False + True + 0 + + + + + True + False + The name of the existing company to associate this campaign with. + True + 1 + 0 + + + False + False + company name (OPTIONAL) + + + + + True + True + 1 + + + + + + + + + True + False + Existing Company + + False - False - 1 + True + 2 - - - False - True - 1 - - - - - 400 - 300 - True - False - vertical - - - True - True - + True - True - - + False + False + 0 + + + True + False + 5 + 5 + 12 + 5 + + + + + + + + True + False + New Company + + + False + True + 3 + - True - True - 0 + Company + True + False - + True False 5 - 5 + 10 5 - 5 + vertical + 10 - - Export + True - True - True - + False + center + Expiration + + + + False - False + True 0 - - Refresh + True - True - True - + False + Campaigns can optionally be set to expire after a user specified date and time. After the campaign expires, no more data will be collected for the campaign. + True + 45 False - False + True 1 - - - False - True - 1 - - - - - 400 - 300 - True - False - vertical - - - True - True - + + Enable Campaign Expiration True True - - - - - - - - True - True - 0 - - - - - True - False - 5 - 5 - 5 - 5 - - - Export - True - True - True - - - - False - False - 0 - - - - - Refresh - True - True - True - + False + start + True + False - False - 1 + True + 2 - - - False - True - 1 - - - - - 400 - 300 - True - False - vertical - - - True - True - + True - True - - + False + False + 0 + none + + + True + False + 5 + 5 + 10 + 5 + + + True + False + vertical + 5 + + + True + False + 5 + + + + + + + + + + + + 00:00 + True + True + True + + + + False + True + 3 + + + + + False + True + 0 + + + + + True + True + 2015 + 6 + 21 + + + + + False + True + 1 + + + + + + + + + True + False + Expiration Date + - - - - - True - True - 1 - - - - - True - False - 5 - 5 - 5 - 5 - - - Export - True - True - True - - - - False - False - 0 - - - - - Refresh - True - True - True - False - False - 1 + True + 3 - False - True - 2 + Expiration + True + False - - - 23 - 1 - 6 - - - 59 - 1 - 10 - - - 450 - 350 - False - New Campaign - center-on-parent - True - 1 - - - - - + True False 5 @@ -874,13 +806,13 @@ Spencer McIntyre vertical 10 - + True False center 5 - + True False end @@ -894,11 +826,11 @@ Spencer McIntyre - + True False start - place holder + Confirm Configuration @@ -918,7 +850,7 @@ Spencer McIntyre - + True False place holder @@ -933,261 +865,214 @@ Spencer McIntyre - intro - Intro + confirm + Confirm True False - - True - False - 5 - 10 - 5 - vertical - 10 - - - True - False - center - Basic Settings - - - - - - - False - True - 0 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + 750 + 500 + + + + + + True + False + vertical + + + + True + False + center + 3 + 3 + CampaignCompWindow.stack_main + + + False + True + 0 + - + True False - 3 - 10 - True - - - True - False - Name - - - 0 - 0 - - - - - True - True - The name of this campaign. - True - 60 - - False - False - campaign name (REQUIRED) - - - 1 - 0 - - - - - True - False - Description - - - 0 - 1 - - - - - True - True - Any descriptive notes to store with this campaign. - True - 200 - campaign description (OPTIONAL) - - - 1 - 1 - - - + True False - Type + vertical + + + True + True + in + + + True + True + + + + + + + + True + True + 1 + + - 0 - 2 + Select + Select - + True False - The type of this campaign. - True - True - 1 - 0 - - + vertical + + + True True - 20 - campaign type (OPTIONAL) + never + in + + + + + True + True + 0 + + + + - 1 - 2 + Compare + Compare + 1 - False + True True 1 - - Basic Settings - True - False - + + + 650 + 350 + False + 5 + King Phisher Campaigns + dialog - + + + + True False - 5 - 10 - 5 vertical - 10 - - - True - False - center - Advanced Settings - - - - - - - False - True - 0 - - - - + 5 + + True False - 5 - 5 - vertical + end - - Subscribe To Event Alerts + + Cancel True True - False - Receive messages for events regarding this campaign. - start - True + True False False - 0 + 1 - - Deny Requests After Credential Submission + + Select True True - False - Deny HTTP requests after credentials have been received from the client. - start - True + True False - True - 1 + False + 2 False True + end 1 - - - Advanced Settings - True - False - - - - - True - False - 5 - 10 - 5 - vertical - 10 - - - True - False - center - Company - - - - - - - False - True - 0 - - - + True False - Select whether to create a new company, use an existing one or none at all. - 10 + 5 - + True False - Company + start + Showing 0 of 0 Campaigns + + + False @@ -1196,59 +1081,10 @@ Spencer McIntyre - + True False - 10 - True - - - Existing - True - True - False - True - CampaignAssistant.radiobutton_company_none - - - - False - True - 0 - - - - - New - True - True - False - True - CampaignAssistant.radiobutton_company_none - - - - False - True - 1 - - - - - None - True - True - False - True - True - - - - False - True - 2 - - + Expired Campaigns False @@ -1257,417 +1093,609 @@ Spencer McIntyre 1 + + + 20 + 20 + True + False + center + center + + + + False + True + end + 2 + + False True - 1 + 0 - + True - False - False - 0 + True - + True - False - 5 - 5 - 12 - 5 - - - True - False - 10 - - - True - False - Company - - - False - True - 0 - - - - - True - False - The name of the existing company to associate this campaign with. - True - 1 - 0 - - - False - False - company name (OPTIONAL) - - - - - True - True - 1 - - - + True + + + - - - True - False - Existing Company - - - False + True True - 2 + 1 - + True - False False - 0 + 10 - + True - False - 5 - 5 - 12 - 5 + True + True + Filtering options for displayed campaigns. + right - + + True + False + Display Criteria + + + False + False + 1 + - - + + + New Campaign True - False - New Company + True + True + Create a new campaign. + + + False + False + end + 2 + False True - 3 + 2 - - Company - True - False - + + CampaignSelectionDialog.button_cancel + CampaignSelectionDialog.button_select + + + + 400 + 300 + True + False + vertical - + True - False - 5 - 10 - 5 - vertical - 10 + True - + True - False - center - Expiration - - - - + True + + + - - False - True - 0 - + + + True + True + 0 + + + + + True + False + 5 + 5 + 5 + 5 - + + Show Passwords True - False - Campaigns can optionally be set to expire after a user specified date and time. After the campaign expires, no more data will be collected for the campaign. - True - 45 + True + True + False True - 1 + 0 - - Enable Campaign Expiration + + Refresh True True - False - start - True - + True + False - True - 2 + False + 1 + True + + + False + True + 1 + + + + + 400 + 300 + True + False + vertical + + + True + True + vertical - + True - False - False - 0 - none + True - + True False - 5 - 5 - 10 - 5 + vertical - + True - False - vertical - 5 - - - True - True - 2015 - 6 - 21 - - - - - False - True - 0 - - + True + in - - True - False - 5 - - - True - True - The minute for the campaign expiration time. - 2 - number - ClockMinuteAdjustment - True - - - False - True - end - 0 - - - - - True - False - : - - - - - - False - True - end - 1 - - - - - True - False - Time: - - - False - True - 2 - - - - - True - True - The hour for the campaign expiration time on a 24-hour clock. - 2 - number - ClockHourAdjustment - True - - - False - True - end - 3 - - - - - False - True - 1 - + + + True + True + 0 + + + + + + True + True + - - + + True False - Expiration Date + vertical + + + True + True + in + + + + + + True + True + 0 + + + + + + + True + True + - False - True - 3 + True + True - - - Expiration - True - False - - - - - True - False - 5 - 10 - 5 - vertical - 10 - + True False - center - 5 + vertical - + True - False - end - gtk-add - 6 + True + in + + + - False + True True 0 - - True - False - start - Confirm Configuration - - - - - - - False - True - 1 - + - False - True - 0 + True + True + + + True + True + 0 + + + + + True + False + 5 + 5 + 5 + 5 + end - + + Refresh True - False - place holder - True - 45 + True + True + False - True + False 1 - confirm - Confirm - True - False + False + True + 1 + + + 400 + 300 + True + False + vertical - - - - - - - + + True + True + + + True + True + + + + + + + + True + True + 0 + - + + True + False + 5 + 5 + 5 + 5 + + + Export + True + True + True + + + + False + False + 0 + + + + + Refresh + True + True + True + + + + False + False + 1 + + + + + False + True + 1 + + + + 400 + 300 + True + False + vertical - + + True + True + + + True + True + + + + + + + + True + True + 0 + - + + True + False + 5 + 5 + 5 + 5 + + + Export + True + True + True + + + + False + False + 0 + + + + + Refresh + True + True + True + + + + False + False + 1 + + + + + False + True + 1 + + + + 400 + 300 + True + False + vertical - + + True + True + + + True + True + + + + + + + + True + True + 1 + - + + True + False + 5 + 5 + 5 + 5 + + + Export + True + True + True + + + + False + False + 0 + + + + + Refresh + True + True + True + + + + False + False + 1 + + + + + False + True + 2 + + + + 23 + 1 + 6 + + + 59 + 1 + 10 + + + TimeSelector + False - + + TimeSelector + True + False + + + True + True + The hour for the campaign expiration time on a 24-hour clock. + 2 + 00 + 0.5 + 1 + 00 + number + vertical + ClockHourAdjustment + True + 1 + + + 0 + 0 + + + + + 40 + True + True + The minute for the campaign expiration time. + True + 2 + 00 + 0.5 + 1 + 00 + url + vertical + ClockMinuteAdjustment + True + + + 2 + 0 + + + + + True + False + center + True + : + + + 1 + 0 + + + diff --git a/king_phisher/client/assistants/campaign.py b/king_phisher/client/assistants/campaign.py index d3301f64..03b4d7df 100644 --- a/king_phisher/client/assistants/campaign.py +++ b/king_phisher/client/assistants/campaign.py @@ -35,6 +35,7 @@ from king_phisher import utilities from king_phisher.client import gui_utilities from king_phisher.client.widget import resources +from king_phisher.client.widget import managers import advancedhttpserver @@ -73,8 +74,7 @@ class CampaignAssistant(gui_utilities.GladeGObject): 'radiobutton_company_existing', 'radiobutton_company_new', 'radiobutton_company_none', - 'spinbutton_campaign_expiration_hour', - 'spinbutton_campaign_expiration_minute' + 'togglebutton_expiration_time' ), top_level=( 'ClockHourAdjustment', @@ -99,6 +99,7 @@ def __init__(self, application, campaign_id=None): if page_title: self._page_titles[page_title] = page_n + self.time_selector = managers.TimeSelectorButtonManager(self.gobjects['togglebutton_expiration_time'], self.application) self._set_comboboxes() self._set_defaults() @@ -118,6 +119,7 @@ def __init__(self, application, campaign_id=None): self.gobjects['label_intro_body'].set_text('This assistant will walk you through creating and configuring a new King Phisher campaign.') self.gobjects['label_intro_title'].set_text('New Campaign') + @property def campaign_name(self): """ @@ -184,9 +186,9 @@ def _set_defaults(self): if campaign['expiration'] is not None: expiration = utilities.datetime_utc_to_local(campaign['expiration']) self.gobjects['checkbutton_expire_campaign'].set_active(True) - gui_utilities.gtk_calendar_set_pydate(self.gobjects['calendar_campaign_expiration'], expiration) - self.gobjects['spinbutton_campaign_expiration_hour'].set_value(expiration.hour) - self.gobjects['spinbutton_campaign_expiration_minute'].set_value(expiration.minute) + gui_utilities.gtk_calendar_set_pydate(self.gobjects['calendar_campaign_expiration'], expiration.date()) + + def _get_tag_from_combobox(self, combobox, db_table): model = combobox.get_model() @@ -249,6 +251,9 @@ def _get_graphql_company(self, company_name): }""", {'name': company_name}) return results['db']['company'] + def signal_toggle_time(self, _): + return self.time_selector.signal_button_toggled(_) + def signal_assistant_apply(self, _): self._close_ready = False # have to do it this way because the next page will be selected when the apply signal is complete @@ -282,10 +287,7 @@ def signal_assistant_apply(self, _): if self.gobjects['checkbutton_expire_campaign'].get_property('active'): expiration = datetime.datetime.combine( gui_utilities.gtk_calendar_get_pydate(self.gobjects['calendar_campaign_expiration']), - datetime.time( - int(self.gobjects['spinbutton_campaign_expiration_hour'].get_value()), - int(self.gobjects['spinbutton_campaign_expiration_minute'].get_value()) - ) + self.time_selector.time ) expiration = utilities.datetime_local_to_utc(expiration) if self.is_new_campaign and expiration <= datetime.datetime.now(): diff --git a/king_phisher/client/widget/managers.py b/king_phisher/client/widget/managers.py index 0012a020..e334ed2b 100644 --- a/king_phisher/client/widget/managers.py +++ b/king_phisher/client/widget/managers.py @@ -30,6 +30,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import collections +import datetime import functools from king_phisher import utilities @@ -373,3 +374,63 @@ def signal_activate_popup_menu_copy(self, menuitem, column_ids): def signal_activate_popup_menu_delete(self, menuitem): self._call_cb_delete() + +class TimeSelector(gui_utilities.GladeGObject): + dependencies = gui_utilities.GladeDependencies( + children=( + 'spinbutton_hour', + 'spinbutton_minute' + ), + top_level=( + 'ClockHourAdjustment', + 'ClockMinuteAdjustment' + ) + ) + top_gobject = 'popover' + +class TimeSelectorButtonManager(object): + def __init__(self, button, application, value=None): + """ + :param button: The button to activate TimeSelectorPopover() + :type button: :py:class:`Gtk.ToggleButton` + :param application: The application instance which this object belongs to. + :param value: The present datetime value (initialized to 00:00) + :type datetime.time: The value, (initialized to NoneType) will always return datetime.time(hr, min) + """ + self.popover = TimeSelector(application) + self.button = button + self.application = application + self._hour_spin = self.popover.gobjects['spinbutton_hour'] + self._minute_spin = self.popover.gobjects['spinbutton_minute'] + + self.popover.popover.set_relative_to(self.button) + self.button.connect('toggled', self.signal_button_toggled) + self.time = value or datetime.time(0, 0) + + self.button.set_label(f"{0:02}:{0:02}") + + def __repr__(self): + return "<{0} time='{1:%H:%M}' >".format(self.__class__.__name__, self.time) + + def signal_button_toggled(self, _): + if not self.button.get_active(): + self.popover.popover.popup() + else: + self.popover.popover.popdown() + self.button.set_label(f"{self.time.hour:02}:{self.time.minute:02}") + + @property + def time(self): + """ + :return: The current time value. + :rtype: :py:class:`datetime.time` + """ + return datetime.time(self._hour_spin.get_value_as_int(), self._minute_spin.get_value_as_int()) + + @time.setter + def time(self, value): + if not isinstance(value, datetime.time): + raise TypeError('argument 1 must be a datetime.time instance') + self._hour_spin.set_value(value.hour) + self._minute_spin.set_value(value.minute) + self.button.set_label(f"{value.hour:02}:{value.minute:02}") From a563e69fa58215318b02cf31a3bef6812bb415af Mon Sep 17 00:00:00 2001 From: True Demon Date: Mon, 27 Aug 2018 16:41:33 -0400 Subject: [PATCH 11/49] Cleanup --- king_phisher/client/widget/managers.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/king_phisher/client/widget/managers.py b/king_phisher/client/widget/managers.py index e334ed2b..ee3f6be3 100644 --- a/king_phisher/client/widget/managers.py +++ b/king_phisher/client/widget/managers.py @@ -402,11 +402,9 @@ def __init__(self, button, application, value=None): self.application = application self._hour_spin = self.popover.gobjects['spinbutton_hour'] self._minute_spin = self.popover.gobjects['spinbutton_minute'] - - self.popover.popover.set_relative_to(self.button) - self.button.connect('toggled', self.signal_button_toggled) self.time = value or datetime.time(0, 0) - + self.button.connect('toggled', self.signal_button_toggled) + self.popover.popover.set_relative_to(self.button) self.button.set_label(f"{0:02}:{0:02}") def __repr__(self): From 510abc3f8dbb3a58957945e8a5de0931d5de5ab7 Mon Sep 17 00:00:00 2001 From: True Demon Date: Mon, 27 Aug 2018 16:43:25 -0400 Subject: [PATCH 12/49] Cleanup --- king_phisher/client/assistants/campaign.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/king_phisher/client/assistants/campaign.py b/king_phisher/client/assistants/campaign.py index 03b4d7df..76596400 100644 --- a/king_phisher/client/assistants/campaign.py +++ b/king_phisher/client/assistants/campaign.py @@ -119,7 +119,6 @@ def __init__(self, application, campaign_id=None): self.gobjects['label_intro_body'].set_text('This assistant will walk you through creating and configuring a new King Phisher campaign.') self.gobjects['label_intro_title'].set_text('New Campaign') - @property def campaign_name(self): """ @@ -188,8 +187,6 @@ def _set_defaults(self): self.gobjects['checkbutton_expire_campaign'].set_active(True) gui_utilities.gtk_calendar_set_pydate(self.gobjects['calendar_campaign_expiration'], expiration.date()) - - def _get_tag_from_combobox(self, combobox, db_table): model = combobox.get_model() model_iter = combobox.get_active_iter() From fb068e001eecb495d2670d80a6bfa27b87a7637c Mon Sep 17 00:00:00 2001 From: True Demon Date: Tue, 28 Aug 2018 15:12:38 -0400 Subject: [PATCH 13/49] Review changes --- king_phisher/client/widget/managers.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/king_phisher/client/widget/managers.py b/king_phisher/client/widget/managers.py index ee3f6be3..b249afd1 100644 --- a/king_phisher/client/widget/managers.py +++ b/king_phisher/client/widget/managers.py @@ -376,6 +376,16 @@ def signal_activate_popup_menu_delete(self, menuitem): self._call_cb_delete() class TimeSelector(gui_utilities.GladeGObject): + """ + This is the TimeSelector Popover object containing the spinbutton + widgets. This class should be treated as private, as it is created + by the TimeSelectorButtonManager() Class. It should not be used + directly. + + :param GTK.Popover: The popover TimeSelector Glade object + :type TimeSelector: An empty GTK.Popover class reserved for time selections + :return TimeSelector: + """ dependencies = gui_utilities.GladeDependencies( children=( 'spinbutton_hour', @@ -391,11 +401,17 @@ class TimeSelector(gui_utilities.GladeGObject): class TimeSelectorButtonManager(object): def __init__(self, button, application, value=None): """ + Button Manager digests the GTK.ToggleButton to be used to show + the TimeSelector object. This should be + instantiated within the window where the TimeSelector widget is + desired as an attribute of the window class. + :param button: The button to activate TimeSelectorPopover() :type button: :py:class:`Gtk.ToggleButton` - :param application: The application instance which this object belongs to. + :param application: The application instance which owns this object. :param value: The present datetime value (initialized to 00:00) - :type datetime.time: The value, (initialized to NoneType) will always return datetime.time(hr, min) + :type datetime.time: The value; initialized to NoneType but will always return datetime.time() + :return: (GTK.ToggleButton, GTK.Popover, datetime.time) """ self.popover = TimeSelector(application) self.button = button @@ -405,7 +421,7 @@ def __init__(self, button, application, value=None): self.time = value or datetime.time(0, 0) self.button.connect('toggled', self.signal_button_toggled) self.popover.popover.set_relative_to(self.button) - self.button.set_label(f"{0:02}:{0:02}") + self.button.set_label(f"{self.time.hour:02}:{self.time.minute:02}") def __repr__(self): return "<{0} time='{1:%H:%M}' >".format(self.__class__.__name__, self.time) @@ -427,6 +443,10 @@ def time(self): @time.setter def time(self, value): + """ + :param value: value from self.popover.gobjects['spinbutton_xx'] + :return: The new time value to self.time + """ if not isinstance(value, datetime.time): raise TypeError('argument 1 must be a datetime.time instance') self._hour_spin.set_value(value.hour) From 4d496832c580b65a08b45c932e26710ee4af935f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 31 Aug 2018 08:51:54 -0400 Subject: [PATCH 14/49] Fix an unsorted import statement --- king_phisher/templates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/king_phisher/templates.py b/king_phisher/templates.py index c4f2e69e..9491ca61 100644 --- a/king_phisher/templates.py +++ b/king_phisher/templates.py @@ -35,6 +35,7 @@ import datetime import hashlib import html +import json import logging import os import random @@ -48,7 +49,6 @@ import boltons.strutils import jinja2 -import json import requests import requests.exceptions import requests_file From 6b39efa704a6e09c47557b1918483f80b9c9d132 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 31 Aug 2018 15:09:34 -0400 Subject: [PATCH 15/49] Make some changes to PR 317 --- .../king_phisher/king-phisher-client.ui | 26 +++++--- .../king_phisher/client/widget/managers.rst | 7 +++ king_phisher/client/assistants/campaign.py | 8 +-- king_phisher/client/widget/managers.py | 61 ++++++++++--------- 4 files changed, 59 insertions(+), 43 deletions(-) diff --git a/data/client/king_phisher/king-phisher-client.ui b/data/client/king_phisher/king-phisher-client.ui index 875bdf54..42df7b99 100644 --- a/data/client/king_phisher/king-phisher-client.ui +++ b/data/client/king_phisher/king-phisher-client.ui @@ -724,15 +724,19 @@ Spencer McIntyre True False + end 5 - - - - - - - + + True + False + Expiration Time: + + + False + True + 0 + @@ -740,7 +744,6 @@ Spencer McIntyre True True True - False @@ -779,7 +782,7 @@ Spencer McIntyre True False - Expiration Date + Campaign Expiration @@ -1634,6 +1637,7 @@ Spencer McIntyre TimeSelector False + bottom TimeSelector @@ -1641,6 +1645,7 @@ Spencer McIntyre False + 40 True True The hour for the campaign expiration time on a 24-hour clock. @@ -1654,6 +1659,7 @@ Spencer McIntyre ClockHourAdjustment True 1 + 0 @@ -1666,7 +1672,6 @@ Spencer McIntyre True True The minute for the campaign expiration time. - True 2 00 0.5 @@ -1676,6 +1681,7 @@ Spencer McIntyre vertical ClockMinuteAdjustment True + 2 diff --git a/docs/source/king_phisher/client/widget/managers.rst b/docs/source/king_phisher/client/widget/managers.rst index 44a2f16a..d0de768d 100644 --- a/docs/source/king_phisher/client/widget/managers.rst +++ b/docs/source/king_phisher/client/widget/managers.rst @@ -24,6 +24,13 @@ Classes :members: :special-members: __init__ +.. autoclass:: TimeSelectorButtonManager + :show-inheritance: + :members: + :special-members: __init__ + + .. py:attribute:: time + .. autoclass:: ToggleButtonGroupManager :show-inheritance: :members: diff --git a/king_phisher/client/assistants/campaign.py b/king_phisher/client/assistants/campaign.py index 76596400..ee1e7c98 100644 --- a/king_phisher/client/assistants/campaign.py +++ b/king_phisher/client/assistants/campaign.py @@ -99,7 +99,7 @@ def __init__(self, application, campaign_id=None): if page_title: self._page_titles[page_title] = page_n - self.time_selector = managers.TimeSelectorButtonManager(self.gobjects['togglebutton_expiration_time'], self.application) + self._expiration_time = managers.TimeSelectorButtonManager(self.application, self.gobjects['togglebutton_expiration_time']) self._set_comboboxes() self._set_defaults() @@ -185,6 +185,7 @@ def _set_defaults(self): if campaign['expiration'] is not None: expiration = utilities.datetime_utc_to_local(campaign['expiration']) self.gobjects['checkbutton_expire_campaign'].set_active(True) + self._expiration_time.time = expiration.time() gui_utilities.gtk_calendar_set_pydate(self.gobjects['calendar_campaign_expiration'], expiration.date()) def _get_tag_from_combobox(self, combobox, db_table): @@ -248,9 +249,6 @@ def _get_graphql_company(self, company_name): }""", {'name': company_name}) return results['db']['company'] - def signal_toggle_time(self, _): - return self.time_selector.signal_button_toggled(_) - def signal_assistant_apply(self, _): self._close_ready = False # have to do it this way because the next page will be selected when the apply signal is complete @@ -284,7 +282,7 @@ def signal_assistant_apply(self, _): if self.gobjects['checkbutton_expire_campaign'].get_property('active'): expiration = datetime.datetime.combine( gui_utilities.gtk_calendar_get_pydate(self.gobjects['calendar_campaign_expiration']), - self.time_selector.time + self._expiration_time.time ) expiration = utilities.datetime_local_to_utc(expiration) if self.is_new_campaign and expiration <= datetime.datetime.now(): diff --git a/king_phisher/client/widget/managers.py b/king_phisher/client/widget/managers.py index b249afd1..348de1ae 100644 --- a/king_phisher/client/widget/managers.py +++ b/king_phisher/client/widget/managers.py @@ -375,16 +375,12 @@ def signal_activate_popup_menu_copy(self, menuitem, column_ids): def signal_activate_popup_menu_delete(self, menuitem): self._call_cb_delete() -class TimeSelector(gui_utilities.GladeGObject): +class _TimeSelector(gui_utilities.GladeGObject): """ - This is the TimeSelector Popover object containing the spinbutton - widgets. This class should be treated as private, as it is created - by the TimeSelectorButtonManager() Class. It should not be used - directly. - - :param GTK.Popover: The popover TimeSelector Glade object - :type TimeSelector: An empty GTK.Popover class reserved for time selections - :return TimeSelector: + This is the TimeSelector :py:class:`~Gtk.Popover` object containing the + :py:class:`~Gtk.SpinButton` widgets. This class should be treated as + private, as it is created by the :py:class:`~TimeSelectorButtonManager` + class. It should not be used directly. """ dependencies = gui_utilities.GladeDependencies( children=( @@ -394,48 +390,57 @@ class TimeSelector(gui_utilities.GladeGObject): top_level=( 'ClockHourAdjustment', 'ClockMinuteAdjustment' - ) + ), + name='TimeSelector' ) top_gobject = 'popover' + def signal_spinbutton_output(self, spinbutton): + adjustment = spinbutton.get_adjustment() + value = adjustment.get_value() + spinbutton.set_text("{0:02.0f}".format(value)) + return True class TimeSelectorButtonManager(object): - def __init__(self, button, application, value=None): + """ + A manager class to convert a :py:class:`~Gtk.ToggleButton` to be used for + showing a time selector py:class:`~.Gtk.Popover` object with inputs for + setting the hour and minutes. This then exposes the selected time through + the :py:attr:`.time` attribute. + """ + def __init__(self, application, button, value=None): """ - Button Manager digests the GTK.ToggleButton to be used to show - the TimeSelector object. This should be - instantiated within the window where the TimeSelector widget is - desired as an attribute of the window class. - - :param button: The button to activate TimeSelectorPopover() + :param button: The button used for activation. :type button: :py:class:`Gtk.ToggleButton` :param application: The application instance which owns this object. - :param value: The present datetime value (initialized to 00:00) - :type datetime.time: The value; initialized to NoneType but will always return datetime.time() - :return: (GTK.ToggleButton, GTK.Popover, datetime.time) + :param value: The present datetime value (defaults to 00:00). + :type value: :py:class:`datetime.time` """ - self.popover = TimeSelector(application) + self.popover = _TimeSelector(application) self.button = button self.application = application + self._time_format = "{time.hour:02}:{time.minute:02}" self._hour_spin = self.popover.gobjects['spinbutton_hour'] + self._hour_spin.connect('value-changed', lambda _: self.button.set_label(self._time_format.format(time=self.time))) self._minute_spin = self.popover.gobjects['spinbutton_minute'] + self._minute_spin.connect('value-changed', lambda _: self.button.set_label(self._time_format.format(time=self.time))) self.time = value or datetime.time(0, 0) - self.button.connect('toggled', self.signal_button_toggled) self.popover.popover.set_relative_to(self.button) - self.button.set_label(f"{self.time.hour:02}:{self.time.minute:02}") + self.popover.popover.connect('closed', lambda _: self.button.set_active(False)) + self.button.connect('toggled', self.signal_button_toggled) def __repr__(self): return "<{0} time='{1:%H:%M}' >".format(self.__class__.__name__, self.time) def signal_button_toggled(self, _): - if not self.button.get_active(): + if self.button.get_active(): self.popover.popover.popup() - else: - self.popover.popover.popdown() - self.button.set_label(f"{self.time.hour:02}:{self.time.minute:02}") @property def time(self): """ + This property represents the current time value and when set, updates + the associated button. + :return: The current time value. :rtype: :py:class:`datetime.time` """ @@ -451,4 +456,4 @@ def time(self, value): raise TypeError('argument 1 must be a datetime.time instance') self._hour_spin.set_value(value.hour) self._minute_spin.set_value(value.minute) - self.button.set_label(f"{value.hour:02}:{value.minute:02}") + self.button.set_label(self._time_format.format(time=value)) From 45db1787c474182fb2a72eefb80a239d4a08988f Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 4 Sep 2018 16:06:31 -0400 Subject: [PATCH 16/49] Pinned client-websockets to fix websocket issues --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index beac162f..683665ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,5 +35,5 @@ smoke-zephyr>=1.2.0 SQLAlchemy>=1.2.6 termcolor>=1.1.0 tzlocal>=1.5.1 -websocket-client>=0.49.0 +websocket-client==0.49.0 XlsxWriter>=0.9.6 # dpkg kali From dd2612fd3d5eb09abb6a2b78937a425cd1c77b5b Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 10 Sep 2018 10:54:27 -0400 Subject: [PATCH 17/49] Cleanup and test the new campaign expiration signals --- docs/source/server/signals.rst | 10 +++++++ king_phisher/server/server.py | 55 +++++++++++++++------------------- king_phisher/server/signals.py | 5 ++++ king_phisher/version.py | 2 +- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/docs/source/server/signals.rst b/docs/source/server/signals.rst index 2a2f857a..6643e94f 100644 --- a/docs/source/server/signals.rst +++ b/docs/source/server/signals.rst @@ -12,6 +12,13 @@ This allows plugins to subscribe specific functions to be executed when a particular event occurs. These signals are defined in the :py:mod:`~server.signals` module. +Signal Sender +------------- + +The first parameter of each signal is the `signal sender`_. It can be used by +subscribers to only receive signal when emitted by a particular sender, +effectively providing a filter. See the `blinker documentation`_ for more +information. .. _server-signals-campaign-label: @@ -123,3 +130,6 @@ Signals which are emitted for a .. autodata:: server_initialized :annotation: :noindex: + +.. _blinker documentation: https://pythonhosted.org/blinker/ +.. _signal sender: https://pythonhosted.org/blinker/#subscribing-to-specific-senders diff --git a/king_phisher/server/server.py b/king_phisher/server/server.py index 5a92d013..0bd3b11a 100644 --- a/king_phisher/server/server.py +++ b/king_phisher/server/server.py @@ -59,10 +59,30 @@ from king_phisher.server.database import models as db_models import advancedhttpserver +import blinker import jinja2 import smoke_zephyr.job import smoke_zephyr.utilities +def _send_safe_campaign_alerts(campaign, signal_name, sender, **kwargs): + alert_subscriptions = tuple(subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) + logger = logging.getLogger('KingPhisher.Server.CampaignAlerts') + logger.debug("dispatching campaign alerts for '{0}' (sender: {1!r}) to {2:,} active subscriptions".format(signal_name, sender, len(alert_subscriptions))) + if not alert_subscriptions: + return + signal = blinker.signal(signal_name) + if not signal.receivers: + logger.warning("users are subscribed to '{0}', and no signal handlers are connected".format(signal_name)) + return + if not signal.has_receivers_for(sender): + logger.info("users are subscribed to '{0}', and no signal handlers are connected for sender: {1}".format(signal_name, sender)) + return + for subscription in alert_subscriptions: + results = signals.send_safe(signal_name, logger, sender, alert_subscription=subscription, **kwargs) + if any((result for (_, result) in results)): + continue + logger.warning("user {0} is subscribed to '{1}', and no signal handlers succeeded to send an alert".format(subscription.user.name, signal_name)) + class KingPhisherRequestHandler(advancedhttpserver.RequestHandler): _logger = logging.getLogger('KingPhisher.Server.RequestHandler') def __init__(self, request, client_address, server, **kwargs): @@ -111,25 +131,7 @@ def issue_alert(self, campaign_id, table, count): """ session = db_manager.Session() campaign = db_manager.get_row_by_id(session, db_models.Campaign, campaign_id) - alert_subscriptions = tuple(subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) - if not alert_subscriptions: - self.server.logger.debug("no active alert subscriptions are present for campaign id: {0} ({1})".format(campaign.id, campaign.name)) - session.close() - return - if not signals.campaign_alert.receivers: - self.server.logger.warning('users are subscribed to campaign alerts, and no signal handlers are connected') - session.close() - return - if not signals.campaign_alert.has_receivers_for(table): - self.server.logger.info('users are subscribed to campaign alerts, and no signal handlers are connected for sender: ' + table) - session.close() - return - - for subscription in alert_subscriptions: - results = signals.send_safe('campaign-alert', self.server.logger, table, alert_subscription=subscription, count=count) - if any((result for (_, result) in results)): - continue - self.server.logger.warning("user {0} is subscribed to campaign alerts, and no signal handlers succeeded to send an alert".format(subscription.user.name)) + _send_safe_campaign_alerts(campaign, 'campaign-alert', table, count=count) session.close() return @@ -885,6 +887,7 @@ def __init__(self, config, plugin_manager, handler_klass, *args, **kwargs): """A :py:class:`~smoke_zephyr.job.JobManager` instance for scheduling tasks.""" self.job_manager.start() maintenance_interval = 900 # 15 minutes + maintenance_interval = 20 self._maintenance_job = self.job_manager.job_add(self._maintenance, parameters=(maintenance_interval,), seconds=maintenance_interval) loader = jinja2.FileSystemLoader(config.get('server.web_root')) @@ -947,18 +950,8 @@ def _maintenance(self, interval): db_models.Campaign.expiration >= now - datetime.timedelta(seconds=interval) ) for campaign in campaigns: - signals.send_safe('campaign-expired', self.server.logger, campaign) - alert_subscriptions = tuple(subscription for subscription in campaign.alert_subscriptions if not subscription.has_expired) - if not alert_subscriptions: - continue - if not signals.campaign_alert.receivers: - self.server.logger.warning('users are subscribed to campaign expiration alerts, and no signal handlers are connected') - continue - for subscription in alert_subscriptions: - results = signals.send_safe('campaign-alert-expired', self.server.logger, alert_subscription=subscription) - if any((result for (_, result) in results)): - continue - self.server.logger.warning("user {0} is subscribed to campaign alerts, and no signal handlers succeeded to send an alert".format(subscription.user.name)) + signals.send_safe('campaign-expired', self.logger, campaign) + _send_safe_campaign_alerts(campaign, 'campaign-alert-expired', campaign) session.close() def shutdown(self, *args, **kwargs): diff --git a/king_phisher/server/signals.py b/king_phisher/server/signals.py index 20f84dd5..2e323da5 100644 --- a/king_phisher/server/signals.py +++ b/king_phisher/server/signals.py @@ -94,6 +94,8 @@ def send_safe(signal, logger, sender, **kwargs): Emitted for each user who is subscribed to alerts for a particular campaign after it has expired. + :param campaign: The campaign which is expiring. + :type campaign: :py:class:`king_phisher.server.database.models.Campaign` :param alert_subscription: The alert subscription. :type alert_subscription: :py:class:`king_phisher.server.database.models.AlertSubscription` """ @@ -108,6 +110,9 @@ def send_safe(signal, logger, sender, **kwargs): interval. If a campaign is updated to expire at a time less than the next check minus the interval, then this signal will not be emitted for the campaign. + + :param campaign: The campaign which is expiring. + :type campaign: :py:class:`king_phisher.server.database.models.Campaign` """ ) diff --git a/king_phisher/version.py b/king_phisher/version.py index 305d4c2c..eac85153 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -66,7 +66,7 @@ def get_revision(): version_info = collections.namedtuple('version_info', ('major', 'minor', 'micro'))(1, 12, 0) """A tuple representing the version information in the format ('major', 'minor', 'micro')""" -version_label = 'beta1' +version_label = 'beta2' """A version label such as alpha or beta.""" version = "{0}.{1}.{2}".format(version_info.major, version_info.minor, version_info.micro) From 2643e3bec4fab1bbad40d89cb19017c7b2a952ad Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 10 Sep 2018 13:16:49 -0400 Subject: [PATCH 18/49] Remove the debugging maintenance interval --- king_phisher/catalog.py | 2 +- king_phisher/server/server.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/king_phisher/catalog.py b/king_phisher/catalog.py index a19d2472..c3349376 100644 --- a/king_phisher/catalog.py +++ b/king_phisher/catalog.py @@ -501,7 +501,7 @@ def sign_item_files(local_path, signing_key, repo_path=None): .. warning:: This function contains a black list of file extensions which will be skipped. This is to avoid signing files originating from the development - process. + process such as ``.pyc`` and ``.ui~``. :param str local_path: The real location of where the files exist on disk. :param signing_key: The key with which to sign the files for verification. diff --git a/king_phisher/server/server.py b/king_phisher/server/server.py index 6e2f7e76..7b530306 100644 --- a/king_phisher/server/server.py +++ b/king_phisher/server/server.py @@ -881,7 +881,6 @@ def __init__(self, config, plugin_manager, handler_klass, *args, **kwargs): """A :py:class:`~smoke_zephyr.job.JobManager` instance for scheduling tasks.""" self.job_manager.start() maintenance_interval = 900 # 15 minutes - maintenance_interval = 20 self._maintenance_job = self.job_manager.job_add(self._maintenance, parameters=(maintenance_interval,), seconds=maintenance_interval) loader = jinja2.FileSystemLoader(config.get('server.web_root')) From f6507dd06c8a0a564dee089218f76ebbc0cc460b Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Thu, 13 Sep 2018 12:52:37 -0400 Subject: [PATCH 19/49] Load plugins for missing catalogs/repos --- king_phisher/client/windows/plugin_manager.py | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/king_phisher/client/windows/plugin_manager.py b/king_phisher/client/windows/plugin_manager.py index f413e407..85d24d26 100644 --- a/king_phisher/client/windows/plugin_manager.py +++ b/king_phisher/client/windows/plugin_manager.py @@ -31,6 +31,7 @@ # import collections +import copy import datetime import errno import functools @@ -175,6 +176,7 @@ def __init__(self, *args, **kwargs): self.catalog_plugins = plugins.ClientCatalogManager(self.application.user_data_path) self.plugin_path = os.path.join(self.application.user_data_path, 'plugins') self.status_bar = self.gobjects['statusbar'] + self._installed_plugins_treeview_tracker = None self._worker_thread = None self._worker_thread_start(self._load_catalogs_tsafe) self.__load_errors = {} @@ -288,6 +290,8 @@ def _add_catalog_to_tree_tsafe(self, catalog): install_src = self.config['plugins.installed'].get(plugin_name) if install_src and repo.id == install_src['repo_id'] and catalog.id == install_src['catalog_id']: installed = True + # plugin was added to treeview so it is removed from the temporary tracking dict + self._installed_plugins_treeview_tracker.pop(plugin_name) enabled = plugin_name in self.config['plugins.enabled'] repo_node.children.append(_ModelNamedRow( id=plugin_name, @@ -681,7 +685,15 @@ def _worker_thread_is_ready(self): # Each of these functions loads the catalog and handles add it to the # TreeView as necessary. # + # self._installed_plugins_treeview_tracker is a temporary copy of the list + # of installed plugins from the users configuration file. + # Plugins are removed from it as they are added to the treeview. + # If there any plugins left over, their associated catalog was not found + # and the plugin is then moved to local. + # The users config is then updated accordingly. + # def _load_catalogs_tsafe(self, refresh=False): + self._installed_plugins_treeview_tracker = copy.deepcopy(self.config['plugins.installed']) if refresh: gui_utilities.glib_idle_add_once(self._model.clear) expiration = datetime.timedelta(seconds=smoke_zephyr.utilities.parse_timespan(self.config.get('cache.age', '4h'))) @@ -702,7 +714,24 @@ def _load_catalogs_tsafe(self, refresh=False): if catalog is None and catalog_cache_dict is not None: self.logger.warning('failing over to loading the catalog from the cache') self._load_catalog_from_cache_tsafe(catalog_cache_dict) + if self._installed_plugins_treeview_tracker: + self._load_missing_plugins_tsafe() self._update_status_bar_tsafe('Loading completed') + self._installed_plugins_treeview_tracker = None + + def _load_missing_plugins_tsafe(self): + local_model_row = None + for plugin in self._installed_plugins_treeview_tracker.keys(): + self.logger.warning("plugin {} was not found in any catalog or repo loaded, moving to locally installed.".format(plugin)) + self.config['plugins.installed'][plugin] = None + self._installed_plugins_treeview_tracker[plugin] = None + # must wait for gtktree view to finish loading all nodes + while not local_model_row: + for model_row in self._model: + if _ModelNamedRow(*model_row).id == _LOCAL_REPOSITORY_ID: + local_model_row = model_row + gui_utilities.glib_idle_add_once(self._model.remove, local_model_row.iter) + self._load_catalog_local_tsafe() def _load_catalog_from_cache_tsafe(self, catalog_cache_dict): catalog = None From 23f16df4bc21e589b1d625acf424f60a50240fb1 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Thu, 13 Sep 2018 16:46:31 -0400 Subject: [PATCH 20/49] Updated based on comments --- king_phisher/client/windows/plugin_manager.py | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/king_phisher/client/windows/plugin_manager.py b/king_phisher/client/windows/plugin_manager.py index 85d24d26..5f371ac1 100644 --- a/king_phisher/client/windows/plugin_manager.py +++ b/king_phisher/client/windows/plugin_manager.py @@ -177,6 +177,7 @@ def __init__(self, *args, **kwargs): self.plugin_path = os.path.join(self.application.user_data_path, 'plugins') self.status_bar = self.gobjects['statusbar'] self._installed_plugins_treeview_tracker = None + """This is used to track and make sure all plugins make it into the treeview. It is set each time catalogs are loaded or refreshed.""" self._worker_thread = None self._worker_thread_start(self._load_catalogs_tsafe) self.__load_errors = {} @@ -685,13 +686,6 @@ def _worker_thread_is_ready(self): # Each of these functions loads the catalog and handles add it to the # TreeView as necessary. # - # self._installed_plugins_treeview_tracker is a temporary copy of the list - # of installed plugins from the users configuration file. - # Plugins are removed from it as they are added to the treeview. - # If there any plugins left over, their associated catalog was not found - # and the plugin is then moved to local. - # The users config is then updated accordingly. - # def _load_catalogs_tsafe(self, refresh=False): self._installed_plugins_treeview_tracker = copy.deepcopy(self.config['plugins.installed']) if refresh: @@ -722,15 +716,15 @@ def _load_catalogs_tsafe(self, refresh=False): def _load_missing_plugins_tsafe(self): local_model_row = None for plugin in self._installed_plugins_treeview_tracker.keys(): - self.logger.warning("plugin {} was not found in any catalog or repo loaded, moving to locally installed.".format(plugin)) + self.logger.warning("plugin {} was not found in any loaded catalog or repo, moving to locally installed".format(plugin)) self.config['plugins.installed'][plugin] = None self._installed_plugins_treeview_tracker[plugin] = None - # must wait for gtktree view to finish loading all nodes - while not local_model_row: - for model_row in self._model: - if _ModelNamedRow(*model_row).id == _LOCAL_REPOSITORY_ID: - local_model_row = model_row - gui_utilities.glib_idle_add_once(self._model.remove, local_model_row.iter) + for model_row in self._model: + if _ModelNamedRow(*model_row).id == _LOCAL_REPOSITORY_ID: + gui_utilities.glib_idle_add_wait(self._model.remove, model_row.iter) + break + else: + raise RuntimeError('failed to find the local plugin repository') self._load_catalog_local_tsafe() def _load_catalog_from_cache_tsafe(self, catalog_cache_dict): @@ -799,7 +793,7 @@ def _load_catalog_local_tsafe(self): sensitive_installed=False, type=_ROW_TYPE_PLUGIN )) - gui_utilities.glib_idle_add_once(self.__store_add_node, node) + gui_utilities.glib_idle_add_wait(self.__store_add_node, node) # # Signal Handlers From d44c5646694689e6c00b7a32d0ef858e32061973 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Fri, 14 Sep 2018 09:59:06 -0400 Subject: [PATCH 21/49] Don't track locally installed plugins --- king_phisher/client/windows/plugin_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/king_phisher/client/windows/plugin_manager.py b/king_phisher/client/windows/plugin_manager.py index 5f371ac1..92fcb8b6 100644 --- a/king_phisher/client/windows/plugin_manager.py +++ b/king_phisher/client/windows/plugin_manager.py @@ -688,6 +688,10 @@ def _worker_thread_is_ready(self): # def _load_catalogs_tsafe(self, refresh=False): self._installed_plugins_treeview_tracker = copy.deepcopy(self.config['plugins.installed']) + for plugin in self._installed_plugins_treeview_tracker: + # Remove plugins already found to be locally installed. + if not self._installed_plugins_treeview_tracker[plugin]: + self._installed_plugins_treeview_tracker.pop(plugin) if refresh: gui_utilities.glib_idle_add_once(self._model.clear) expiration = datetime.timedelta(seconds=smoke_zephyr.utilities.parse_timespan(self.config.get('cache.age', '4h'))) From 08bedff381e64eb18e98c7f5f91e67a07752a070 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Sat, 15 Sep 2018 14:17:04 -0400 Subject: [PATCH 22/49] Add a bit more clarification to the doc string --- king_phisher/client/windows/plugin_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/king_phisher/client/windows/plugin_manager.py b/king_phisher/client/windows/plugin_manager.py index 92fcb8b6..7fc99e3b 100644 --- a/king_phisher/client/windows/plugin_manager.py +++ b/king_phisher/client/windows/plugin_manager.py @@ -177,7 +177,13 @@ def __init__(self, *args, **kwargs): self.plugin_path = os.path.join(self.application.user_data_path, 'plugins') self.status_bar = self.gobjects['statusbar'] self._installed_plugins_treeview_tracker = None - """This is used to track and make sure all plugins make it into the treeview. It is set each time catalogs are loaded or refreshed.""" + """ + This is used to track and make sure all plugins make it into the + treeview. It is set each time catalogs are loaded or refreshed. Once the + loading operation is complete, plugins that remain were not loaded due + their data (repo or id) missing from the catalog, likely due to it + having been removed. + """ self._worker_thread = None self._worker_thread_start(self._load_catalogs_tsafe) self.__load_errors = {} From 4fa894f2fea9fc3e7d8403125b95ceacc31feac9 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 18 Sep 2018 16:43:22 -0400 Subject: [PATCH 23/49] Fixed plugin manager dict iteration issue --- king_phisher/client/windows/plugin_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/king_phisher/client/windows/plugin_manager.py b/king_phisher/client/windows/plugin_manager.py index 7fc99e3b..e9ba2243 100644 --- a/king_phisher/client/windows/plugin_manager.py +++ b/king_phisher/client/windows/plugin_manager.py @@ -694,7 +694,7 @@ def _worker_thread_is_ready(self): # def _load_catalogs_tsafe(self, refresh=False): self._installed_plugins_treeview_tracker = copy.deepcopy(self.config['plugins.installed']) - for plugin in self._installed_plugins_treeview_tracker: + for plugin in list(self._installed_plugins_treeview_tracker.keys()): # Remove plugins already found to be locally installed. if not self._installed_plugins_treeview_tracker[plugin]: self._installed_plugins_treeview_tracker.pop(plugin) From 985a7e32b0a3f59c74041fcbcb44e50fc638ffcd Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 8 Oct 2018 12:08:26 -0400 Subject: [PATCH 24/49] Fix #324, pin the markdown package to 2.6.11 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 683665ad..37d67162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ icalendar>=4.0.1 ipaddress>=1.0.19 ; python_version < '3.3' jsonschema>=2.6.0 jinja2>=2.10 -markdown>=2.6.11 +markdown==2.6.11 markupsafe>=1.0 matplotlib>=2.2.2 msgpack-python>=0.5.6 From ae624731a992e4268b84baf90a86a9c930faba79 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 10 Oct 2018 12:59:01 -0700 Subject: [PATCH 25/49] Add GraphQL details to the client exception dialog --- king_phisher/client/dialogs/exception.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/king_phisher/client/dialogs/exception.py b/king_phisher/client/dialogs/exception.py index f0e49faa..c0ab232c 100644 --- a/king_phisher/client/dialogs/exception.py +++ b/king_phisher/client/dialogs/exception.py @@ -32,11 +32,13 @@ import platform import sys +import textwrap import threading import traceback import tzlocal import uuid +from king_phisher import errors from king_phisher import its from king_phisher import utilities from king_phisher import version @@ -61,9 +63,11 @@ Timezone: {timezone} Thread Information: +=================== {thread_info} Stack Trace: +============ {stack_trace} """ @@ -123,6 +127,14 @@ def format_exception_details(exc_type, exc_value, exc_traceback, error_uid=None) timezone=tzlocal.get_localzone().zone ) details = details.strip() + '\n' + # add on additional details for context as necessary + if isinstance(exc_value, errors.KingPhisherGraphQLQueryError): + details += '\nGraphQL Exception Information:\n=============================\n\n' + if exc_value.errors: + details += 'GraphQL Errors:\n---------------\n' + details += '\n'.join(error.strip() for error in exc_value.errors) + '\n\n' + details += 'GraphQL Query:\n--------------\n' + details += textwrap.dedent(exc_value.query) + '\n' return details def format_exception_name(exc_type): From 164462bf421585dff6078fec35e0d9ce1148e511 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 10 Oct 2018 13:09:27 -0700 Subject: [PATCH 26/49] Fix #326, catch a geoip2 database error in GraphQL --- king_phisher/server/graphql/types/misc.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/king_phisher/server/graphql/types/misc.py b/king_phisher/server/graphql/types/misc.py index d4134497..e7114404 100644 --- a/king_phisher/server/graphql/types/misc.py +++ b/king_phisher/server/graphql/types/misc.py @@ -37,6 +37,7 @@ import king_phisher.geoip as geoip import king_phisher.ipaddress as ipaddress +import geoip2.errors import graphene.types.utils import graphql.language.ast @@ -110,7 +111,10 @@ def from_ip_address(cls, ip_address): ip_address = ipaddress.ip_address(ip_address) if ip_address.is_private: return - result = geoip.lookup(ip_address) + try: + result = geoip.lookup(ip_address) + except geoip2.errors.AddressNotFoundError: + result = None if result is None: return return cls(**result) From 1b41272f759714824616b30309dcf4f98a65db84 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Mon, 15 Oct 2018 12:08:29 -0400 Subject: [PATCH 27/49] pipenv setup Pipenv install updates Set entries as the wrapper Updated basemaps to v1.2.0 pipenv pip18 fix add clean utilities Install pipenv from github Added gobject-interspection for package installs Pipenv enviroment --- KingPhisher | 108 +-- KingPhisherServer | 250 ++----- Pipfile | 58 ++ Pipfile.lock | 694 ++++++++++++++++++ .../server/service_files/king-phisher.service | 1 + king_phisher/client/__main__.py | 105 +++ king_phisher/server/__main__.py | 243 ++++++ king_phisher/startup.py | 90 +++ king_phisher/utilities.py | 9 +- king_phisher/version.py | 4 +- requirements.txt | 39 - tools/development/pipenv_wrapper | 138 ++++ tools/install.sh | 29 +- 13 files changed, 1447 insertions(+), 321 deletions(-) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100755 king_phisher/client/__main__.py create mode 100755 king_phisher/server/__main__.py create mode 100644 king_phisher/startup.py delete mode 100644 requirements.txt create mode 100755 tools/development/pipenv_wrapper diff --git a/KingPhisher b/KingPhisher index 92940cf8..6c92e071 100755 --- a/KingPhisher +++ b/KingPhisher @@ -33,75 +33,75 @@ import argparse import logging import os +import subprocess import sys -import threading -import time -if getattr(sys, 'frozen', False): - # set the basemap data directory for frozen builds - os.environ['BASEMAPDATA'] = os.path.join(os.path.dirname(sys.executable), 'mpl-basemap-data') - -from king_phisher import color -from king_phisher import find -from king_phisher import utilities -from king_phisher import version -from king_phisher.client import application -from king_phisher.client import gui_utilities - -from gi.repository import GObject -from gi.repository import Gtk +from king_phisher import startup def main(): parser = argparse.ArgumentParser(description='King Phisher Client GUI', conflict_handler='resolve') - utilities.argp_add_args(parser, default_root='KingPhisher') - parser.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') - parser.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') - parser.add_argument('--no-style', dest='use_style', default=True, action='store_false', help='disable interface styling') - arguments = parser.parse_args() + startup.argp_add_wrapper(parser) + startup.argp_add_client(parser) + startup.argb_add_default_args(parser) + + arguments, unknown_args = parser.parse_known_args() + sys_argv = sys.argv + sys_argv.pop(0) - # basic runtime checks if sys.version_info < (3, 4): - color.print_error('the Python version is too old (minimum required is 3.4)') + print('[-] the Python version is too old (minimum required is 3.4)') return 0 - - if Gtk.check_version(3, 14, 0): - color.print_error('the GTK+ version is too old (minimum required is 3.14)') + logger = logging.getLogger('KingPhisher.wrapper') + logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler = logging.StreamHandler() + console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) + logger.addHandler(console_log_handler) + + target_directory = os.path.abspath(os.path.dirname(__file__)) + logger.debug("target diretory: {}".format(target_directory)) + os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') + os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) + logger.info('checking for pipenv for environment') + if not startup.which('pipenv'): + logger.info('installing pipenv') + process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) + if return_code: + logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) + return 0 + pipenv_path = startup.which('pipenv') + logger.debug('pipenv path: {}'.format(pipenv_path)) + if not pipenv_path: + logger.exception("failed to find pipenv") return 0 - if sys.platform.startswith('linux') and not os.environ.get('DISPLAY'): - color.print_error('no display was detected, this must be run with an interactive X session') + if arguments.pipenv_install: + logger.info("installing King Phisher's pipenv environment") + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + return 0 return 0 - config_file = arguments.config_file - use_plugins = arguments.use_plugins - use_style = arguments.use_style - del arguments, parser - logger = logging.getLogger('KingPhisher.Client.CLI') - - if sys.platform.startswith('linux') and not os.getuid(): - logger.warning('it is not necessary to run the king phisher client as root') - - find.init_data_path('client') - - if not gui_utilities.which_glade(): - color.print_error('unable to locate the glade ui data file') + if arguments.pipenv_update: + logger.info("updating King Phisher's pipenv") + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) + return 0 + logger.info('pipenv environment updated') return 0 - logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2])) - logger.debug("client running in process: {0} main tid: 0x{1:x}".format(os.getpid(), threading.current_thread().ident)) - - start_time = time.time() - logger.debug('using ui data from glade file: ' + gui_utilities.which_glade()) - try: - app = application.KingPhisherClientApplication(config_file=config_file, use_plugins=use_plugins, use_style=use_style) - except Exception as error: - logger.critical("initialization error: {0} ({1})".format(error.__class__.__name__, getattr(error, 'message', 'n/a'))) - color.print_error('failed to initialize the King Phisher client') - return 0 - logger.debug("client loaded in {0:.2f} seconds".format(time.time() - start_time)) + if not os.path.isdir(os.path.join(target_directory, '.venv')): + logger.info("installing King Phisher's pipenv environment") + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + return 0 + logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv - GObject.threads_init() - return app.run([]) + os.execve(pipenv_path, passing_argv, os.environ) if __name__ == '__main__': sys.exit(main()) diff --git a/KingPhisherServer b/KingPhisherServer index b63c0893..549cb061 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -29,218 +29,74 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # -# pylint: disable=too-many-locals import argparse import logging import os -import pwd -import signal +import subprocess import sys -import threading -from king_phisher import color -from king_phisher import constants -from king_phisher import errors -from king_phisher import find -from king_phisher import geoip -from king_phisher import its -from king_phisher import utilities -from king_phisher import version -from king_phisher.server import build -from king_phisher.server import configuration -from king_phisher.server import plugins - -from boltons import strutils - -logger = logging.getLogger('KingPhisher.Server.CLI') - -def build_and_run(arguments, config, plugin_manager, log_file=None): - # fork into the background - should_fork = True - if arguments.foreground: - should_fork = False - elif config.has_option('server.fork'): - should_fork = bool(config.get('server.fork')) - if should_fork: - if os.fork(): - return sys.exit(os.EX_OK) - os.setsid() - - try: - king_phisher_server = build.server_from_config(config, plugin_manager=plugin_manager) - except errors.KingPhisherDatabaseAuthenticationError: - logger.critical('failed to authenticate to the database, this usually means the password is incorrect and needs to be updated') - return os.EX_SOFTWARE - except errors.KingPhisherError as error: - logger.critical('server failed to build with error: ' + error.message) - return os.EX_SOFTWARE - - server_pid = os.getpid() - logger.info("server running in process: {0} main tid: 0x{1:x}".format(server_pid, threading.current_thread().ident)) - - if should_fork and config.has_option('server.pid_file'): - pid_file = open(config.get('server.pid_file'), 'w') - pid_file.write(str(server_pid)) - pid_file.close() - - if config.has_option('server.setuid_username'): - setuid_username = config.get('server.setuid_username') - try: - user_info = pwd.getpwnam(setuid_username) - except KeyError: - logger.critical('an invalid username was specified as \'server.setuid_username\'') - king_phisher_server.shutdown() - return os.EX_NOUSER - if log_file is not None: - os.chown(log_file, user_info.pw_uid, user_info.pw_gid) - os.setgroups([]) - os.setresgid(user_info.pw_gid, user_info.pw_gid, user_info.pw_gid) - os.setresuid(user_info.pw_uid, user_info.pw_uid, user_info.pw_uid) - logger.info("dropped privileges to the {0} account".format(setuid_username)) - else: - logger.warning('running with root privileges is dangerous, drop them by configuring \'server.setuid_username\'') - os.umask(0o077) - - db_engine_url = king_phisher_server.database_engine.url - if db_engine_url.drivername == 'sqlite': - logger.warning('sqlite is no longer fully supported, see https://github.com/securestate/king-phisher/wiki/Database#sqlite for more details') - database_dir = os.path.dirname(db_engine_url.database) - if not os.access(database_dir, os.W_OK): - logger.critical('sqlite requires write permissions to the folder containing the database') - king_phisher_server.shutdown() - return os.EX_NOPERM - sighup_handler = lambda: threading.Thread(target=king_phisher_server.shutdown).start() - signal.signal(signal.SIGHUP, lambda signum, frame: sighup_handler()) - try: - king_phisher_server.serve_forever(fork=False) - except KeyboardInterrupt: - pass - king_phisher_server.shutdown() - return os.EX_OK - -def _ex_config_logging(arguments, config, console_handler): - """ - If a setting is configured improperly, this will terminate execution via - :py:func:`sys.exit`. - - :return: The path to a log file if one is in use. - :rtype: str - """ - default_log_level = min( - getattr(logging, (arguments.loglvl or constants.DEFAULT_LOG_LEVEL)), - getattr(logging, config.get_if_exists('logging.level', 'critical').upper()) - ) - log_levels = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL') - file_path = None - if config.has_option('logging.file'): - options = config.get('logging.file') - for _ in range(1): - default_format = '%(asctime)s %(name)-50s %(levelname)-8s %(message)s' - if isinstance(options, dict): # new style - if not options.get('enabled', True): - break - if 'path' not in options: - color.print_error('logging.file is missing required key \'path\'') - sys.exit(os.EX_CONFIG) - if 'level' not in options: - color.print_error('logging.file is missing required key \'level\'') - sys.exit(os.EX_CONFIG) - file_path = options['path'] - formatter = logging.Formatter(options.get('format', default_format)) - if not options['level'].upper() in log_levels: - color.print_error('logging.file.level is invalid, must be one of: ' + ', '.join(log_levels)) - sys.exit(os.EX_CONFIG) - log_level = getattr(logging, options['level'].upper()) - root = options.get('root', '') - elif isinstance(options, str): # old style - file_path = options - formatter = logging.Formatter(default_format) - log_level = default_log_level - root = '' - else: - break - file_handler = logging.FileHandler(file_path) - file_handler.setFormatter(formatter) - logging.getLogger(root).addHandler(file_handler) - file_handler.setLevel(log_level) - - if config.has_option('logging.console'): - options = config.get('logging.console') - for _ in range(1): - if isinstance(options, dict): # new style - if not options.get('enabled', True): - break - if 'format' in options: - console_handler.setFormatter(color.ColoredLogFormatter(options['format'])) - - if arguments.loglvl is None and 'level' in options: - log_level = str(options.get('level', '')).upper() - if log_level not in log_levels: - color.print_error('logging.console.level is invalid, must be one of: ' + ', '.join(log_levels)) - sys.exit(os.EX_CONFIG) - console_handler.setLevel(getattr(logging, log_level)) - elif isinstance(options, str): # old style - console_handler.setLevel(default_log_level) - return file_path +from king_phisher import startup def main(): parser = argparse.ArgumentParser(description='King Phisher Server', conflict_handler='resolve') - utilities.argp_add_args(parser) - parser.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') - parser.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') - parser.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') - parser.add_argument('config_file', action='store', help='configuration file to use') - arguments = parser.parse_args() + startup.argp_add_wrapper(parser) + startup.argp_add_server(parser) + startup.argb_add_default_args(parser) + + arguments, unknown_args = parser.parse_known_args() + sys_argv = sys.argv + sys_argv.pop(0) - # basic runtime checks if sys.version_info < (3, 4): - color.print_error('the Python version is too old (minimum required is 3.4)') + print('[-] the Python version is too old (minimum required is 3.4)') + return 0 + logger = logging.getLogger('KingPhisher.wrapper') + logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler = logging.StreamHandler() + console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) + logger.addHandler(console_log_handler) + + target_directory = os.path.abspath(os.path.dirname(__file__)) + logger.debug("target diretory: {}".format(target_directory)) + os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') + os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) + if not startup.which('pipenv'): + logger.info('installing pipenv') + process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) + if return_code: + logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) + return 0 + pipenv_path = startup.which('pipenv') + logger.debug('pipenv path: {}'.format(pipenv_path)) + + if arguments.pipenv_install: + logger.info('installing pipenv environment') + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + return 0 return 0 - console_log_handler = utilities.configure_stream_logger(arguments.logger, arguments.loglvl) - del parser - - if os.getuid(): - color.print_error('the server must be started as root, configure the') - color.print_error('\'server.setuid_username\' option in the config file to drop privileges') - return os.EX_NOPERM - - # configure environment variables and load the config - find.init_data_path('server') - config = configuration.ex_load_config(arguments.config_file) - if arguments.verify_config: - color.print_good('configuration verification passed') - color.print_good('all required settings are present') - return os.EX_OK - if config.has_option('server.data_path'): - find.data_path_append(config.get('server.data_path')) - - if arguments.update_geoip_db: - color.print_status('downloading a new geoip database') - size = geoip.download_geolite2_city_db(config.get('server.geoip.database')) - color.print_good("download complete, file size: {0}".format(strutils.bytes2human(size))) - return os.EX_OK - - # setup logging based on the configuration - if config.has_section('logging'): - log_file = _ex_config_logging(arguments, config, console_log_handler) - logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2])) + if arguments.pipenv_update: + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) + return 0 + logger.info('pipenv environment successfully updated') + return 0 - # initialize the plugin manager - try: - plugin_manager = plugins.ServerPluginManager(config) - except errors.KingPhisherError as error: - if isinstance(error, errors.KingPhisherPluginError): - color.print_error("plugin error: {0} ({1})".format(error.plugin_name, error.message)) - else: - color.print_error(error.message) - return os.EX_SOFTWARE + if not os.path.isdir(os.path.join(target_directory, '.venv')): + logger.info('installing pipenv environment') + process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if return_code: + logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + return 0 + logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv - status_code = build_and_run(arguments, config, plugin_manager, log_file) - plugin_manager.shutdown() - logging.shutdown() - return status_code + os.execve(pipenv_path, passing_argv, os.environ) if __name__ == '__main__': sys.exit(main()) diff --git a/Pipfile b/Pipfile new file mode 100644 index 00000000..1d4bba0b --- /dev/null +++ b/Pipfile @@ -0,0 +1,58 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +setuptools = "==40.4.3" +alembic = "==0.9.7" +blinker = "==1.4" +boltons = "==18.0.0" +cryptography = "==2.1.4" +dnspython = "==1.15.0" +ecdsa = "==0.13" +"geoip2" = "==2.8.0" +geojson = "==2.3.0" +graphene = "==2.0.1" +graphene-sqlalchemy = "==2.0.0" +graphql-relay = "==0.4.5" +icalendar = "==4.0.1" +ipaddress = "==1.0.19" +jsonschema = "==2.6.0" +matplotlib = "==2.2.2" +msgpack-python = "==0.5.6" +paramiko = "==2.4.0" +pluginbase = "==0.5" +"psycopg2" = "==2.7.3.2,<2.8" +py-gfm = "==0.1.3" +pygobject = "==3.28.3" +pyotp = "==2.2.6" +python-dateutil = "==2.7.2" +python-pam = "==1.8.3" +pytz = "==2018.4" +requests = "==2.18.4" +requests-file = "==1.4.3" +six = "==1.11.0" +smoke-zephyr = "==1.2.0" +termcolor = "==1.1.0" +tzlocal = "==1.5.1" +websocket-client = "==0.49.0" +AdvancedHTTPServer = "==2.0.11" +email_validator = "==1.0.3" +"Jinja2" = "==2.10" +Markdown = "==2.6.11" +MarkupSafe = "==1.0" +PyYAML = "==3.12" +SQLAlchemy = "==1.2.6" +XlsxWriter = "==0.9.6" +numpy = "==1.14.5" +"basemap" = {file = "https://github.com/matplotlib/basemap/archive/v1.2.0rel.tar.gz"} + +[dev-packages] + +[scripts] +KingPhisher = "python -m king_phisher.client" +KingPhisherServer = "python -m king_phisher.server" + +[pipenv] +allow_site_packages = true \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 00000000..da12b197 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,694 @@ +{ + "_meta": { + "hash": { + "sha256": "028ebb662efb648c3fb9ebe7d33e2048000cae68f95b5d846433284a6427df00" + }, + "pipfile-spec": 6, + "requires": {}, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "advancedhttpserver": { + "hashes": [ + "sha256:62d7126a152767c65e2a7c2be7197b5c8630848ff1e0e37c24f2772ee0a5e80c" + ], + "index": "pypi", + "version": "==2.0.11" + }, + "alembic": { + "hashes": [ + "sha256:46f4849c6dce69f54dd5001b3215b6a983dee6b17512efee10e237fa11f20cfa" + ], + "index": "pypi", + "version": "==0.9.7" + }, + "asn1crypto": { + "hashes": [ + "sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87", + "sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49" + ], + "version": "==0.24.0" + }, + "basemap": { + "file": "https://github.com/matplotlib/basemap/archive/v1.2.0rel.tar.gz" + }, + "bcrypt": { + "hashes": [ + "sha256:01477981abf74e306e8ee31629a940a5e9138de000c6b0898f7f850461c4a0a5", + "sha256:054d6e0acaea429e6da3613fcd12d05ee29a531794d96f6ab959f29a39f33391", + "sha256:0872eeecdf9a429c1420158500eedb323a132bc5bf3339475151c52414729e70", + "sha256:09a3b8c258b815eadb611bad04ca15ec77d86aa9ce56070e1af0d5932f17642a", + "sha256:0f317e4ffbdd15c3c0f8ab5fbd86aa9aabc7bea18b5cc5951b456fe39e9f738c", + "sha256:2788c32673a2ad0062bea850ab73cffc0dba874db10d7a3682b6f2f280553f20", + "sha256:321d4d48be25b8d77594d8324c0585c80ae91ac214f62db9098734e5e7fb280f", + "sha256:346d6f84ff0b493dbc90c6b77136df83e81f903f0b95525ee80e5e6d5e4eef84", + "sha256:34dd60b90b0f6de94a89e71fcd19913a30e83091c8468d0923a93a0cccbfbbff", + "sha256:3b4c23300c4eded8895442c003ae9b14328ae69309ac5867e7530de8bdd7875d", + "sha256:43d1960e7db14042319c46925892d5fa99b08ff21d57482e6f5328a1aca03588", + "sha256:49e96267cd9be55a349fd74f9852eb9ae2c427cd7f6455d0f1765d7332292832", + "sha256:63e06ffdaf4054a89757a3a1ab07f1b922daf911743114a54f7c561b9e1baa58", + "sha256:67ed1a374c9155ec0840214ce804616de49c3df9c5bc66740687c1c9b1cd9e8d", + "sha256:6b662a5669186439f4f583636c8d6ea77cf92f7cfe6aae8d22edf16c36840574", + "sha256:6efd9ca20aefbaf2e7e6817a2c6ed4a50ff6900fafdea1bcb1d0e9471743b144", + "sha256:8569844a5d8e1fdde4d7712a05ab2e6061343ac34af6e7e3d7935b2bd1907bfd", + "sha256:8629ea6a8a59f865add1d6a87464c3c676e60101b8d16ef404d0a031424a8491", + "sha256:988cac675e25133d01a78f2286189c1f01974470817a33eaf4cfee573cfb72a5", + "sha256:9a6fedda73aba1568962f7543a1f586051c54febbc74e87769bad6a4b8587c39", + "sha256:9eced8962ce3b7124fe20fd358cf8c7470706437fa064b9874f849ad4c5866fc", + "sha256:a005ed6163490988711ff732386b08effcbf8df62ae93dd1e5bda0714fad8afb", + "sha256:ae35dbcb6b011af6c840893b32399252d81ff57d52c13e12422e16b5fea1d0fb", + "sha256:b1e8491c6740f21b37cca77bc64677696a3fb9f32360794d57fa8477b7329eda", + "sha256:c906bdb482162e9ef48eea9f8c0d967acceb5c84f2d25574c7d2a58d04861df1", + "sha256:cb18ffdc861dbb244f14be32c47ab69604d0aca415bee53485fcea4f8e93d5ef", + "sha256:cc2f24dc1c6c88c56248e93f28d439ee4018338567b0bbb490ea26a381a29b1e", + "sha256:d860c7fff18d49e20339fc6dffc2d485635e36d4b2cccf58f45db815b64100b4", + "sha256:d86da365dda59010ba0d1ac45aa78390f56bf7f992e65f70b3b081d5e5257b09", + "sha256:e22f0997622e1ceec834fd25947dc2ee2962c2133ea693d61805bc867abaf7ea", + "sha256:f2fe545d27a619a552396533cddf70d83cecd880a611cdfdbb87ca6aec52f66b", + "sha256:f425e925485b3be48051f913dbe17e08e8c48588fdf44a26b8b14067041c0da6", + "sha256:f7fd3ed3745fe6e81e28dc3b3d76cce31525a91f32a387e1febd6b982caf8cdb", + "sha256:f9210820ee4818d84658ed7df16a7f30c9fba7d8b139959950acef91745cc0f7" + ], + "version": "==3.1.4" + }, + "blinker": { + "hashes": [ + "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6" + ], + "index": "pypi", + "version": "==1.4" + }, + "boltons": { + "hashes": [ + "sha256:a11e113cf3f0915a21ee2c8c69c315b02f46546ad61d3640e1037b7603f6e16f", + "sha256:c7a71928c2b818f2d1263c3c66d3641331350879fb586c8f3ad6f670c72eee2d" + ], + "index": "pypi", + "version": "==18.0.0" + }, + "certifi": { + "hashes": [ + "sha256:339dc09518b07e2fa7eda5450740925974815557727d6bd35d319c1524a04a4c", + "sha256:6d58c986d22b038c8c0df30d639f23a3e6d172a05c3583e766f4c0b785c0986a" + ], + "version": "==2018.10.15" + }, + "cffi": { + "hashes": [ + "sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743", + "sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef", + "sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50", + "sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f", + "sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30", + "sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93", + "sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257", + "sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b", + "sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3", + "sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e", + "sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc", + "sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04", + "sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6", + "sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359", + "sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596", + "sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b", + "sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd", + "sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95", + "sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5", + "sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e", + "sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6", + "sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca", + "sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31", + "sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1", + "sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2", + "sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085", + "sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801", + "sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4", + "sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184", + "sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917", + "sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f", + "sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.11.5" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "cryptography": { + "hashes": [ + "sha256:0d39a93cf25edeae1f796bbc5960e587f34513a852564f6345ea4491a86c5997", + "sha256:18d0b0fc21f39b35ea469a82584f55eeecec1f65a92d85af712c425bdef627b3", + "sha256:27a208b9600166976182351174948e128818e7fc95cbdba18143f3106a211546", + "sha256:28e4e9a97713aa47b5ef9c5003def2eb58aec89781ef3ef82b1c2916a8b0639b", + "sha256:2cfcee8829c5dec55597826d52c26bc26e7ce39adb4771584459d0636b0b7108", + "sha256:33b564196dcd563e309a0b07444e31611368afe3a3822160c046f5e4c3b5cdd7", + "sha256:41f94194ae78f83fd94ca94fb8ad65f92210a76a2421169ffa5c33c3ec7605f4", + "sha256:4f385ee7d39ee1ed74f1d6b1da03d0734ea82855a7b28a9e6e88c4091bc58664", + "sha256:55555d784cfdf9033e81f044c0df04babed2aa141213765d960d233b0139e353", + "sha256:69285f5615507b6625f89ea1048addd1d9218585fb886eb90bdebb1d2b2d26f5", + "sha256:6cb1224da391fa90f1be524eafb375b62baf8d3df9690b32e8cc475ccfccee5e", + "sha256:6fb22f63e17813f3d1d8e30dd1e249e2c34233ba1d3de977fd31cb5db764c7d0", + "sha256:7a2409f1564c84bcf2563d379c9b6148c5bc6b0ae46e109f6a7b4bebadf551df", + "sha256:8487524a1212223ca6dc7e2c8913024618f7ff29855c98869088e3818d5f6733", + "sha256:9a2945efcff84830c8e237ab037d0269380d75d400a89cc9e5628e52647e21be", + "sha256:9a47a80f65f4feaaf8415a40c339806c7d7d867152ddccc6ca87f707c8b7b565", + "sha256:a3c180d12ffb1d8ee5b33a514a5bcb2a9cc06cc89aa74038015591170c82f55d", + "sha256:a5f2c681fd040ed648513939a1dd2242af19bd5e9e79e53b6dcfa33bdae61217", + "sha256:b984523d28737e373c7c35c8b6db6001537609d47534892de189bebebaa42a47", + "sha256:d18df9cf3f3212df28d445ea82ce702c4d7a35817ef7a38ee38879ffa8f7e857", + "sha256:e4d967371c5b6b2e67855066471d844c5d52d210c36c28d49a8507b96e2c5291", + "sha256:ee245f185fae723133511e2450be08a66c2eebb53ad27c0c19b228029f4748a5", + "sha256:fc2208d95d9ecc8032f5e38330d5ace2e3b0b998e42b08c30c35b2ab3a4a3bc8" + ], + "index": "pypi", + "version": "==2.1.4" + }, + "cycler": { + "hashes": [ + "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d", + "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8" + ], + "version": "==0.10.0" + }, + "dnspython": { + "hashes": [ + "sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c", + "sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "ecdsa": { + "hashes": [ + "sha256:40d002cf360d0e035cf2cb985e1308d41aaa087cbfc135b2dc2d844296ea546c", + "sha256:64cf1ee26d1cde3c73c6d7d107f835fed7c6a2904aef9eac223d57ad800c43fa" + ], + "index": "pypi", + "version": "==0.13" + }, + "email-validator": { + "hashes": [ + "sha256:ddc4b5b59fa699bb10127adcf7ad4de78fde4ec539a072b104b8bb16da666ae5" + ], + "index": "pypi", + "version": "==1.0.3" + }, + "geoip2": { + "hashes": [ + "sha256:762e5ea1400d40772249c0778328b6fc82b82503092ceb48c00a7500d7ce4a64", + "sha256:7e119472ca841fb39293272403476c7012fa7127541b2780a9e432a5ebbb4a89" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "geojson": { + "hashes": [ + "sha256:8831556d1ac18805c4955aa881e022e6ddcef44020b315200680eec6dd209e40", + "sha256:c8873d12421665f6731b8e4c3b2484a4516c0d9b286c26cb7cffa781d062301a" + ], + "index": "pypi", + "version": "==2.3.0" + }, + "graphene": { + "hashes": [ + "sha256:0763563fdccb6817311f15d0f0a4d4fe820a46cbe5de022076f95cd99413fd0f", + "sha256:698543001858c908c5e3526ddf38ae1d13049c6c1313ec3bb0afad02c81bd242" + ], + "index": "pypi", + "version": "==2.0.1" + }, + "graphene-sqlalchemy": { + "hashes": [ + "sha256:9a7a8875894671273a88cd4ce2203a71fca88bbe3eb2590f902168a87ea9c1cd", + "sha256:c62f158fc5224698eba973520d620fc0eb98bc0c6b4daad6495961872e9e4dba" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "graphql-core": { + "hashes": [ + "sha256:889e869be5574d02af77baf1f30b5db9ca2959f1c9f5be7b2863ead5a3ec6181", + "sha256:9462e22e32c7f03b667373ec0a84d95fba10e8ce2ead08f29fbddc63b671b0c1" + ], + "version": "==2.1" + }, + "graphql-relay": { + "hashes": [ + "sha256:2716b7245d97091af21abf096fabafac576905096d21ba7118fba722596f65db" + ], + "index": "pypi", + "version": "==0.4.5" + }, + "icalendar": { + "hashes": [ + "sha256:396ad75bc6c7cc23c97c69370a81c001aca06cf99da32e6bb40856ea0bdf7368", + "sha256:682a42023d3d43a3a83933b4e329d109aabb07c9e11cb94a4d83ca687c3a3e8d" + ], + "index": "pypi", + "version": "==4.0.1" + }, + "idna": { + "hashes": [ + "sha256:2c6a5de3089009e3da7c5dde64a141dbc8551d5b7f6cf4ed7c2568d0cc520a8f", + "sha256:8c7309c718f94b3a625cb648ace320157ad16ff131ae0af362c9f21b80ef6ec4" + ], + "version": "==2.6" + }, + "ipaddress": { + "hashes": [ + "sha256:200d8686011d470b5e4de207d803445deee427455cd0cb7c982b68cf82524f81" + ], + "index": "pypi", + "version": "==1.0.19" + }, + "iso8601": { + "hashes": [ + "sha256:210e0134677cc0d02f6028087fee1df1e1d76d372ee1db0bf30bf66c5c1c89a3", + "sha256:49c4b20e1f38aa5cf109ddcd39647ac419f928512c869dc01d5c7098eddede82", + "sha256:bbbae5fb4a7abfe71d4688fd64bff70b91bbd74ef6a99d964bab18f7fdf286dd" + ], + "version": "==0.1.12" + }, + "jinja2": { + "hashes": [ + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" + ], + "index": "pypi", + "version": "==2.10" + }, + "jsonschema": { + "hashes": [ + "sha256:000e68abd33c972a5248544925a0cae7d1125f9bf6c58280d37546b946769a08", + "sha256:6ff5f3180870836cae40f06fa10419f557208175f13ad7bc26caa77beb1f6e02" + ], + "index": "pypi", + "version": "==2.6.0" + }, + "kiwisolver": { + "hashes": [ + "sha256:0ee4ed8b3ae8f5f712b0aa9ebd2858b5b232f1b9a96b0943dceb34df2a223bc3", + "sha256:0f7f532f3c94e99545a29f4c3f05637f4d2713e7fd91b4dd8abfc18340b86cd5", + "sha256:1a078f5dd7e99317098f0e0d490257fd0349d79363e8c923d5bb76428f318421", + "sha256:1aa0b55a0eb1bd3fa82e704f44fb8f16e26702af1a073cc5030eea399e617b56", + "sha256:2874060b91e131ceeff00574b7c2140749c9355817a4ed498e82a4ffa308ecbc", + "sha256:379d97783ba8d2934d52221c833407f20ca287b36d949b4bba6c75274bcf6363", + "sha256:3b791ddf2aefc56382aadc26ea5b352e86a2921e4e85c31c1f770f527eb06ce4", + "sha256:4329008a167fac233e398e8a600d1b91539dc33c5a3eadee84c0d4b04d4494fa", + "sha256:45813e0873bbb679334a161b28cb9606d9665e70561fd6caa8863e279b5e464b", + "sha256:53a5b27e6b5717bdc0125338a822605084054c80f382051fb945d2c0e6899a20", + "sha256:574f24b9805cb1c72d02b9f7749aa0cc0b81aa82571be5201aa1453190390ae5", + "sha256:66f82819ff47fa67a11540da96966fb9245504b7f496034f534b81cacf333861", + "sha256:79e5fe3ccd5144ae80777e12973027bd2f4f5e3ae8eb286cabe787bed9780138", + "sha256:83410258eb886f3456714eea4d4304db3a1fc8624623fc3f38a487ab36c0f653", + "sha256:8b6a7b596ce1d2a6d93c3562f1178ebd3b7bb445b3b0dd33b09f9255e312a965", + "sha256:9576cb63897fbfa69df60f994082c3f4b8e6adb49cccb60efb2a80a208e6f996", + "sha256:95a25d9f3449046ecbe9065be8f8380c03c56081bc5d41fe0fb964aaa30b2195", + "sha256:a424f048bebc4476620e77f3e4d1f282920cef9bc376ba16d0b8fe97eec87cde", + "sha256:aaec1cfd94f4f3e9a25e144d5b0ed1eb8a9596ec36d7318a504d813412563a85", + "sha256:acb673eecbae089ea3be3dcf75bfe45fc8d4dcdc951e27d8691887963cf421c7", + "sha256:b15bc8d2c2848a4a7c04f76c9b3dc3561e95d4dabc6b4f24bfabe5fd81a0b14f", + "sha256:b1c240d565e977d80c0083404c01e4d59c5772c977fae2c483f100567f50847b", + "sha256:c595693de998461bcd49b8d20568c8870b3209b8ea323b2a7b0ea86d85864694", + "sha256:ce3be5d520b4d2c3e5eeb4cd2ef62b9b9ab8ac6b6fedbaa0e39cdb6f50644278", + "sha256:e0f910f84b35c36a3513b96d816e6442ae138862257ae18a0019d2fc67b041dc", + "sha256:ea36e19ac0a483eea239320aef0bd40702404ff8c7e42179a2d9d36c5afcb55c", + "sha256:efabbcd4f406b532206b8801058c8bab9e79645b9880329253ae3322b7b02cd5", + "sha256:f923406e6b32c86309261b8195e24e18b6a8801df0cfc7814ac44017bfcb3939" + ], + "version": "==1.0.1" + }, + "mako": { + "hashes": [ + "sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae" + ], + "version": "==1.0.7" + }, + "markdown": { + "hashes": [ + "sha256:9ba587db9daee7ec761cfc656272be6aabe2ed300fece21208e4aab2e457bc8f", + "sha256:a856869c7ff079ad84a3e19cd87a64998350c2b94e9e08e44270faef33400f81" + ], + "index": "pypi", + "version": "==2.6.11" + }, + "markupsafe": { + "hashes": [ + "sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665" + ], + "index": "pypi", + "version": "==1.0" + }, + "matplotlib": { + "hashes": [ + "sha256:07055eb872fa109bd88f599bdb52065704b2e22d475b67675f345d75d32038a0", + "sha256:0f2f253d6d51f5ed52a819921f8a0a8e054ce0daefcfbc2557e1c433f14dc77d", + "sha256:1770b70622b78f3fd76638526b8c176af8b23a0b129e0190d9407fa403855e0c", + "sha256:1ef9fd285334bd6b0495b6de9d56a39dc95081577f27bafabcf28e0d318bed31", + "sha256:3eb17a4dc45e1ceefb899423e8152e10169fa281f960421c762fd8532186c323", + "sha256:3fb2db66ef98246bafc04b4ef4e9b0e73c6369f38a29716844e939d197df816a", + "sha256:3fd90b407d1ab0dae686a4200030ce305526ff20b85a443dc490d194114b2dfa", + "sha256:45dac8589ef1721d7f2ab0f48f986694494dfcc5d13a3e43a5cb6c816276094e", + "sha256:4bb10087e09629ba3f9b25b6c734fd3f99542f93d71c5b9c023f28cb377b43a9", + "sha256:4dc7ef528aad21f22be85e95725234c5178c0f938e2228ca76640e5e84d8cde8", + "sha256:4f6a516d5ef39128bb16af7457e73dde25c30625c4916d8fbd1cc7c14c55e691", + "sha256:70f0e407fbe9e97f16597223269c849597047421af5eb8b60dbaca0382037e78", + "sha256:7b3d03c876684618e2a2be6abeb8d3a033c3a1bb38a786f751199753ef6227e6", + "sha256:8944d311ce37bee1ba0e41a9b58dcf330ffe0cf29d7654c3d07c572215da68ac", + "sha256:8ff08eaa25c66383fe3b6c7eb288da3c22dcedc4b110a0b592b35f68d0e093b2", + "sha256:9d12378d6a236aa38326e27f3a29427b63edce4ce325745785aec1a7535b1f85", + "sha256:abfd3d9390eb4f2d82cbcaa3a5c2834c581329b64eccb7a071ed9d5df27424f7", + "sha256:bc4d7481f0e8ec94cb1afc4a59905d6274b3b4c389aba7a2539e071766671735", + "sha256:c78883145da9b5620aec523fa14ea814e07f2ba26a16b7f68ff7300604a911b3", + "sha256:dc0ba2080fd0cfdd07b3458ee4324d35806733feb2b080838d7094731d3f73d9", + "sha256:f26fba7fc68994ab2805d77e0695417f9377a00d36ba4248b5d0f1e5adb08d24", + "sha256:f397479742c1ca31805a4bffdfcc5f6189a31116b79aa0b83a50c689f42a23bb" + ], + "index": "pypi", + "version": "==2.2.2" + }, + "maxminddb": { + "hashes": [ + "sha256:df1451bcd848199905ac0de4631b3d02d6a655ad28ba5e5a4ca29a23358db712" + ], + "version": "==1.4.1" + }, + "msgpack-python": { + "hashes": [ + "sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b" + ], + "index": "pypi", + "version": "==0.5.6" + }, + "numpy": { + "hashes": [ + "sha256:07379fe0b450f6fd6e5934a9bc015025bb4ce1c8fbed3ca8bef29328b1bc9570", + "sha256:085afac75bbc97a096744fcfc97a4b321c5a87220286811e85089ae04885acdd", + "sha256:2d6481c6bdab1c75affc0fc71eb1bd4b3ecef620d06f2f60c3f00521d54be04f", + "sha256:2df854df882d322d5c23087a4959e145b953dfff2abe1774fec4f639ac2f3160", + "sha256:381ad13c30cd1d0b2f3da8a0c1a4aa697487e8bb0e9e0cbeb7439776bcb645f8", + "sha256:385f1ce46e08676505b692bfde918c1e0b350963a15ef52d77691c2cf0f5dbf6", + "sha256:4130e5ae16c656b7de654dc5e595cfeb85d3a4b0bb0734d19c0dce6dc7ee0e07", + "sha256:4d278c2261be6423c5e63d8f0ceb1b0c6db3ff83f2906f4b860db6ae99ca1bb5", + "sha256:51c5dcb51cf88b34b7d04c15f600b07c6ccbb73a089a38af2ab83c02862318da", + "sha256:589336ba5199c8061239cf446ee2f2f1fcc0c68e8531ee1382b6fc0c66b2d388", + "sha256:5ae3564cb630e155a650f4f9c054589848e97836bebae5637240a0d8099f817b", + "sha256:5edf1acc827ed139086af95ce4449b7b664f57a8c29eb755411a634be280d9f2", + "sha256:6b82b81c6b3b70ed40bc6d0b71222ebfcd6b6c04a6e7945a936e514b9113d5a3", + "sha256:6c57f973218b776195d0356e556ec932698f3a563e2f640cfca7020086383f50", + "sha256:758d1091a501fd2d75034e55e7e98bfd1370dc089160845c242db1c760d944d9", + "sha256:8622db292b766719810e0cb0f62ef6141e15fe32b04e4eb2959888319e59336b", + "sha256:8b8dcfcd630f1981f0f1e3846fae883376762a0c1b472baa35b145b911683b7b", + "sha256:91fdd510743ae4df862dbd51a4354519dd9fb8941347526cd9c2194b792b3da9", + "sha256:97fa8f1dceffab782069b291e38c4c2227f255cdac5f1e3346666931df87373e", + "sha256:9b705f18b26fb551366ab6347ba9941b62272bf71c6bbcadcd8af94d10535241", + "sha256:9d69967673ab7b028c2df09cae05ba56bf4e39e3cb04ebe452b6035c3b49848e", + "sha256:9e1f53afae865cc32459ad211493cf9e2a3651a7295b7a38654ef3d123808996", + "sha256:a4a433b3a264dbc9aa9c7c241e87c0358a503ea6394f8737df1683c7c9a102ac", + "sha256:baadc5f770917ada556afb7651a68176559f4dca5f4b2d0947cd15b9fb84fb51", + "sha256:c725d11990a9243e6ceffe0ab25a07c46c1cc2c5dc55e305717b5afe856c9608", + "sha256:d696a8c87315a83983fc59dd27efe034292b9e8ad667aeae51a68b4be14690d9", + "sha256:e1864a4e9f93ddb2dc6b62ccc2ec1f8250ff4ac0d3d7a15c8985dd4e1fbd6418", + "sha256:e1d18421a7e2ad4a655b76e65d549d4159f8874c18a417464c1d439ee7ccc7cd" + ], + "index": "pypi", + "version": "==1.14.5" + }, + "paramiko": { + "hashes": [ + "sha256:486f637f0a33a4792e0e567be37426c287efaa8c4c4a45e3216f9ce7fd70b1fc", + "sha256:8851e728e8b7590989e68e3936c48ee3ca4dad91d29e3d7ff0305b6c5fc582db" + ], + "index": "pypi", + "version": "==2.4.0" + }, + "pluginbase": { + "hashes": [ + "sha256:b4f830242a078a4f44c978a84f3365bba4d008fdd71a591c71447f4df35354dd" + ], + "index": "pypi", + "version": "==0.5" + }, + "promise": { + "hashes": [ + "sha256:2ebbfc10b7abf6354403ed785fe4f04b9dfd421eb1a474ac8d187022228332af", + "sha256:348f5f6c3edd4fd47c9cd65aed03ac1b31136d375aa63871a57d3e444c85655c" + ], + "version": "==2.2.1" + }, + "psycopg2": { + "hashes": [ + "sha256:009e0bc09a57dbef4b601cb8b46a2abad51f5274c8be4bba276ff2884cd4cc53", + "sha256:0344b181e1aea37a58c218ccb0f0f771295de9aa25a625ed076e6996c6530f9e", + "sha256:0cd4c848f0e9d805d531e44973c8f48962e20eb7fc0edac3db4f9dbf9ed5ab82", + "sha256:1286dd16d0e46d59fa54582725986704a7a3f3d9aca6c5902a7eceb10c60cb7e", + "sha256:1cf5d84290c771eeecb734abe2c6c3120e9837eb12f99474141a862b9061ac51", + "sha256:207ba4f9125a0a4200691e82d5eee7ea1485708eabe99a07fc7f08696fae62f4", + "sha256:25250867a4cd1510fb755ef9cb38da3065def999d8e92c44e49a39b9b76bc893", + "sha256:2954557393cfc9a5c11a5199c7a78cd9c0c793a047552d27b1636da50d013916", + "sha256:317612d5d0ca4a9f7e42afb2add69b10be360784d21ce4ecfbca19f1f5eadf43", + "sha256:37f54452c7787dbdc0a634ca9773362b91709917f0b365ed14b831f03cbd34ba", + "sha256:40fa5630cd7d237cd93c4d4b64b9e5ed9273d1cfce55241c7f9066f5db70629d", + "sha256:57baf63aeb2965ca4b52613ce78e968b6d2bde700c97f6a7e8c6c236b51ab83e", + "sha256:594aa9a095de16614f703d759e10c018bdffeafce2921b8e80a0e8a0ebbc12e5", + "sha256:5c3213be557d0468f9df8fe2487eaf2990d9799202c5ff5cb8d394d09fad9b2a", + "sha256:697ff63bc5451e0b0db48ad205151123d25683b3754198be7ab5fcb44334e519", + "sha256:6c2f1a76a9ebd9ecf7825b9e20860139ca502c2bf1beabf6accf6c9e66a7e0c3", + "sha256:7a75565181e75ba0b9fb174b58172bf6ea9b4331631cfe7bafff03f3641f5d73", + "sha256:7a9c6c62e6e05df5406e9b5235c31c376a22620ef26715a663cee57083b3c2ea", + "sha256:7c31dade89634807196a6b20ced831fbd5bec8a21c4e458ea950c9102c3aa96f", + "sha256:82c40ea3ac1555e0462803380609fbe8b26f52620f3d4f8eb480cfd8ceed8a14", + "sha256:8f5942a4daf1ffac42109dc4a72f786af4baa4fa702ede1d7c57b4b696c2e7d6", + "sha256:92179bd68c2efe72924a99b6745a9172471931fc296f9bfdf9645b75eebd6344", + "sha256:94e4128ba1ea56f02522fffac65520091a9de3f5c00da31539e085e13db4771b", + "sha256:988d2ec7560d42ef0ac34b3b97aad14c4f068792f00e1524fa1d3749fe4e4b64", + "sha256:9d6266348b15b4a48623bf4d3e50445d8e581da413644f365805b321703d0fac", + "sha256:9d64fed2681552ed642e9c0cc831a9e95ab91de72b47d0cb68b5bf506ba88647", + "sha256:b9358e203168fef7bfe9f430afaed3a2a624717a1d19c7afa7dfcbd76e3cd95c", + "sha256:bf708455cd1e9fa96c05126e89a0c59b200d086c7df7bbafc7d9be769e4149a3", + "sha256:d3ac07240e2304181ffdb13c099840b5eb555efc7be9344503c0c03aa681de79", + "sha256:ddca39cc55877653b5fcf59976d073e3d58c7c406ef54ae8e61ddf8782867182", + "sha256:fc993c9331d91766d54757bbc70231e29d5ceb2d1ac08b1570feaa0c38ab9582" + ], + "index": "pypi", + "version": "==2.7.3.2" + }, + "py-gfm": { + "hashes": [ + "sha256:721f6d7ad340c78c0e6fab37f25a7f8e09757890b210ce79c87612133ec57788", + "sha256:f107e43248eba6236b19ceda78531f40c7bdb85ba4a219f478c92920397f4f98" + ], + "index": "pypi", + "version": "==0.1.3" + }, + "pyasn1": { + "hashes": [ + "sha256:b9d3abc5031e61927c82d4d96c1cec1e55676c1a991623cfed28faea73cdd7ca", + "sha256:f58f2a3d12fd754aa123e9fa74fb7345333000a035f3921dbdaa08597aa53137" + ], + "version": "==0.4.4" + }, + "pycairo": { + "hashes": [ + "sha256:0f0a35ec923d87bc495f6753b1e540fd046d95db56a35250c44089fbce03b698" + ], + "version": "==1.17.1" + }, + "pycparser": { + "hashes": [ + "sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3" + ], + "version": "==2.19" + }, + "pygobject": { + "hashes": [ + "sha256:250fb669b6ac64eba034cc4404fcbcc993717b1f77c29dff29f8c9202da20d55" + ], + "index": "pypi", + "version": "==3.28.3" + }, + "pynacl": { + "hashes": [ + "sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255", + "sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c", + "sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e", + "sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae", + "sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621", + "sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56", + "sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39", + "sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310", + "sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1", + "sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a", + "sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786", + "sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b", + "sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b", + "sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f", + "sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20", + "sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415", + "sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715", + "sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1", + "sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0" + ], + "version": "==1.3.0" + }, + "pyotp": { + "hashes": [ + "sha256:8f0df1fcf9e86cec41f0a31c91212b1a04fca6dd353426917222b21864b9310b", + "sha256:dd9130dd91a0340d89a0f06f887dbd76dd07fb95a8886dc4bc401239f2eebd69" + ], + "index": "pypi", + "version": "==2.2.6" + }, + "pyparsing": { + "hashes": [ + "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", + "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401" + ], + "version": "==2.2.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:3220490fb9741e2342e1cf29a503394fdac874bc39568288717ee67047ff29df", + "sha256:9d8074be4c993fbe4947878ce593052f71dac82932a677d49194d8ce9778002e" + ], + "index": "pypi", + "version": "==2.7.2" + }, + "python-editor": { + "hashes": [ + "sha256:a3c066acee22a1c94f63938341d4fb374e3fdd69366ed6603d7b24bed1efc565" + ], + "version": "==1.0.3" + }, + "python-pam": { + "hashes": [ + "sha256:6306cd996ee35870a4a94bf042c5e428ff34ad454168807ecc89437ef78719ab", + "sha256:a394bb56cfd82451f775429a19857a3c242be84746066939f6db2c18a19a44b6" + ], + "index": "pypi", + "version": "==1.8.3" + }, + "pytz": { + "hashes": [ + "sha256:65ae0c8101309c45772196b21b74c46b2e5d11b6275c45d251b150d5da334555", + "sha256:c06425302f2cf668f1bba7a0a03f3c1d34d4ebeef2c72003da308b3947c7f749" + ], + "index": "pypi", + "version": "==2018.4" + }, + "pyyaml": { + "hashes": [ + "sha256:16b20e970597e051997d90dc2cddc713a2876c47e3d92d59ee198700c5427736", + "sha256:3262c96a1ca437e7e4763e2843746588a965426550f3797a79fca9c6199c431f", + "sha256:592766c6303207a20efc445587778322d7f73b161bd994f227adaa341ba212ab", + "sha256:5ac82e411044fb129bae5cfbeb3ba626acb2af31a8d17d175004b70862a741a7", + "sha256:827dc04b8fa7d07c44de11fabbc888e627fa8293b695e0f99cb544fdfa1bf0d1", + "sha256:bc6bced57f826ca7cb5125a10b23fd0f2fff3b7c4701d64c439a300ce665fff8", + "sha256:c01b880ec30b5a6e6aa67b09a2fe3fb30473008c85cd6a67359a1b15ed6d83a4", + "sha256:e863072cdf4c72eebf179342c94e6989c67185842d9997960b3e69290b2fa269" + ], + "index": "pypi", + "version": "==3.12" + }, + "requests": { + "hashes": [ + "sha256:6a1b267aa90cac58ac3a765d067950e7dbbf75b1da07e895d1f594193a40a38b", + "sha256:9c443e7324ba5b85070c4a818ade28bfabedf16ea10206da1132edaa6dda237e" + ], + "index": "pypi", + "version": "==2.18.4" + }, + "requests-file": { + "hashes": [ + "sha256:75c175eed739270aec3c5279ffd74e6527dada275c5c0d76b5817e9c86bb7dea", + "sha256:8f04aa6201bacda0567e7ac7f677f1499b0fc76b22140c54bc06edf1ba92e2fa" + ], + "index": "pypi", + "version": "==1.4.3" + }, + "rx": { + "hashes": [ + "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23", + "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105" + ], + "version": "==1.6.1" + }, + "singledispatch": { + "hashes": [ + "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c", + "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8" + ], + "version": "==3.4.0.3" + }, + "six": { + "hashes": [ + "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", + "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + ], + "index": "pypi", + "version": "==1.11.0" + }, + "smoke-zephyr": { + "hashes": [ + "sha256:c8f17cd57303cb4c62c8ccf596d4dd921b723f63085521ecc127964f6e86762d" + ], + "index": "pypi", + "version": "==1.2.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:7cb00cc9b9f92ef8b4391c8a2051f81eeafefe32d63c6b395fd51401e9a39edb" + ], + "index": "pypi", + "version": "==1.2.6" + }, + "termcolor": { + "hashes": [ + "sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "tzlocal": { + "hashes": [ + "sha256:4ebeb848845ac898da6519b9b31879cf13b6626f7184c496037b818e238f2c4e" + ], + "index": "pypi", + "version": "==1.5.1" + }, + "urllib3": { + "hashes": [ + "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b", + "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f" + ], + "version": "==1.22" + }, + "websocket-client": { + "hashes": [ + "sha256:728f405ba502e39fbd8a5903ca55161749ee77633caedc7b11222d83b344bb7c", + "sha256:bf36b4b4726cab3bf93e842deef3c5bf12bd9c134e45e9a852c76140309f5ae2" + ], + "index": "pypi", + "version": "==0.49.0" + }, + "xlsxwriter": { + "hashes": [ + "sha256:b51c00a1d6572ba35e8c06e0af8dd14c34977ef0b5022c3d85d5f1de9f1220c3", + "sha256:feaa2699e82a09653e1b47a25f9541194e21f6e78db35bed5c7006f8b59e7a88" + ], + "index": "pypi", + "version": "==0.9.6" + } + }, + "develop": {} +} diff --git a/data/server/service_files/king-phisher.service b/data/server/service_files/king-phisher.service index ddd0cb47..613958a4 100644 --- a/data/server/service_files/king-phisher.service +++ b/data/server/service_files/king-phisher.service @@ -8,6 +8,7 @@ After=syslog.target network.target auditd.service [Service] Type=forking PIDFile=/var/run/king-phisher.pid +WorkingDirectory=/opt/king-phisher # Installed Together In /opt/king-phisher ExecStart=/usr/bin/python3 /opt/king-phisher/KingPhisherServer /opt/king-phisher/server_config.yml ExecStop=/bin/kill -INT $MAINPID diff --git a/king_phisher/client/__main__.py b/king_phisher/client/__main__.py new file mode 100755 index 00000000..b6b9875b --- /dev/null +++ b/king_phisher/client/__main__.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# +# client/__main__.py +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of the project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import argparse +import logging +import os +import sys +import threading +import time + +if getattr(sys, 'frozen', False): + # set the basemap data directory for frozen builds + os.environ['BASEMAPDATA'] = os.path.join(os.path.dirname(sys.executable), 'mpl-basemap-data') + +from king_phisher import startup +from king_phisher import color +from king_phisher import find +from king_phisher import utilities +from king_phisher import version +from king_phisher.client import application +from king_phisher.client import gui_utilities + +from gi.repository import GObject +from gi.repository import Gtk + +def main(): + parser = argparse.ArgumentParser(description='King Phisher Client GUI', conflict_handler='resolve') + utilities.argp_add_args(parser, default_root='KingPhisher') + startup.argp_add_client(parser) + arguments = parser.parse_args() + + # basic runtime checks + if sys.version_info < (3, 4): + color.print_error('the Python version is too old (minimum required is 3.4)') + return 0 + + if Gtk.check_version(3, 14, 0): + color.print_error('the GTK+ version is too old (minimum required is 3.14)') + return 0 + + if sys.platform.startswith('linux') and not os.environ.get('DISPLAY'): + color.print_error('no display was detected, this must be run with an interactive X session') + return 0 + + config_file = arguments.config_file + use_plugins = arguments.use_plugins + use_style = arguments.use_style + del arguments, parser + logger = logging.getLogger('KingPhisher.Client.CLI') + + if sys.platform.startswith('linux') and not os.getuid(): + logger.warning('it is not necessary to run the king phisher client as root') + + find.init_data_path('client') + + if not gui_utilities.which_glade(): + color.print_error('unable to locate the glade ui data file') + return 0 + + logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2])) + logger.debug("client running in process: {0} main tid: 0x{1:x}".format(os.getpid(), threading.current_thread().ident)) + + start_time = time.time() + logger.debug('using ui data from glade file: ' + gui_utilities.which_glade()) + try: + app = application.KingPhisherClientApplication(config_file=config_file, use_plugins=use_plugins, use_style=use_style) + except Exception as error: + logger.critical("initialization error: {0} ({1})".format(error.__class__.__name__, getattr(error, 'message', 'n/a'))) + color.print_error('failed to initialize the King Phisher client') + return 0 + logger.debug("client loaded in {0:.2f} seconds".format(time.time() - start_time)) + + GObject.threads_init() + return app.run([]) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/king_phisher/server/__main__.py b/king_phisher/server/__main__.py new file mode 100755 index 00000000..a69a1f04 --- /dev/null +++ b/king_phisher/server/__main__.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# server/__main__.py +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of the project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# pylint: disable=too-many-locals + +import argparse +import logging +import os +import pwd +import signal +import sys +import threading + +from king_phisher import startup +from king_phisher import color +from king_phisher import constants +from king_phisher import errors +from king_phisher import find +from king_phisher import geoip +from king_phisher import its +from king_phisher import utilities +from king_phisher import version +from king_phisher.server import build +from king_phisher.server import configuration +from king_phisher.server import plugins + +from boltons import strutils + +logger = logging.getLogger('KingPhisher.Server.CLI') + +def build_and_run(arguments, config, plugin_manager, log_file=None): + # fork into the background + should_fork = True + if arguments.foreground: + should_fork = False + elif config.has_option('server.fork'): + should_fork = bool(config.get('server.fork')) + if should_fork: + if os.fork(): + return sys.exit(os.EX_OK) + os.setsid() + + try: + king_phisher_server = build.server_from_config(config, plugin_manager=plugin_manager) + except errors.KingPhisherDatabaseAuthenticationError: + logger.critical('failed to authenticate to the database, this usually means the password is incorrect and needs to be updated') + return os.EX_SOFTWARE + except errors.KingPhisherError as error: + logger.critical('server failed to build with error: ' + error.message) + return os.EX_SOFTWARE + + server_pid = os.getpid() + logger.info("server running in process: {0} main tid: 0x{1:x}".format(server_pid, threading.current_thread().ident)) + + if should_fork and config.has_option('server.pid_file'): + pid_file = open(config.get('server.pid_file'), 'w') + pid_file.write(str(server_pid)) + pid_file.close() + + if config.has_option('server.setuid_username'): + setuid_username = config.get('server.setuid_username') + try: + user_info = pwd.getpwnam(setuid_username) + except KeyError: + logger.critical('an invalid username was specified as \'server.setuid_username\'') + king_phisher_server.shutdown() + return os.EX_NOUSER + if log_file is not None: + os.chown(log_file, user_info.pw_uid, user_info.pw_gid) + os.setgroups([]) + os.setresgid(user_info.pw_gid, user_info.pw_gid, user_info.pw_gid) + os.setresuid(user_info.pw_uid, user_info.pw_uid, user_info.pw_uid) + logger.info("dropped privileges to the {0} account".format(setuid_username)) + else: + logger.warning('running with root privileges is dangerous, drop them by configuring \'server.setuid_username\'') + os.umask(0o077) + + db_engine_url = king_phisher_server.database_engine.url + if db_engine_url.drivername == 'sqlite': + logger.warning('sqlite is no longer fully supported, see https://github.com/securestate/king-phisher/wiki/Database#sqlite for more details') + database_dir = os.path.dirname(db_engine_url.database) + if not os.access(database_dir, os.W_OK): + logger.critical('sqlite requires write permissions to the folder containing the database') + king_phisher_server.shutdown() + return os.EX_NOPERM + sighup_handler = lambda: threading.Thread(target=king_phisher_server.shutdown).start() + signal.signal(signal.SIGHUP, lambda signum, frame: sighup_handler()) + try: + king_phisher_server.serve_forever(fork=False) + except KeyboardInterrupt: + pass + king_phisher_server.shutdown() + return os.EX_OK + +def _ex_config_logging(arguments, config, console_handler): + """ + If a setting is configured improperly, this will terminate execution via + :py:func:`sys.exit`. + + :return: The path to a log file if one is in use. + :rtype: str + """ + default_log_level = min( + getattr(logging, (arguments.loglvl or constants.DEFAULT_LOG_LEVEL)), + getattr(logging, config.get_if_exists('logging.level', 'critical').upper()) + ) + log_levels = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL') + file_path = None + if config.has_option('logging.file'): + options = config.get('logging.file') + for _ in range(1): + default_format = '%(asctime)s %(name)-50s %(levelname)-8s %(message)s' + if isinstance(options, dict): # new style + if not options.get('enabled', True): + break + if 'path' not in options: + color.print_error('logging.file is missing required key \'path\'') + sys.exit(os.EX_CONFIG) + if 'level' not in options: + color.print_error('logging.file is missing required key \'level\'') + sys.exit(os.EX_CONFIG) + file_path = options['path'] + formatter = logging.Formatter(options.get('format', default_format)) + if not options['level'].upper() in log_levels: + color.print_error('logging.file.level is invalid, must be one of: ' + ', '.join(log_levels)) + sys.exit(os.EX_CONFIG) + log_level = getattr(logging, options['level'].upper()) + root = options.get('root', '') + elif isinstance(options, str): # old style + file_path = options + formatter = logging.Formatter(default_format) + log_level = default_log_level + root = '' + else: + break + file_handler = logging.FileHandler(file_path) + file_handler.setFormatter(formatter) + logging.getLogger(root).addHandler(file_handler) + file_handler.setLevel(log_level) + + if config.has_option('logging.console'): + options = config.get('logging.console') + for _ in range(1): + if isinstance(options, dict): # new style + if not options.get('enabled', True): + break + if 'format' in options: + console_handler.setFormatter(color.ColoredLogFormatter(options['format'])) + + if arguments.loglvl is None and 'level' in options: + log_level = str(options.get('level', '')).upper() + if log_level not in log_levels: + color.print_error('logging.console.level is invalid, must be one of: ' + ', '.join(log_levels)) + sys.exit(os.EX_CONFIG) + console_handler.setLevel(getattr(logging, log_level)) + elif isinstance(options, str): # old style + console_handler.setLevel(default_log_level) + return file_path + +def main(): + parser = argparse.ArgumentParser(description='King Phisher Server', conflict_handler='resolve') + utilities.argp_add_args(parser) + startup.argp_add_server(parser) + arguments = parser.parse_args() + + # basic runtime checks + if sys.version_info < (3, 4): + color.print_error('the Python version is too old (minimum required is 3.4)') + return 0 + + console_log_handler = utilities.configure_stream_logger(arguments.logger, arguments.loglvl) + del parser + + if os.getuid(): + color.print_error('the server must be started as root, configure the') + color.print_error('\'server.setuid_username\' option in the config file to drop privileges') + return os.EX_NOPERM + + # configure environment variables and load the config + find.init_data_path('server') + config = configuration.ex_load_config(arguments.config_file) + if arguments.verify_config: + color.print_good('configuration verification passed') + color.print_good('all required settings are present') + return os.EX_OK + if config.has_option('server.data_path'): + find.data_path_append(config.get('server.data_path')) + + if arguments.update_geoip_db: + color.print_status('downloading a new geoip database') + size = geoip.download_geolite2_city_db(config.get('server.geoip.database')) + color.print_good("download complete, file size: {0}".format(strutils.bytes2human(size))) + return os.EX_OK + + # setup logging based on the configuration + if config.has_section('logging'): + log_file = _ex_config_logging(arguments, config, console_log_handler) + logger.debug("king phisher version: {0} python version: {1}.{2}.{3}".format(version.version, sys.version_info[0], sys.version_info[1], sys.version_info[2])) + + # initialize the plugin manager + try: + plugin_manager = plugins.ServerPluginManager(config) + except errors.KingPhisherError as error: + if isinstance(error, errors.KingPhisherPluginError): + color.print_error("plugin error: {0} ({1})".format(error.plugin_name, error.message)) + else: + color.print_error(error.message) + return os.EX_SOFTWARE + + status_code = build_and_run(arguments, config, plugin_manager, log_file) + plugin_manager.shutdown() + logging.shutdown() + return status_code + +if __name__ == '__main__': + sys.exit(main()) diff --git a/king_phisher/startup.py b/king_phisher/startup.py new file mode 100644 index 00000000..5815ddde --- /dev/null +++ b/king_phisher/startup.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# king_phisher/startup.py +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of the project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import gc +import subprocess + +from king_phisher import version + +def run_process(process_args, cwd=os.getcwd()): + process_handle = subprocess.Popen(process_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) + process_handle.wait() + return process_handle.stdout.read().decode('utf-8'), process_handle.returncode + +def which(program): + is_exe = lambda fpath: (os.path.isfile(fpath) and os.access(fpath, os.X_OK)) + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + if is_exe(program): + return os.path.abspath(program) + return None + +def argb_add_default_args(parser, default_root=''): + """ + Add standard arguments to a new :py:class:`argparse.ArgumentParser` instance + Used to add the utilities argparse options to the wrapper for display + + :param parser: The parser to add arguments to. + :type parser: :py:class:`argparse.ArgumentParser` + :param str default_root: The default root logger to specify. + """ + parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + version.version) + parser.add_argument('-L', '--log', dest='loglvl', choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL'), help='set the logging level') + parser.add_argument('--logger', default=default_root, help='specify the root logger') + gc_group = parser.add_argument_group('garbage collector options') + gc_group.add_argument('--gc-debug-leak', action='store_const', const=gc.DEBUG_LEAK, default=0, help='set the DEBUG_LEAK flag') + gc_group.add_argument('--gc-debug-stats', action='store_const', const=gc.DEBUG_STATS, default=0, help='set the DEBUG_STATS flag') + return parser + +def argp_add_client(parser): + kpc_group = parser.add_argument_group('King Phisher Client') + kpc_group.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') + kpc_group.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') + kpc_group.add_argument('--no-style', dest='use_style', default=True, action='store_false', help='disable interface styling') + return parser + +def argp_add_server(parser): + kps_group = parser.add_argument_group('King Phisher Server') + kps_group.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') + kps_group.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') + kps_group.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') + kps_group.add_argument('config_file', action='store', help='configuration file to use') + return parser + +def argp_add_wrapper(parser): + kpw_group = parser.add_argument_group('King Phisher pipenv wrapper') + kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirments and exit') + kpw_group.add_argument('--env-install', dest='pipenv_install', default=False, action='store_true', help='install pipenv environment and exit') + return parser diff --git a/king_phisher/utilities.py b/king_phisher/utilities.py index 49301f21..1bf641ce 100644 --- a/king_phisher/utilities.py +++ b/king_phisher/utilities.py @@ -52,7 +52,7 @@ from king_phisher import constants from king_phisher import find from king_phisher import its -from king_phisher import version +from king_phisher import startup import dateutil import dateutil.tz @@ -177,12 +177,7 @@ def argp_add_args(parser, default_root=''): :type parser: :py:class:`argparse.ArgumentParser` :param str default_root: The default root logger to specify. """ - parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + version.version) - parser.add_argument('-L', '--log', dest='loglvl', choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL'), help='set the logging level') - parser.add_argument('--logger', default=default_root, help='specify the root logger') - gc_group = parser.add_argument_group('garbage collector options') - gc_group.add_argument('--gc-debug-leak', action='store_const', const=gc.DEBUG_LEAK, default=0, help='set the DEBUG_LEAK flag') - gc_group.add_argument('--gc-debug-stats', action='store_const', const=gc.DEBUG_STATS, default=0, help='set the DEBUG_STATS flag') + startup.argb_add_default_args(parser, default_root='') @functools.wraps(parser.parse_args) def parse_args_hook(*args, **kwargs): diff --git a/king_phisher/version.py b/king_phisher/version.py index eac85153..0748d323 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -34,7 +34,7 @@ import os import subprocess -import smoke_zephyr.utilities +from king_phisher import startup def get_revision(): """ @@ -44,7 +44,7 @@ def get_revision(): :return: The git revision tag if it's available. :rtype: str """ - git_bin = smoke_zephyr.utilities.which('git') + git_bin = startup.which('git') if not git_bin: return None proc_h = subprocess.Popen( diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 37d67162..00000000 --- a/requirements.txt +++ /dev/null @@ -1,39 +0,0 @@ -advancedhttpserver>=2.0.11 -alembic>=0.9.7 # dpkg kali -blinker>=1.4 # dpkg kali ubuntu -boltons>=18.0.0 -cryptography>=2.1.4 # dpkg ubuntu -dnspython>=1.15.0 -ecdsa>=0.13 -email-validator>=1.0.3 -geoip2>=2.8.0 -geojson>=2.3.0 -graphene==2.0.1 -graphene-sqlalchemy==2.0.0 -graphql-relay==0.4.5 -icalendar>=4.0.1 -ipaddress>=1.0.19 ; python_version < '3.3' -jsonschema>=2.6.0 -jinja2>=2.10 -markdown==2.6.11 -markupsafe>=1.0 -matplotlib>=2.2.2 -msgpack-python>=0.5.6 -paramiko>=2.4.0 # dpkg kali -pluginbase>=0.5 -psycopg2>=2.7.3.2,<2.8 --no-binary psycopg2 -py-gfm>=0.1.3 -pyotp>=2.2.6 -python-dateutil>=2.7.2 -python-pam>=1.8.3 -pytz>=2018.4 -PyYAML>=3.12 -requests>=2.18.4 -requests-file>=1.4.3 -six>=1.11.0 -smoke-zephyr>=1.2.0 -SQLAlchemy>=1.2.6 -termcolor>=1.1.0 -tzlocal>=1.5.1 -websocket-client==0.49.0 -XlsxWriter>=0.9.6 # dpkg kali diff --git a/tools/development/pipenv_wrapper b/tools/development/pipenv_wrapper new file mode 100755 index 00000000..e7077582 --- /dev/null +++ b/tools/development/pipenv_wrapper @@ -0,0 +1,138 @@ +#!/usr/bin/python3 -B +# -*- coding: utf-8 -*- +# +# KingPhisherVENV.py +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following disclaimer +# in the documentation and/or other materials provided with the +# distribution. +# * Neither the name of the project nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +import argparse +import gc +import logging +import os +import sys +import subprocess + +from king_phisher import version + +def which(program): + is_exe = lambda fpath: (os.path.isfile(fpath) and os.access(fpath, os.X_OK)) + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + if is_exe(program): + return os.path.abspath(program) + return None + +def argp_add_args(parser, default_root=''): + """ + Add standard arguments to a new :py:class:`argparse.ArgumentParser` instance + Used to add the utilities argparse options to the wrapper for display + + :param parser: The parser to add arguments to. + :type parser: :py:class:`argparse.ArgumentParser` + :param str default_root: The default root logger to specify. + """ + parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + version.version) + parser.add_argument('-L', '--log', dest='loglvl', choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL'), help='set the logging level') + parser.add_argument('--logger', default=default_root, help='specify the root logger') + gc_group = parser.add_argument_group('garbage collector options') + gc_group.add_argument('--gc-debug-leak', action='store_const', const=gc.DEBUG_LEAK, default=0, help='set the DEBUG_LEAK flag') + gc_group.add_argument('--gc-debug-stats', action='store_const', const=gc.DEBUG_STATS, default=0, help='set the DEBUG_STATS flag') + + return parser + +def main(): + parser = argparse.ArgumentParser(description='King Phisher pipenv wrapper', conflict_handler='resolve') + parser.add_argument('--env_update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirments and exit') + parser.add_argument('--env_install', dest='pipenv_install', default=False, action='store_true', help='install pipenv enviroment and exit') + if os.path.abspath(__file__).split(os.sep)[-1] == 'KingPhisher': + kpc_group = parser.add_argument_group('King Phisher client') + kpc_group.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') + kpc_group.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') + kpc_group.add_argument('--no-style', dest='use_style', default=True, action='store_false', help='disable interface styling') + if os.path.abspath(__file__).split(os.sep)[-1] == 'KingPhisherServer': + kps_group = parser.add_argument_group('King Phisher Server') + kps_group.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') + kps_group.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') + kps_group.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') + kps_group.add_argument('config_file', action='store', help='configuration file to use') + argp_add_args(parser) + + arguments, unknown_args = parser.parse_known_args() + sys_argv = sys.argv + sys_argv.pop(0) + + if sys.version_info < (3, 4): + print('the Python version is too old (minimum required is 3.4)') + return 0 + logger = logging.getLogger('KingPhisher.wrapper') + logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler = logging.StreamHandler() + console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) + logger.addHandler(console_log_handler) + + # Default logging stuff honor logging level being passed in + # set target folder for pipenv virual enviroment and set pipenv environment up + target_directory = os.path.abspath(os.path.dirname(__file__)) + logger.debug("target diretory: {}".format(target_directory)) + os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') + os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', str(os.path.join(target_directory, 'Pipfile'))) + os.chdir(target_directory) + logger.info('checking for pipenv for environment') + if not which('pipenv'): + logger.info('pipenv not found installing') + subprocess.call(['python3', '-m', 'pip', 'install', 'pipenv']) + pipenv_path = which('pipenv') + + if arguments.pipenv_install: + logger.warning('installing pipenv environment') + subprocess.call([pipenv_path, '--site-packages', 'install']) + logger.info('pipenv environment installation complete exiting') + return 0 + + if arguments.pipenv_update: + logger.info('updating pipenv environment') + subprocess.call([pipenv_path, 'update']) + logger.info('pipenv environment updated') + return 0 + + if not os.path.isdir(os.path.join(target_directory, '.venv')): + logger.info('no pipenv environment found') + subprocess.call([pipenv_path, '--site-packages', 'install']) + logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + passing_argv = [' ', 'run', os.path.abspath(__file__).split(os.sep)[-1]] + sys_argv + logger.warning('passing_argv: {}'.format(passing_argv)) + logger.debug('starting king phisher with pipenv') + + os.execve(pipenv_path, passing_argv, os.environ) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/install.sh b/tools/install.sh index d5c0eb91..a1768b87 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -337,7 +337,7 @@ elif [ "$LINUX_VERSION" == "Fedora" ]; then libpng-devel postgresql-devel python3-devel python3-pip \ libffi-devel openssl-devel if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then - dnf install -y geos geos-devel gtksourceview3 vte3 + dnf install -y geos geos-devel gtksourceview3 vte3 gobject-introspection-devel fi if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then dnf install -y postgresql-server @@ -353,7 +353,7 @@ elif [ "$LINUX_VERSION" == "BackBox" ] || \ apt-get install -y libfreetype6-dev python3-dev python3-pip pkg-config if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then if ! apt-get install -y gir1.2-gtk-3.0 gir1.2-gtksource-3.0 \ - gir1.2-webkit-3.0 python3-cairo libgeos++-dev \ + gir1.2-webkit-3.0 python3-cairo libgeos++-dev libgirepository1.0-dev \ libgtk-3-dev libpq-dev python3-gi python3-gi-cairo libpq-dev; then echo "ERROR: Failed to install dependencies with apt-get" exit @@ -391,7 +391,8 @@ elif [ "$LINUX_VERSION" == "Arch" ]; then pacman --noconfirm -S freetype2 python python-pip pkg-config if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then if ! pacman --noconfirm -S gobject-introspection python-cairo geos gtk3 \ - gtksourceview3 webkit2gtk vte3 postgresql-libs python-gobject; then + gtksourceview3 webkit2gtk vte3 postgresql-libs \ + python-gobject gobject-introspection; then echo "ERROR: Failed to install dependencies with pacman" exit fi @@ -412,18 +413,10 @@ fi python3 -m pip install --upgrade setuptools python3 -m pip install --upgrade six +python3 -m pip install pipenv -if [ "$LINUX_VERSION" == "Kali" ]; then - if ! python3 -m pip install -I -r requirements.txt; then - echo "ERROR: Failed to install python requirements with pip" - exit $E_SOFTWARE - fi -else - if ! python3 -m pip install -I -r requirements.txt; then - echo "ERROR: Failed to install python requirements with pip" - exit $E_SOFTWARE - fi -fi +cd $KING_PHISHER_DIR +./KingPhisher --env-install if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then DESKTOP_APPLICATIONS_DIR="" @@ -444,14 +437,6 @@ if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then gtk-update-icon-cache --force /usr/share/icons/hicolor fi fi - # try to install basemap directly from it's sourceforge tarball - if python3 -m pip install https://github.com/matplotlib/basemap/archive/v1.1.0.tar.gz &> /dev/null; then - echo "INFO: Successfully installed basemap with pip" - else - echo "WARNING: Failed to install basemap with pip, this is not a required dependency" - echo "WARNING: for King Phisher. See https://bit.ly/kp-pip-basemaps for more" - echo "WARNING: information." - fi fi if [ -z "$KING_PHISHER_SKIP_SERVER" ]; then From 18591d98cb8ddf2ffbfc1b12c999a4415172dd06 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 16 Oct 2018 10:24:07 -0400 Subject: [PATCH 28/49] pipenv updates --- KingPhisher | 12 +-- KingPhisherServer | 12 +-- king_phisher/client/__main__.py | 2 +- king_phisher/server/__main__.py | 3 +- king_phisher/startup.py | 4 +- king_phisher/utilities.py | 2 +- tools/development/pipenv_wrapper | 138 ------------------------------- 7 files changed, 17 insertions(+), 156 deletions(-) delete mode 100755 tools/development/pipenv_wrapper diff --git a/KingPhisher b/KingPhisher index 6c92e071..3b9ae8cb 100755 --- a/KingPhisher +++ b/KingPhisher @@ -33,7 +33,6 @@ import argparse import logging import os -import subprocess import sys from king_phisher import startup @@ -42,7 +41,7 @@ def main(): parser = argparse.ArgumentParser(description='King Phisher Client GUI', conflict_handler='resolve') startup.argp_add_wrapper(parser) startup.argp_add_client(parser) - startup.argb_add_default_args(parser) + startup.argp_add_default_args(parser) arguments, unknown_args = parser.parse_known_args() sys_argv = sys.argv @@ -68,7 +67,7 @@ def main(): process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) if return_code: logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) - return 0 + return return_code pipenv_path = startup.which('pipenv') logger.debug('pipenv path: {}'.format(pipenv_path)) if not pipenv_path: @@ -80,7 +79,7 @@ def main(): process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return 0 + return return_code return 0 if arguments.pipenv_update: @@ -88,7 +87,7 @@ def main(): process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) - return 0 + return return_code logger.info('pipenv environment updated') return 0 @@ -97,8 +96,9 @@ def main(): process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return 0 + return return_code logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + # the blank arg being passed is required for pipenv passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv os.execve(pipenv_path, passing_argv, os.environ) diff --git a/KingPhisherServer b/KingPhisherServer index 549cb061..4f4bc06e 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -33,7 +33,6 @@ import argparse import logging import os -import subprocess import sys from king_phisher import startup @@ -42,7 +41,7 @@ def main(): parser = argparse.ArgumentParser(description='King Phisher Server', conflict_handler='resolve') startup.argp_add_wrapper(parser) startup.argp_add_server(parser) - startup.argb_add_default_args(parser) + startup.argp_add_default_args(parser) arguments, unknown_args = parser.parse_known_args() sys_argv = sys.argv @@ -67,7 +66,7 @@ def main(): process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) if return_code: logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) - return 0 + return return_code pipenv_path = startup.which('pipenv') logger.debug('pipenv path: {}'.format(pipenv_path)) @@ -76,14 +75,14 @@ def main(): process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return 0 + return return_code return 0 if arguments.pipenv_update: process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) - return 0 + return return_code logger.info('pipenv environment successfully updated') return 0 @@ -92,8 +91,9 @@ def main(): process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) if return_code: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return 0 + return return_code logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + # the blank arg being passed is required for pipenv passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv os.execve(pipenv_path, passing_argv, os.environ) diff --git a/king_phisher/client/__main__.py b/king_phisher/client/__main__.py index b6b9875b..7f4ce14c 100755 --- a/king_phisher/client/__main__.py +++ b/king_phisher/client/__main__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# client/__main__.py +# king_phisher/client/__main__.py # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are diff --git a/king_phisher/server/__main__.py b/king_phisher/server/__main__.py index a69a1f04..b4d9604c 100755 --- a/king_phisher/server/__main__.py +++ b/king_phisher/server/__main__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# server/__main__.py +# king_phisher/server/__main__.py # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are @@ -44,7 +44,6 @@ from king_phisher import errors from king_phisher import find from king_phisher import geoip -from king_phisher import its from king_phisher import utilities from king_phisher import version from king_phisher.server import build diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 5815ddde..4a934238 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -51,7 +51,7 @@ def which(program): return os.path.abspath(program) return None -def argb_add_default_args(parser, default_root=''): +def argp_add_default_args(parser, default_root=''): """ Add standard arguments to a new :py:class:`argparse.ArgumentParser` instance Used to add the utilities argparse options to the wrapper for display @@ -85,6 +85,6 @@ def argp_add_server(parser): def argp_add_wrapper(parser): kpw_group = parser.add_argument_group('King Phisher pipenv wrapper') - kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirments and exit') + kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirements and exit') kpw_group.add_argument('--env-install', dest='pipenv_install', default=False, action='store_true', help='install pipenv environment and exit') return parser diff --git a/king_phisher/utilities.py b/king_phisher/utilities.py index 1bf641ce..da2ee2da 100644 --- a/king_phisher/utilities.py +++ b/king_phisher/utilities.py @@ -177,7 +177,7 @@ def argp_add_args(parser, default_root=''): :type parser: :py:class:`argparse.ArgumentParser` :param str default_root: The default root logger to specify. """ - startup.argb_add_default_args(parser, default_root='') + startup.argp_add_default_args(parser, default_root='') @functools.wraps(parser.parse_args) def parse_args_hook(*args, **kwargs): diff --git a/tools/development/pipenv_wrapper b/tools/development/pipenv_wrapper deleted file mode 100755 index e7077582..00000000 --- a/tools/development/pipenv_wrapper +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python3 -B -# -*- coding: utf-8 -*- -# -# KingPhisherVENV.py -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are -# met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of the project nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# - -import argparse -import gc -import logging -import os -import sys -import subprocess - -from king_phisher import version - -def which(program): - is_exe = lambda fpath: (os.path.isfile(fpath) and os.access(fpath, os.X_OK)) - for path in os.environ['PATH'].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - if is_exe(program): - return os.path.abspath(program) - return None - -def argp_add_args(parser, default_root=''): - """ - Add standard arguments to a new :py:class:`argparse.ArgumentParser` instance - Used to add the utilities argparse options to the wrapper for display - - :param parser: The parser to add arguments to. - :type parser: :py:class:`argparse.ArgumentParser` - :param str default_root: The default root logger to specify. - """ - parser.add_argument('-v', '--version', action='version', version=parser.prog + ' Version: ' + version.version) - parser.add_argument('-L', '--log', dest='loglvl', choices=('DEBUG', 'INFO', 'WARNING', 'ERROR', 'FATAL'), help='set the logging level') - parser.add_argument('--logger', default=default_root, help='specify the root logger') - gc_group = parser.add_argument_group('garbage collector options') - gc_group.add_argument('--gc-debug-leak', action='store_const', const=gc.DEBUG_LEAK, default=0, help='set the DEBUG_LEAK flag') - gc_group.add_argument('--gc-debug-stats', action='store_const', const=gc.DEBUG_STATS, default=0, help='set the DEBUG_STATS flag') - - return parser - -def main(): - parser = argparse.ArgumentParser(description='King Phisher pipenv wrapper', conflict_handler='resolve') - parser.add_argument('--env_update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirments and exit') - parser.add_argument('--env_install', dest='pipenv_install', default=False, action='store_true', help='install pipenv enviroment and exit') - if os.path.abspath(__file__).split(os.sep)[-1] == 'KingPhisher': - kpc_group = parser.add_argument_group('King Phisher client') - kpc_group.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') - kpc_group.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') - kpc_group.add_argument('--no-style', dest='use_style', default=True, action='store_false', help='disable interface styling') - if os.path.abspath(__file__).split(os.sep)[-1] == 'KingPhisherServer': - kps_group = parser.add_argument_group('King Phisher Server') - kps_group.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') - kps_group.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') - kps_group.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') - kps_group.add_argument('config_file', action='store', help='configuration file to use') - argp_add_args(parser) - - arguments, unknown_args = parser.parse_known_args() - sys_argv = sys.argv - sys_argv.pop(0) - - if sys.version_info < (3, 4): - print('the Python version is too old (minimum required is 3.4)') - return 0 - logger = logging.getLogger('KingPhisher.wrapper') - logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler = logging.StreamHandler() - console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) - logger.addHandler(console_log_handler) - - # Default logging stuff honor logging level being passed in - # set target folder for pipenv virual enviroment and set pipenv environment up - target_directory = os.path.abspath(os.path.dirname(__file__)) - logger.debug("target diretory: {}".format(target_directory)) - os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') - os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', str(os.path.join(target_directory, 'Pipfile'))) - os.chdir(target_directory) - logger.info('checking for pipenv for environment') - if not which('pipenv'): - logger.info('pipenv not found installing') - subprocess.call(['python3', '-m', 'pip', 'install', 'pipenv']) - pipenv_path = which('pipenv') - - if arguments.pipenv_install: - logger.warning('installing pipenv environment') - subprocess.call([pipenv_path, '--site-packages', 'install']) - logger.info('pipenv environment installation complete exiting') - return 0 - - if arguments.pipenv_update: - logger.info('updating pipenv environment') - subprocess.call([pipenv_path, 'update']) - logger.info('pipenv environment updated') - return 0 - - if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.info('no pipenv environment found') - subprocess.call([pipenv_path, '--site-packages', 'install']) - logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) - passing_argv = [' ', 'run', os.path.abspath(__file__).split(os.sep)[-1]] + sys_argv - logger.warning('passing_argv: {}'.format(passing_argv)) - logger.debug('starting king phisher with pipenv') - - os.execve(pipenv_path, passing_argv, os.environ) - -if __name__ == '__main__': - sys.exit(main()) From 2605856e31dbe64ea64b1df7f3404d2316cc8c4f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 16 Oct 2018 16:53:34 -0400 Subject: [PATCH 29/49] Cleanups and a bug fix for PR #325 --- .gitignore | 1 + KingPhisher | 52 ++++++++++++++++++++------------------ KingPhisherServer | 46 ++++++++++++++++++--------------- docs/source/change_log.rst | 3 +++ king_phisher/startup.py | 39 ++++++++++++++++++++++------ tools/install.sh | 2 +- 6 files changed, 90 insertions(+), 53 deletions(-) diff --git a/.gitignore b/.gitignore index 55fb51b9..0d1c7cfb 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .pylintrc .python-version +.venv/* build/* configs/* dist/* diff --git a/KingPhisher b/KingPhisher index 3b9ae8cb..97cd27da 100755 --- a/KingPhisher +++ b/KingPhisher @@ -33,14 +33,15 @@ import argparse import logging import os +import shutil import sys from king_phisher import startup def main(): - parser = argparse.ArgumentParser(description='King Phisher Client GUI', conflict_handler='resolve') - startup.argp_add_wrapper(parser) + parser = argparse.ArgumentParser(description='King Phisher Client', conflict_handler='resolve') startup.argp_add_client(parser) + startup.argp_add_wrapper(parser) startup.argp_add_default_args(parser) arguments, unknown_args = parser.parse_known_args() @@ -61,42 +62,45 @@ def main(): logger.debug("target diretory: {}".format(target_directory)) os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) - logger.info('checking for pipenv for environment') + logger.info('checking for the pipenv environment') if not startup.which('pipenv'): logger.info('installing pipenv') - process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) - if return_code: - logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) - return return_code + results = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) + if results.status: + sys.stderr.write('the following issue occurred during installation of pipenv:\n') + sys.stderr.write(results.stdout) + return results.status pipenv_path = startup.which('pipenv') - logger.debug('pipenv path: {}'.format(pipenv_path)) + logger.debug("pipenv path: {0!r}".format(pipenv_path)) if not pipenv_path: - logger.exception("failed to find pipenv") + logger.exception('failed to find pipenv') return 0 if arguments.pipenv_install: - logger.info("installing King Phisher's pipenv environment") - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if return_code: + logger.info('installing the pipenv environment') + results = startup.run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if results.status: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return return_code + return results.status return 0 if arguments.pipenv_update: - logger.info("updating King Phisher's pipenv") - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) - if return_code: - logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) - return return_code - logger.info('pipenv environment updated') + logger.info('updating the pipenv environment') + results = startup.run_pipenv(('--site-packages', 'update'), cwd=target_directory) + if results.status: + logger.error('failed to update the pipenv environment') + return results.status + logger.info('the pipenv environment has been updated') return 0 if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.info("installing King Phisher's pipenv environment") - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if return_code: - logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return return_code + logger.warning('no pre-existing pipenv environment was found, installing it now') + results = startup.run_pipenv(('--site-packages', 'install'), cwd=target_directory) + if results.status: + logger.error('failed to install the pipenv environment') + logger.info('removing the incomplete .venv directory') + shutil.rmtree(os.path.join(target_directory, '.venv')) + return results.status logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) # the blank arg being passed is required for pipenv passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv diff --git a/KingPhisherServer b/KingPhisherServer index 4f4bc06e..9de55d65 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -39,8 +39,8 @@ from king_phisher import startup def main(): parser = argparse.ArgumentParser(description='King Phisher Server', conflict_handler='resolve') - startup.argp_add_wrapper(parser) startup.argp_add_server(parser) + startup.argp_add_wrapper(parser) startup.argp_add_default_args(parser) arguments, unknown_args = parser.parse_known_args() @@ -61,37 +61,43 @@ def main(): logger.debug("target diretory: {}".format(target_directory)) os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) + logger.info('checking for the pipenv environment') if not startup.which('pipenv'): logger.info('installing pipenv') - process_output, return_code = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) - if return_code: - logger.warning("the following issue occurred during installation of pipenv: {}".format(process_output)) - return return_code + results = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) + if results.status: + sys.stderr.write('the following issue occurred during installation of pipenv:\n') + sys.stderr.write(results.stdout) + return results.status pipenv_path = startup.which('pipenv') - logger.debug('pipenv path: {}'.format(pipenv_path)) + logger.debug("pipenv path: {0!r}".format(pipenv_path)) + if not pipenv_path: + logger.exception('failed to find pipenv') + return 0 if arguments.pipenv_install: - logger.info('installing pipenv environment') - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if return_code: + logger.info('installing the pipenv environment') + results = startup.run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if results.status: logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return return_code + return results.status return 0 if arguments.pipenv_update: - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'update'], cwd=target_directory) - if return_code: - logger.warning("the following error occurred during pipenv environment update: {}".format(process_output)) - return return_code - logger.info('pipenv environment successfully updated') + logger.info('updating the pipenv environment') + results = startup.run_pipenv(('--site-packages', 'update'), cwd=target_directory) + if results.status: + logger.error('failed to update the pipenv environment') + return results.status + logger.info('the pipenv environment has been updated') return 0 if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.info('installing pipenv environment') - process_output, return_code = startup.run_process([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if return_code: - logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return return_code + logger.warning('no pre-existing pipenv environment was found, installing it now') + results = startup.run_pipenv(('--site-packages', 'install'), cwd=target_directory) + if results.status: + logger.error('failed to install the pipenv environment') + return results.status logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) # the blank arg being passed is required for pipenv passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst index d1f2d785..6a7ad903 100644 --- a/docs/source/change_log.rst +++ b/docs/source/change_log.rst @@ -24,6 +24,7 @@ Version 1.12.0 * Added the new ``fetch`` Jinja function and ``fromjson`` Jinja filter * Added ``campaign-alert-expired`` and ``campaign-expired`` server signals +* Switched to using `Pipenv`_ to manage the environment and dependencies Version 1.11.0 ^^^^^^^^^^^^^^ @@ -334,3 +335,5 @@ Released :release:`0.1.3` on June 4th, 2014 * Jinja2 templates for both the client and server * API version checking to warn when the client and server versions are incompatible + +.. _Pipenv: https://pipenv.readthedocs.io/en/latest/ diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 4a934238..666588ba 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -29,16 +29,38 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import collections import os import gc import subprocess +import sys from king_phisher import version -def run_process(process_args, cwd=os.getcwd()): +ProcessResults = collections.namedtuple('ProcessResults', ('stdout', 'stderr', 'status')) + +def run_pipenv(args, cwd=None): + path = which('pipenv') + if path is None: + return RuntimeError('pipenv could not be found') + args = (path,) + tuple(args) + results = run_process(args, cwd=cwd) + if results.status: + sys.stderr.write('pipenv encountered the following error:\n') + sys.stderr.write(results.stdout) + sys.stderr.flush() + return results + +def run_process(process_args, cwd=None, encoding='utf-8'): + cwd = cwd or os.getcwd() process_handle = subprocess.Popen(process_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) process_handle.wait() - return process_handle.stdout.read().decode('utf-8'), process_handle.returncode + results = ProcessResults( + process_handle.stdout.read().decode(encoding), + process_handle.stderr.read().decode(encoding), + process_handle.returncode + ) + return results def which(program): is_exe = lambda fpath: (os.path.isfile(fpath) and os.access(fpath, os.X_OK)) @@ -53,8 +75,9 @@ def which(program): def argp_add_default_args(parser, default_root=''): """ - Add standard arguments to a new :py:class:`argparse.ArgumentParser` instance - Used to add the utilities argparse options to the wrapper for display + Add standard arguments to a new :py:class:`argparse.ArgumentParser` + instance. Used to add the utilities argparse options to the wrapper for + display. :param parser: The parser to add arguments to. :type parser: :py:class:`argparse.ArgumentParser` @@ -69,14 +92,14 @@ def argp_add_default_args(parser, default_root=''): return parser def argp_add_client(parser): - kpc_group = parser.add_argument_group('King Phisher Client') + kpc_group = parser.add_argument_group('client specific options') kpc_group.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') kpc_group.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') kpc_group.add_argument('--no-style', dest='use_style', default=True, action='store_false', help='disable interface styling') return parser def argp_add_server(parser): - kps_group = parser.add_argument_group('King Phisher Server') + kps_group = parser.add_argument_group('server specific options') kps_group.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') kps_group.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') kps_group.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') @@ -84,7 +107,7 @@ def argp_add_server(parser): return parser def argp_add_wrapper(parser): - kpw_group = parser.add_argument_group('King Phisher pipenv wrapper') - kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirements and exit') + kpw_group = parser.add_argument_group('environment wrapper options') kpw_group.add_argument('--env-install', dest='pipenv_install', default=False, action='store_true', help='install pipenv environment and exit') + kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirements and exit') return parser diff --git a/tools/install.sh b/tools/install.sh index a1768b87..49277267 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -230,7 +230,7 @@ else echo "INFO: Backing up the previous server configuration to server_config.yml.bck.~#~" prompt_yes_or_no "Use the same database connection? (recommended)" SAVE_DATABASE if [ $SAVE_DATABASE ]; then - POSTGRES_BACKUP=$(cat server_config.yml| grep postgresql: | awk '{print $2}') + POSTGRES_BACKUP=$(awk ' /postgresql:/ {print $2}' server_config.yml) fi cp --backup=numbered server_config.yml ./server_config.yml.bck BACKUP=true From e9da878c5185db8ff877314fe5613b0e903aef44 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 16 Oct 2018 17:14:19 -0400 Subject: [PATCH 30/49] Consolidate duplicate code in pipenv_entry --- KingPhisher | 68 +---------------------------------- KingPhisherServer | 65 +-------------------------------- king_phisher/startup.py | 79 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 132 deletions(-) diff --git a/KingPhisher b/KingPhisher index 97cd27da..1a429ee3 100755 --- a/KingPhisher +++ b/KingPhisher @@ -31,9 +31,7 @@ # import argparse -import logging import os -import shutil import sys from king_phisher import startup @@ -41,71 +39,7 @@ from king_phisher import startup def main(): parser = argparse.ArgumentParser(description='King Phisher Client', conflict_handler='resolve') startup.argp_add_client(parser) - startup.argp_add_wrapper(parser) - startup.argp_add_default_args(parser) - - arguments, unknown_args = parser.parse_known_args() - sys_argv = sys.argv - sys_argv.pop(0) - - if sys.version_info < (3, 4): - print('[-] the Python version is too old (minimum required is 3.4)') - return 0 - logger = logging.getLogger('KingPhisher.wrapper') - logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler = logging.StreamHandler() - console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) - logger.addHandler(console_log_handler) - - target_directory = os.path.abspath(os.path.dirname(__file__)) - logger.debug("target diretory: {}".format(target_directory)) - os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') - os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) - logger.info('checking for the pipenv environment') - if not startup.which('pipenv'): - logger.info('installing pipenv') - results = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) - if results.status: - sys.stderr.write('the following issue occurred during installation of pipenv:\n') - sys.stderr.write(results.stdout) - return results.status - pipenv_path = startup.which('pipenv') - logger.debug("pipenv path: {0!r}".format(pipenv_path)) - if not pipenv_path: - logger.exception('failed to find pipenv') - return 0 - - if arguments.pipenv_install: - logger.info('installing the pipenv environment') - results = startup.run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if results.status: - logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return results.status - return 0 - - if arguments.pipenv_update: - logger.info('updating the pipenv environment') - results = startup.run_pipenv(('--site-packages', 'update'), cwd=target_directory) - if results.status: - logger.error('failed to update the pipenv environment') - return results.status - logger.info('the pipenv environment has been updated') - return 0 - - if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.warning('no pre-existing pipenv environment was found, installing it now') - results = startup.run_pipenv(('--site-packages', 'install'), cwd=target_directory) - if results.status: - logger.error('failed to install the pipenv environment') - logger.info('removing the incomplete .venv directory') - shutil.rmtree(os.path.join(target_directory, '.venv')) - return results.status - logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) - # the blank arg being passed is required for pipenv - passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv - - os.execve(pipenv_path, passing_argv, os.environ) + return startup.pipenv_entry(parser, os.path.basename(__file__)) if __name__ == '__main__': sys.exit(main()) diff --git a/KingPhisherServer b/KingPhisherServer index 9de55d65..64cf023e 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -31,7 +31,6 @@ # import argparse -import logging import os import sys @@ -40,69 +39,7 @@ from king_phisher import startup def main(): parser = argparse.ArgumentParser(description='King Phisher Server', conflict_handler='resolve') startup.argp_add_server(parser) - startup.argp_add_wrapper(parser) - startup.argp_add_default_args(parser) - - arguments, unknown_args = parser.parse_known_args() - sys_argv = sys.argv - sys_argv.pop(0) - - if sys.version_info < (3, 4): - print('[-] the Python version is too old (minimum required is 3.4)') - return 0 - logger = logging.getLogger('KingPhisher.wrapper') - logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler = logging.StreamHandler() - console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') - console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) - logger.addHandler(console_log_handler) - - target_directory = os.path.abspath(os.path.dirname(__file__)) - logger.debug("target diretory: {}".format(target_directory)) - os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') - os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) - logger.info('checking for the pipenv environment') - if not startup.which('pipenv'): - logger.info('installing pipenv') - results = startup.run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) - if results.status: - sys.stderr.write('the following issue occurred during installation of pipenv:\n') - sys.stderr.write(results.stdout) - return results.status - pipenv_path = startup.which('pipenv') - logger.debug("pipenv path: {0!r}".format(pipenv_path)) - if not pipenv_path: - logger.exception('failed to find pipenv') - return 0 - - if arguments.pipenv_install: - logger.info('installing the pipenv environment') - results = startup.run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) - if results.status: - logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) - return results.status - return 0 - - if arguments.pipenv_update: - logger.info('updating the pipenv environment') - results = startup.run_pipenv(('--site-packages', 'update'), cwd=target_directory) - if results.status: - logger.error('failed to update the pipenv environment') - return results.status - logger.info('the pipenv environment has been updated') - return 0 - - if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.warning('no pre-existing pipenv environment was found, installing it now') - results = startup.run_pipenv(('--site-packages', 'install'), cwd=target_directory) - if results.status: - logger.error('failed to install the pipenv environment') - return results.status - logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) - # the blank arg being passed is required for pipenv - passing_argv = [' ', 'run', os.path.basename(__file__)] + sys_argv - - os.execve(pipenv_path, passing_argv, os.environ) + return startup.pipenv_entry(parser, os.path.basename(__file__)) if __name__ == '__main__': sys.exit(main()) diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 666588ba..48ca2184 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -30,15 +30,92 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import collections -import os import gc +import logging +import os +import shutil import subprocess import sys +from king_phisher import its from king_phisher import version ProcessResults = collections.namedtuple('ProcessResults', ('stdout', 'stderr', 'status')) +def pipenv_entry(parser, entrypoint): + if its.on_windows: + raise RuntimeError('pipenv_entry is incompatible with windows') + argp_add_wrapper(parser) + argp_add_default_args(parser) + + arguments, _ = parser.parse_known_args() + sys_argv = sys.argv + sys_argv.pop(0) + + if sys.version_info < (3, 4): + print('[-] the Python version is too old (minimum required is 3.4)') + return os.EX_SOFTWARE + + # initialize basic stream logging + logger = logging.getLogger('KingPhisher.wrapper') + logger.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler = logging.StreamHandler() + console_log_handler.setLevel(arguments.loglvl if arguments.loglvl else 'WARNING') + console_log_handler.setFormatter(logging.Formatter('%(levelname)-8s %(message)s')) + logger.addHandler(console_log_handler) + + target_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) + logger.debug("target diretory: {}".format(target_directory)) + + os.environ['PIPENV_VENV_IN_PROJECT'] = os.environ.get('PIPENV_VENV_IN_PROJECT', 'True') + os.environ['PIPENV_PIPFILE'] = os.environ.get('PIPENV_PIPFILE', os.path.join(target_directory, 'Pipfile')) + + logger.info('checking for the pipenv environment') + if which('pipenv') is None: + logger.info('installing pipenv from PyPi using pip') + results = run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) + if results.status: + sys.stderr.write('the following issue occurred during installation of pipenv:\n') + sys.stderr.write(results.stdout) + return results.status + + pipenv_path = which('pipenv') + logger.debug("pipenv path: {0!r}".format(pipenv_path)) + if not pipenv_path: + logger.exception('failed to find pipenv') + return os.EX_UNAVAILABLE + + if arguments.pipenv_install: + logger.info('installing the pipenv environment') + results = run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if results.status: + logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + return results.status + return os.EX_OK + + if arguments.pipenv_update: + logger.info('updating the pipenv environment') + results = run_pipenv(('--site-packages', 'update'), cwd=target_directory) + if results.status: + logger.error('failed to update the pipenv environment') + return results.status + logger.info('the pipenv environment has been updated') + return os.EX_OK + + if not os.path.isdir(os.path.join(target_directory, '.venv')): + logger.warning('no pre-existing pipenv environment was found, installing it now') + results = run_pipenv(('--site-packages', 'install'), cwd=target_directory) + if results.status: + logger.error('failed to install the pipenv environment') + logger.info('removing the incomplete .venv directory') + shutil.rmtree(os.path.join(target_directory, '.venv')) + return results.status + + logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) + # the blank arg being passed is required for pipenv + passing_argv = [' ', 'run', entrypoint] + sys_argv + os.execve(pipenv_path, passing_argv, os.environ) + def run_pipenv(args, cwd=None): path = which('pipenv') if path is None: From c1e275c4a6cb9a2c865ba2d0ffd90770c6b6ff2f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 16 Oct 2018 17:40:06 -0400 Subject: [PATCH 31/49] Remove a circular dependency --- king_phisher/startup.py | 39 +++++++++++++++++++++++++-------------- king_phisher/version.py | 26 +++++++++++++------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 48ca2184..e45164d3 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -44,6 +44,7 @@ def pipenv_entry(parser, entrypoint): if its.on_windows: + # this is because of the os.exec call and os.EX_* status codes raise RuntimeError('pipenv_entry is incompatible with windows') argp_add_wrapper(parser) argp_add_default_args(parser) @@ -85,13 +86,22 @@ def pipenv_entry(parser, entrypoint): logger.exception('failed to find pipenv') return os.EX_UNAVAILABLE - if arguments.pipenv_install: - logger.info('installing the pipenv environment') - results = run_pipenv([pipenv_path, '--site-packages', 'install'], cwd=target_directory) + if arguments.pipenv_install or not os.path.isdir(os.path.join(target_directory, '.venv')): + if arguments.pipenv_install: + logger.info('installing the pipenv environment') + else: + logger.warning('no pre-existing pipenv environment was found, installing it now') + results = run_pipenv(('--site-packages', 'install'), cwd=target_directory) if results.status: - logger.warning("the following error occurred during pipenv environment setup: {}".format(process_output)) + logger.error('failed to install the pipenv environment') + logger.info('removing the incomplete .venv directory') + try: + shutil.rmtree(os.path.join(target_directory, '.venv')) + except OSError: + logger.error('failed to remove the incomplete .venv directory', exc_info=True) return results.status - return os.EX_OK + if arguments.pipenv_install: + return os.EX_OK if arguments.pipenv_update: logger.info('updating the pipenv environment') @@ -102,15 +112,6 @@ def pipenv_entry(parser, entrypoint): logger.info('the pipenv environment has been updated') return os.EX_OK - if not os.path.isdir(os.path.join(target_directory, '.venv')): - logger.warning('no pre-existing pipenv environment was found, installing it now') - results = run_pipenv(('--site-packages', 'install'), cwd=target_directory) - if results.status: - logger.error('failed to install the pipenv environment') - logger.info('removing the incomplete .venv directory') - shutil.rmtree(os.path.join(target_directory, '.venv')) - return results.status - logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) # the blank arg being passed is required for pipenv passing_argv = [' ', 'run', entrypoint] + sys_argv @@ -129,6 +130,16 @@ def run_pipenv(args, cwd=None): return results def run_process(process_args, cwd=None, encoding='utf-8'): + """ + Start a process, wait for it to complete and return a + :py:class:`~.ProcessResults` object. + + :param process_args: The arguments for the processes including the binary. + :param cwd: An optional current working directory to use for the process. + :param str encoding: The encoding to use for strings. + :return: The results of the process including the status code and any text printed to stdout or stderr. + :rtype: :py:class:`~.ProcessResults` + """ cwd = cwd or os.getcwd() process_handle = subprocess.Popen(process_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) process_handle.wait() diff --git a/king_phisher/version.py b/king_phisher/version.py index 0748d323..28bbe5de 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -34,31 +34,31 @@ import os import subprocess -from king_phisher import startup - -def get_revision(): +def get_revision(encoding='utf-8'): """ Retrieve the current git revision identifier. If the git binary can not be found or the repository information is unavailable, None will be returned. + :param str encoding: The encoding to use for strings. :return: The git revision tag if it's available. :rtype: str """ - git_bin = startup.which('git') - if not git_bin: + # use error handling instead of startup.which to avoid a circular dependency + try: + proc_h = subprocess.Popen( + ('git', 'rev-parse', 'HEAD'), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + close_fds=True, + cwd=os.path.dirname(os.path.abspath(__file__)) + ) + except OSError: return None - proc_h = subprocess.Popen( - (git_bin, 'rev-parse', 'HEAD'), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - close_fds=True, - cwd=os.path.dirname(os.path.abspath(__file__)) - ) rev = proc_h.stdout.read().strip() proc_h.wait() if not len(rev): return None - return rev.decode('utf-8') + return rev.decode(encoding) revision = get_revision() """The git revision identifying the latest commit if available.""" From 5f350bbdb2ba824d4c78179b5e2ca25776be7dc4 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 Oct 2018 10:17:02 -0400 Subject: [PATCH 32/49] Document the new startup module --- docs/source/king_phisher/index.rst | 1 + docs/source/king_phisher/startup.rst | 29 +++++++ king_phisher/startup.py | 110 +++++++++++++++++++++------ 3 files changed, 116 insertions(+), 24 deletions(-) create mode 100644 docs/source/king_phisher/startup.rst diff --git a/docs/source/king_phisher/index.rst b/docs/source/king_phisher/index.rst index 9b429e98..2ab45d2b 100644 --- a/docs/source/king_phisher/index.rst +++ b/docs/source/king_phisher/index.rst @@ -25,6 +25,7 @@ The King Phisher Package smtp_server.rst spf.rst ssh_forward.rst + startup.rst templates.rst testing.rst ua_parser.rst diff --git a/docs/source/king_phisher/startup.rst b/docs/source/king_phisher/startup.rst new file mode 100644 index 00000000..86268034 --- /dev/null +++ b/docs/source/king_phisher/startup.rst @@ -0,0 +1,29 @@ +:mod:`startup` +============== + +.. module:: king_phisher.startup + :synopsis: + +This module provides generic functions for the early initialization of the +project's environment. This is primarily used for the management of external +dependencies. + +Functions +--------- + +.. autofunction:: argp_add_client + +.. autofunction:: argp_add_default_args + +.. autofunction:: argp_add_server + +.. autofunction:: pipenv_entry + +.. autofunction:: run_process + +.. autofunction:: which + +Classes +------- + +.. autoclass:: ProcessResults diff --git a/king_phisher/startup.py b/king_phisher/startup.py index e45164d3..a0b36081 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -41,12 +41,68 @@ from king_phisher import version ProcessResults = collections.namedtuple('ProcessResults', ('stdout', 'stderr', 'status')) +""" +A named tuple for holding the results of an executed external process. -def pipenv_entry(parser, entrypoint): +.. py:attribute:: stdout + + A string containing the data the process wrote to stdout. + +.. py:attribute:: stderr + + A string containing the data the process wrote to stderr. + +.. py:attribute:: status + + An integer representing the process's exit code. +""" + +def _run_pipenv(args, cwd=None): + """ + Execute Pipenv with the supplied arguments and return the + :py:class:`~.ProcessResults`. If the exit status is non-zero, then the + stdout buffer from the Pipenv execution will be written to stderr. + + :param tuple args: The arguments for the Pipenv. + :param str cwd: An optional current working directory to use for the + process. + :return: The results of the execution. + :rtype: :py:class:`~.ProcessResults` + """ + path = which('pipenv') + if path is None: + return RuntimeError('pipenv could not be found') + args = (path,) + tuple(args) + results = run_process(args, cwd=cwd) + if results.status: + sys.stderr.write('pipenv encountered the following error:\n') + sys.stderr.write(results.stdout) + sys.stderr.flush() + return results + +def pipenv_entry(parser, entry_point): + """ + Run through startup logic for a Pipenv script. This sets up a basic stream + logging configuration, establishes the Pipenv environment and finally calls + the actual entry point using :py:func:`os.execve`. + + .. note:: + Due to the use of :py:func:`os.execve`, this function does not return. + + .. note:: + Due to the use of :py:func:`os.execve` and ``os.EX_*`` exit codes, this + function is not available on Windows. + + :param parser: The argument parser to use. Arguments are added to it and + extracted before passing the remainder to the entry point. + :param str entry_point: The name of the entry point using Pipenv. + """ if its.on_windows: # this is because of the os.exec call and os.EX_* status codes raise RuntimeError('pipenv_entry is incompatible with windows') - argp_add_wrapper(parser) + env_group = parser.add_argument_group('environment wrapper options') + env_group.add_argument('--env-install', dest='pipenv_install', default=False, action='store_true', help='install pipenv environment and exit') + env_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirements and exit') argp_add_default_args(parser) arguments, _ = parser.parse_known_args() @@ -91,7 +147,7 @@ def pipenv_entry(parser, entrypoint): logger.info('installing the pipenv environment') else: logger.warning('no pre-existing pipenv environment was found, installing it now') - results = run_pipenv(('--site-packages', 'install'), cwd=target_directory) + results = _run_pipenv(('--site-packages', 'install'), cwd=target_directory) if results.status: logger.error('failed to install the pipenv environment') logger.info('removing the incomplete .venv directory') @@ -105,7 +161,7 @@ def pipenv_entry(parser, entrypoint): if arguments.pipenv_update: logger.info('updating the pipenv environment') - results = run_pipenv(('--site-packages', 'update'), cwd=target_directory) + results = _run_pipenv(('--site-packages', 'update'), cwd=target_directory) if results.status: logger.error('failed to update the pipenv environment') return results.status @@ -114,21 +170,9 @@ def pipenv_entry(parser, entrypoint): logger.debug('pipenv Pipfile: {}'.format(os.environ['PIPENV_PIPFILE'])) # the blank arg being passed is required for pipenv - passing_argv = [' ', 'run', entrypoint] + sys_argv + passing_argv = [' ', 'run', entry_point] + sys_argv os.execve(pipenv_path, passing_argv, os.environ) -def run_pipenv(args, cwd=None): - path = which('pipenv') - if path is None: - return RuntimeError('pipenv could not be found') - args = (path,) + tuple(args) - results = run_process(args, cwd=cwd) - if results.status: - sys.stderr.write('pipenv encountered the following error:\n') - sys.stderr.write(results.stdout) - sys.stderr.flush() - return results - def run_process(process_args, cwd=None, encoding='utf-8'): """ Start a process, wait for it to complete and return a @@ -137,7 +181,8 @@ def run_process(process_args, cwd=None, encoding='utf-8'): :param process_args: The arguments for the processes including the binary. :param cwd: An optional current working directory to use for the process. :param str encoding: The encoding to use for strings. - :return: The results of the process including the status code and any text printed to stdout or stderr. + :return: The results of the process including the status code and any text + printed to stdout or stderr. :rtype: :py:class:`~.ProcessResults` """ cwd = cwd or os.getcwd() @@ -151,6 +196,15 @@ def run_process(process_args, cwd=None, encoding='utf-8'): return results def which(program): + """ + Examine the ``PATH`` environment variable to determine the location for the + specified program. If it can not be found None is returned. This is + fundamentally similar to the Unix utility of the same name. + + :param str program: The name of the program to search for. + :return: The absolute path to the program if found. + :rtype: str + """ is_exe = lambda fpath: (os.path.isfile(fpath) and os.access(fpath, os.X_OK)) for path in os.environ['PATH'].split(os.pathsep): path = path.strip('"') @@ -180,6 +234,13 @@ def argp_add_default_args(parser, default_root=''): return parser def argp_add_client(parser): + """ + Add client-specific arguments to a new :py:class:`argparse.ArgumentParser` + instance. + + :param parser: The parser to add arguments to. + :type parser: :py:class:`argparse.ArgumentParser` + """ kpc_group = parser.add_argument_group('client specific options') kpc_group.add_argument('-c', '--config', dest='config_file', required=False, help='specify a configuration file to use') kpc_group.add_argument('--no-plugins', dest='use_plugins', default=True, action='store_false', help='disable all plugins') @@ -187,15 +248,16 @@ def argp_add_client(parser): return parser def argp_add_server(parser): + """ + Add server-specific arguments to a new :py:class:`argparse.ArgumentParser` + instance. + + :param parser: The parser to add arguments to. + :type parser: :py:class:`argparse.ArgumentParser` + """ kps_group = parser.add_argument_group('server specific options') kps_group.add_argument('-f', '--foreground', dest='foreground', action='store_true', default=False, help='run in the foreground (do not fork)') kps_group.add_argument('--update-geoip-db', dest='update_geoip_db', action='store_true', default=False, help='update the geoip database and exit') kps_group.add_argument('--verify-config', dest='verify_config', action='store_true', default=False, help='verify the configuration and exit') kps_group.add_argument('config_file', action='store', help='configuration file to use') return parser - -def argp_add_wrapper(parser): - kpw_group = parser.add_argument_group('environment wrapper options') - kpw_group.add_argument('--env-install', dest='pipenv_install', default=False, action='store_true', help='install pipenv environment and exit') - kpw_group.add_argument('--env-update', dest='pipenv_update', default=False, action='store_true', help='update pipenv requirements and exit') - return parser From 4af5296734e28122aa226f7e9b07e101338f03bb Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 Oct 2018 12:44:59 -0400 Subject: [PATCH 33/49] Add some additional documentation details --- docs/source/client/configuration.rst | 9 ++++++++- king_phisher/startup.py | 9 ++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/source/client/configuration.rst b/docs/source/client/configuration.rst index b1fafff8..fe6124a6 100644 --- a/docs/source/client/configuration.rst +++ b/docs/source/client/configuration.rst @@ -2,7 +2,14 @@ Additional Configuration ======================== The following configuration settings will be honored but can not be set from -within the client's user interface. +within the client's user interface. The client configuration file is usually +located in the following locations depending on the host operating system: + +:Linux: + ``~/.config/king-phisher/config.json`` + +:Windows: + ``%LOCALAPPDATA%\king-phisher\config.json`` .. note:: The King Phisher client will overwrite its configuration file when it exits diff --git a/king_phisher/startup.py b/king_phisher/startup.py index a0b36081..23dde434 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -82,9 +82,10 @@ def _run_pipenv(args, cwd=None): def pipenv_entry(parser, entry_point): """ - Run through startup logic for a Pipenv script. This sets up a basic stream - logging configuration, establishes the Pipenv environment and finally calls - the actual entry point using :py:func:`os.execve`. + Run through startup logic for a Pipenv script (see Pipenv: `Custom Script + Shortcuts`_ for more information). This sets up a basic stream logging + configuration, establishes the Pipenv environment and finally calls the + actual entry point using :py:func:`os.execve`. .. note:: Due to the use of :py:func:`os.execve`, this function does not return. @@ -96,6 +97,8 @@ def pipenv_entry(parser, entry_point): :param parser: The argument parser to use. Arguments are added to it and extracted before passing the remainder to the entry point. :param str entry_point: The name of the entry point using Pipenv. + + .. _Custom Script Shortcuts: https://pipenv.readthedocs.io/en/latest/advanced/#custom-script-shortcuts """ if its.on_windows: # this is because of the os.exec call and os.EX_* status codes From 9d23fcfd1f7e92cc44c0b9e8da9fcfb881b4143a Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 Oct 2018 13:02:22 -0400 Subject: [PATCH 34/49] Fix the requirements for the RTD environment --- docs/requirements.txt | 69 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index e2f93657..775224f5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,70 @@ -# this is a pip requirements file specifically for the ReadTheDocs environment --r ../requirements.txt # include the core requirements +# due to Pipenv only supporting the production and development package groups, +# this file must be maintained seperately and can not be simply generated with +# pipenv lock -r > docs/requirements.txt + +-i https://pypi.org/simple +advancedhttpserver==2.0.11 +alembic==0.9.7 +asn1crypto==0.24.0 +bcrypt==3.1.4 +blinker==1.4 +boltons==18.0.0 +certifi==2018.10.15 +cffi==1.11.5 ; platform_python_implementation != 'PyPy' +chardet==3.0.4 +cryptography==2.1.4 +cycler==0.10.0 +dnspython==1.15.0 +ecdsa==0.13 +email-validator==1.0.3 +geoip2==2.8.0 +geojson==2.3.0 +graphene-sqlalchemy==2.0.0 +graphene==2.0.1 +graphql-core==2.1 +graphql-relay==0.4.5 +icalendar==4.0.1 +idna==2.6 +ipaddress==1.0.19 +iso8601==0.1.12 +jinja2==2.10 +jsonschema==2.6.0 +kiwisolver==1.0.1 +mako==1.0.7 +markdown==2.6.11 +markupsafe==1.0 +matplotlib==2.2.2 +maxminddb==1.4.1 +msgpack-python==0.5.6 +numpy==1.14.5 +paramiko==2.4.0 +pluginbase==0.5 +promise==2.2.1 +psycopg2==2.7.3.2 +py-gfm==0.1.3 +pyasn1==0.4.4 +pycairo==1.17.1 +pycparser==2.19 +pynacl==1.3.0 +pyotp==2.2.6 +pyparsing==2.2.2 +python-dateutil==2.7.2 +python-editor==1.0.3 +python-pam==1.8.3 +pytz==2018.4 +pyyaml==3.12 +requests-file==1.4.3 +requests==2.18.4 +rx==1.6.1 +singledispatch==3.4.0.3 +six==1.11.0 +smoke-zephyr==1.2.0 +sqlalchemy==1.2.6 +termcolor==1.1.0 +tzlocal==1.5.1 +urllib3==1.22 +websocket-client==0.49.0 +xlsxwriter==0.9.6 # additional sphinx-specific requirements sphinxcontrib-httpdomain>=1.5.0 From 6b268da5ea29751050b04f90dd141513027f05c8 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Wed, 17 Oct 2018 13:59:20 -0400 Subject: [PATCH 35/49] added --update to install.sh Added --update to install.sh --- KingPhisherServer | 1 + tools/install.sh | 284 ++++++++++++++++++++++++++-------------------- 2 files changed, 162 insertions(+), 123 deletions(-) diff --git a/KingPhisherServer b/KingPhisherServer index 64cf023e..bcb8b021 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -32,6 +32,7 @@ import argparse import os +import shutil import sys from king_phisher import startup diff --git a/tools/install.sh b/tools/install.sh index 49277267..ea390cfd 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -79,6 +79,7 @@ function show_help { echo " -y, --yes answer yes to all questions" echo " --skip-client skip installing client components" echo " --skip-server skip installing server components" + echo " --update update King Phisher requirements" return 0; } @@ -100,6 +101,144 @@ function select_nix_distro { esac } +function install_dependencies { + echo "Installing $LINUX_VERSION dependencies" + if [ "$LINUX_VERSION" == "RedHat" ]; then + if [ ! "$(command -v python3)" ]; then + echo "INFO: Installing Python3.5 for Red Hat 7" + # manually add rpms for easy python35 install + yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 + yum -y install https://rhel7.iuscommunity.org/ius-release.rpm + rpm --import /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY + yum install -y python35u python35u-devel python35u-pip + echo "INFO: Symlinking $(which python3.5) -> /usr/bin/python3" + ln -s $(which python3.5) /usr/bin/python3 + fi + yum install -y freetype-devel gcc gcc-c++ libpng-devel make \ + openssl-devel postgresql-devel + if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then + yum install -y postgresql-server + # manually init the database + postgresql-setup initdb + fi + fi + if [ "$LINUX_VERSION" == "CentOS" ]; then + if [ ! "$(command -v python3)" ]; then + # manually install python3.5 on CentOS 7 and symlink it to python3 + echo "INFO: Installing Python3.5 for CentOS 7" + yum install -y https://centos7.iuscommunity.org/ius-release.rpm + yum install -y python35u python35u-devel python35u-pip + echo "INFO: Symlinking $(which python3.5) -> /usr/bin/python3" + ln -s $(which python3.5) /usr/bin/python3 + fi + yum install -y epel-release + yum install -y freetype-devel gcc gcc-c++ libpng-devel make \ + openssl-devel postgresql-devel + if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then + yum install -y postgresql-server + # manually init the database + postgresql-setup initdb + fi + elif [ "$LINUX_VERSION" == "Fedora" ]; then + dnf install -y freetype-devel gcc gcc-c++ gtk3-devel \ + libpng-devel postgresql-devel python3-devel python3-pip \ + libffi-devel openssl-devel + if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then + dnf install -y geos geos-devel gtksourceview3 vte3 gobject-introspection-devel + fi + # Fedora 23 is missing an rpm lib required, check to see if it has been installed. + if [ ! -d "$/usr/lib/rpm/redhat/redhat-hardened-cc1" ]; then + dnf install -y rpm-build + fi + elif [ "$LINUX_VERSION" == "BackBox" ] || \ + [ "$LINUX_VERSION" == "Debian" ] || \ + [ "$LINUX_VERSION" == "Kali" ] || \ + [ "$LINUX_VERSION" == "Ubuntu" ]; then + apt-get install -y libfreetype6-dev python3-dev python3-pip pkg-config + if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then + if ! apt-get install -y gir1.2-gtk-3.0 gir1.2-gtksource-3.0 \ + gir1.2-webkit-3.0 python3-cairo libgeos++-dev libgirepository1.0-dev \ + libgtk-3-dev libpq-dev python3-gi python3-gi-cairo libpq-dev; then + echo "ERROR: Failed to install dependencies with apt-get" + exit + fi + + if [ "$LINUX_VERSION" == "Ubuntu" ]; then + apt-get install -y adwaita-icon-theme-full + fi + + if apt-cache search gir1.2-vte-2.91 &> /dev/null; then + if ! apt-get -y install gir1.2-vte-2.91; then + echo "ERROR: Failed to install gir1.2-vte-2.91" + fi + else + if ! apt-get -y install gir1.2-vte-2.90; then + echo "ERROR: Failed to install gir1.2-vte-2.90" + fi + fi + + if apt-get install -y gir1.2-webkit2-3.0 &> /dev/null; then + echo "INFO: Successfully installed gir1.2-webkit2-3.0 with apt-get" + else + echo "ERROR: Failed to install gir1.2-webkit2-3.0 with apt-get" + fi + fi + elif [ "$LINUX_VERSION" == "Arch" ]; then + pacman --noconfirm -S freetype2 python python-pip pkg-config + if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then + if ! pacman --noconfirm -S gobject-introspection python-cairo geos gtk3 \ + gtksourceview3 webkit2gtk vte3 postgresql-libs \ + python-gobject gobject-introspection; then + echo "ERROR: Failed to install dependencies with pacman" + exit + fi + fi + fi + + echo "INFO: Installing Python package dependencies from PyPi" + # six needs to be installed before requirements.txt for matplotlib + PIP_VERSION=$(pip --version) + python3 -m pip install --upgrade pip + # set pip back to python2 if python2 was default + if echo $PIP_VERSION | grep "python 2.7"; then + python -m pip install -U pip -I &> /dev/null + fi + + python3 -m pip install --upgrade setuptools + python3 -m pip install --upgrade six + python3 -m pip install pipenv + + cd $KING_PHISHER_DIR + echo "Setting up King Phisher's pipenv environment" + ./KingPhisher --env-install +} + +function install_postgres { + if [ "$LINUX_VERSION" == "RedHat" ]; then + yum install -y postgresql-server + # manually init the database + postgresql-setup initdb + elif [ "$LINUX_VERSION" == "CentOS" ]; then + yum install -y postgresql-server + # manually init the database + postgresql-setup initdb + elif [ "$LINUX_VERSION" == "Fedora" ]; then + dnf install -y postgresql-server + elif [ "$LINUX_VERSION" == "BackBox" ] || \ + [ "$LINUX_VERSION" == "Debian" ] || \ + [ "$LINUX_VERSION" == "Kali" ] || \ + [ "$LINUX_VERSION" == "Ubuntu" ]; then + apt-get install -y postgresql postgresql-server-dev-all &> /dev/null + if [ "$LINUX_VERSION" == "Kali" ]; then + easy_install -U distribute + apt-get install -y postgresql-server-dev-all &> /dev/null + fi + elif [ "$LINUX_VERSION" == "Arch" ]; then + pacman --noconfirm -S postgresql &> /dev/null + fi +} + while :; do case $1 in -h|-\?|--help) @@ -126,6 +265,9 @@ while :; do --skip-server) KING_PHISHER_SKIP_SERVER="x" ;; + --update) + UPDATE='x' + ;; --) shift break @@ -221,7 +363,22 @@ if [ ! -z "$KING_PHISHER_SKIP_CLIENT" ]; then fi if [ ! -z "$KING_PHISHER_SKIP_SERVER" ]; then echo "INFO: Skipping installing King Phisher Server components" -else +fi + +if [ ! -z "$UPDATE" ]; then + if git status &> /dev/null; then + KING_PHISHER_DIR="$(git rev-parse --show-toplevel)" + echo "INFO: Git repo found at $KING_PHISHER_DIR" + elif [ -d "$(dirname $(dirname $FILE_NAME))/king_phisher" ]; then + KING_PHISHER_DIR="$(dirname $(dirname $FILE_NAME))" + echo "INFO: Project directory found at $KING_PHISHER_DIR" + fi + echo "installing King Phisher dependencies" + install_dependencies + exit 0 +fi + +if [ -z "$KING_PHISHER_SKIP_SERVER" ]; then prompt_yes_or_no "Install and use PostgreSQL? (Highly recommended and required for upgrading)" KING_PHISHER_USE_POSTGRESQL if [ $KING_PHISHER_USE_POSTGRESQL == "yes" ]; then echo "INFO: Will install and configure PostgreSQL for the server" @@ -294,129 +451,10 @@ if [ "$LINUX_VERSION" == "Kali" ]; then fi fi -echo "Installing $LINUX_VERSION dependencies" -if [ "$LINUX_VERSION" == "RedHat" ]; then - if [ ! "$(command -v python3)" ]; then - echo "INFO: Installing Python3.5 for Red Hat 7" - # manually add rpms for easy python35 install - yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm - rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 - yum -y install https://rhel7.iuscommunity.org/ius-release.rpm - rpm --import /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY - yum install -y python35u python35u-devel python35u-pip - echo "INFO: Symlinking $(which python3.5) -> /usr/bin/python3" - ln -s $(which python3.5) /usr/bin/python3 - fi - yum install -y freetype-devel gcc gcc-c++ libpng-devel make \ - openssl-devel postgresql-devel - if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then - yum install -y postgresql-server - # manually init the database - postgresql-setup initdb - fi +install_dependencies +if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then + install_postgres fi -if [ "$LINUX_VERSION" == "CentOS" ]; then - if [ ! "$(command -v python3)" ]; then - # manually install python3.5 on CentOS 7 and symlink it to python3 - echo "INFO: Installing Python3.5 for CentOS 7" - yum install -y https://centos7.iuscommunity.org/ius-release.rpm - yum install -y python35u python35u-devel python35u-pip - echo "INFO: Symlinking $(which python3.5) -> /usr/bin/python3" - ln -s $(which python3.5) /usr/bin/python3 - fi - yum install -y epel-release - yum install -y freetype-devel gcc gcc-c++ libpng-devel make \ - openssl-devel postgresql-devel - if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then - yum install -y postgresql-server - # manually init the database - postgresql-setup initdb - fi -elif [ "$LINUX_VERSION" == "Fedora" ]; then - dnf install -y freetype-devel gcc gcc-c++ gtk3-devel \ - libpng-devel postgresql-devel python3-devel python3-pip \ - libffi-devel openssl-devel - if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then - dnf install -y geos geos-devel gtksourceview3 vte3 gobject-introspection-devel - fi - if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then - dnf install -y postgresql-server - fi - # Fedora 23 is missing an rpm lib required, check to see if it has been installed. - if [ ! -d "$/usr/lib/rpm/redhat/redhat-hardened-cc1" ]; then - dnf install -y rpm-build - fi -elif [ "$LINUX_VERSION" == "BackBox" ] || \ - [ "$LINUX_VERSION" == "Debian" ] || \ - [ "$LINUX_VERSION" == "Kali" ] || \ - [ "$LINUX_VERSION" == "Ubuntu" ]; then - apt-get install -y libfreetype6-dev python3-dev python3-pip pkg-config - if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then - if ! apt-get install -y gir1.2-gtk-3.0 gir1.2-gtksource-3.0 \ - gir1.2-webkit-3.0 python3-cairo libgeos++-dev libgirepository1.0-dev \ - libgtk-3-dev libpq-dev python3-gi python3-gi-cairo libpq-dev; then - echo "ERROR: Failed to install dependencies with apt-get" - exit - fi - - if [ "$LINUX_VERSION" == "Ubuntu" ]; then - apt-get install -y adwaita-icon-theme-full - fi - - if apt-cache search gir1.2-vte-2.91 &> /dev/null; then - if ! apt-get -y install gir1.2-vte-2.91; then - echo "ERROR: Failed to install gir1.2-vte-2.91" - fi - else - if ! apt-get -y install gir1.2-vte-2.90; then - echo "ERROR: Failed to install gir1.2-vte-2.90" - fi - fi - - if apt-get install -y gir1.2-webkit2-3.0 &> /dev/null; then - echo "INFO: Successfully installed gir1.2-webkit2-3.0 with apt-get" - else - echo "ERROR: Failed to install gir1.2-webkit2-3.0 with apt-get" - fi - fi - - if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then - apt-get install -y postgresql postgresql-server-dev-all &> /dev/null - fi - if [ "$LINUX_VERSION" == "Kali" ]; then - easy_install -U distribute - apt-get install -y postgresql-server-dev-all &> /dev/null - fi -elif [ "$LINUX_VERSION" == "Arch" ]; then - pacman --noconfirm -S freetype2 python python-pip pkg-config - if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then - if ! pacman --noconfirm -S gobject-introspection python-cairo geos gtk3 \ - gtksourceview3 webkit2gtk vte3 postgresql-libs \ - python-gobject gobject-introspection; then - echo "ERROR: Failed to install dependencies with pacman" - exit - fi - if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then - pacman --noconfirm -S postgresql &> /dev/null - fi - fi -fi - -echo "INFO: Installing Python package dependencies from PyPi" -# six needs to be installed before requirements.txt for matplotlib -PIP_VERSION=$(pip --version) -python3 -m pip install --upgrade pip -# set pip back to python2 if python2 was default -if echo $PIP_VERSION | grep "python 2.7"; then - python -m pip install -U pip -I &> /dev/null -fi - -python3 -m pip install --upgrade setuptools -python3 -m pip install --upgrade six -python3 -m pip install pipenv - -cd $KING_PHISHER_DIR -./KingPhisher --env-install if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then DESKTOP_APPLICATIONS_DIR="" From 412cf7232ff6e63bb96944c0f7d6b05527dc9c2d Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Wed, 17 Oct 2018 14:45:39 -0400 Subject: [PATCH 36/49] Added .env file --- .env | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 00000000..df23b72e --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +PIPENV_VENV_IN_PROJECT=True + From bc1baee4dcc357bb9327c7ed460c04489e52e00a Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 17 Oct 2018 15:30:31 -0400 Subject: [PATCH 37/49] Adjust the messages in tools/install.sh --- KingPhisherServer | 1 - tools/install.sh | 37 ++++++++++++++++++------------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/KingPhisherServer b/KingPhisherServer index bcb8b021..64cf023e 100755 --- a/KingPhisherServer +++ b/KingPhisherServer @@ -32,7 +32,6 @@ import argparse import os -import shutil import sys from king_phisher import startup diff --git a/tools/install.sh b/tools/install.sh index ea390cfd..efcbccd5 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -75,11 +75,11 @@ function show_help { echo "" echo "optional arguments" echo " -h, --help show this help message and exit" + echo " -u, --update update an existing installation" echo " -n, --no answer no to all questions" echo " -y, --yes answer yes to all questions" echo " --skip-client skip installing client components" echo " --skip-server skip installing server components" - echo " --update update King Phisher requirements" return 0; } @@ -96,16 +96,16 @@ function select_nix_distro { KING_PHISHER_SKIP_CLIENT="x";; 8) LINUX_VERSION="RedHat" KING_PHISHER_SKIP_CLIENT="x";; - *) echo "Invalid Linux selection, must be 1-8" + *) echo "ERROR: Invalid Linux selection, must be 1-8" exit 0;; esac } -function install_dependencies { - echo "Installing $LINUX_VERSION dependencies" +function sync_dependencies { + echo "INFO: Synchronizing $LINUX_VERSION OS dependencies" if [ "$LINUX_VERSION" == "RedHat" ]; then if [ ! "$(command -v python3)" ]; then - echo "INFO: Installing Python3.5 for Red Hat 7" + echo "INFO: Synchronizing Python3.5 for Red Hat 7" # manually add rpms for easy python35 install yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7 @@ -126,7 +126,7 @@ function install_dependencies { if [ "$LINUX_VERSION" == "CentOS" ]; then if [ ! "$(command -v python3)" ]; then # manually install python3.5 on CentOS 7 and symlink it to python3 - echo "INFO: Installing Python3.5 for CentOS 7" + echo "INFO: Synchronizing Python3.5 for CentOS 7" yum install -y https://centos7.iuscommunity.org/ius-release.rpm yum install -y python35u python35u-devel python35u-pip echo "INFO: Symlinking $(which python3.5) -> /usr/bin/python3" @@ -196,7 +196,7 @@ function install_dependencies { fi fi - echo "INFO: Installing Python package dependencies from PyPi" + echo "INFO: Synchronizing Python package dependencies from PyPi" # six needs to be installed before requirements.txt for matplotlib PIP_VERSION=$(pip --version) python3 -m pip install --upgrade pip @@ -210,7 +210,7 @@ function install_dependencies { python3 -m pip install pipenv cd $KING_PHISHER_DIR - echo "Setting up King Phisher's pipenv environment" + echo "INFO: Synchronizing King Phisher's pipenv environment" ./KingPhisher --env-install } @@ -247,14 +247,14 @@ while :; do ;; -n|--no) if [ "$answer_all_yes" == "true" ]; then - echo "Can not use -n and -y together" + echo "ERROR: Can not use -n and -y together" exit $E_USAGE fi answer_all_no=true ;; -y|--yes) if [ "$answer_all_no" == "true" ]; then - echo "Can not use -n and -y together" + echo "ERROR: Can not use -n and -y together" exit $E_USAGE fi answer_all_yes=true @@ -265,15 +265,15 @@ while :; do --skip-server) KING_PHISHER_SKIP_SERVER="x" ;; - --update) - UPDATE='x' + -u|--update) + UPDATE="x" ;; --) shift break ;; -?*) - printf "Unknown option: %s\n" "$1" >&2 + printf "ERROR: Unknown option: %s\n" "$1" >&2 exit $E_USAGE ;; *) @@ -348,7 +348,7 @@ if [ -z "$LINUX_VERSION" ]; then echo "" echo -n "Select 1-8: " select_nix_distro - echo "Selected Linux version is $LINUX_VERSION" + echo "INFO: Selected Linux version is $LINUX_VERSION" prompt_yes_or_no "Continue? (There is no guarantee or support beyond this point)" select_nix_continue if [ $select_nix_continue == "no" ]; then echo "INFO: Installation aborted by user" @@ -373,8 +373,7 @@ if [ ! -z "$UPDATE" ]; then KING_PHISHER_DIR="$(dirname $(dirname $FILE_NAME))" echo "INFO: Project directory found at $KING_PHISHER_DIR" fi - echo "installing King Phisher dependencies" - install_dependencies + sync_dependencies exit 0 fi @@ -426,7 +425,7 @@ else echo "INFO: Downloading and installing the King Phisher server to $KING_PHISHER_DIR" if [ ! -d "$KING_PHISHER_DIR" ]; then if ! git clone $GIT_CLONE_URL $KING_PHISHER_DIR &> /dev/null; then - echo "Failed to clone the Git repo" + echo "ERROR: Failed to clone the Git repo" exit $E_SOFTWARE fi echo "INFO: Successfully cloned the git repo" @@ -441,7 +440,7 @@ fi if [ "$LINUX_VERSION" == "Kali" ]; then if ! grep -i 'rolling' /etc/debian_version &> /dev/null; then - echo "Checking Kali 2 apt sources" + echo "INFO: Checking Kali 2 apt sources" if ! grep -E "deb http://http\.kali\.org/kali sana main non-free contrib" /etc/apt/sources.list &> /dev/null; then echo "INFO: Standard Kali 2 apt sources are missing, now adding them, see" echo "INFO: http://docs.kali.org/general-use/kali-linux-sources-list-repositories for more details" @@ -451,7 +450,7 @@ if [ "$LINUX_VERSION" == "Kali" ]; then fi fi -install_dependencies +sync_dependencies if [ "$KING_PHISHER_USE_POSTGRESQL" == "yes" ]; then install_postgres fi From 4f4d6ebf8f0002099ab76f877d9b473426458a0d Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 22 Oct 2018 10:17:01 -0400 Subject: [PATCH 38/49] Add some additional documentation --- docs/source/development/index.rst | 1 + docs/source/development/modules.rst | 45 +++++++++++++++++++ .../king_phisher/client/assistants/index.rst | 2 + .../king_phisher/client/dialogs/index.rst | 2 + docs/source/king_phisher/client/index.rst | 2 + .../source/king_phisher/client/tabs/index.rst | 2 + .../king_phisher/client/widget/index.rst | 2 + .../king_phisher/client/windows/index.rst | 2 + docs/source/king_phisher/index.rst | 3 ++ docs/source/king_phisher/its.rst | 29 ++++++++++++ .../king_phisher/server/database/index.rst | 2 + .../king_phisher/server/graphql/index.rst | 2 + .../server/graphql/types/index.rst | 2 + docs/source/king_phisher/server/index.rst | 2 + docs/source/king_phisher/startup.rst | 4 ++ docs/source/king_phisher/version.rst | 4 ++ king_phisher/its.py | 21 +++++++++ king_phisher/startup.py | 11 +++++ king_phisher/version.py | 11 +++++ 19 files changed, 149 insertions(+) create mode 100644 docs/source/development/modules.rst create mode 100644 docs/source/king_phisher/its.rst diff --git a/docs/source/development/index.rst b/docs/source/development/index.rst index f9fdfc98..5218f1f9 100644 --- a/docs/source/development/index.rst +++ b/docs/source/development/index.rst @@ -6,6 +6,7 @@ Development References :titlesonly: architecture_overview.rst + modules.rst environment_vars.rst style_guide.rst release_steps.rst diff --git a/docs/source/development/modules.rst b/docs/source/development/modules.rst new file mode 100644 index 00000000..1448baca --- /dev/null +++ b/docs/source/development/modules.rst @@ -0,0 +1,45 @@ +Modules +======= + +The projects code base is split among multiple Python modules under the primary +:py:mod:`king_phisher` package. Code which is not specific to either the client +or server code bases is directly in the root of the :py:mod:`king_phisher` +package with code that is specific to either the client or server being under +either the :py:mod:`king_phisher.client` sub-package or +:py:mod:`king_phisher.server` sub-package respectively. + +Special Modules +--------------- + +Some modules have special designations to identify them as having particular +qualities. + +.. _clean-room-modules: + +Clean Room Modules +^^^^^^^^^^^^^^^^^^ + +Modules that qualify for the "Clean Room" classification are suitable for use +during the early phases of the application's initialization. They may also be +used for general purposes. + +* Modules must not import any code which is not either included in the Python + standard library or packaged with King Phisher. +* Modules may only import other King Phisher modules which also have the "Clean + Room" classification. + +Modules with this designation have the following comment banner included in +their source file just below the standard splat. + +.. code-block:: none + + ################################################################################ + # + # CLEAN ROOM MODULE + # + # This module is classified as a "Clean Room" module and is subject to + # restrictions on what it may import. + # + # See: https://king-phisher.readthedocs.io/en/latest/development/modules.html#clean-room-modules + # + ################################################################################ diff --git a/docs/source/king_phisher/client/assistants/index.rst b/docs/source/king_phisher/client/assistants/index.rst index 0bd1e77a..e2843c5a 100644 --- a/docs/source/king_phisher/client/assistants/index.rst +++ b/docs/source/king_phisher/client/assistants/index.rst @@ -1,6 +1,8 @@ :mod:`assistants` ================= +.. module:: king_phisher.client.assistants + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/source/king_phisher/client/dialogs/index.rst b/docs/source/king_phisher/client/dialogs/index.rst index 975554a6..5f583212 100644 --- a/docs/source/king_phisher/client/dialogs/index.rst +++ b/docs/source/king_phisher/client/dialogs/index.rst @@ -1,6 +1,8 @@ :mod:`dialogs` ============== +.. module:: king_phisher.client.dialogs + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/source/king_phisher/client/index.rst b/docs/source/king_phisher/client/index.rst index ad47932b..2c99f221 100644 --- a/docs/source/king_phisher/client/index.rst +++ b/docs/source/king_phisher/client/index.rst @@ -1,6 +1,8 @@ :mod:`client` ============= +.. module:: king_phisher.client + This package contains all packages and modules specific to the client application. diff --git a/docs/source/king_phisher/client/tabs/index.rst b/docs/source/king_phisher/client/tabs/index.rst index 9b1eb904..ce66acea 100644 --- a/docs/source/king_phisher/client/tabs/index.rst +++ b/docs/source/king_phisher/client/tabs/index.rst @@ -1,6 +1,8 @@ :mod:`tabs` =========== +.. module:: king_phisher.client.tabs + This package contains modules for providing the content of the top level tabs used by the main application window. diff --git a/docs/source/king_phisher/client/widget/index.rst b/docs/source/king_phisher/client/widget/index.rst index 6fa40041..9c2c16a4 100644 --- a/docs/source/king_phisher/client/widget/index.rst +++ b/docs/source/king_phisher/client/widget/index.rst @@ -1,6 +1,8 @@ :mod:`widget` ============= +.. module:: king_phisher.client.widget + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/source/king_phisher/client/windows/index.rst b/docs/source/king_phisher/client/windows/index.rst index a7755cf3..788b3fae 100644 --- a/docs/source/king_phisher/client/windows/index.rst +++ b/docs/source/king_phisher/client/windows/index.rst @@ -1,6 +1,8 @@ :mod:`windows` ============== +.. module:: king_phisher.client.windows + This package contains modules for providing GTK Window objects used by the client application. diff --git a/docs/source/king_phisher/index.rst b/docs/source/king_phisher/index.rst index 2ab45d2b..609e8bbd 100644 --- a/docs/source/king_phisher/index.rst +++ b/docs/source/king_phisher/index.rst @@ -1,6 +1,8 @@ The King Phisher Package ======================== +.. module:: king_phisher + .. toctree:: :maxdepth: 3 :titlesonly: @@ -18,6 +20,7 @@ The King Phisher Package geoip.rst ics.rst ipaddress.rst + its.rst plugins.rst security_keys.rst serializers.rst diff --git a/docs/source/king_phisher/its.rst b/docs/source/king_phisher/its.rst new file mode 100644 index 00000000..edf50d1f --- /dev/null +++ b/docs/source/king_phisher/its.rst @@ -0,0 +1,29 @@ +:mod:`its` +========== + +.. module:: king_phisher.its + :synopsis: + +This module contains variables regarding the runtime environment in a standard +location. + +.. note:: + This is a :ref:`"Clean Room" module ` and is suitable for + use during initialization. + +Data +---- + +.. autodata:: frozen + +.. autodata:: mocked + +.. autodata:: on_linux + +.. autodata:: on_rtd + +.. autodata:: on_windows + +.. autodata:: py_v2 + +.. autodata:: py_v3 diff --git a/docs/source/king_phisher/server/database/index.rst b/docs/source/king_phisher/server/database/index.rst index 1861cfca..40e0a902 100644 --- a/docs/source/king_phisher/server/database/index.rst +++ b/docs/source/king_phisher/server/database/index.rst @@ -1,6 +1,8 @@ :mod:`database` =============== +.. module:: king_phisher.server.database + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/source/king_phisher/server/graphql/index.rst b/docs/source/king_phisher/server/graphql/index.rst index e220a4fb..674f664a 100644 --- a/docs/source/king_phisher/server/graphql/index.rst +++ b/docs/source/king_phisher/server/graphql/index.rst @@ -1,6 +1,8 @@ :mod:`graphql` ============== +.. module:: king_phisher.server.graphql + This package provides the `GraphQL `_ interface for querying information from the King Phisher server. This allows flexibility in how the client would like for the returned data to be formatted. This interface diff --git a/docs/source/king_phisher/server/graphql/types/index.rst b/docs/source/king_phisher/server/graphql/types/index.rst index c5894ade..535a350f 100644 --- a/docs/source/king_phisher/server/graphql/types/index.rst +++ b/docs/source/king_phisher/server/graphql/types/index.rst @@ -1,6 +1,8 @@ :mod:`types` ============ +.. module:: king_phisher.server.graphql.types + .. toctree:: :maxdepth: 2 :titlesonly: diff --git a/docs/source/king_phisher/server/index.rst b/docs/source/king_phisher/server/index.rst index 5be03c95..2874a1af 100644 --- a/docs/source/king_phisher/server/index.rst +++ b/docs/source/king_phisher/server/index.rst @@ -1,6 +1,8 @@ :mod:`server` ============= +.. module:: king_phisher.server + This package contains all packages and modules specific to the server application. diff --git a/docs/source/king_phisher/startup.rst b/docs/source/king_phisher/startup.rst index 86268034..8d8dd737 100644 --- a/docs/source/king_phisher/startup.rst +++ b/docs/source/king_phisher/startup.rst @@ -8,6 +8,10 @@ This module provides generic functions for the early initialization of the project's environment. This is primarily used for the management of external dependencies. +.. note:: + This is a :ref:`"Clean Room" module ` and is suitable for + use during initialization. + Functions --------- diff --git a/docs/source/king_phisher/version.rst b/docs/source/king_phisher/version.rst index 11196209..b4d508df 100644 --- a/docs/source/king_phisher/version.rst +++ b/docs/source/king_phisher/version.rst @@ -8,6 +8,10 @@ This module collects all import version information for the application. This is the authoritative source for the applications version information and should be used anywhere the version is required. +.. note:: + This is a :ref:`"Clean Room" module ` and is suitable for + use during initialization. + Data ---- diff --git a/king_phisher/its.py b/king_phisher/its.py index a037cb19..a38b9e30 100644 --- a/king_phisher/its.py +++ b/king_phisher/its.py @@ -30,16 +30,37 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +################################################################################ +# +# CLEAN ROOM MODULE +# +# This module is classified as a "Clean Room" module and is subject to +# restrictions on what it may import. +# +# See: https://king-phisher.readthedocs.io/en/latest/development/modules.html#clean-room-modules +# +################################################################################ + import os import sys frozen = getattr(sys, 'frozen', False) +"""Whether or not the current environment is a frozen Windows build.""" on_linux = sys.platform.startswith('linux') +"""Whether or not the current platform is Linux.""" on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +"""Whether or not the current platform is ReadTheDocs.""" on_windows = sys.platform.startswith('win') +"""Whether or not the current platform is Windows.""" py_v2 = sys.version_info[0] == 2 +"""Whether or not the current Python version is 2.x.""" py_v3 = sys.version_info[0] == 3 +"""Whether or not the current Python version is 3.x.""" mocked = False +""" +Whether or not certain objects are non-functional mock implementations. These +are used for the purpose of generating documentation. +""" diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 23dde434..97c24500 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -29,6 +29,17 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +################################################################################ +# +# CLEAN ROOM MODULE +# +# This module is classified as a "Clean Room" module and is subject to +# restrictions on what it may import. +# +# See: https://king-phisher.readthedocs.io/en/latest/development/modules.html#clean-room-modules +# +################################################################################ + import collections import gc import logging diff --git a/king_phisher/version.py b/king_phisher/version.py index 28bbe5de..50a77cff 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -30,6 +30,17 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # +################################################################################ +# +# CLEAN ROOM MODULE +# +# This module is classified as a "Clean Room" module and is subject to +# restrictions on what it may import. +# +# See: https://king-phisher.readthedocs.io/en/latest/development/modules.html#clean-room-modules +# +################################################################################ + import collections import os import subprocess From d1598039a8de5e9a077903007e068ef3419ccc6f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 22 Oct 2018 11:02:44 -0400 Subject: [PATCH 39/49] Add some additional RPC version documentation --- .../development/architecture_overview.rst | 2 + docs/source/server/database.rst | 3 +- docs/source/server/rpc_api.rst | 40 ++++++++++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/docs/source/development/architecture_overview.rst b/docs/source/development/architecture_overview.rst index d87c9d5d..70356328 100644 --- a/docs/source/development/architecture_overview.rst +++ b/docs/source/development/architecture_overview.rst @@ -53,6 +53,8 @@ Server Responsibilities King Phisher client can also subscribe to a subset of events which are forwarded over websockets. +.. _login-process: + Login Process ------------- diff --git a/docs/source/server/database.rst b/docs/source/server/database.rst index 7906a390..c00508dc 100644 --- a/docs/source/server/database.rst +++ b/docs/source/server/database.rst @@ -12,6 +12,8 @@ references the object which has the constraint. .. graphviz:: database_relationships.dot +.. _schema-versioning: + Schema Versioning ----------------- @@ -23,7 +25,6 @@ in the source code). When the schemas are not the same, the database is considered to be incompatible. The King Phisher process will then attempt to upgrade the stored database schema. - If the stored database schema is newer than the running schema, the King Phisher process can not downgrade it. This would happen for example if a developer were to use version control to revert the project code to an older version. In this diff --git a/docs/source/server/rpc_api.rst b/docs/source/server/rpc_api.rst index c9b83f62..85a5ab7d 100644 --- a/docs/source/server/rpc_api.rst +++ b/docs/source/server/rpc_api.rst @@ -7,12 +7,40 @@ Overview -------- The RPC API is used by the King Phisher client to communicate with the server. -It uses the RPC capabilities provided by the -:py:mod:`AdvancedHTTPServer` module for the underlying communications. The RPC -API provides a way for the client to retrieve and set information regarding -campaigns as well as the server's configuration. RPC requests must be -authenticated and are only permitted from the loopback interface. The client is -responsible for using SSH to set up a port forward for requests. +It uses the RPC capabilities provided by the :py:mod:`AdvancedHTTPServer` module +for the underlying communications. The RPC API provides a way for the client to +retrieve and set information regarding campaigns as well as the server's +configuration. RPC requests must be authenticated and are only permitted from +the loopback interface. The client is responsible for using SSH to set up a port +forward for requests. See the :ref:`Login Process ` documentation +for more information. + +RPC API Versioning +------------------ + +It's important for the client and server components to have a compatible RPC +version. The version each understands is described in the +:py:data:`~king_phisher.version.rpc_api_version` object. This object contains +both a major and minor version identifier. The major version is incremented when +backwards-incompatible changes are made such as an argument or method is +removed. The minor version is incremented when backwards-compatible changes are +made such as when a new method is added or when a keyword argument is added +whose default value maintains the original behavior. + +In this way, it is possible for the server to support a newer RPC version than +the client. This would be the case when the server is newer and provides more +functionality than the older client requires. + +Since version :release:`1.10.0`, the GraphQL API loosens the interdependency +between the RPC API version and the database's +:ref:`schema version `. Since GraphQL allows the client to +specify only the fields it requires, new fields can be added to the database +without incrementing the major RPC API version. **It is still important to +increment the minor RPC API version** so the client knows that those fields are +available to be requested through the :rpc:func:`graphql` endpoint. If database +fields are removed, columns are renamed, columns types are changed, or columns +have additional restrictions placed on them, the major RPC API version must be +incremented. .. _rpc-api-general-api-label: From f486c26352b06648cbee6e2c20c1f191397b1245 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Thu, 25 Oct 2018 11:50:05 -0400 Subject: [PATCH 40/49] Added gir1.2-webkit-4.0 to install.sh --- tools/install.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/install.sh b/tools/install.sh index efcbccd5..853a587c 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -180,8 +180,10 @@ function sync_dependencies { if apt-get install -y gir1.2-webkit2-3.0 &> /dev/null; then echo "INFO: Successfully installed gir1.2-webkit2-3.0 with apt-get" + elif apt-get install -y gir1.2-webkit2-4.0 &> /dev/null; then + echo "INFO: Successfully installed gir1.2-webkit2-4.0 with apt-get" else - echo "ERROR: Failed to install gir1.2-webkit2-3.0 with apt-get" + echo "ERROR: Failed to install gir1.2-webkit2 3.0 or 4.0 with apt-get" fi fi elif [ "$LINUX_VERSION" == "Arch" ]; then From 772376a8260c1e9b07e20efdab70ea3578257a21 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Fri, 26 Oct 2018 17:18:00 -0400 Subject: [PATCH 41/49] Windows build fixs Removed unused vars --- king_phisher/client/application.py | 2 +- king_phisher/version.py | 4 +++- tools/development/build_msi.bat | 19 +++++++++++++++++++ tools/development/cx_freeze.py | 13 +++---------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/king_phisher/client/application.py b/king_phisher/client/application.py index fd31666c..2ebf8e4a 100644 --- a/king_phisher/client/application.py +++ b/king_phisher/client/application.py @@ -133,7 +133,7 @@ class KingPhisherClientApplication(_Gtk_Application): def __init__(self, config_file=None, use_plugins=True, use_style=True): super(KingPhisherClientApplication, self).__init__() if use_style: - if Gtk.check_version(3, 18, 0): + if Gtk.check_version(3, 18, 0) or its.on_windows: self._theme_file = 'theme.v1.css' else: self._theme_file = 'theme.v2.css' diff --git a/king_phisher/version.py b/king_phisher/version.py index 50a77cff..1a3afd3b 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -45,6 +45,8 @@ import os import subprocess +from king_phisher import its + def get_revision(encoding='utf-8'): """ Retrieve the current git revision identifier. If the git binary can not be @@ -60,7 +62,7 @@ def get_revision(encoding='utf-8'): ('git', 'rev-parse', 'HEAD'), stdout=subprocess.PIPE, stderr=subprocess.PIPE, - close_fds=True, + close_fds=False if its.on_windows else True, cwd=os.path.dirname(os.path.abspath(__file__)) ) except OSError: diff --git a/tools/development/build_msi.bat b/tools/development/build_msi.bat index 0c50601d..5217f40a 100644 --- a/tools/development/build_msi.bat +++ b/tools/development/build_msi.bat @@ -1,11 +1,30 @@ @echo off @setlocal +:Variables set start=%time% +:: make entry point for King Phisher client build +copy king_phisher\client\__main__.py .\KingPhisher +if %ERRORLEVEL% NEQ 0 ( + echo Failed to copy client entry point + echo Error level: %ERRORLEVEL% + exit /b %ERRORLEVEL% +) + :: perform the build python tools\development\cx_freeze.py build +if %ERRORLEVEL% NEQ 0 ( + echo Failed to build King Phisher exe + echo Error level: %ERRORLEVEL% + exit /b %ERRORLEVEL% +) python tools\development\cx_freeze.py bdist_msi +if %ERRORLEVEL% NEQ 0 ( + echo Failed to build King Phisher msi package + echo Error level: %ERRORLEVEL% + exit /b %ERRORLEVEL% +) :: build complete, calculate the time elapsed set end=%time% diff --git a/tools/development/cx_freeze.py b/tools/development/cx_freeze.py index a26ed100..e8a86b98 100644 --- a/tools/development/cx_freeze.py +++ b/tools/development/cx_freeze.py @@ -31,10 +31,11 @@ # import os +import re import site import sys -sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) +sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))) from king_phisher import version @@ -127,14 +128,6 @@ if os.path.isdir(os.path.join(site.getsitepackages()[1], path)): include_files.append((os.path.join(site.getsitepackages()[1], path), path)) -# include windows complied version of geos for basemaps -include_files.append((os.path.join(site.getsitepackages()[1], 'geos.dll'), 'geos.dll')) -include_files.append((os.path.join(site.getsitepackages()[1], 'geos_c.dll'), 'geos_c.dll')) -include_files.append((os.path.join(site.getsitepackages()[1], '_geoslib.pyd'), '_geoslib.pyd')) -include_files.append((os.path.join(site.getsitepackages()[0], 'libs', 'geos_c.lib'), os.path.join('libs', 'geos_c.lib'))) -include_files.append((os.path.join(site.getsitepackages()[0], 'libs', 'geos.lib'), os.path.join('libs', 'geos.lib'))) - - include_files.append((matplotlib.get_data_path(), 'mpl-data')) include_files.append((basemap.basemap_datadir, 'mpl-basemap-data')) include_files.append(('data/client/king_phisher', 'king_phisher')) @@ -203,7 +196,7 @@ setup( name='KingPhisher', author='SecureState', - version=version.distutils_version, + version=re.sub("[^0-9.]", "", version.distutils_version), description='King Phisher Client', options=dict(build_exe=build_exe_options), executables=executables From 41c0e57ca793b3130893a7d5c383897bc1b04010 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Wed, 31 Oct 2018 09:37:24 -0400 Subject: [PATCH 42/49] Fixed cx_freeze and theme.v1 --- data/client/king_phisher/style/theme.v1.css | 7 +++++-- tools/development/cx_freeze.py | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/data/client/king_phisher/style/theme.v1.css b/data/client/king_phisher/style/theme.v1.css index 801c0556..19ba1b9f 100644 --- a/data/client/king_phisher/style/theme.v1.css +++ b/data/client/king_phisher/style/theme.v1.css @@ -442,10 +442,13 @@ GtkWindow:hover { padding: 5px 5px 6px; } .multilineentry:insensitive { background-color: alpha(darkgray, 0.8); } - .multilineentry > GtkTextView { + .multilineentry GtkTextView { background-color: transparent; background-image: none; color: @theme_color_0; } - .multilineentry > GtkTextView:selected { + .multilineentry GtkTextView:selected { background-color: @theme_color_1; color: white; } + .multilineentry GtkTextView:insensitive { + background-color: alpha(darkgray, 0.8); + background-image: none; } diff --git a/tools/development/cx_freeze.py b/tools/development/cx_freeze.py index e8a86b98..e4a28f99 100644 --- a/tools/development/cx_freeze.py +++ b/tools/development/cx_freeze.py @@ -31,7 +31,6 @@ # import os -import re import site import sys @@ -193,10 +192,12 @@ excludes=['jinja2.asyncfilters', 'jinja2.asyncsupport'], # not supported with python 3.4 ) +version_build = '.'.join((str(version.version_info[0]), str(version.version_info[1]), str(version.version_info[2]))) setup( name='KingPhisher', author='SecureState', - version=re.sub("[^0-9.]", "", version.distutils_version), + version=version_build, + comments="build version: {}".format(version.distutils_version), description='King Phisher Client', options=dict(build_exe=build_exe_options), executables=executables From 0caee2a558b284de43cbe50bf788fbc20df72775 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 31 Oct 2018 13:04:04 -0400 Subject: [PATCH 43/49] Cleanup some english verbiage in dev tools --- tools/development/build_msi.bat | 6 +++--- tools/development/cx_freeze.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/development/build_msi.bat b/tools/development/build_msi.bat index 5217f40a..6d1ac370 100644 --- a/tools/development/build_msi.bat +++ b/tools/development/build_msi.bat @@ -4,7 +4,7 @@ :Variables set start=%time% -:: make entry point for King Phisher client build +:: make the entry point for the King Phisher client build copy king_phisher\client\__main__.py .\KingPhisher if %ERRORLEVEL% NEQ 0 ( echo Failed to copy client entry point @@ -15,13 +15,13 @@ if %ERRORLEVEL% NEQ 0 ( :: perform the build python tools\development\cx_freeze.py build if %ERRORLEVEL% NEQ 0 ( - echo Failed to build King Phisher exe + echo Failed to build the King Phisher exe echo Error level: %ERRORLEVEL% exit /b %ERRORLEVEL% ) python tools\development\cx_freeze.py bdist_msi if %ERRORLEVEL% NEQ 0 ( - echo Failed to build King Phisher msi package + echo Failed to build the King Phisher msi package echo Error level: %ERRORLEVEL% exit /b %ERRORLEVEL% ) diff --git a/tools/development/cx_freeze.py b/tools/development/cx_freeze.py index e4a28f99..bc3b798e 100644 --- a/tools/development/cx_freeze.py +++ b/tools/development/cx_freeze.py @@ -192,12 +192,12 @@ excludes=['jinja2.asyncfilters', 'jinja2.asyncsupport'], # not supported with python 3.4 ) -version_build = '.'.join((str(version.version_info[0]), str(version.version_info[1]), str(version.version_info[2]))) +version_build = '.'.join(map(str, version.version_info)) setup( name='KingPhisher', author='SecureState', version=version_build, - comments="build version: {}".format(version.distutils_version), + comments="Version: {}".format(version.distutils_version), description='King Phisher Client', options=dict(build_exe=build_exe_options), executables=executables From fa9e01e34e8a46e4ced609f1dd5f416ea4b10e20 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 6 Nov 2018 15:46:55 -0500 Subject: [PATCH 44/49] Fedora29 and Ubuntu18 headless install fixes --- Pipfile | 4 +++- Pipfile.lock | 50 ++++++++++++++++++++++++++++++++++++++++++------ tools/install.sh | 18 +++++++++++++++-- 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Pipfile b/Pipfile index 1d4bba0b..ad675245 100644 --- a/Pipfile +++ b/Pipfile @@ -9,6 +9,7 @@ alembic = "==0.9.7" blinker = "==1.4" boltons = "==18.0.0" cryptography = "==2.1.4" +Cython = "==0.29" dnspython = "==1.15.0" ecdsa = "==0.13" "geoip2" = "==2.8.0" @@ -47,6 +48,7 @@ SQLAlchemy = "==1.2.6" XlsxWriter = "==0.9.6" numpy = "==1.14.5" "basemap" = {file = "https://github.com/matplotlib/basemap/archive/v1.2.0rel.tar.gz"} +pyproj = {git = "https://github.com/jswhit/pyproj.git"} [dev-packages] @@ -55,4 +57,4 @@ KingPhisher = "python -m king_phisher.client" KingPhisherServer = "python -m king_phisher.server" [pipenv] -allow_site_packages = true \ No newline at end of file +allow_site_packages = true diff --git a/Pipfile.lock b/Pipfile.lock index da12b197..7d313869 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "028ebb662efb648c3fb9ebe7d33e2048000cae68f95b5d846433284a6427df00" + "sha256": "26e0fb663ae3d443e3178b4cac2f62973bb65631d505a8adef0b69eab2af7b7a" }, "pipfile-spec": 6, "requires": {}, @@ -180,6 +180,40 @@ ], "version": "==0.10.0" }, + "cython": { + "hashes": [ + "sha256:019008a69e6b7c102f2ed3d733a288d1784363802b437dd2b91e6256b12746da", + "sha256:1441fe19c56c90b8c2159d7b861c31a134d543ef7886fd82a5d267f9f11f35ac", + "sha256:1d1a5e9d6ed415e75a676b72200ad67082242ec4d2d76eb7446da255ae72d3f7", + "sha256:339f5b985de3662b1d6c69991ab46fdbdc736feb4ac903ef6b8c00e14d87f4d8", + "sha256:35bdf3f48535891fee2eaade70e91d5b2cc1ee9fc2a551847c7ec18bce55a92c", + "sha256:3d0afba0aec878639608f013045697fb0969ff60b3aea2daec771ea8d01ad112", + "sha256:42c53786806e24569571a7a24ebe78ec6b364fe53e79a3f27eddd573cacd398f", + "sha256:48b919da89614d201e72fbd8247b5ae8881e296cf968feb5595a015a14c67f1f", + "sha256:49906e008eeb91912654a36c200566392bd448b87a529086694053a280f8af2d", + "sha256:49fc01a7c9c4e3c1784e9a15d162c2cac3990fcc28728227a6f8f0837aabda7c", + "sha256:501b671b639b9ca17ad303f8807deb1d0ff754d1dab106f2607d14b53cb0ff0b", + "sha256:5574574142364804423ab4428bd331a05c65f7ecfd31ac97c936f0c720fe6a53", + "sha256:6092239a772b3c6604be9e94b9ab4f0dacb7452e8ad299fd97eae0611355b679", + "sha256:71ff5c7632501c4f60edb8a24fd0a772e04c5bdca2856d978d04271b63666ef7", + "sha256:7dcf2ad14e25b05eda8bdd104f8c03a642a384aeefd25a5b51deac0826e646fa", + "sha256:8ca3a99f5a7443a6a8f83a5d8fcc11854b44e6907e92ba8640d8a8f7b9085e21", + "sha256:927da3b5710fb705aab173ad630b45a4a04c78e63dcd89411a065b2fe60e4770", + "sha256:94916d1ede67682638d3cc0feb10648ff14dc51fb7a7f147f4fedce78eaaea97", + "sha256:a3e5e5ca325527d312cdb12a4dab8b0459c458cad1c738c6f019d0d8d147081c", + "sha256:a7716a98f0b9b8f61ddb2bae7997daf546ac8fc594be6ba397f4bde7d76bfc62", + "sha256:acf10d1054de92af8d5bfc6620bb79b85f04c98214b4da7db77525bfa9fc2a89", + "sha256:de46ffb67e723975f5acab101c5235747af1e84fbbc89bf3533e2ea93fb26947", + "sha256:df428969154a9a4cd9748c7e6efd18432111fbea3d700f7376046c38c5e27081", + "sha256:f5ebf24b599caf466f9da8c4115398d663b2567b89e92f58a835e9da4f74669f", + "sha256:f79e45d5c122c4fb1fd54029bf1d475cecc05f4ed5b68136b0d6ec268bae68b6", + "sha256:f7a43097d143bd7846ffba6d2d8cd1cc97f233318dbd0f50a235ea01297a096b", + "sha256:fceb8271bc2fd3477094ca157c824e8ea840a7b393e89e766eea9a3b9ce7e0c6", + "sha256:ff919ceb40259f5332db43803aa6c22ff487e86036ce3921ae04b9185efc99a4" + ], + "index": "pypi", + "version": "==0.29" + }, "dnspython": { "hashes": [ "sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c", @@ -500,9 +534,9 @@ }, "pycairo": { "hashes": [ - "sha256:0f0a35ec923d87bc495f6753b1e540fd046d95db56a35250c44089fbce03b698" + "sha256:abd42a4c9c2069febb4c38fe74bfc4b4a9d3a89fea3bc2e4ba7baff7a20f783f" ], - "version": "==1.17.1" + "version": "==1.18.0" }, "pycparser": { "hashes": [ @@ -551,10 +585,14 @@ }, "pyparsing": { "hashes": [ - "sha256:bc6c7146b91af3f567cf6daeaec360bc07d45ffec4cf5353f4d7a208ce7ca30a", - "sha256:d29593d8ebe7b57d6967b62494f8c72b03ac0262b1eed63826c6f788b3606401" + "sha256:40856e74d4987de5d01761a22d1621ae1c7f8774585acae358aa5c5936c6c90b", + "sha256:f353aab21fd474459d97b709e527b5571314ee5f067441dc9f88e33eecd96592" ], - "version": "==2.2.2" + "version": "==2.3.0" + }, + "pyproj": { + "git": "https://github.com/jswhit/pyproj.git", + "ref": "882074864bb2e567a164624a3710907f34a4d478" }, "python-dateutil": { "hashes": [ diff --git a/tools/install.sh b/tools/install.sh index 853a587c..4f9edcf1 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -145,7 +145,8 @@ function sync_dependencies { libpng-devel postgresql-devel python3-devel python3-pip \ libffi-devel openssl-devel if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then - dnf install -y geos geos-devel gtksourceview3 vte3 gobject-introspection-devel + dnf install -y geos geos-devel gtksourceview3 gobject-introspection-devel + dnf install -y vte3 fi # Fedora 23 is missing an rpm lib required, check to see if it has been installed. if [ ! -d "$/usr/lib/rpm/redhat/redhat-hardened-cc1" ]; then @@ -155,7 +156,16 @@ function sync_dependencies { [ "$LINUX_VERSION" == "Debian" ] || \ [ "$LINUX_VERSION" == "Kali" ] || \ [ "$LINUX_VERSION" == "Ubuntu" ]; then - apt-get install -y libfreetype6-dev python3-dev python3-pip pkg-config + apt-get install -y libfreetype6-dev python3-dev pkg-config + if ! python3 -m pip --version; then + if apt-get install python3-pip; then + echo "Installed python3-pip via apt-get" + else + curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py + python3 get-pip.py + echo "Installed pip via get-pip.py" + fi + fi if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then if ! apt-get install -y gir1.2-gtk-3.0 gir1.2-gtksource-3.0 \ gir1.2-webkit-3.0 python3-cairo libgeos++-dev libgirepository1.0-dev \ @@ -200,6 +210,10 @@ function sync_dependencies { echo "INFO: Synchronizing Python package dependencies from PyPi" # six needs to be installed before requirements.txt for matplotlib + if ! python3 -m pip --version; then + echo "ERROR: Failed to find pip package for python3" + exit + fi PIP_VERSION=$(pip --version) python3 -m pip install --upgrade pip # set pip back to python2 if python2 was default From 57f1778b6860458164c8ceac018b015b82090276 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 6 Nov 2018 16:41:01 -0500 Subject: [PATCH 45/49] Updated based on comments --- tools/install.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/install.sh b/tools/install.sh index 4f9edcf1..4bfbdbf0 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -159,11 +159,10 @@ function sync_dependencies { apt-get install -y libfreetype6-dev python3-dev pkg-config if ! python3 -m pip --version; then if apt-get install python3-pip; then - echo "Installed python3-pip via apt-get" + echo "INFO: Installed python3-pip via apt-get" else - curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py - python3 get-pip.py - echo "Installed pip via get-pip.py" + curl https://bootstrap.pypa.io/get-pip.py | python3 + echo "INFO: Installed pip via get-pip.py" fi fi if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then From 059b1117515dd54d7ec0abe9738714136b1a8c8d Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Tue, 6 Nov 2018 17:06:14 -0500 Subject: [PATCH 46/49] Added comment about vte3 --- tools/install.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/install.sh b/tools/install.sh index 4bfbdbf0..f41c7520 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -146,6 +146,7 @@ function sync_dependencies { libffi-devel openssl-devel if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then dnf install -y geos geos-devel gtksourceview3 gobject-introspection-devel + # vte3 not available in Fedora29. Try to install it in case its an older versions. dnf install -y vte3 fi # Fedora 23 is missing an rpm lib required, check to see if it has been installed. From 92de70ee9854f92fedba002dfc1a356cad06290b Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 6 Nov 2018 19:12:56 -0500 Subject: [PATCH 47/49] Update a simple install version comment --- tools/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/install.sh b/tools/install.sh index f41c7520..a15507e7 100755 --- a/tools/install.sh +++ b/tools/install.sh @@ -146,7 +146,7 @@ function sync_dependencies { libffi-devel openssl-devel if [ -z "$KING_PHISHER_SKIP_CLIENT" ]; then dnf install -y geos geos-devel gtksourceview3 gobject-introspection-devel - # vte3 not available in Fedora29. Try to install it in case its an older versions. + # vte3 is not available in Fedora 29, try to install it in case it's an older version. dnf install -y vte3 fi # Fedora 23 is missing an rpm lib required, check to see if it has been installed. From f4645826fd08a463c4d356fda7ec354260032cc5 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Wed, 7 Nov 2018 10:14:54 -0500 Subject: [PATCH 48/49] Exit if pipenv is not found --- king_phisher/startup.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/king_phisher/startup.py b/king_phisher/startup.py index 97c24500..1a32a206 100644 --- a/king_phisher/startup.py +++ b/king_phisher/startup.py @@ -143,18 +143,11 @@ def pipenv_entry(parser, entry_point): logger.info('checking for the pipenv environment') if which('pipenv') is None: - logger.info('installing pipenv from PyPi using pip') - results = run_process([sys.executable, '-m', 'pip', 'install', 'pipenv'], cwd=target_directory) - if results.status: - sys.stderr.write('the following issue occurred during installation of pipenv:\n') - sys.stderr.write(results.stdout) - return results.status + logger.exception('pipenv not found, run tools/install.sh --update') + return os.EX_UNAVAILABLE pipenv_path = which('pipenv') logger.debug("pipenv path: {0!r}".format(pipenv_path)) - if not pipenv_path: - logger.exception('failed to find pipenv') - return os.EX_UNAVAILABLE if arguments.pipenv_install or not os.path.isdir(os.path.join(target_directory, '.venv')): if arguments.pipenv_install: From 5125ab827c88784ff73b4914b956764857f7f249 Mon Sep 17 00:00:00 2001 From: Erik Daguerre Date: Wed, 7 Nov 2018 14:10:22 -0500 Subject: [PATCH 49/49] Version 1.12.0 Final --- docs/source/change_log.rst | 2 +- king_phisher/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/change_log.rst b/docs/source/change_log.rst index 6a7ad903..b6e3c93b 100644 --- a/docs/source/change_log.rst +++ b/docs/source/change_log.rst @@ -11,7 +11,7 @@ Version 1.x.x Version 1.12.0 ^^^^^^^^^^^^^^ -*In Progress* +Released :release:`1.12.0` on November 7th, 2018 * Added support for users to set their email address for campaign alerts via email * Added additional plugin metadata fields for reference URLs and category classifiers diff --git a/king_phisher/version.py b/king_phisher/version.py index 1a3afd3b..e90a4510 100644 --- a/king_phisher/version.py +++ b/king_phisher/version.py @@ -79,7 +79,7 @@ def get_revision(encoding='utf-8'): version_info = collections.namedtuple('version_info', ('major', 'minor', 'micro'))(1, 12, 0) """A tuple representing the version information in the format ('major', 'minor', 'micro')""" -version_label = 'beta2' +version_label = '' """A version label such as alpha or beta.""" version = "{0}.{1}.{2}".format(version_info.major, version_info.minor, version_info.micro)