def signal_kpm_select_clicked(self, _): 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 False target_path = response['target_path'] self.gobjects['entry_kpm_file'].set_text(target_path) self._set_page_complete(self._get_kpm_path().is_valid) if not _kpm_file_path_is_valid(target_path): return # open the KPM for reading to extract the target URL for the assistant, # ignore the directory to allow the user to optionally only import the URL kpm = archive.ArchiveFile(target_path, 'r') if not kpm.has_file('message_config.json'): self.logger.warning( 'the kpm archive is missing the message_config.json file') return message_config = kpm.get_json('message_config.json') webserver_url = message_config.get('webserver_url') if not webserver_url: return self._set_webserver_url(webserver_url)
def import_database(target_file, clear=True): """ Import the contents of a serialized database from an archive previously created with the :py:func:`.export_database` function. The current :py:data:`~king_phisher.server.database.models.SCHEMA_VERSION` must be the same as the exported archive. .. warning:: This will by default delete the contents of the current database in accordance with the *clear* parameter. If *clear* is not specified and objects in the database and import share an ID, they will be merged. :param str target_file: The database archive file to import from. :param bool clear: Whether or not to delete the contents of the existing database before importing the new data. """ kpdb = archive.ArchiveFile(target_file, 'r') schema_version = kpdb.metadata['database-schema'] if schema_version != models.SCHEMA_VERSION: raise errors.KingPhisherDatabaseError( "incompatible database schema versions ({0} vs {1})".format( schema_version, models.SCHEMA_VERSION)) if clear: clear_database() session = Session() for table in models.metadata.sorted_tables: table_data = kpdb.get_data('tables/' + table.name) for row in sqlalchemy.ext.serializer.loads(table_data): session.merge(row) session.commit() kpdb.close()
def message_data_to_kpm(message_config, target_file, encoding='utf-8'): """ Save details describing a message to the target file. :param dict message_config: The message details from the client configuration. :param str target_file: The file to write the data to. :param str encoding: The encoding to use for strings. """ message_config = copy.copy(message_config) kpm = archive.ArchiveFile(target_file, 'w') for config_name, file_name in KPM_ARCHIVE_FILES.items(): if os.access(message_config.get(config_name, ''), os.R_OK): kpm.add_file(file_name, message_config[config_name]) message_config[config_name] = os.path.basename( message_config[config_name]) continue if len(message_config.get(config_name, '')): logger.info( "the specified {0} '{1}' is not readable, the setting will be removed" .format(config_name, message_config[config_name])) if config_name in message_config: del message_config[config_name] if os.access(message_config.get('html_file', ''), os.R_OK): with codecs.open(message_config['html_file'], 'r', encoding=encoding) as file_h: template = file_h.read() message_config['html_file'] = os.path.basename( message_config['html_file']) template, attachments = message_template_to_kpm(template) logger.debug("identified {0} attachment file{1} to be archived".format( len(attachments), 's' if len(attachments) > 1 else '')) kpm.add_data('message_content.html', template) for attachment in attachments: if os.access(attachment, os.R_OK): kpm.add_file( os.path.join('attachments', os.path.basename(attachment)), attachment) else: if len(message_config.get('html_file', '')): logger.info( "the specified html_file '{0}' is not readable, the setting will be removed" .format(message_config['html_file'])) if 'html_file' in message_config: del message_config['html_file'] kpm.add_data('message_config.json', serializers.JSON.dumps(message_config, pretty=True)) kpm.close() return
def export_database(target_file): """ Export the contents of the database using SQLAlchemy's serialization. This creates an archive file containing all of the tables and their data. The resulting export can be imported into another supported database so long as the :py:data:`~king_phisher.server.database.models.SCHEMA_VERSION` is the same. :param str target_file: The file to write the export to. """ session = Session() kpdb = archive.ArchiveFile(target_file, 'w') kpdb.metadata['database-schema'] = models.SCHEMA_VERSION for table in models.metadata.sorted_tables: table_name = table.name table = models.database_table_objects[table_name] kpdb.add_data('tables/' + table_name, sqlalchemy.ext.serializer.dumps(session.query(table).all())) kpdb.close()
def message_data_from_kpm(target_file, dest_dir, encoding='utf-8'): """ Retrieve the stored details describing a message from a previously exported file. :param str target_file: The file to load as a message archive. :param str dest_dir: The directory to extract data and attachment files to. :param str encoding: The encoding to use for strings. :return: The restored details from the message config. :rtype: dict """ if not archive.is_archive(target_file): logger.warning('the file is not recognized as a valid archive') raise errors.KingPhisherInputValidationError('file is not in the correct format') kpm = archive.ArchiveFile(target_file, 'r') attachment_member_names = [n for n in kpm.file_names if n.startswith('attachments' + os.path.sep)] attachments = [] if not kpm.has_file('message_config.json'): logger.warning('the kpm archive is missing the message_config.json file') raise errors.KingPhisherInputValidationError('data is missing from the message archive') message_config = kpm.get_json('message_config.json') message_config.pop('company_name', None) if attachment_member_names: attachment_dir = os.path.join(dest_dir, 'attachments') if not os.path.isdir(attachment_dir): os.mkdir(attachment_dir) for file_name in attachment_member_names: arcfile_h = kpm.get_file(file_name) file_path = os.path.join(attachment_dir, os.path.basename(file_name)) with open(file_path, 'wb') as file_h: shutil.copyfileobj(arcfile_h, file_h) attachments.append(file_path) logger.debug("extracted {0} attachment file{1} from the archive".format(len(attachments), 's' if len(attachments) > 1 else '')) for config_name, file_name in KPM_ARCHIVE_FILES.items(): if not file_name in kpm.file_names: if config_name in message_config: logger.warning("the kpm archive is missing the {0} file".format(file_name)) raise errors.KingPhisherInputValidationError('data is missing from the message archive') continue if not message_config.get(config_name): logger.warning("the kpm message configuration is missing the {0} setting".format(config_name)) raise errors.KingPhisherInputValidationError('data is missing from the message archive') arcfile_h = kpm.get_file(file_name) file_path = os.path.join(dest_dir, os.path.basename(message_config[config_name])) with open(file_path, 'wb') as file_h: shutil.copyfileobj(arcfile_h, file_h) message_config[config_name] = file_path if 'message_content.html' in kpm.file_names: if 'html_file' not in message_config: logger.warning('the kpm message configuration is missing the html_file setting') raise errors.KingPhisherInputValidationError('data is missing from the message archive') arcfile_h = kpm.get_file('message_content.html') file_path = os.path.join(dest_dir, os.path.basename(message_config['html_file'])) with open(file_path, 'wb') as file_h: file_h.write(message_template_from_kpm(arcfile_h.read().decode(encoding), attachments).encode(encoding)) message_config['html_file'] = file_path elif 'html_file' in message_config: logger.warning('the kpm archive is missing the message_content.html file') raise errors.KingPhisherInputValidationError('data is missing from the message archive') kpm.close() return message_config