def signal_renderer_toggled_enable(self, _, path): pm = self.application.plugin_manager named_row = self._named_model(*self._model[path]) if named_row.type != _ROW_TYPE_PLUGIN: return if named_row.id not in pm.loaded_plugins: return if named_row.id in self._module_errors: gui_utilities.show_dialog_error( 'Can Not Enable Plugin', self.window, 'Can not enable a plugin which failed to load.') return if named_row.enabled: self._disable_plugin(path) else: if not pm.loaded_plugins[named_row.id].is_compatible: gui_utilities.show_dialog_error( 'Incompatible Plugin', self.window, 'This plugin is not compatible.') return if not pm.enable(named_row.id): return self._set_model_item(path, 'enabled', True) self.config['plugins.enabled'].append(named_row.id)
def do_message_data_import(self, target_file, dest_dir): config_tab = self.tabs.get('config') config_prefix = config_tab.config_prefix try: message_data = export.message_data_from_kpm(target_file, dest_dir) except KingPhisherInputValidationError as error: gui_utilities.show_dialog_error('Import Error', self.parent, error.message.capitalize() + '.') return False config_keys = set(key for key in self.config.keys() if key.startswith(config_prefix)) config_types = dict(zip(config_keys, [type(self.config[key]) for key in config_keys])) for key, value in message_data.items(): key = config_prefix + key if not key in config_keys: continue self.config[key] = value config_keys.remove(key) for unset_key in config_keys: config_type = config_types[unset_key] if not config_type in (bool, dict, int, list, str, tuple): continue self.config[unset_key] = config_type() # set missing defaults for backwards compatibility if not self.config.get('mailer.message_type'): self.config['mailer.message_type'] = 'email' if not self.config.get('mailer.target_type'): self.config['mailer.target_type'] = 'file' config_tab.objects_load_from_config() return True
def delete_campaign(self): if not gui_utilities.show_dialog_yes_no('Delete This Campaign?', self, 'This action is irreversible. All campaign data will be lost.'): return self.rpc('campaign/delete', self.config['campaign_id']) if not self.show_campaign_selection(): gui_utilities.show_dialog_error('A Campaign Must Be Selected', self, 'Now exiting') self.client_quit()
def run_quick_save(self, current_name=None): """ Display a dialog which asks the user where a file should be saved. The value of target_path in the returned dictionary is an absolute path. :param set current_name: The name of the file to save. :return: A dictionary with target_uri and target_path keys representing the path choosen. :rtype: dict """ self.set_action(Gtk.FileChooserAction.SAVE) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) self.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT) self.set_do_overwrite_confirmation(True) if current_name: self.set_current_name(current_name) self.show_all() response = self.run() if response == Gtk.ResponseType.CANCEL: return None target_path = self.get_filename() if os.path.isfile(target_path): if not os.access(target_path, os.W_OK): gui_utilities.show_dialog_error("Can not write to the selected file", self.parent) return None elif not os.access(os.path.dirname(target_path), os.W_OK): gui_utilities.show_dialog_error("Can not create the selected file", self.parent) return None target_uri = self.get_uri() return {"target_uri": target_uri, "target_path": target_path}
def _save_editor_file(self): """ Handles the save file action for the editor instance when button is pressed or when tabs are changed """ if not self.editor: self.editor_tab_save_button.set_sensitive(False) self.notebook.set_current_page(0) self.notebook.set_show_tabs(False) return buffer_contents = self.editor.sourceview_buffer.get_text( self.editor.sourceview_buffer.get_start_iter(), self.editor.sourceview_buffer.get_end_iter(), False) if buffer_contents == self.editor.file_contents: logger.debug('editor found nothing to save') self.editor_tab_save_button.set_sensitive(False) return buffer_contents = buffer_contents.encode('utf-8') try: self.editor.directory.write_file(self.editor.file_path, buffer_contents) self.editor.file_contents = buffer_contents logger.info("saved editor contents to {} file path {}".format( self.editor.file_location, self.editor.file_path)) except IOError: logger.warning("could not write to {} file: {}".format( self.editor.file_location, self.editor.file_path)) self.editor_tab_save_button.set_sensitive(False) gui_utilities.show_dialog_error( 'Permission Denied', self.application.get_active_window(), "Cannot write to {} file".format(self.editor.file_location)) return self.editor_tab_save_button.set_sensitive(False)
def prompt_and_generate(self): active_window = self.application.get_active_window() dialog_txt = 'Would you like to submit anonymized data to SecureState for research purposes?' if not gui_utilities.show_dialog_yes_no('Submit Phishing Data', active_window, dialog_txt): return get_stats = StatsGenerator(self.application.rpc) stats = get_stats.generate_stats() stats = stats.encode('utf-8') stats = bz2.compress(stats) stats = base64.b64encode(stats) stats = stats.decode('utf-8') stats = '\n'.join(textwrap.wrap(stats, width=80)) try: response = requests.post( 'https://forms.hubspot.com/uploads/form/v2/147369/f374545b-987f-44ce-82e5-889293a0e6b3', data={ 'email': '*****@*****.**', 'statistics': stats } ) assert response.ok except (AssertionError, requests.exceptions.RequestException): self.logger.error('failed to submit data', exc_info=True) gui_utilities.show_dialog_error('Error Submitting Data', active_window, 'An Error occurred while submitting the data.') return gui_utilities.show_dialog_info('Submitted Data', active_window, 'Successfully submitted anonymized phishing data.\nThank you for your support!') self.config['last_date'] = datetime.datetime.utcnow()
def interact(self): self._highlight_campaign(self.config.get('campaign_name')) self.dialog.show_all() response = self.dialog.run() old_campaign_id = self.config.get('campaign_id') old_campaign_name = self.config.get('campaign_name') while response != Gtk.ResponseType.CANCEL: treeview = self.gobjects['treeview_campaigns'] selection = treeview.get_selection() (model, tree_iter) = selection.get_selected() if tree_iter: break gui_utilities.show_dialog_error( 'No Campaign Selected', self.dialog, 'Either select a campaign or create a new one.') response = self.dialog.run() if response == Gtk.ResponseType.APPLY: campaign_id = model.get_value(tree_iter, 0) self.config['campaign_id'] = campaign_id campaign_name = model.get_value(tree_iter, 1) self.config['campaign_name'] = campaign_name if not (campaign_id == old_campaign_id and campaign_name == old_campaign_name): self.application.emit('campaign-set', campaign_id) self.dialog.destroy() return response
def _queue_file_transfer(self, task_cls, src_path, dst_path): """ Handles the file transfer by stopping bad transfers, creating tasks for transfers, and placing them in the queue. :param task_cls: The type of task the transfer will be. :param str src_path: The source path to be uploaded or downloaded. :param str dst_path: The destination path to be created and data transferred into. """ if issubclass(task_cls, tasks.DownloadTask): if not os.access(os.path.dirname(dst_path), os.W_OK): gui_utilities.show_dialog_error( 'Permission Denied', self.application.get_active_window(), 'Cannot write to the destination folder.') return local_path, remote_path = self.local.get_abspath( dst_path), self.remote.get_abspath(src_path) elif issubclass(task_cls, tasks.UploadTask): if not os.access(src_path, os.R_OK): gui_utilities.show_dialog_error( 'Permission Denied', self.application.get_active_window(), 'Cannot read the source file.') return local_path, remote_path = self.local.get_abspath( src_path), self.remote.get_abspath(dst_path) file_task = task_cls(local_path, remote_path) if isinstance(file_task, tasks.UploadTask): file_size = self.local.get_file_size(local_path) elif isinstance(file_task, tasks.DownloadTask): file_size = self.remote.get_file_size(remote_path) file_task.size = file_size self.queue.put(file_task) self.status_display.sync_view(file_task)
def make_preview(self, _): mailer_tab = self.application.main_tabs['mailer'] config_tab = mailer_tab.tabs['config'] config_tab.objects_save_to_config() input_path = self.application.config['mailer.attachment_file'] if not (os.path.isfile(input_path) and os.access(input_path, os.R_OK)): gui_utilities.show_dialog_error( 'PDF Build Error', self.application.get_active_window(), 'Attachment path is invalid or is not readable.' ) return dialog = extras.FileChooserDialog('Save Generated PDF File', self.application.get_active_window()) response = dialog.run_quick_save('PDF Preview.pdf') dialog.destroy() if response is None: return output_path = response['target_path'] if not self.process_attachment_file(input_path, output_path): return gui_utilities.show_dialog_info( 'PDF Created', self.application.get_active_window(), 'Successfully created the PDF file.' )
def prompt_and_generate(self): active_window = self.application.get_active_window() dialog_txt = 'Would you like to submit anonymized data to SecureState for research purposes?' if not gui_utilities.show_dialog_yes_no('Submit Phishing Data', active_window, dialog_txt): return get_stats = StatsGenerator(self.application.rpc) stats = get_stats.generate_stats() stats = stats.encode('utf-8') stats = bz2.compress(stats) stats = base64.b64encode(stats) stats = stats.decode('utf-8') stats = '\n'.join(textwrap.wrap(stats, width=80)) try: response = requests.post( 'https://forms.hubspot.com/uploads/form/v2/147369/f374545b-987f-44ce-82e5-889293a0e6b3', data={ 'email': '*****@*****.**', 'statistics': stats }) assert response.ok except (AssertionError, requests.exceptions.RequestException): self.logger.error('failed to submit data', exc_info=True) gui_utilities.show_dialog_error( 'Error Submitting Data', active_window, 'An Error occurred while submitting the data.') return gui_utilities.show_dialog_info( 'Submitted Data', active_window, 'Successfully submitted anonymized phishing data.\nThank you for your support!' ) self.config['last_date'] = datetime.datetime.utcnow()
def interact(self): self._highlight_campaign(self.config.get('campaign_name')) self.dialog.show_all() response = self.dialog.run() old_campaign_id = self.config.get('campaign_id') while response != Gtk.ResponseType.CANCEL: treeview = self.gobjects['treeview_campaigns'] selection = treeview.get_selection() (model, tree_iter) = selection.get_selected() if tree_iter: break gui_utilities.show_dialog_error( 'No Campaign Selected', self.dialog, 'Either select a campaign or create a new one.') response = self.dialog.run() if response == Gtk.ResponseType.APPLY: named_row = _ModelNamedRow(*model[tree_iter]) if old_campaign_id is not None and named_row.id != str( old_campaign_id): self.config['campaign_id'] = named_row.id self.config['campaign_name'] = named_row.name self.application.emit('campaign-set', old_campaign_id, named_row.id) self.dialog.destroy() return response
def _create_ssh_forwarder(self, server, username, password): """ Create and set the :py:attr:`~.KingPhisherClientApplication._ssh_forwarder` attribute. :param tuple server: The server information as a host and port tuple. :param str username: The username to authenticate to the SSH server with. :param str password: The password to authenticate to the SSH server with. :rtype: int :return: The local port that is forwarded to the remote server or None if the connection failed. """ active_window = self.get_active_window() title_ssh_error = 'Failed To Connect To The SSH Service' server_remote_port = self.config['server_remote_port'] local_port = random.randint(2000, 6000) try: self._ssh_forwarder = SSHTCPForwarder(server, username, password, local_port, ('127.0.0.1', server_remote_port), preferred_private_key=self.config['ssh_preferred_key']) self._ssh_forwarder.start() time.sleep(0.5) self.logger.info('started ssh port forwarding') except paramiko.AuthenticationException: self.logger.warning('failed to authenticate to the remote ssh server') gui_utilities.show_dialog_error(title_ssh_error, active_window, 'The server responded that the credentials are invalid.') except socket.error as error: gui_utilities.show_dialog_exc_socket_error(error, active_window, title=title_ssh_error) except Exception as error: self.logger.warning('failed to connect to the remote ssh server', exc_info=True) gui_utilities.show_dialog_error(title_ssh_error, active_window, "An {0}.{1} error occurred.".format(error.__class__.__module__, error.__class__.__name__)) else: return local_port self.server_disconnect() return
def signal_login_dialog_response(self, dialog, response): if response == Gtk.ResponseType.CANCEL or response == Gtk.ResponseType.DELETE_EVENT: dialog.destroy() self.application.emit('exit') return True self.login_dialog.objects_save_to_config() username = self.config['server_username'] password = self.config['server_password'] otp = self.config['server_one_time_password'] if not otp: otp = None _, reason = self.application.server_connect(username, password, otp) if reason == ConnectionErrorReason.ERROR_INVALID_OTP: revealer = self.login_dialog.gobjects[ 'revealer_server_one_time_password'] if revealer.get_child_revealed(): gui_utilities.show_dialog_error( 'Login Failed', self, 'A valid one time password (OTP) token is required.') else: revealer.set_reveal_child(True) entry = self.login_dialog.gobjects[ 'entry_server_one_time_password'] entry.grab_focus() elif reason == ConnectionErrorReason.ERROR_INVALID_CREDENTIALS: gui_utilities.show_dialog_error( 'Login Failed', self, 'The provided credentials are incorrect.')
def make_preview(self, _): input_path = self.application.config['mailer.attachment_file'] if not os.path.isfile(input_path) and os.access(input_path, os.R_OK): gui_utilities.show_dialog_error( 'PDF Build Error', self.application.get_active_window(), 'An invalid attachment file is specified.' ) return dialog = extras.FileChooserDialog('Save Generated PDF File', self.application.get_active_window()) response = dialog.run_quick_save('preview.pdf') dialog.destroy() if response is None: return output_path = response['target_path'] if not self.process_attachment_file(input_path, output_path): gui_utilities.show_dialog_error( 'PDF Build Error', self.application.get_active_window(), 'Failed to create the PDF file.' ) return gui_utilities.show_dialog_info( 'PDF Created', self.application.get_active_window(), 'Successfully created the PDF file.' )
def signal_menu_activate_create_folder(self, _): selection = self.treeview.get_selection() model, treeiter = selection.get_selected() if treeiter: if not self.get_is_folder(model[treeiter][2]): logger.warning('cannot create a directory under a file') gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), 'Cannot create a directory under a file.' ) return if treeiter is None: current = self._tv_model.append(treeiter, [' ', None, None, None, None, None, None]) self.rename(current) return treeiter = self._treeiter_sort_to_model(treeiter) path = self._tv_model.get_path(treeiter) if not self.treeview.row_expanded(path): self.treeview.expand_row(path, False) if self._tv_model.iter_children(treeiter) is None: # If no children, one dummy node must be created to make row expandable and the other to be used as a placemark for new folder self._tv_model.append(treeiter, [None, None, None, None, None, None, None]) current = self._tv_model.append(treeiter, [' ', None, None, None, None, None, None]) self.treeview.expand_row(path, False) else: current = self._tv_model.append(treeiter, [' ', None, None, None, None, None, None]) self.rename(current)
def _queue_file_transfer(self, task_cls, src_path, dst_path): """ Handles the file transfer by stopping bad transfers, creating tasks for transfers, and placing them in the queue. :param task_cls: The type of task the transfer will be. :param str src_path: The source path to be uploaded or downloaded. :param str dst_path: The destination path to be created and data transferred into. """ if issubclass(task_cls, tasks.DownloadTask): if not os.access(os.path.dirname(dst_path), os.W_OK): gui_utilities.show_dialog_error( 'Permission Denied', self.application.get_active_window(), 'Cannot write to the destination folder.' ) return local_path, remote_path = self.local.get_abspath(dst_path), self.remote.get_abspath(src_path) elif issubclass(task_cls, tasks.UploadTask): if not os.access(src_path, os.R_OK): gui_utilities.show_dialog_error( 'Permission Denied', self.application.get_active_window(), 'Cannot read the source file.' ) return local_path, remote_path = self.local.get_abspath(src_path), self.remote.get_abspath(dst_path) file_task = task_cls(local_path, remote_path) if isinstance(file_task, tasks.UploadTask): file_size = self.local.get_file_size(local_path) elif isinstance(file_task, tasks.DownloadTask): file_size = self.remote.get_file_size(remote_path) file_task.size = file_size self.queue.put(file_task) self.status_display.sync_view(file_task)
def initialize(self): self._color = None try: self._blink1 = blink1.Blink1() self._blink1_off() except usb.core.USBError as error: gui_utilities.show_dialog_error( 'Connection Error', self.application.get_active_window(), 'Unable to connect to the Blink(1) device.' ) return False except blink1.BlinkConnectionFailed: gui_utilities.show_dialog_error( 'Connection Error', self.application.get_active_window(), 'Unable to find the Blink(1) device.' ) return False self._gsrc_id = None if self.application.server_events is None: self.signal_connect('server-connected', lambda app: self._connect_server_events()) else: self._connect_server_events() return True
def run_quick_save(self, current_name=None): """ Display a dialog which asks the user where a file should be saved. The value of target_path in the returned dictionary is an absolute path. :param set current_name: The name of the file to save. :return: A dictionary with target_uri and target_path keys representing the path choosen. :rtype: dict """ self.set_action(Gtk.FileChooserAction.SAVE) self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) self.add_button(Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT) self.set_do_overwrite_confirmation(True) if current_name: self.set_current_name(current_name) self.show_all() response = self.run() if response == Gtk.ResponseType.CANCEL: return None target_path = self.get_filename() if os.path.isfile(target_path): if not os.access(target_path, os.W_OK): gui_utilities.show_dialog_error( 'Can not write to the selected file', self.parent) return None elif not os.access(os.path.dirname(target_path), os.W_OK): gui_utilities.show_dialog_error('Can not create the selected file', self.parent) return None target_uri = self.get_uri() return {'target_uri': target_uri, 'target_path': target_path}
def signal_menu_activate_create_folder(self, _): selection = self.treeview.get_selection() model, treeiter = selection.get_selected() if treeiter: if not self.get_is_folder(model[treeiter][2]): logger.warning('cannot create a directory under a file') gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), 'Cannot create a directory under a file.') return if treeiter is None: current = self._tv_model.append( treeiter, [' ', None, None, None, None, None, None]) self.rename(current) return treeiter = self._treeiter_sort_to_model(treeiter) path = self._tv_model.get_path(treeiter) if not self.treeview.row_expanded(path): self.treeview.expand_row(path, False) if self._tv_model.iter_children(treeiter) is None: # If no children, one dummy node must be created to make row expandable and the other to be used as a placemark for new folder self._tv_model.append(treeiter, [None, None, None, None, None, None, None]) current = self._tv_model.append( treeiter, [' ', None, None, None, None, None, None]) self.treeview.expand_row(path, False) else: current = self._tv_model.append( treeiter, [' ', None, None, None, None, None, None]) self.rename(current)
def stop_remote_service(self): if not gui_utilities.show_dialog_yes_no('Stop The Remote King Phisher Service?', self, 'This will stop the remote King Phisher service and\nnew incoming requests will not be processed.'): return self.rpc('shutdown') self.logger.info('the remote king phisher service has been stopped') gui_utilities.show_dialog_error('The Remote Service Has Been Stopped', self, 'Now exiting') self.client_quit() return
def sender_start_failure(self, message=None, text=None): if text: self.text_insert(text) self.gobjects['button_mail_sender_stop'].set_sensitive(False) self.gobjects['button_mail_sender_start'].set_sensitive(True) if message: gui_utilities.show_dialog_error(message, self.parent) self.sender_thread = None
def _plugin_enable(self, model_row): named_row = _ModelNamedRow(*model_row) pm = self.application.plugin_manager if not pm.loaded_plugins[named_row.id].is_compatible: gui_utilities.show_dialog_error('Incompatible Plugin', self.window, 'This plugin is not compatible.') return if not pm.enable(named_row.id): return self._set_model_item(model_row.path, 'enabled', True) self.config['plugins.enabled'].append(named_row.id)
def change_cwd(self, new_dir): """ Changes current working directory to given parameter. :param str new_dir: The directory to change the CWD to. :return: The absolute path of the new working directory if it was changed. :rtype: str """ if not self.path_mod.isabs(new_dir): new_dir = self.get_abspath(new_dir) if new_dir == self.cwd: return logger.debug("{} is trying to change current working directory to {}".format(self.location, new_dir)) try: self._chdir(new_dir) except OSError: logger.warning("user does not have permissions to read {}".format(new_dir)) gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), "You do not have permissions to access {}.".format(new_dir) ) return self._tv_model.clear() try: self.load_dirs(new_dir) except OSError: logger.warning("user does not have permissions to read {}".format(new_dir)) if self.cwd: self.load_dirs(self.cwd) gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), "You do not have permissions to access {}.".format(new_dir) ) else: logger.warning(self.location + " has no current working directory, using the root directory") self.default_directory = self.root_directory self.change_cwd(self.default_directory) return self.cwd = new_dir # clear and rebuild the model self._wdcb_model.clear() self._wdcb_model.append((self.root_directory,)) if self.default_directory != self.root_directory: self._wdcb_model.append((self.default_directory,)) if new_dir not in self.wd_history and gui_utilities.gtk_list_store_search(self._wdcb_model, new_dir) is None: self.wd_history.appendleft(new_dir) for directory in self.wd_history: self._wdcb_model.append((directory,)) active_iter = gui_utilities.gtk_list_store_search(self._wdcb_model, new_dir) self.wdcb_dropdown.set_active_iter(active_iter) return new_dir
def __async_rpc_cb_issue_cert_error(self, error, message=None): self._set_page_complete(True, page='Web Server URL') self.gobjects['button_url_ssl_issue_certificate'].set_sensitive(True) _set_icon(self.gobjects['image_url_ssl_status'], 'gtk-dialog-warning') label = self.gobjects['label_url_ssl_status'] label.set_text( 'An error occurred while requesting a certificate for the specified hostname' ) gui_utilities.show_dialog_error( 'Operation Error', self.application.get_active_window(), message or "Unknown error: {!r}".format(error))
def signal_menu_activate_set_working_directory(self, _): model, treeiter = self.treeview.get_selection().get_selected() if not treeiter: return if not self.get_is_folder(model[treeiter][2]): logger.warning('cannot set a file as an active working directory') gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), 'Cannot set a file the working directory.') return self.change_cwd(model[treeiter][2])
def import_message_data(self): """ Process a previously exported message archive file and restore the message data, settings, and applicable files from it. """ config_tab = self.tabs.get("config") if not config_tab: self.logger.warning("attempted to import message data while the config tab was unavailable") return config_prefix = config_tab.config_prefix config_tab.objects_save_to_config() dialog = extras.FileChooserDialog("Import Message Configuration", self.parent) dialog.quick_add_filter("King Phisher Message Files", "*.kpm") dialog.quick_add_filter("All Files", "*") response = dialog.run_quick_open() dialog.destroy() if not response: return target_file = response["target_path"] dialog = extras.FileChooserDialog("Destination Directory", self.parent) response = dialog.run_quick_select_directory() dialog.destroy() if not response: return dest_dir = response["target_path"] try: message_data = export.message_data_from_kpm(target_file, dest_dir) except KingPhisherInputValidationError as error: gui_utilities.show_dialog_error("Import Error", self.parent, error.message.capitalize() + ".") return config_keys = set(key for key in self.config.keys() if key.startswith(config_prefix)) config_types = dict(zip(config_keys, [type(self.config[key]) for key in config_keys])) for key, value in message_data.items(): key = config_prefix + key if not key in config_keys: continue self.config[key] = value config_keys.remove(key) for unset_key in config_keys: config_type = config_types[unset_key] if not config_type in (bool, dict, int, list, str, tuple): continue self.config[unset_key] = config_type() # set missing defaults for backwards compatibility if not self.config.get("mailer.message_type"): self.config["mailer.message_type"] = "email" if not self.config.get("mailer.target_type"): self.config["mailer.target_type"] = "file" config_tab.objects_load_from_config() gui_utilities.show_dialog_info("Success", self.parent, "Successfully imported the message.")
def wrapper(self, *args, **kwargs): try: function(self, *args, **kwargs) except (IOError, OSError) as error: logger.error('an exception occurred during an operation', exc_info=True) err_message = "An error occured: {0}".format(error) gui_utilities.show_dialog_error( 'Error', self.application.get_active_window(), err_message) return False return True
def do_campaign_delete(self, campaign_id): """ Delete the campaign on the server. A confirmation dialog will be displayed before the operation is performed. If the campaign is deleted and a new campaign is not selected with :py:meth:`.show_campaign_selection`, the client will quit. """ self.rpc('db/table/delete', 'campaigns', campaign_id) if campaign_id == self.config['campaign_id'] and not self.show_campaign_selection(): gui_utilities.show_dialog_error('Now Exiting', self.get_active_window(), 'A campaign must be selected.') self.quit()
def import_message_data(self): """ Process a previously exported message archive file and restore the message data, settings, and applicable files from it. """ config_tab = self.tabs.get('config') if not config_tab: self.logger.warning( 'attempted to import message data while the config tab was unavailable' ) return config_prefix = config_tab.config_prefix config_tab.objects_save_to_config() dialog = gui_utilities.UtilityFileChooser( 'Import Message Configuration', self.parent) dialog.quick_add_filter('King Phisher Message Files', '*.kpm') dialog.quick_add_filter('All Files', '*') response = dialog.run_quick_open() dialog.destroy() if not response: return target_file = response['target_path'] dialog = gui_utilities.UtilityFileChooser('Destination Directory', self.parent) response = dialog.run_quick_select_directory() dialog.destroy() if not response: return dest_dir = response['target_path'] try: message_data = export.message_data_from_kpm(target_file, dest_dir) except KingPhisherInputValidationError as error: gui_utilities.show_dialog_error('Import Error', self.parent, error.message.capitalize() + '.') return config_keys = set( filter(lambda k: k.startswith(config_prefix), self.config.keys())) config_types = dict(zip(config_keys, map(type, config_keys))) for key, value in message_data.items(): key = config_prefix + key if not key in config_keys: continue self.config[key] = value config_keys.remove(key) for unset_key in config_keys: config_type = config_types[unset_key] if not config_type in (bool, dict, int, list, str, tuple): continue self.config[unset_key] = config_type() config_tab.objects_load_from_config() gui_utilities.show_dialog_info('Successfully imported the message', self.parent)
def signal_popup_menu_activate_update(self, _): model_row = self._selected_model_row named_row = None if model_row is None else _ModelNamedRow(*model_row) if named_row is None: return if not (named_row.type == _ROW_TYPE_PLUGIN and named_row.installed and named_row.sensitive_installed): return if not self._plugin_uninstall(model_row): gui_utilities.show_dialog_error('Update Failed', self.window, 'Failed to uninstall the existing plugin data.') return self._plugin_install(model_row)
def signal_renderer_edited(self, cell, path, property_value, details): tag_table, store_id, property_name = details model = self.gobjects['treeview_' + tag_table].get_model() model_iter = model.get_iter(path) tag_id = model.get_value(model_iter, 0) try: self.application.rpc('db/table/set', tag_table, tag_id, (property_name,), (property_value,)) except AdvancedHTTPServer.AdvancedHTTPServerRPCError: gui_utilities.show_dialog_error('Failed To Modify', self.dialog, 'An error occurred while modifying the information.') else: model.set_value(model_iter, store_id, property_value)
def __init__(self, config, parent): self.config = config self.parent = parent self.logger = logging.getLogger('KingPhisher.Client.' + self.__class__.__name__) if not has_vte: gui_utilities.show_dialog_error('RPC terminal is unavailable', parent, 'VTE is not installed') return self.window = Gtk.Window() self.window.set_property('title', 'King Phisher RPC') self.window.set_transient_for(parent) self.window.set_destroy_with_parent(True) self.window.connect('destroy', self.signal_window_destroy) self.terminal = Vte.Terminal() self.terminal.set_scroll_on_keystroke(True) vbox = Gtk.VBox() self.window.add(vbox) vbox.pack_end(self.terminal, True, True, 0) action_group = Gtk.ActionGroup("rpc_terminal_window_actions") self._add_menu_actions(action_group) uimanager = self._create_ui_manager() uimanager.insert_action_group(action_group) menubar = uimanager.get_widget("/MenuBar") vbox.pack_start(menubar, False, False, 0) rpc = self.parent.rpc config = {} config['campaign_id'] = self.config['campaign_id'] config['campaign_name'] = self.config['campaign_name'] config['rpc_data'] = { 'address': (rpc.host, rpc.port), 'use_ssl': rpc.use_ssl, 'username': rpc.username, 'uri_base': rpc.uri_base, 'hmac_key': rpc.hmac_key, } config = json.dumps(config) argv = [] argv.append(utilities.which('python')) argv.append('-c') argv.append("import {0}; {0}.{1}.child_routine('{2}')".format(self.__module__, self.__class__.__name__, config)) _, child_pid = self.terminal.fork_command_full(Vte.PtyFlags.DEFAULT, os.getcwd(), argv, None, GLib.SpawnFlags.DEFAULT, None, None) self.logger.info("vte spawned child process with pid: {0}".format(child_pid)) self.child_pid = child_pid self.terminal.connect('child-exited', lambda vt: self.window.destroy()) self.window.show_all() # Automatically enter the password vte_pty = self.terminal.get_pty_object() vte_pty_fd = vte_pty.get_fd() if len(select.select([vte_pty_fd], [], [], 0.5)[0]): os.write(vte_pty_fd, rpc.password + '\n') return
def campaign_delete(self): """ Delete the campaign on the server. A confirmation dialog will be displayed before the operation is performed. If the campaign is deleted and a new campaign is not selected with :py:meth:`.show_campaign_selection`, the client will quit. """ if not gui_utilities.show_dialog_yes_no('Delete This Campaign?', self.get_active_window(), 'This action is irreversible, all campaign data will be lost.'): return self.rpc('db/table/delete', 'campaigns', self.config['campaign_id']) if not self.show_campaign_selection(): gui_utilities.show_dialog_error('Now Exiting', self.get_active_window(), 'A campaign must be selected.') self.quit()
def initialize(self): if not os.access(gtk_builder_file, os.R_OK): gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), "The GTK Builder data file ({0}) is not available.".format(os.path.basename(gtk_builder_file)) ) return False self.menu_items = {} self.add_submenu('Tools > TOTP Self Enrollment') self.menu_items['setup'] = self.add_menu_item('Tools > TOTP Self Enrollment > Setup', self.enrollment_setup) self.menu_items['remove'] = self.add_menu_item('Tools > TOTP Self Enrollment > Remove', self.enrollment_remove) return True
def signal_menu_activate_set_working_directory(self, _): model, treeiter = self.treeview.get_selection().get_selected() if not treeiter: return if not self.get_is_folder(model[treeiter][2]): logger.warning('cannot set a file as an active working directory') gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), 'Cannot set a file the working directory.' ) return self.change_cwd(model[treeiter][2])
def expand_path(self, output_file, *args, **kwargs): expanded_path = _expand_path(output_file, *args, **kwargs) try: expanded_path = mailer.render_message_template(expanded_path, self.application.config) except jinja2.exceptions.TemplateSyntaxError as error: self.logger.error("jinja2 syntax error ({0}) in directory: {1}".format(error.message, output_file)) gui_utilities.show_dialog_error('Error', self.application.get_active_window(), 'Error creating the HTML file.') return None except ValueError as error: self.logger.error("value error ({0}) in directory: {1}".format(error, output_file)) gui_utilities.show_dialog_error('Error', self.application.get_active_window(), 'Error creating the HTML file.') return None return expanded_path
def wrapper(self, *args, **kwargs): try: function(self, *args, **kwargs) except (IOError, OSError) as error: logger.error('an exception occurred during an operation', exc_info=True) err_message = "An error occured: {0}".format(error) gui_utilities.show_dialog_error( 'Error', self.application.get_active_window(), err_message ) return False return True
def signal_popup_menu_activate_update(self, _): model_row = self._selected_model_row named_row = None if model_row is None else _ModelNamedRow(*model_row) if named_row is None: return if not (named_row.type == _ROW_TYPE_PLUGIN and named_row.installed and named_row.sensitive_installed): return if not self._plugin_uninstall(model_row): gui_utilities.show_dialog_error( 'Update Failed', self.window, 'Failed to uninstall the existing plugin data.') return self._plugin_install(model_row)
def signal_button_clicked(self, button): campaign_name_entry = self.gobjects['entry_new_campaign_name'] campaign_name = campaign_name_entry.get_property('text') if not campaign_name: gui_utilities.show_dialog_warning('Invalid Campaign Name', self.dialog, 'Please specify a new campaign name') return try: self.parent.rpc('campaign/new', campaign_name) except AdvancedHTTPServer.AdvancedHTTPServerRPCError: gui_utilities.show_dialog_error('Failed To Create New Campaign', self.dialog, 'Encountered an error creating the new campaign') return campaign_name_entry.set_property('text', '') self.load_campaigns() self._highlight_campaign(campaign_name)
def _create_ssh_forwarder(self, server, username, password, window=None): """ Create and set the :py:attr:`~.KingPhisherClientApplication._ssh_forwarder` attribute. :param tuple server: The server information as a host and port tuple. :param str username: The username to authenticate to the SSH server with. :param str password: The password to authenticate to the SSH server with. :param window: The GTK window to use as the parent for error dialogs. :type window: :py:class:`Gtk.Window` :rtype: int :return: The local port that is forwarded to the remote server or None if the connection failed. """ window = window or self.get_active_window() title_ssh_error = 'Failed To Connect To The SSH Service' server_remote_port = self.config['server_remote_port'] try: self._ssh_forwarder = ssh_forward.SSHTCPForwarder( server, username, password, ('127.0.0.1', server_remote_port), private_key=self.config.get('ssh_preferred_key'), missing_host_key_policy=ssh_host_key.MissingHostKeyPolicy( self)) self._ssh_forwarder.start() except ssh_forward.KingPhisherSSHKeyError as error: gui_utilities.show_dialog_error('SSH Key Configuration Error', window, error.message) except errors.KingPhisherAbortError as error: self.logger.info("ssh connection aborted ({0})".format( error.message)) except paramiko.PasswordRequiredException: gui_utilities.show_dialog_error( title_ssh_error, window, 'The specified SSH key requires a password.') except paramiko.AuthenticationException: self.logger.warning( 'failed to authenticate to the remote ssh server') gui_utilities.show_dialog_error( title_ssh_error, window, 'The server responded that the credentials are invalid.') except paramiko.SSHException as error: self.logger.warning("failed with ssh exception '{0}'".format( error.args[0])) except socket.error as error: gui_utilities.show_dialog_exc_socket_error(error, window, title=title_ssh_error) except Exception as error: self.logger.warning('failed to connect to the remote ssh server', exc_info=True) gui_utilities.show_dialog_error( title_ssh_error, window, "An {0}.{1} error occurred.".format(error.__class__.__module__, error.__class__.__name__)) else: return self._ssh_forwarder.local_server self.emit('server-disconnected') return
def signal_button_clicked(self, button): campaign_name_entry = self.gobjects['entry_new_campaign_name'] campaign_name = campaign_name_entry.get_property('text') if not campaign_name: gui_utilities.show_dialog_warning('Invalid Campaign Name', self.dialog, 'Please specify a new campaign name') return try: self.parent.rpc('campaign/new', campaign_name) except: gui_utilities.show_dialog_error('Failed To Create New Campaign', self.dialog, 'Encountered an error creating the new campaign') return campaign_name_entry.set_property('text', '') self.load_campaigns() self._highlight_campaign(campaign_name)
def signal_menuitem_activate_import_config(self, _): dialog = extras.FileChooserDialog('Import Configuration File', self.dialog) response = dialog.run_quick_open() dialog.destroy() if response is None: return config_path = response['target_path'] try: self.application.merge_config(config_path, strict=False) except Exception: self.logger.warning('failed to merge configuration file: ' + config_path, exc_info=True) gui_utilities.show_dialog_error('Invalid Configuration File', self.dialog, 'Could not import the configuration file.') else: self.objects_load_from_config()
def stop_remote_service(self): """ Stop the remote King Phisher server. This will request that the server stop processing new requests and exit. This will display a confirmation dialog before performing the operation. If the remote service is stopped, the client will quit. """ if not gui_utilities.show_dialog_yes_no('Stop The Remote King Phisher Service?', self.get_active_window(), 'This will stop the remote King Phisher service and\nnew incoming requests will not be processed.'): return self.rpc('shutdown') self.logger.info('the remote king phisher service has been stopped') gui_utilities.show_dialog_error('Now Exiting', self.get_active_window(), 'The remote service has been stopped.') self.quit() return
def interact(self): self.dialog.show_all() self.set_status('Waiting') if not web_cloner.has_webkit2: gui_utilities.show_dialog_error('WebKit2GTK+ Is Unavailable', self.dialog, 'The WebKit2GTK+ package is not available.') self.dialog.destroy() return while self.dialog.run() == Gtk.ResponseType.APPLY: target_url = self.entry_target.get_text() if not target_url: gui_utilities.show_dialog_error('Missing Information', self.dialog, 'Please set the target URL.') self.set_status('Missing Information') continue dest_dir = self.entry_directory.get_text() if not dest_dir: gui_utilities.show_dialog_error('Missing Information', self.dialog, 'Please set the destination directory.') self.set_status('Missing Information') continue if not os.access(dest_dir, os.W_OK): gui_utilities.show_dialog_error('Invalid Directory', self.dialog, 'Can not write to the specified directory.') self.set_status('Invalid Directory') continue self.objects_save_to_config() self.set_status('Cloning', spinner_active=True) cloner = web_cloner.WebPageCloner(target_url, dest_dir) signal_id = self.button_cancel.connect('clicked', lambda _: cloner.stop_cloning()) original_label = self.button_cancel.get_label() self.button_cancel.set_label('Cancel') cloner.wait() self.button_cancel.set_label(original_label) self.button_cancel.disconnect(signal_id) if cloner.load_failed: self.set_status('Failed') gui_utilities.show_dialog_error('Operation Failed', self.dialog, 'The web page clone operation failed.') continue for resource in cloner.cloned_resources.values(): if gui_utilities.gtk_list_store_search(self.resources, resource.resource, column=0): continue self.resources.append(_ModelNamedRow( path=resource.resource, mime_type=resource.mime_type or 'N/A', size=resource.size )) self.set_status('Done') gui_utilities.gtk_sync() if len(self.resources) and gui_utilities.show_dialog_yes_no('Transfer Cloned Pages', self.dialog, 'Would you like to start the SFTP client\nto upload the cloned pages?'): self.application.emit('sftp-client-start') self.dialog.destroy()
def stop_remote_service(self): """ Stop the remote King Phisher server. This will request that the server stop processing new requests and exit. This will display a confirmation dialog before performing the operation. If the remote service is stopped, the client will quit. """ active_window = self.get_active_window() if not gui_utilities.show_dialog_yes_no('Stop The Remote King Phisher Service?', active_window, 'This will stop the remote King Phisher service and\nnew incoming requests will not be processed.'): return self.rpc('shutdown') self.logger.info('the remote king phisher service has been stopped') gui_utilities.show_dialog_error('Now Exiting', active_window, 'The remote service has been stopped.') self.quit() return
def signal_renderer_toggled_enable(self, _, path): model_row = self._model[path] named_row = _ModelNamedRow(*model_row) if named_row.type != _ROW_TYPE_PLUGIN: return if named_row.id not in self.application.plugin_manager.loaded_plugins: return if named_row.id in self.__load_errors: gui_utilities.show_dialog_error('Can Not Enable Plugin', self.window, 'Can not enable a plugin which failed to load.') return if named_row.enabled: self._plugin_disable(model_row) else: self._plugin_enable(model_row)
def signal_renderer_edited(self, cell, path, property_value, details): tag_table, store_id, property_name = details model = self.gobjects['treeview_' + tag_table].get_model() model_iter = model.get_iter(path) tag_id = model.get_value(model_iter, 0) try: self.application.rpc('db/table/set', tag_table, tag_id, (property_name, ), (property_value, )) except AdvancedHTTPServer.AdvancedHTTPServerRPCError: gui_utilities.show_dialog_error( 'Failed To Modify', self.dialog, 'An error occurred while modifying the information.') else: model.set_value(model_iter, store_id, property_value)
def import_message_data(self): """ Process a previously exported message archive file and restore the message data, settings, and applicable files from it. """ config_tab = self.tabs.get('config') if not config_tab: self.logger.warning('attempted to import message data while the config tab was unavailable') return config_prefix = config_tab.config_prefix config_tab.objects_save_to_config() dialog = gui_utilities.FileChooser('Import Message Configuration', self.parent) dialog.quick_add_filter('King Phisher Message Files', '*.kpm') dialog.quick_add_filter('All Files', '*') response = dialog.run_quick_open() dialog.destroy() if not response: return target_file = response['target_path'] dialog = gui_utilities.FileChooser('Destination Directory', self.parent) response = dialog.run_quick_select_directory() dialog.destroy() if not response: return dest_dir = response['target_path'] try: message_data = export.message_data_from_kpm(target_file, dest_dir) except KingPhisherInputValidationError as error: gui_utilities.show_dialog_error('Import Error', self.parent, error.message.capitalize() + '.') return config_keys = set(key for key in self.config.keys() if key.startswith(config_prefix)) config_types = dict(zip(config_keys, map(type, config_keys))) for key, value in message_data.items(): key = config_prefix + key if not key in config_keys: continue self.config[key] = value config_keys.remove(key) for unset_key in config_keys: config_type = config_types[unset_key] if not config_type in (bool, dict, int, list, str, tuple): continue self.config[unset_key] = config_type() config_tab.objects_load_from_config() gui_utilities.show_dialog_info('Success', self.parent, 'Successfully imported the message.')
def delete_campaign(self): """ Delete the campaign on the server. A confirmation dialog will be displayed before the operation is performed. If the campaign is deleted and a new campaign is not selected with :py:meth:`.show_campaign_selection`, the client will quit. """ if not gui_utilities.show_dialog_yes_no( 'Delete This Campaign?', self, 'This action is irreversible, all campaign data will be lost.' ): return self.rpc('campaign/delete', self.config['campaign_id']) if not self.show_campaign_selection(): gui_utilities.show_dialog_error('Now Exiting', self, 'A campaign must be selected.') self.client_quit()
def signal_renderer_toggled_enable(self, _, path): model_row = self._model[path] named_row = _ModelNamedRow(*model_row) if named_row.type != _ROW_TYPE_PLUGIN: return if named_row.id not in self.application.plugin_manager.loaded_plugins: return if named_row.id in self.__load_errors: gui_utilities.show_dialog_error( 'Can Not Enable Plugin', self.window, 'Can not enable a plugin which failed to load.') return if named_row.enabled: self._plugin_disable(model_row) else: self._plugin_enable(model_row)
def _create_ssh_forwarder(self, server, username, password): """ Create and set the :py:attr:`~.KingPhisherClient._ssh_forwarder` attribute. :param tuple server: The server information as a host and port tuple. :param str username: The username to authenticate to the SSH server with. :param str password: The password to authenticate to the SSH server with. :rtype: int :return: The local port that is forwarded to the remote server or None if the connection failed. """ title_ssh_error = 'Failed To Connect To The SSH Service' server_remote_port = self.config['server_remote_port'] local_port = random.randint(2000, 6000) try: self._ssh_forwarder = SSHTCPForwarder( server, username, password, local_port, ('127.0.0.1', server_remote_port), preferred_private_key=self.config['ssh_preferred_key']) self._ssh_forwarder.start() time.sleep(0.5) self.logger.info('started ssh port forwarding') except paramiko.AuthenticationException: self.logger.warning( 'failed to authenticate to the remote ssh server') gui_utilities.show_dialog_error( title_ssh_error, self, 'The server responded that the credentials are invalid.') except socket.error as error: gui_utilities.show_dialog_exc_socket_error(error, self, title=title_ssh_error) except Exception as error: self.logger.warning('failed to connect to the remote ssh server', exc_info=True) gui_utilities.show_dialog_error( title_ssh_error, self, "An {0}.{1} error occurred.".format(error.__class__.__module__, error.__class__.__name__)) else: return local_port self.server_disconnect() return
def initialize(self): """Connects to the start SFTP Client Signal to the plugin and checks for .ui file.""" self.sftp_window = None if not os.access(sftp_utilities.gtk_builder_file, os.R_OK): gui_utilities.show_dialog_error( 'Plugin Error', self.application.get_active_window(), "The GTK Builder data file ({0}) is not available.".format( os.path.basename(sftp_utilities.gtk_builder_file))) return False if 'directories' not in self.config: self.config['directories'] = {} if 'transfer_hidden' not in self.config: self.config['transfer_hidden'] = False if 'show_hidden' not in self.config: self.config['show_hidden'] = False self.signal_connect('sftp-client-start', self.signal_sftp_start) return True
def signal_renderer_toggled(self, _, path): pm = self.application.plugin_manager name = self._model[path][0] # pylint: disable=unsubscriptable-object if name in self._module_errors: gui_utilities.show_dialog_error('Can Not Enable Plugin', self.window, 'Can not enable a plugin which failed to load.') return if self._model[path][1]: # pylint: disable=unsubscriptable-object pm.disable(name) self._model[path][1] = False # pylint: disable=unsubscriptable-object self.config['plugins.enabled'].remove(name) else: if not pm.loaded_plugins[name].is_compatible: gui_utilities.show_dialog_error('Incompatible Plugin', self.window, 'This plugin is not compatible.') return if not pm.enable(name): return self._model[path][1] = True # pylint: disable=unsubscriptable-object self.config['plugins.enabled'].append(name)