From 3f1f69688059e88f898044dcb4dbfa438ced0889 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Mon, 4 Jan 2016 17:10:51 -0500 Subject: [PATCH 1/2] First draft of VMExtractor module prototype --- .../modules/vmextractor/Bundle.properties | 3 + .../vmextractor/VMExtractorIngestModule.java | 183 ++++++++++++++++++ .../VMExtractorIngestModuleFactory.java | 84 ++++++++ 3 files changed, 270 insertions(+) create mode 100644 Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties create mode 100644 Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java create mode 100644 Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties new file mode 100644 index 0000000000..e14e90fdc6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties @@ -0,0 +1,3 @@ +VMExtractorIngestModuleFactory.moduleDisplayName=Virtual Machine Extractor +VMExtractorIngestModuleFactory.moduleDescription=Extracts virtual machine files and adds them to a case as data sources. +VMExtractorIngestModuleFactory.version=1.0 \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java new file mode 100644 index 0000000000..42a8987386 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java @@ -0,0 +1,183 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2012-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.vmextractor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.logging.Level; +import javax.annotation.concurrent.Immutable; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleAdapter; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * An ingest module that extracts virtual machine files and adds them to a case + * as data sources. + */ +@Immutable +final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { + + private static final Logger logger = Logger.getLogger(VMExtractorIngestModule.class.getName()); + private final List vmImages = new ArrayList<>(); + + /** + * @inheritDoc + */ + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + try { + List vmFiles = Case.getCurrentCase().getServices().getFileManager().findFiles(dataSource, "%.img"); + // RJCTODO: Progress bar set up + for (AbstractFile file : vmFiles) { + try { + ingestVirtualMachineImage(file); + // RJCTODO: Progress bar update + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Interrupted while adding image", ex); // RJCTODO: Improve logging + } catch (IOException ex) { + logger.log(Level.INFO, "Unable to save VM file to disk", ex); // RJCTODO: Improve logging + } + } + return ProcessResult.OK; + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error querying case database", ex); // RJCTODO: Improve logging + return ProcessResult.ERROR; + } + } + + /** + * RJCTODO + * + * @param vmFile + */ + private void ingestVirtualMachineImage(AbstractFile vmFile) throws InterruptedException, IOException { + /* + * Write the virtual machine file to disk. + */ + String imageFileName = vmFile.getName() + "_" + vmFile.getId(); + Path imageFilePath = Paths.get(Case.getCurrentCase().getModuleDirectory(), imageFileName); + File imageFile = imageFilePath.toFile(); + ContentUtils.writeToFile(vmFile, imageFile); + + /* + * Try to add the virtual machine file to the case as an image. + */ + vmImages.clear(); + UUID taskId = UUID.randomUUID(); + Case.getCurrentCase().notifyAddingDataSource(taskId); + ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); + dataSourceProcessor.setDataSourceOptions(imageFile.getAbsolutePath(), "", false); // RJCTODO: Setting for FAT orphans? + synchronized (this) { + dataSourceProcessor.run(new AddDataSourceProgressMonitor(), new AddDataSourceCallback()); + this.wait(); + } + + /* + * If the image was added, analyze it with the ingest modules for this + * ingest context. + */ + if (!vmImages.isEmpty()) { + Case.getCurrentCase().notifyDataSourceAdded(vmImages.get(0), taskId); + List images = new ArrayList<>(vmImages); + IngestJobSettings ingestJobSettings = new IngestJobSettings(RunIngestModulesDialog.class.getCanonicalName()); // RJCTODO: Problem to solve, context string sharing! + for (String warning : ingestJobSettings.getWarnings()) { + logger.log(Level.WARNING, warning); + } + IngestManager.getInstance().queueIngestJob(images, ingestJobSettings); + } else { + Case.getCurrentCase().notifyFailedAddingDataSource(taskId); + // RJCTODO: Some logging here + } + } + + /** + * RJCTODO + */ + // RJCTODO: Consider implementing in terms of the ingest progress monitor + private static final class AddDataSourceProgressMonitor implements DataSourceProcessorProgressMonitor { + + @Override + public void setIndeterminate(final boolean indeterminate) { + } + + @Override + public void setProgress(final int progress) { + } + + @Override + public void setProgressText(final String text) { + } + + } + + /** + * A callback for the data source processor. + */ + private final class AddDataSourceCallback extends DataSourceProcessorCallback { + + /** + * @inheritDoc + */ + @Override + public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List content) { + /* + * Save a reference to the content object so it can be used to + * create a new ingest job. + */ + if (!content.isEmpty()) { + vmImages.add(content.get(0)); + } + + // RJCTODO: Log errors if any + + /* + * Unblock the processing thread. + */ + synchronized (VMExtractorIngestModule.this) { + VMExtractorIngestModule.this.notify(); + } + } + + /** + * @inheritDoc + */ + @Override + public void doneEDT(DataSourceProcessorResult result, List errList, List newContents) { + done(result, errList, newContents); + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java new file mode 100644 index 0000000000..c853dbd488 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModuleFactory.java @@ -0,0 +1,84 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2012-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.modules.vmextractor; + +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * An factory for ingest modules that extracts virtual machine files and adds + * them to a case as data sources. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public final class VMExtractorIngestModuleFactory extends IngestModuleFactoryAdapter { + + /** + * Gets the module name. + * + * @return The module name as a string. + */ + static String getModuleName() { + return NbBundle.getMessage(VMExtractorIngestModuleFactory.class, "VMExtractorIngestModuleFactory.moduleDisplayName"); + } + + /** + * @inheritDoc + */ + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + /** + * @inheritDoc + */ + @Override + public String getModuleDescription() { + return NbBundle.getMessage(this.getClass(), "VMExtractorIngestModuleFactory.moduleDescription"); + } + + /** + * @inheritDoc + */ + @Override + public String getModuleVersionNumber() { + return NbBundle.getMessage(this.getClass(), "VMExtractorIngestModuleFactory.version"); + } + + /** + * @inheritDoc + */ + @Override + public boolean isDataSourceIngestModuleFactory() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) { + return new VMExtractorIngestModule(); + } + +} From 4dbaca4bb7f667bbfd3445c3a8497a33e96b0779 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Tue, 5 Jan 2016 14:26:02 -0500 Subject: [PATCH 2/2] Complete VMExtractor module prototype --- .../autopsy/ingest/DataSourceIngestJob.java | 9 ++ .../autopsy/ingest/IngestJobContext.java | 9 ++ .../autopsy/ingest/IngestJobSettings.java | 55 ++++--- .../autopsy/ingest/IngestManager.java | 7 - .../modules/vmextractor/Bundle.properties | 5 +- .../vmextractor/VMExtractorIngestModule.java | 148 +++++++++++++----- 6 files changed, 166 insertions(+), 67 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index b05ece3a3b..a84f262988 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -280,6 +280,15 @@ final class DataSourceIngestJob { return this.id; } + /** + * Get the ingest execution context identifier. + * + * @return The context string. + */ + String getExecutionContext() { + return this.settings.getExecutionContext(); + } + /** * Gets the data source to be ingested by this job. * diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobContext.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobContext.java index 3adea88613..74da1d814b 100755 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobContext.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobContext.java @@ -34,6 +34,15 @@ public final class IngestJobContext { this.ingestJob = ingestJob; } + /** + * Gets the ingest job execution context identifier. + * + * @return The context string. + */ + public String getExecutionContext() { + return this.ingestJob.getExecutionContext(); + } + /** * Gets the data source associated with this context. * diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java index 75af39f49a..98a307d544 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestJobSettings.java @@ -52,7 +52,7 @@ public class IngestJobSettings { private static final String MODULE_SETTINGS_FOLDER_PATH = Paths.get(PlatformUtil.getUserConfigDirectory(), IngestJobSettings.MODULE_SETTINGS_FOLDER).toAbsolutePath().toString(); private static final String MODULE_SETTINGS_FILE_EXT = ".settings"; //NON-NLS private static final Logger logger = Logger.getLogger(IngestJobSettings.class.getName()); - private final String context; + private final String executionContext; private final IngestType ingestType; private String moduleSettingsFolderPath; private static final CharSequence pythonModuleSettingsPrefixCS = "org.python.proxies.".subSequence(0, "org.python.proxies.".length() - 1); @@ -74,10 +74,10 @@ public class IngestJobSettings { /** * Constructs an ingest job settings object for a given context. * - * @param context The context identifier string. + * @param executionContext The ingest execution context identifier. */ - public IngestJobSettings(String context) { - this.context = context; + public IngestJobSettings(String executionContext) { + this.executionContext = executionContext; this.ingestType = IngestType.ALL_MODULES; this.moduleTemplates = new ArrayList<>(); this.processUnallocatedSpace = Boolean.parseBoolean(IngestJobSettings.PROCESS_UNALLOC_SPACE_DEFAULT); @@ -89,16 +89,16 @@ public class IngestJobSettings { /** * Constructs an ingest job settings object for a given context. * - * @param context The context identifier string. + * @param context The context identifier string. * @param ingestType The type of modules ingest is running. */ public IngestJobSettings(String context, IngestType ingestType) { this.ingestType = ingestType; if (this.ingestType.equals(IngestType.ALL_MODULES)) { - this.context = context; + this.executionContext = context; } else { - this.context = context + "." + this.ingestType.name(); + this.executionContext = context + "." + this.ingestType.name(); } this.moduleTemplates = new ArrayList<>(); @@ -115,6 +115,15 @@ public class IngestJobSettings { this.store(); } + /** + * Get the ingest execution context identifier. + * + * @return The context string. + */ + String getExecutionContext() { + return this.executionContext; + } + /** * Gets and clears any accumulated warnings associated with these ingest job * settings. @@ -186,8 +195,8 @@ public class IngestJobSettings { * * @return path to the module settings folder */ - public Path getSavedModuleSettingsFolder(){ - return Paths.get(IngestJobSettings.MODULE_SETTINGS_FOLDER_PATH, context); + public Path getSavedModuleSettingsFolder() { + return Paths.get(IngestJobSettings.MODULE_SETTINGS_FOLDER_PATH, executionContext); } /** @@ -286,15 +295,15 @@ public class IngestJobSettings { * Update the enabled/disabled ingest module settings for this context * to reflect any missing modules or newly discovered modules. */ - ModuleSettings.setConfigSetting(this.context, IngestJobSettings.ENABLED_MODULES_KEY, makeCommaSeparatedValuesList(enabledModuleNames)); - ModuleSettings.setConfigSetting(this.context, IngestJobSettings.DISABLED_MODULES_KEY, makeCommaSeparatedValuesList(disabledModuleNames)); + ModuleSettings.setConfigSetting(this.executionContext, IngestJobSettings.ENABLED_MODULES_KEY, makeCommaSeparatedValuesList(enabledModuleNames)); + ModuleSettings.setConfigSetting(this.executionContext, IngestJobSettings.DISABLED_MODULES_KEY, makeCommaSeparatedValuesList(disabledModuleNames)); // Get the process unallocated space flag setting. If the setting does // not exist yet, default it to true. - if (ModuleSettings.settingExists(this.context, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY) == false) { - ModuleSettings.setConfigSetting(this.context, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY, IngestJobSettings.PROCESS_UNALLOC_SPACE_DEFAULT); + if (ModuleSettings.settingExists(this.executionContext, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY) == false) { + ModuleSettings.setConfigSetting(this.executionContext, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY, IngestJobSettings.PROCESS_UNALLOC_SPACE_DEFAULT); } - this.processUnallocatedSpace = Boolean.parseBoolean(ModuleSettings.getConfigSetting(this.context, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY)); + this.processUnallocatedSpace = Boolean.parseBoolean(ModuleSettings.getConfigSetting(this.executionContext, IngestJobSettings.PARSE_UNALLOC_SPACE_KEY)); } /** @@ -306,11 +315,11 @@ public class IngestJobSettings { * @return The list of module names associated with the key. */ private HashSet getModulesNamesFromSetting(String key, String defaultSetting) { - if (ModuleSettings.settingExists(this.context, key) == false) { - ModuleSettings.setConfigSetting(this.context, key, defaultSetting); + if (ModuleSettings.settingExists(this.executionContext, key) == false) { + ModuleSettings.setConfigSetting(this.executionContext, key, defaultSetting); } HashSet moduleNames = new HashSet<>(); - String modulesSetting = ModuleSettings.getConfigSetting(this.context, key); + String modulesSetting = ModuleSettings.getConfigSetting(this.executionContext, key); if (!modulesSetting.isEmpty()) { String[] settingNames = modulesSetting.split(", "); for (String name : settingNames) { @@ -369,7 +378,7 @@ public class IngestJobSettings { try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { settings = (IngestModuleIngestJobSettings) in.readObject(); } catch (IOException | ClassNotFoundException ex) { - String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.context); //NON-NLS + String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS logger.log(Level.WARNING, warning, ex); this.warnings.add(warning); } @@ -377,7 +386,7 @@ public class IngestJobSettings { try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { settings = (IngestModuleIngestJobSettings) in.readObject(); } catch (IOException | ClassNotFoundException exception) { - String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.context); //NON-NLS + String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS logger.log(Level.WARNING, warning, exception); this.warnings.add(warning); } @@ -421,14 +430,14 @@ public class IngestJobSettings { disabledModuleNames.add(moduleName); } } - ModuleSettings.setConfigSetting(this.context, ENABLED_MODULES_KEY, makeCommaSeparatedValuesList(enabledModuleNames)); - ModuleSettings.setConfigSetting(this.context, DISABLED_MODULES_KEY, makeCommaSeparatedValuesList(disabledModuleNames)); + ModuleSettings.setConfigSetting(this.executionContext, ENABLED_MODULES_KEY, makeCommaSeparatedValuesList(enabledModuleNames)); + ModuleSettings.setConfigSetting(this.executionContext, DISABLED_MODULES_KEY, makeCommaSeparatedValuesList(disabledModuleNames)); /** * Save the process unallocated space setting. */ String processUnalloc = Boolean.toString(this.processUnallocatedSpace); - ModuleSettings.setConfigSetting(this.context, PARSE_UNALLOC_SPACE_KEY, processUnalloc); + ModuleSettings.setConfigSetting(this.executionContext, PARSE_UNALLOC_SPACE_KEY, processUnalloc); } /** @@ -451,7 +460,7 @@ public class IngestJobSettings { out.writeObject(settings); } } catch (IOException ex) { - String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsSave.warning", factory.getModuleDisplayName(), this.context); //NON-NLS + String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsSave.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS logger.log(Level.SEVERE, warning, ex); this.warnings.add(warning); } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 263bbe275b..926ad71d78 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -515,13 +515,6 @@ public class IngestManager { private boolean startIngestJob(IngestJob job) { boolean success = false; if (this.jobCreationIsEnabled) { - /** - * TODO: This is not really reliable. - */ - if (RuntimeProperties.coreComponentsAreActive() && jobsById.size() == 1) { - clearIngestMessageBox(); - } - // multi-user cases must have multi-user database service running if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { try { diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties index e14e90fdc6..bda9f2f3b2 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/Bundle.properties @@ -1,3 +1,6 @@ VMExtractorIngestModuleFactory.moduleDisplayName=Virtual Machine Extractor VMExtractorIngestModuleFactory.moduleDescription=Extracts virtual machine files and adds them to a case as data sources. -VMExtractorIngestModuleFactory.version=1.0 \ No newline at end of file +VMExtractorIngestModuleFactory.version=1.0 +VMExtractorIngestModule.cannotCreateOutputDir.message=Unable to create output directory: {0} +VMExtractorIngestModule.addedVirtualMachineImage.message=Added virtual machine image {0} + diff --git a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java index 42a8987386..480742eace 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/vmextractor/VMExtractorIngestModule.java @@ -20,24 +20,31 @@ package org.sleuthkit.autopsy.modules.vmextractor; import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import java.util.UUID; import java.util.logging.Level; -import javax.annotation.concurrent.Immutable; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.ImageDSProcessor; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.ContentUtils; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleAdapter; import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestManager; -import org.sleuthkit.autopsy.ingest.RunIngestModulesDialog; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestModule; +import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -46,11 +53,25 @@ import org.sleuthkit.datamodel.TskCoreException; * An ingest module that extracts virtual machine files and adds them to a case * as data sources. */ -@Immutable final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { private static final Logger logger = Logger.getLogger(VMExtractorIngestModule.class.getName()); - private final List vmImages = new ArrayList<>(); + private IngestJobContext context; + private Path ingestJobOutputDir; + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); + String timeStamp = dateFormat.format(Calendar.getInstance().getTime()); + String ingestJobOutputDirName = context.getDataSource().getName() + "_" + context.getDataSource().getId() + "_" + timeStamp; + ingestJobOutputDir = Paths.get(Case.getCurrentCase().getModuleDirectory(), VMExtractorIngestModuleFactory.getModuleName(), ingestJobOutputDirName); + try { + Files.createDirectories(ingestJobOutputDir); + } catch (IOException | SecurityException | UnsupportedOperationException ex) { + throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.cannotCreateOutputDir.message", ex.getLocalizedMessage())); + } + } /** * @inheritDoc @@ -58,49 +79,82 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { @Override public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { try { - List vmFiles = Case.getCurrentCase().getServices().getFileManager().findFiles(dataSource, "%.img"); - // RJCTODO: Progress bar set up - for (AbstractFile file : vmFiles) { + List vmFiles = findVirtualMachineFiles(dataSource); + /* + * TODO: Configure and start progress bar + */ + for (AbstractFile vmFile : vmFiles) { + if (context.dataSourceIngestIsCancelled()) { + break; + } try { - ingestVirtualMachineImage(file); - // RJCTODO: Progress bar update + ingestVirtualMachineImage(vmFile); + /* + * TODO: Update progress bar + */ } catch (InterruptedException ex) { - logger.log(Level.INFO, "Interrupted while adding image", ex); // RJCTODO: Improve logging + logger.log(Level.INFO, String.format("Interrupted while adding virtual machine file %s (id=%d)", vmFile.getName(), vmFile.getId()), ex); } catch (IOException ex) { - logger.log(Level.INFO, "Unable to save VM file to disk", ex); // RJCTODO: Improve logging + logger.log(Level.SEVERE, String.format("Failed to write virtual machine file %s (id=%d) to disk", vmFile.getName(), vmFile.getId()), ex); + MessageNotifyUtil.Notify.error("Failed to extract virtual machine file", String.format("Failed to write virtual machine file %s to disk", vmFile.getName())); } } return ProcessResult.OK; } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error querying case database", ex); // RJCTODO: Improve logging + logger.log(Level.SEVERE, "Error querying case database", ex); return ProcessResult.ERROR; + } finally { + /* + * TODO: Finish progress bar + */ } } /** - * RJCTODO + * Locate the virtual machine file, if any, contained in a data source. * - * @param vmFile + * @param dataSource The data source. + * + * @return A list of virtual machine files, possibly empty. + * + * @throws TskCoreException if there is a problem querying the case + * database. + */ + private static List findVirtualMachineFiles(Content dataSource) throws TskCoreException { + /* + * TODO: Adapt this code as necessary to actual VM files + */ + return Case.getCurrentCase().getServices().getFileManager().findFiles(dataSource, "%.img"); + } + + /** + * Add a virtual machine file to the case as a data source and analyze it + * with the ingest modules. + * + * @param vmFile A virtual machine file. */ private void ingestVirtualMachineImage(AbstractFile vmFile) throws InterruptedException, IOException { /* * Write the virtual machine file to disk. */ - String imageFileName = vmFile.getName() + "_" + vmFile.getId(); - Path imageFilePath = Paths.get(Case.getCurrentCase().getModuleDirectory(), imageFileName); - File imageFile = imageFilePath.toFile(); - ContentUtils.writeToFile(vmFile, imageFile); + String localFileName = vmFile.getName() + "_" + vmFile.getId(); + Path localFilePath = Paths.get(ingestJobOutputDir.toString(), localFileName); + File localFile = localFilePath.toFile(); + ContentUtils.writeToFile(vmFile, localFile); /* - * Try to add the virtual machine file to the case as an image. + * Try to add the virtual machine file to the case as a data source. */ - vmImages.clear(); UUID taskId = UUID.randomUUID(); Case.getCurrentCase().notifyAddingDataSource(taskId); ImageDSProcessor dataSourceProcessor = new ImageDSProcessor(); - dataSourceProcessor.setDataSourceOptions(imageFile.getAbsolutePath(), "", false); // RJCTODO: Setting for FAT orphans? + dataSourceProcessor.setDataSourceOptions(localFile.getAbsolutePath(), "", false); + AddDataSourceCallback dspCallback = new AddDataSourceCallback(vmFile); synchronized (this) { - dataSourceProcessor.run(new AddDataSourceProgressMonitor(), new AddDataSourceCallback()); + dataSourceProcessor.run(new AddDataSourceProgressMonitor(), dspCallback); + /* + * Block the ingest thread until the data source processor finishes. + */ this.wait(); } @@ -108,24 +162,25 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { * If the image was added, analyze it with the ingest modules for this * ingest context. */ - if (!vmImages.isEmpty()) { - Case.getCurrentCase().notifyDataSourceAdded(vmImages.get(0), taskId); - List images = new ArrayList<>(vmImages); - IngestJobSettings ingestJobSettings = new IngestJobSettings(RunIngestModulesDialog.class.getCanonicalName()); // RJCTODO: Problem to solve, context string sharing! + if (!dspCallback.vmDataSources.isEmpty()) { + Case.getCurrentCase().notifyDataSourceAdded(dspCallback.vmDataSources.get(0), taskId); + List dataSourceContent = new ArrayList<>(dspCallback.vmDataSources); + IngestJobSettings ingestJobSettings = new IngestJobSettings(context.getExecutionContext()); for (String warning : ingestJobSettings.getWarnings()) { - logger.log(Level.WARNING, warning); + logger.log(Level.WARNING, String.format("Ingest job settings warning for virtual machine file %s (id=%d): %s", vmFile.getName(), vmFile.getId(), warning)); } - IngestManager.getInstance().queueIngestJob(images, ingestJobSettings); + IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO, + VMExtractorIngestModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), "VMExtractorIngestModule.addedVirtualMachineImage.message", localFileName))); + IngestManager.getInstance().queueIngestJob(dataSourceContent, ingestJobSettings); } else { Case.getCurrentCase().notifyFailedAddingDataSource(taskId); - // RJCTODO: Some logging here } } /** - * RJCTODO + * A do nothing data source processor progress monitor. */ - // RJCTODO: Consider implementing in terms of the ingest progress monitor private static final class AddDataSourceProgressMonitor implements DataSourceProcessorProgressMonitor { @Override @@ -143,27 +198,48 @@ final class VMExtractorIngestModule extends DataSourceIngestModuleAdapter { } /** - * A callback for the data source processor. + * A callback for the data source processor that captures the content + * objects for the data source and unblocks the ingest thread. */ private final class AddDataSourceCallback extends DataSourceProcessorCallback { + private final AbstractFile vmFile; + private final List vmDataSources; + + /** + * Constructs a callback for the data source processor. + * + * @param vmFile The virtual machine file to be added as a data source. + */ + private AddDataSourceCallback(AbstractFile vmFile) { + this.vmFile = vmFile; + vmDataSources = new ArrayList<>(); + } + /** * @inheritDoc */ @Override public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List errList, List content) { + for (String error : errList) { + String logMessage = String.format("Data source processor error for virtual machine file %s (id=%d): %s", vmFile.getName(), vmFile.getId(), error); + if (DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS == result) { + logger.log(Level.SEVERE, logMessage); + } else { + logger.log(Level.WARNING, logMessage); + } + } + /* * Save a reference to the content object so it can be used to * create a new ingest job. */ if (!content.isEmpty()) { - vmImages.add(content.get(0)); + vmDataSources.add(content.get(0)); } - // RJCTODO: Log errors if any - /* - * Unblock the processing thread. + * Unblock the ingest thread. */ synchronized (VMExtractorIngestModule.this) { VMExtractorIngestModule.this.notify();