From 109850eb6a46dc9b83403797895cc430e75eec6b Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 8 Feb 2019 14:50:47 -0500 Subject: [PATCH 01/13] Moved the report generation into CaseUcoFormatExporter class with static methods --- Core/nbproject/project.xml | 1 + .../case_uco/CaseUcoFormatExporter.java | 378 ++++++++++++++++ .../modules/case_uco/ReportCaseUco.java | 424 ++---------------- 3 files changed, 418 insertions(+), 385 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 37ea029fba..3bc73bac5f 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -332,6 +332,7 @@ org.sleuthkit.autopsy.ingest.events org.sleuthkit.autopsy.keywordsearchservice org.sleuthkit.autopsy.menuactions + org.sleuthkit.autopsy.modules.case_uco org.sleuthkit.autopsy.modules.encryptiondetection org.sleuthkit.autopsy.modules.filetypeid org.sleuthkit.autopsy.modules.hashdatabase diff --git a/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java new file mode 100755 index 0000000000..ccad477ca6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java @@ -0,0 +1,378 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.modules.case_uco; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.SimpleTimeZone; +import java.util.logging.Level; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.report.ReportProgressPanel; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +public class CaseUcoFormatExporter { + + private static final Logger logger = Logger.getLogger(CaseUcoFormatExporter.class.getName()); + + private CaseUcoFormatExporter() { + } + + @NbBundle.Messages({ + "ReportCaseUco.noCaseOpen=Unable to open currect case", + "ReportCaseUco.unableToCreateDirectories=Unable to create directory for CASE-UCO report", + "ReportCaseUco.initializing=Creating directories...", + "ReportCaseUco.querying=Querying files...", + "ReportCaseUco.ingestWarning=Warning, this report will be created before ingest services completed", + "ReportCaseUco.processing=Saving files in CASE-UCO format...", + "ReportCaseUco.srcModuleName.text=CASE-UCO Report" + }) + @SuppressWarnings("deprecation") + public static void generateReport(Long selectedDataSourceId, String reportOutputPath, ReportProgressPanel progressPanel) { + + // Start the progress bar and setup the report + progressPanel.setIndeterminate(false); + progressPanel.start(); + progressPanel.updateStatusLabel(Bundle.ReportCaseUco_initializing()); + + // Create the JSON generator + JsonFactory jsonGeneratorFactory = new JsonFactory(); + java.io.File reportFile = Paths.get(reportOutputPath).toFile(); + try { + Files.createDirectories(Paths.get(reportFile.getParent())); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Unable to create directory for CASE-UCO report", ex); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_unableToCreateDirectories()); + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR); + return; + } + + // Check if ingest has finished + if (IngestManager.getInstance().isIngestRunning()) { + MessageNotifyUtil.Message.warn(Bundle.ReportCaseUco_ingestWarning()); + } + + JsonGenerator jsonGenerator = null; + SimpleTimeZone timeZone = new SimpleTimeZone(0, "GMT"); + try { + jsonGenerator = jsonGeneratorFactory.createGenerator(reportFile, JsonEncoding.UTF8); + // instert \n after each field for more readable formatting + jsonGenerator.setPrettyPrinter(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter(" ", "\n"))); + + SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); + + progressPanel.updateStatusLabel(Bundle.ReportCaseUco_querying()); + + // create the required CASE-UCO entries at the beginning of the output file + initializeJsonOutputFile(jsonGenerator); + + // create CASE-UCO entry for the Autopsy case + String caseTraceId = saveCaseInfo(skCase, jsonGenerator); + + // create CASE-UCO data source entry + String dataSourceTraceId = saveDataSourceInfo(selectedDataSourceId, caseTraceId, skCase, jsonGenerator); + + // Run getAllFilesQuery to get all files, exclude directories + final String getAllFilesQuery = "select obj_id, name, size, crtime, atime, mtime, md5, parent_path, mime_type, extension from tsk_files where " + + "data_source_obj_id = " + Long.toString(selectedDataSourceId) + + " AND ((meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue() + + ") OR (meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() + + ") OR (meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue() + "))"; //NON-NLS + + try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getAllFilesQuery)) { + ResultSet resultSet = queryResult.getResultSet(); + + progressPanel.updateStatusLabel(Bundle.ReportCaseUco_processing()); + + // Loop files and write info to CASE-UCO report + while (resultSet.next()) { + + if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) { + break; + } + + Long objectId = resultSet.getLong(1); + String fileName = resultSet.getString(2); + long size = resultSet.getLong("size"); + String crtime = ContentUtils.getStringTimeISO8601(resultSet.getLong("crtime"), timeZone); + String atime = ContentUtils.getStringTimeISO8601(resultSet.getLong("atime"), timeZone); + String mtime = ContentUtils.getStringTimeISO8601(resultSet.getLong("mtime"), timeZone); + String md5Hash = resultSet.getString("md5"); + String parent_path = resultSet.getString("parent_path"); + String mime_type = resultSet.getString("mime_type"); + String extension = resultSet.getString("extension"); + + saveFileInCaseUcoFormat(objectId, fileName, parent_path, md5Hash, mime_type, size, crtime, atime, mtime, extension, jsonGenerator, dataSourceTraceId); + } + } + + // create the required CASE-UCO entries at the end of the output file + finilizeJsonOutputFile(jsonGenerator); + + Case.getCurrentCaseThrows().addReport(reportOutputPath, Bundle.ReportCaseUco_srcModuleName_text(), ""); + + progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to get list of files from case database", ex); //NON-NLS + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Failed to create JSON output for the CASE-UCO report", ex); //NON-NLS + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR); + } catch (SQLException ex) { + logger.log(Level.WARNING, "Unable to read result set", ex); //NON-NLS + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "No current case open", ex); //NON-NLS + progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR); + } finally { + if (jsonGenerator != null) { + try { + jsonGenerator.close(); + } catch (IOException ex) { + logger.log(Level.WARNING, "Failed to close JSON output file", ex); //NON-NLS + } + } + } + } + + private static void initializeJsonOutputFile(JsonGenerator catalog) throws IOException { + catalog.writeStartObject(); + catalog.writeFieldName("@graph"); + catalog.writeStartArray(); + } + + private static void finilizeJsonOutputFile(JsonGenerator catalog) throws IOException { + catalog.writeEndArray(); + catalog.writeEndObject(); + } + + private static String saveCaseInfo(SleuthkitCase skCase, JsonGenerator catalog) throws TskCoreException, SQLException, IOException, NoCurrentCaseException { + + // create a "trace" entry for the Autopsy case iteself + String uniqueCaseName; + String dbFileName; + TskData.DbType dbType = skCase.getDatabaseType(); + if (dbType == TskData.DbType.SQLITE) { + uniqueCaseName = Case.getCurrentCaseThrows().getName(); + dbFileName = skCase.getDatabaseName(); + } else { + uniqueCaseName = skCase.getDatabaseName(); + dbFileName = ""; + } + + String caseDirPath = skCase.getDbDirPath(); + String caseTraceId = "case-" + uniqueCaseName; + catalog.writeStartObject(); + catalog.writeStringField("@id", caseTraceId); + catalog.writeStringField("@type", "Trace"); + + catalog.writeFieldName("propertyBundle"); + catalog.writeStartArray(); + catalog.writeStartObject(); + + // replace double slashes with single ones + caseDirPath = caseDirPath.replaceAll("\\\\", "/"); + + catalog.writeStringField("@type", "File"); + if (dbType == TskData.DbType.SQLITE) { + catalog.writeStringField("filePath", caseDirPath + "/" + dbFileName); + catalog.writeBooleanField("isDirectory", false); + } else { + catalog.writeStringField("filePath", caseDirPath); + catalog.writeBooleanField("isDirectory", true); + } + catalog.writeEndObject(); + + catalog.writeEndArray(); + catalog.writeEndObject(); + + return caseTraceId; + } + + private static String saveDataSourceInfo(Long selectedDataSourceId, String caseTraceId, SleuthkitCase skCase, JsonGenerator jsonGenerator) throws TskCoreException, SQLException, IOException { + + Long imageSize = (long) 0; + String imageName = ""; + boolean isImageDataSource = false; + String getImageDataSourceQuery = "select size from tsk_image_info where obj_id = " + selectedDataSourceId; + try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getImageDataSourceQuery)) { + ResultSet resultSet = queryResult.getResultSet(); + // check if we got a result + while (resultSet.next()) { + // we got a result so the data source was an image data source + imageSize = resultSet.getLong(1); + isImageDataSource = true; + break; + } + } + + if (isImageDataSource) { + // get caseDirPath to image file + String getPathToDataSourceQuery = "select name from tsk_image_names where obj_id = " + selectedDataSourceId; + try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getPathToDataSourceQuery)) { + ResultSet resultSet = queryResult.getResultSet(); + while (resultSet.next()) { + imageName = resultSet.getString(1); + break; + } + } + } else { + // logical file data source + String getLogicalDataSourceQuery = "select name from tsk_files where obj_id = " + selectedDataSourceId; + try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getLogicalDataSourceQuery)) { + ResultSet resultSet = queryResult.getResultSet(); + while (resultSet.next()) { + imageName = resultSet.getString(1); + break; + } + } + } + + return saveDataSourceInCaseUcoFormat(jsonGenerator, imageName, imageSize, selectedDataSourceId, caseTraceId); + } + + private static String saveDataSourceInCaseUcoFormat(JsonGenerator catalog, String imageName, Long imageSize, Long selectedDataSourceId, String caseTraceId) throws IOException { + + // create a "trace" entry for the data source + String dataSourceTraceId = "data-source-" + selectedDataSourceId; + catalog.writeStartObject(); + catalog.writeStringField("@id", dataSourceTraceId); + catalog.writeStringField("@type", "Trace"); + + catalog.writeFieldName("propertyBundle"); + catalog.writeStartArray(); + + catalog.writeStartObject(); + catalog.writeStringField("@type", "File"); + + // replace double back slashes with single ones + imageName = imageName.replaceAll("\\\\", "/"); + + catalog.writeStringField("filePath", imageName); + catalog.writeEndObject(); + + if (imageSize > 0) { + catalog.writeStartObject(); + catalog.writeStringField("@type", "ContentData"); + catalog.writeStringField("sizeInBytes", Long.toString(imageSize)); + catalog.writeEndObject(); + } + + catalog.writeEndArray(); + catalog.writeEndObject(); + + // create a "relationship" entry between the case and the data source + catalog.writeStartObject(); + catalog.writeStringField("@id", "relationship-" + caseTraceId); + catalog.writeStringField("@type", "Relationship"); + catalog.writeStringField("source", dataSourceTraceId); + catalog.writeStringField("target", caseTraceId); + catalog.writeStringField("kindOfRelationship", "contained-within"); + catalog.writeBooleanField("isDirectional", true); + + catalog.writeFieldName("propertyBundle"); + catalog.writeStartArray(); + catalog.writeStartObject(); + catalog.writeStringField("@type", "PathRelation"); + catalog.writeStringField("path", imageName); + catalog.writeEndObject(); + catalog.writeEndArray(); + + catalog.writeEndObject(); + + return dataSourceTraceId; + } + + private static void saveFileInCaseUcoFormat(Long objectId, String fileName, String parent_path, String md5Hash, String mime_type, long size, String ctime, + String atime, String mtime, String extension, JsonGenerator catalog, String dataSourceTraceId) throws IOException { + + String fileTraceId = "file-" + objectId; + + // create a "trace" entry for the file + catalog.writeStartObject(); + catalog.writeStringField("@id", fileTraceId); + catalog.writeStringField("@type", "Trace"); + + catalog.writeFieldName("propertyBundle"); + catalog.writeStartArray(); + + catalog.writeStartObject(); + catalog.writeStringField("@type", "File"); + catalog.writeStringField("createdTime", ctime); + catalog.writeStringField("accessedTime", atime); + catalog.writeStringField("modifiedTime", mtime); + if (extension != null) { + catalog.writeStringField("extension", extension); + } + catalog.writeStringField("fileName", fileName); + if (parent_path != null) { + catalog.writeStringField("filePath", parent_path + fileName); + } + catalog.writeBooleanField("isDirectory", false); + catalog.writeStringField("sizeInBytes", Long.toString(size)); + catalog.writeEndObject(); + + catalog.writeStartObject(); + catalog.writeStringField("@type", "ContentData"); + if (mime_type != null) { + catalog.writeStringField("mimeType", mime_type); + } + if (md5Hash != null) { + catalog.writeFieldName("hash"); + catalog.writeStartArray(); + catalog.writeStartObject(); + catalog.writeStringField("@type", "Hash"); + catalog.writeStringField("hashMethod", "MD5"); + catalog.writeStringField("hashValue", md5Hash); + catalog.writeEndObject(); + catalog.writeEndArray(); + } + catalog.writeStringField("sizeInBytes", Long.toString(size)); + + catalog.writeEndObject(); + + catalog.writeEndArray(); + catalog.writeEndObject(); + + // create a "relationship" entry between the file and the data source + catalog.writeStartObject(); + catalog.writeStringField("@id", "relationship-" + objectId); + catalog.writeStringField("@type", "Relationship"); + catalog.writeStringField("source", fileTraceId); + catalog.writeStringField("target", dataSourceTraceId); + catalog.writeStringField("kindOfRelationship", "contained-within"); + catalog.writeBooleanField("isDirectional", true); + + catalog.writeFieldName("propertyBundle"); + catalog.writeStartArray(); + catalog.writeStartObject(); + catalog.writeStringField("@type", "PathRelation"); + if (parent_path != null) { + catalog.writeStringField("path", parent_path + fileName); + } else { + catalog.writeStringField("path", fileName); + } + catalog.writeEndObject(); + catalog.writeEndArray(); + + catalog.writeEndObject(); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java b/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java index 37d7ef21c2..38ae816d0b 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java +++ b/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java @@ -1,4 +1,4 @@ - /* +/* * * Autopsy Forensic Browser * @@ -19,43 +19,30 @@ */ package org.sleuthkit.autopsy.modules.case_uco; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.logging.Level; import javax.swing.JPanel; -import com.fasterxml.jackson.core.JsonEncoding; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.util.DefaultIndenter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import java.sql.ResultSet; import java.sql.SQLException; -import java.util.SimpleTimeZone; import org.openide.util.NbBundle; -import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; -import org.sleuthkit.autopsy.datamodel.ContentUtils; -import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.report.GeneralReportModule; import org.sleuthkit.autopsy.report.ReportProgressPanel; import org.sleuthkit.autopsy.report.ReportProgressPanel.ReportStatus; import org.sleuthkit.datamodel.*; /** - * ReportCaseUco generates a report in the CASE-UCO format. It saves basic - file info like full caseDirPath, name, MIME type, times, and hash. + * ReportCaseUco generates a report in the CASE-UCO format. It saves basic file + * info like full caseDirPath, name, MIME type, times, and hash. */ -class ReportCaseUco implements GeneralReportModule { +public class ReportCaseUco implements GeneralReportModule { private static final Logger logger = Logger.getLogger(ReportCaseUco.class.getName()); private static ReportCaseUco instance = null; private ReportCaseUcoConfigPanel configPanel; - + private static final String REPORT_FILE_NAME = "CASE_UCO_output.json-ld"; - + // Hidden constructor for the report private ReportCaseUco() { } @@ -67,373 +54,7 @@ class ReportCaseUco implements GeneralReportModule { } return instance; } - - /** - * Generates a CASE-UCO format report. - * - * @param baseReportDir caseDirPath to save the report - * @param progressPanel panel to update the report's progress - */ - @NbBundle.Messages({ - "ReportCaseUco.notInitialized=CASE-UCO settings panel has not been initialized", - "ReportCaseUco.noDataSourceSelected=No data source selected for CASE-UCO report", - "ReportCaseUco.noCaseOpen=Unable to open currect case", - "ReportCaseUco.unableToCreateDirectories=Unable to create directory for CASE-UCO report", - "ReportCaseUco.initializing=Creating directories...", - "ReportCaseUco.querying=Querying files...", - "ReportCaseUco.ingestWarning=Warning, this report will be created before ingest services completed", - "ReportCaseUco.processing=Saving files in CASE-UCO format...", - "ReportCaseUco.srcModuleName.text=CASE-UCO Report" - }) - @Override - @SuppressWarnings("deprecation") - public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { - - if (configPanel == null) { - logger.log(Level.SEVERE, "CASE-UCO settings panel has not been initialized"); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_notInitialized()); - progressPanel.complete(ReportStatus.ERROR); - return; - } - - Long selectedDataSourceId = configPanel.getSelectedDataSourceId(); - if (selectedDataSourceId == ReportCaseUcoConfigPanel.NO_DATA_SOURCE_SELECTED) { - logger.log(Level.SEVERE, "No data source selected for CASE-UCO report"); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_noDataSourceSelected()); - progressPanel.complete(ReportStatus.ERROR); - return; - } - - // Start the progress bar and setup the report - progressPanel.setIndeterminate(false); - progressPanel.start(); - progressPanel.updateStatusLabel(Bundle.ReportCaseUco_initializing()); - - // Create the JSON generator - JsonFactory jsonGeneratorFactory = new JsonFactory(); - String reportPath = baseReportDir + getRelativeFilePath(); - java.io.File reportFile = Paths.get(reportPath).toFile(); - try { - Files.createDirectories(Paths.get(reportFile.getParent())); - } catch (IOException ex) { - logger.log(Level.SEVERE, "Unable to create directory for CASE-UCO report", ex); //NON-NLS - MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_unableToCreateDirectories()); - progressPanel.complete(ReportStatus.ERROR); - return; - } - - // Check if ingest has finished - if (IngestManager.getInstance().isIngestRunning()) { - MessageNotifyUtil.Message.warn(Bundle.ReportCaseUco_ingestWarning()); - } - - - JsonGenerator jsonGenerator = null; - SimpleTimeZone timeZone = new SimpleTimeZone(0, "GMT"); - try { - jsonGenerator = jsonGeneratorFactory.createGenerator(reportFile, JsonEncoding.UTF8); - // instert \n after each field for more readable formatting - jsonGenerator.setPrettyPrinter(new DefaultPrettyPrinter().withObjectIndenter(new DefaultIndenter(" ", "\n"))); - - SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); - - progressPanel.updateStatusLabel(Bundle.ReportCaseUco_querying()); - - // create the required CASE-UCO entries at the beginning of the output file - initializeJsonOutputFile(jsonGenerator); - - // create CASE-UCO entry for the Autopsy case - String caseTraceId = saveCaseInfo(skCase, jsonGenerator); - - // create CASE-UCO data source entry - String dataSourceTraceId = saveDataSourceInfo(selectedDataSourceId, caseTraceId, skCase, jsonGenerator); - - // Run getAllFilesQuery to get all files, exclude directories - final String getAllFilesQuery = "select obj_id, name, size, crtime, atime, mtime, md5, parent_path, mime_type, extension from tsk_files where " - + "data_source_obj_id = " + Long.toString(selectedDataSourceId) - + " AND ((meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_UNDEF.getValue() - + ") OR (meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG.getValue() - + ") OR (meta_type = " + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT.getValue() + "))"; //NON-NLS - - try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getAllFilesQuery)) { - ResultSet resultSet = queryResult.getResultSet(); - - progressPanel.updateStatusLabel(Bundle.ReportCaseUco_processing()); - - // Loop files and write info to CASE-UCO report - while (resultSet.next()) { - - if (progressPanel.getStatus() == ReportStatus.CANCELED) { - break; - } - - Long objectId = resultSet.getLong(1); - String fileName = resultSet.getString(2); - long size = resultSet.getLong("size"); - String crtime = ContentUtils.getStringTimeISO8601(resultSet.getLong("crtime"), timeZone); - String atime = ContentUtils.getStringTimeISO8601(resultSet.getLong("atime"), timeZone); - String mtime = ContentUtils.getStringTimeISO8601(resultSet.getLong("mtime"), timeZone); - String md5Hash = resultSet.getString("md5"); - String parent_path = resultSet.getString("parent_path"); - String mime_type = resultSet.getString("mime_type"); - String extension = resultSet.getString("extension"); - - saveFileInCaseUcoFormat(objectId, fileName, parent_path, md5Hash, mime_type, size, crtime, atime, mtime, extension, jsonGenerator, dataSourceTraceId); - } - } - - // create the required CASE-UCO entries at the end of the output file - finilizeJsonOutputFile(jsonGenerator); - - Case.getCurrentCaseThrows().addReport(reportPath, Bundle.ReportCaseUco_srcModuleName_text(), ""); - - progressPanel.complete(ReportStatus.COMPLETE); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to get list of files from case database", ex); //NON-NLS - progressPanel.complete(ReportStatus.ERROR); - } catch (IOException ex) { - logger.log(Level.SEVERE, "Failed to create JSON output for the CASE-UCO report", ex); //NON-NLS - progressPanel.complete(ReportStatus.ERROR); - } catch (SQLException ex) { - logger.log(Level.WARNING, "Unable to read result set", ex); //NON-NLS - progressPanel.complete(ReportStatus.ERROR); - } catch (NoCurrentCaseException ex) { - logger.log(Level.SEVERE, "No current case open", ex); //NON-NLS - progressPanel.complete(ReportStatus.ERROR); - } finally { - if (jsonGenerator != null) { - try { - jsonGenerator.close(); - } catch (IOException ex) { - logger.log(Level.WARNING, "Failed to close JSON output file", ex); //NON-NLS - } - } - } - } - private void initializeJsonOutputFile(JsonGenerator catalog) throws IOException { - catalog.writeStartObject(); - catalog.writeFieldName("@graph"); - catalog.writeStartArray(); - } - - private void finilizeJsonOutputFile(JsonGenerator catalog) throws IOException { - catalog.writeEndArray(); - catalog.writeEndObject(); - } - - private String saveCaseInfo(SleuthkitCase skCase, JsonGenerator catalog) throws TskCoreException, SQLException, IOException, NoCurrentCaseException { - - // create a "trace" entry for the Autopsy case iteself - String uniqueCaseName; - String dbFileName; - TskData.DbType dbType = skCase.getDatabaseType(); - if (dbType == TskData.DbType.SQLITE) { - uniqueCaseName = Case.getCurrentCaseThrows().getName(); - dbFileName = skCase.getDatabaseName(); - } else { - uniqueCaseName = skCase.getDatabaseName(); - dbFileName = ""; - } - - String caseDirPath = skCase.getDbDirPath(); - String caseTraceId = "case-" + uniqueCaseName; - catalog.writeStartObject(); - catalog.writeStringField("@id", caseTraceId); - catalog.writeStringField("@type", "Trace"); - - catalog.writeFieldName("propertyBundle"); - catalog.writeStartArray(); - catalog.writeStartObject(); - - // replace double slashes with single ones - caseDirPath = caseDirPath.replaceAll("\\\\", "/"); - - catalog.writeStringField("@type", "File"); - if (dbType == TskData.DbType.SQLITE) { - catalog.writeStringField("filePath", caseDirPath + "/" + dbFileName); - catalog.writeBooleanField("isDirectory", false); - } else { - catalog.writeStringField("filePath", caseDirPath); - catalog.writeBooleanField("isDirectory", true); - } - catalog.writeEndObject(); - - catalog.writeEndArray(); - catalog.writeEndObject(); - - return caseTraceId; - } - - private String saveDataSourceInfo(Long selectedDataSourceId, String caseTraceId, SleuthkitCase skCase, JsonGenerator jsonGenerator) throws TskCoreException, SQLException, IOException { - - Long imageSize = (long) 0; - String imageName = ""; - boolean isImageDataSource = false; - String getImageDataSourceQuery = "select size from tsk_image_info where obj_id = " + selectedDataSourceId; - try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getImageDataSourceQuery)) { - ResultSet resultSet = queryResult.getResultSet(); - // check if we got a result - while (resultSet.next()) { - // we got a result so the data source was an image data source - imageSize = resultSet.getLong(1); - isImageDataSource = true; - break; - } - } - - if (isImageDataSource) { - // get caseDirPath to image file - String getPathToDataSourceQuery = "select name from tsk_image_names where obj_id = " + selectedDataSourceId; - try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getPathToDataSourceQuery)) { - ResultSet resultSet = queryResult.getResultSet(); - while (resultSet.next()) { - imageName = resultSet.getString(1); - break; - } - } - } else { - // logical file data source - String getLogicalDataSourceQuery = "select name from tsk_files where obj_id = " + selectedDataSourceId; - try (SleuthkitCase.CaseDbQuery queryResult = skCase.executeQuery(getLogicalDataSourceQuery)) { - ResultSet resultSet = queryResult.getResultSet(); - while (resultSet.next()) { - imageName = resultSet.getString(1); - break; - } - } - } - - return saveDataSourceInCaseUcoFormat(jsonGenerator, imageName, imageSize, selectedDataSourceId, caseTraceId); - } - - private String saveDataSourceInCaseUcoFormat(JsonGenerator catalog, String imageName, Long imageSize, Long selectedDataSourceId, String caseTraceId) throws IOException { - - // create a "trace" entry for the data source - String dataSourceTraceId = "data-source-"+selectedDataSourceId; - catalog.writeStartObject(); - catalog.writeStringField("@id", dataSourceTraceId); - catalog.writeStringField("@type", "Trace"); - - catalog.writeFieldName("propertyBundle"); - catalog.writeStartArray(); - - catalog.writeStartObject(); - catalog.writeStringField("@type", "File"); - - // replace double back slashes with single ones - imageName = imageName.replaceAll("\\\\", "/"); - - catalog.writeStringField("filePath", imageName); - catalog.writeEndObject(); - - if (imageSize > 0) { - catalog.writeStartObject(); - catalog.writeStringField("@type", "ContentData"); - catalog.writeStringField("sizeInBytes", Long.toString(imageSize)); - catalog.writeEndObject(); - } - - catalog.writeEndArray(); - catalog.writeEndObject(); - - // create a "relationship" entry between the case and the data source - catalog.writeStartObject(); - catalog.writeStringField("@id", "relationship-" + caseTraceId); - catalog.writeStringField("@type", "Relationship"); - catalog.writeStringField("source", dataSourceTraceId); - catalog.writeStringField("target", caseTraceId); - catalog.writeStringField("kindOfRelationship", "contained-within"); - catalog.writeBooleanField("isDirectional", true); - - catalog.writeFieldName("propertyBundle"); - catalog.writeStartArray(); - catalog.writeStartObject(); - catalog.writeStringField("@type", "PathRelation"); - catalog.writeStringField("path", imageName); - catalog.writeEndObject(); - catalog.writeEndArray(); - - catalog.writeEndObject(); - - return dataSourceTraceId; - } - - private void saveFileInCaseUcoFormat(Long objectId, String fileName, String parent_path, String md5Hash, String mime_type, long size, String ctime, - String atime, String mtime, String extension, JsonGenerator catalog, String dataSourceTraceId) throws IOException { - - String fileTraceId = "file-" + objectId; - - // create a "trace" entry for the file - catalog.writeStartObject(); - catalog.writeStringField("@id", fileTraceId); - catalog.writeStringField("@type", "Trace"); - - catalog.writeFieldName("propertyBundle"); - catalog.writeStartArray(); - - catalog.writeStartObject(); - catalog.writeStringField("@type", "File"); - catalog.writeStringField("createdTime", ctime); - catalog.writeStringField("accessedTime", atime); - catalog.writeStringField("modifiedTime", mtime); - if (extension != null) { - catalog.writeStringField("extension", extension); - } - catalog.writeStringField("fileName", fileName); - if (parent_path != null) { - catalog.writeStringField("filePath", parent_path + fileName); - } - catalog.writeBooleanField("isDirectory", false); - catalog.writeStringField("sizeInBytes", Long.toString(size)); - catalog.writeEndObject(); - - catalog.writeStartObject(); - catalog.writeStringField("@type", "ContentData"); - if (mime_type != null) { - catalog.writeStringField("mimeType", mime_type); - } - if (md5Hash != null) { - catalog.writeFieldName("hash"); - catalog.writeStartArray(); - catalog.writeStartObject(); - catalog.writeStringField("@type", "Hash"); - catalog.writeStringField("hashMethod", "MD5"); - catalog.writeStringField("hashValue", md5Hash); - catalog.writeEndObject(); - catalog.writeEndArray(); - } - catalog.writeStringField("sizeInBytes", Long.toString(size)); - - catalog.writeEndObject(); - - catalog.writeEndArray(); - catalog.writeEndObject(); - - // create a "relationship" entry between the file and the data source - catalog.writeStartObject(); - catalog.writeStringField("@id", "relationship-" + objectId); - catalog.writeStringField("@type", "Relationship"); - catalog.writeStringField("source", fileTraceId); - catalog.writeStringField("target", dataSourceTraceId); - catalog.writeStringField("kindOfRelationship", "contained-within"); - catalog.writeBooleanField("isDirectional", true); - - catalog.writeFieldName("propertyBundle"); - catalog.writeStartArray(); - catalog.writeStartObject(); - catalog.writeStringField("@type", "PathRelation"); - if (parent_path != null) { - catalog.writeStringField("path", parent_path + fileName); - } else { - catalog.writeStringField("path", fileName); - } - catalog.writeEndObject(); - catalog.writeEndArray(); - - catalog.writeEndObject(); - } - @Override public String getName() { String name = NbBundle.getMessage(this.getClass(), "ReportCaseUco.getName.text"); @@ -461,5 +82,38 @@ class ReportCaseUco implements GeneralReportModule { configPanel = null; } return configPanel; + } + + /** + * Generates a CASE-UCO format report. + * + * @param baseReportDir caseDirPath to save the report + * @param progressPanel panel to update the report's progress + */ + @NbBundle.Messages({ + "ReportCaseUco.notInitialized=CASE-UCO settings panel has not been initialized", + "ReportCaseUco.noDataSourceSelected=No data source selected for CASE-UCO report" + }) + @Override + @SuppressWarnings("deprecation") + public void generateReport(String baseReportDir, ReportProgressPanel progressPanel) { + + if (configPanel == null) { + logger.log(Level.SEVERE, "CASE-UCO settings panel has not been initialized"); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_notInitialized()); + progressPanel.complete(ReportStatus.ERROR); + return; + } + + Long selectedDataSourceId = configPanel.getSelectedDataSourceId(); + if (selectedDataSourceId == ReportCaseUcoConfigPanel.NO_DATA_SOURCE_SELECTED) { + logger.log(Level.SEVERE, "No data source selected for CASE-UCO report"); //NON-NLS + MessageNotifyUtil.Message.error(Bundle.ReportCaseUco_noDataSourceSelected()); + progressPanel.complete(ReportStatus.ERROR); + return; + } + + String reportPath = baseReportDir + getRelativeFilePath(); + CaseUcoFormatExporter.generateReport(selectedDataSourceId, reportPath, progressPanel); } } From d5024956c0542a41fc5e778f0c896d1e9d16ed46 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 12 Feb 2019 09:53:56 -0500 Subject: [PATCH 02/13] Minor chages --- .../autopsy/modules/case_uco/ReportCaseUco.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java b/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java index 38ae816d0b..b9c9e55bad 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java +++ b/Core/src/org/sleuthkit/autopsy/modules/case_uco/ReportCaseUco.java @@ -84,6 +84,15 @@ public class ReportCaseUco implements GeneralReportModule { return configPanel; } + /** + * Returns CASE-UCO report file name + * + * @return the REPORT_FILE_NAME + */ + public static String getReportFileName() { + return REPORT_FILE_NAME; + } + /** * Generates a CASE-UCO format report. * From e597f3904e31048bfe8b186e484fd391b83ee4e7 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 13 Feb 2019 16:27:24 -0500 Subject: [PATCH 03/13] Modified header --- .../case_uco/CaseUcoFormatExporter.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java index ccad477ca6..e0c3aa1be5 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java +++ b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java @@ -1,7 +1,20 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2011-2019 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.case_uco; From cc94069f584f5d1b80359fe902fea020ece5768f Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 22 Feb 2019 14:23:23 -0500 Subject: [PATCH 04/13] Addresed code review comments --- .../case_uco/CaseUcoFormatExporter.java | 46 ++++++++++++++++--- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java index e0c3aa1be5..3e843ca3ad 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java +++ b/Core/src/org/sleuthkit/autopsy/modules/case_uco/CaseUcoFormatExporter.java @@ -42,13 +42,24 @@ import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; +/** + * Generates CASE-UCO report file for a data source + */ public class CaseUcoFormatExporter { - + private static final Logger logger = Logger.getLogger(CaseUcoFormatExporter.class.getName()); - + private CaseUcoFormatExporter() { } - + + /** + * Generates CASE-UCO report for the selected data source. + * + * @param selectedDataSourceId Object ID of the data source + * @param reportOutputPath Full path to directory where to save CASE-UCO + * report file + * @param progressPanel ReportProgressPanel to update progress + */ @NbBundle.Messages({ "ReportCaseUco.noCaseOpen=Unable to open currect case", "ReportCaseUco.unableToCreateDirectories=Unable to create directory for CASE-UCO report", @@ -58,9 +69,9 @@ public class CaseUcoFormatExporter { "ReportCaseUco.processing=Saving files in CASE-UCO format...", "ReportCaseUco.srcModuleName.text=CASE-UCO Report" }) - @SuppressWarnings("deprecation") + @SuppressWarnings("deprecation") public static void generateReport(Long selectedDataSourceId, String reportOutputPath, ReportProgressPanel progressPanel) { - + // Start the progress bar and setup the report progressPanel.setIndeterminate(false); progressPanel.start(); @@ -164,7 +175,7 @@ public class CaseUcoFormatExporter { } } } - } + } private static void initializeJsonOutputFile(JsonGenerator catalog) throws IOException { catalog.writeStartObject(); @@ -177,6 +188,17 @@ public class CaseUcoFormatExporter { catalog.writeEndObject(); } + /** + * Save info about the Autopsy case in CASE-UCo format + * + * @param skCase SleuthkitCase object + * @param catalog JsonGenerator object + * @return CASE-UCO trace ID object for the Autopsy case entry + * @throws TskCoreException + * @throws SQLException + * @throws IOException + * @throws NoCurrentCaseException + */ private static String saveCaseInfo(SleuthkitCase skCase, JsonGenerator catalog) throws TskCoreException, SQLException, IOException, NoCurrentCaseException { // create a "trace" entry for the Autopsy case iteself @@ -220,6 +242,18 @@ public class CaseUcoFormatExporter { return caseTraceId; } + /** + * Save info about the data source in CASE-UCo format + * + * @param selectedDataSourceId Object ID of the data source + * @param caseTraceId CASE-UCO trace ID object for the Autopsy case entry + * @param skCase SleuthkitCase object + * @param catalog JsonGenerator object + * @return + * @throws TskCoreException + * @throws SQLException + * @throws IOException + */ private static String saveDataSourceInfo(Long selectedDataSourceId, String caseTraceId, SleuthkitCase skCase, JsonGenerator jsonGenerator) throws TskCoreException, SQLException, IOException { Long imageSize = (long) 0; From a4d2995014a28e8c38b4bd38fb1d396b966c0c08 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Tue, 26 Feb 2019 20:15:49 -0500 Subject: [PATCH 05/13] Moved command line processing into Autopsy Core --- .../autopsy/casemodule/StartupWindow.java | 2 +- .../casemodule/StartupWindowProvider.java | 41 ++ .../commandline/AddDataSourceCallback.java | 96 +++ .../autopsy/commandline/Bundle.properties | 17 + .../commandline/CommandLineIngestManager.java | 610 ++++++++++++++++++ .../CommandLineIngestSettingsPanel.form | 162 +++++ .../CommandLineIngestSettingsPanel.java | 395 ++++++++++++ ...mandLineIngestSettingsPanelController.java | 132 ++++ .../CommandLineOptionProcessor.java | 168 +++++ .../autopsy/commandline/CommandLinePanel.form | 56 ++ .../autopsy/commandline/CommandLinePanel.java | 71 ++ .../commandline/CommandLineStartupWindow.java | 49 ++ .../autopsy/commandline/DataSource.java | 66 ++ .../DataSourceProcessorUtility.java | 121 ++++ .../autopsy/commandline/UserPreferences.java | 64 ++ .../autopsy/images/command_line_icon.png | Bin 0 -> 474 bytes .../IngestJobRunningService.java | 44 -- 17 files changed, 2049 insertions(+), 45 deletions(-) create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/AddDataSourceCallback.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/Bundle.properties create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestManager.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanelController.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineOptionProcessor.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLinePanel.form create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLinePanel.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/CommandLineStartupWindow.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/DataSource.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/DataSourceProcessorUtility.java create mode 100755 Core/src/org/sleuthkit/autopsy/commandline/UserPreferences.java create mode 100755 Core/src/org/sleuthkit/autopsy/images/command_line_icon.png delete mode 100755 Experimental/src/org/sleuthkit/autopsy/experimental/configuration/IngestJobRunningService.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java index d15a8aa915..18a982ba1d 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindow.java @@ -35,7 +35,7 @@ public final class StartupWindow extends JDialog implements StartupWindowInterfa private static final long serialVersionUID = 1L; private static final String TITLE = NbBundle.getMessage(StartupWindow.class, "StartupWindow.title.text"); private static final Dimension DIMENSIONS = new Dimension(750, 400); - private static CueBannerPanel welcomeWindow; + private static CueBannerPanel welcomeWindow = null; public StartupWindow() { super(WindowManager.getDefault().getMainWindow(), TITLE, true); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java index 1b30f50e60..ddabc7e073 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/StartupWindowProvider.java @@ -21,7 +21,11 @@ package org.sleuthkit.autopsy.casemodule; import java.util.Collection; import java.util.Iterator; import java.util.logging.Level; +import javax.swing.JPanel; +import org.netbeans.spi.sendopts.OptionProcessor; import org.openide.util.Lookup; +import org.sleuthkit.autopsy.commandline.CommandLineIngestManager; +import org.sleuthkit.autopsy.commandline.CommandLineOptionProcessor; import org.sleuthkit.autopsy.coreutils.Logger; /** @@ -58,6 +62,13 @@ public class StartupWindowProvider implements StartupWindowInterface { Collection startupWindows = Lookup.getDefault().lookupAll(StartupWindowInterface.class); + // first check whether Autopsy is being run from command line + if (isRunningFromCommandLine()) { + logger.log(Level.INFO, "Autopsy is running from command line"); //NON-NLS + System.out.println("Autopsy is running from command line"); + return; + } + int windowsCount = startupWindows.size(); if (windowsCount == 1) { startupWindowToUse = startupWindows.iterator().next(); @@ -92,6 +103,36 @@ public class StartupWindowProvider implements StartupWindowInterface { } } } + + private boolean isRunningFromCommandLine() { + + boolean runningFromCommandLine = false; + // first look up all OptionProcessors and see if running from command line option is set + Collection optionProcessors = Lookup.getDefault().lookupAll(OptionProcessor.class); + Iterator optionsIterator = optionProcessors.iterator(); + while (optionsIterator.hasNext()) { + // find CommandLineOptionProcessor + OptionProcessor processor = optionsIterator.next(); + if (!(processor instanceof CommandLineOptionProcessor)) { + continue; + } + + // check if we are running from command line + runningFromCommandLine = ((CommandLineOptionProcessor) processor).isRunFromCommandLine(); + if (!runningFromCommandLine) { + // if we are NOT running from command line, exit here and don't start IngestJobRunningService"); + return false; + } + + // Autopsy is running from command line + CommandLineIngestManager ingestManager = new CommandLineIngestManager(); + startupWindowToUse = ingestManager.getStartupWindow(); + ingestManager.start(); + return true; + } + + return false; + } @Override public void open() { diff --git a/Core/src/org/sleuthkit/autopsy/commandline/AddDataSourceCallback.java b/Core/src/org/sleuthkit/autopsy/commandline/AddDataSourceCallback.java new file mode 100755 index 0000000000..ef29fd429f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/AddDataSourceCallback.java @@ -0,0 +1,96 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-2019 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.commandline; + +import java.util.List; +import java.util.UUID; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.datamodel.Content; + + +/** + * A "callback" that collects the results of running a data source processor on + * a data source and unblocks the job processing thread when the data source + * processor finishes running in its own thread. + */ +class AddDataSourceCallback extends DataSourceProcessorCallback { + + private final Case caseForJob; + private final DataSource dataSourceInfo; + private final UUID taskId; + private final Object lock; + + /** + * Constructs a "callback" that collects the results of running a data + * source processor on a data source and unblocks the job processing thread + * when the data source processor finishes running in its own thread. + * + * @param caseForJob The case for the current job. + * @param dataSourceInfo The data source + * @param taskId The task id to associate with ingest job events. + */ + AddDataSourceCallback(Case caseForJob, DataSource dataSourceInfo, UUID taskId, Object lock) { + this.caseForJob = caseForJob; + this.dataSourceInfo = dataSourceInfo; + this.taskId = taskId; + this.lock = lock; + } + + /** + * Called by the data source processor when it finishes running in its own + * thread. + * + * @param result The result code for the processing of the data source. + * @param errorMessages Any error messages generated during the processing + * of the data source. + * @param dataSourceContent The content produced by processing the data + * source. + */ + @Override + public void done(DataSourceProcessorCallback.DataSourceProcessorResult result, List errorMessages, List dataSourceContent) { + if (!dataSourceContent.isEmpty()) { + caseForJob.notifyDataSourceAdded(dataSourceContent.get(0), taskId); + } else { + caseForJob.notifyFailedAddingDataSource(taskId); + } + dataSourceInfo.setDataSourceProcessorOutput(result, errorMessages, dataSourceContent); + dataSourceContent.addAll(dataSourceContent); + synchronized (lock) { + lock.notify(); + } + } + + /** + * Called by the data source processor when it finishes running in its own + * thread, if that thread is the AWT (Abstract Window Toolkit) event + * dispatch thread (EDT). + * + * @param result The result code for the processing of the data source. + * @param errorMessages Any error messages generated during the processing + * of the data source. + * @param dataSourceContent The content produced by processing the data + * source. + */ + @Override + public void doneEDT(DataSourceProcessorCallback.DataSourceProcessorResult result, List errorMessages, List dataSources) { + done(result, errorMessages, dataSources); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/commandline/Bundle.properties b/Core/src/org/sleuthkit/autopsy/commandline/Bundle.properties new file mode 100755 index 0000000000..ed7ac85c82 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/Bundle.properties @@ -0,0 +1,17 @@ +OpenIDE-Module-Name=CommandLineAutopsy +OptionsCategory_Keywords_Command_Line_Ingest_Settings=Command Line Ingest Settings +OptionsCategory_Keywords_General=Options +OptionsCategory_Name_Command_Line_Ingest=Command Line Ingest +CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified=Output folder must be set +CommandLineIngestSettingsPanel.PathInvalid=Path is not valid +CommandLineIngestSettingsPanel.CannotAccess=Cannot access +CommandLineIngestSettingsPanel.CheckPermissions=Check permissions. +CommandLineIngestSettingsPanel.jLabelSelectOutputFolder.text=Select output folder: +CommandLineIngestSettingsPanel.jLabelInvalidResultsFolder.text=jLabelInvalidOutputFolder +CommandLineIngestSettingsPanel.outputPathTextField.toolTipText=Output folder for command line processing, i.e., the location where case folder will be created by command line processing mode. +CommandLineIngestSettingsPanel.outputPathTextField.text= +CommandLineIngestSettingsPanel.browseOutputFolderButton.text=Browse +CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText=Ingest job settings for the command line processing mode context. +CommandLineIngestSettingsPanel.bnEditIngestSettings.text=Ingest Module Settings +CommandLinePanel.jLabelText.text=Autopsy is running in Command Line mode +CommandLinePanel.jLabel1.text=Autopsy is running from command line diff --git a/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestManager.java b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestManager.java new file mode 100755 index 0000000000..a5b9b88ea2 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestManager.java @@ -0,0 +1,610 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-2019 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.commandline; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FilenameFilter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.util.Collection; +import java.util.Iterator; +import java.util.logging.Level; +import javax.swing.JPanel; +import org.netbeans.spi.sendopts.OptionProcessor; +import org.openide.LifecycleManager; +import org.openide.util.Lookup; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.CaseActionException; +import org.sleuthkit.autopsy.casemodule.CaseDetails; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; +import org.sleuthkit.autopsy.casemodule.StartupWindowInterface; +import org.sleuthkit.autopsy.core.RuntimeProperties; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import static org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.TimeStampUtils; +import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; +import org.sleuthkit.autopsy.events.AutopsyEvent; +import org.sleuthkit.autopsy.ingest.IngestJob; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobStartResult; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.report.ReportProgressPanel; +import org.sleuthkit.autopsy.modules.case_uco.CaseUcoFormatExporter; +import org.sleuthkit.autopsy.modules.case_uco.ReportCaseUco; +import org.sleuthkit.datamodel.Content; + +/** + * Allows Autopsy to be invoked with a command line arguments. Causes Autopsy to + * create a case, add a specified data source, run ingest on that data source, + * produce a CASE/UCO report and exit. + */ +public class CommandLineIngestManager { + + private static final Logger LOGGER = Logger.getLogger(CommandLineIngestManager.class.getName()); + private static final String JOB_RUNNING_THREAD_NAME = "command-line-job-running-service-%d"; + private final ExecutorService jobProcessingExecutor; + private JobProcessingTask jobProcessingTask; + private Future jobProcessingTaskFuture; + private Path rootOutputDirectory; + + public CommandLineIngestManager() { + jobProcessingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat(JOB_RUNNING_THREAD_NAME).build()); + } + + public void start() { + jobProcessingTask = new JobProcessingTask(); + jobProcessingTaskFuture = jobProcessingExecutor.submit(jobProcessingTask); + return; + } + + public void stop() { + + jobProcessingTaskFuture.cancel(true); + jobProcessingExecutor.shutdown(); + + try { + // close current case if there is one open + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + LOGGER.log(Level.WARNING, "Unable to close the case while shutting down ingest jb running service", ex); //NON-NLS + } + + // shut down Autopsy + LifecycleManager.getDefault().exit(); + } + + public StartupWindowInterface getStartupWindow() { + return new CommandLineStartupWindow(); + } + + private final class JobProcessingTask implements Runnable { + + private final Object ingestLock; + + private JobProcessingTask() { + ingestLock = new Object(); + try { + RuntimeProperties.setRunningWithGUI(false); + LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); + } catch (RuntimeProperties.RuntimePropertiesException ex) { + LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); + } + } + + public void run() { + LOGGER.log(Level.INFO, "Job processing task started"); + + try { + // read command line inputs + LOGGER.log(Level.INFO, "Autopsy is running from command line"); //NON-NLS + String dataSourcePath = ""; + String baseCaseName = ""; + + // first look up all OptionProcessors and get input data from CommandLineOptionProcessor + Collection optionProcessors = Lookup.getDefault().lookupAll(OptionProcessor.class); + Iterator optionsIterator = optionProcessors.iterator(); + while (optionsIterator.hasNext()) { + // find CommandLineOptionProcessor + OptionProcessor processor = optionsIterator.next(); + if (processor instanceof CommandLineOptionProcessor) { + // check if we are running from command line + dataSourcePath = ((CommandLineOptionProcessor) processor).getPathToDataSource(); + baseCaseName = ((CommandLineOptionProcessor) processor).getBaseCaseName(); + } + } + + LOGGER.log(Level.INFO, "Data source path = {0}", dataSourcePath); //NON-NLS + LOGGER.log(Level.INFO, "Case name = {0}", baseCaseName); //NON-NLS + System.out.println("Data source path = " + dataSourcePath); + System.out.println("Case name = " + baseCaseName); + + // verify inputs + if (dataSourcePath.isEmpty()) { + LOGGER.log(Level.SEVERE, "Data source path not specified"); + System.out.println("Data source path not specified"); + return; + } + + if (baseCaseName.isEmpty()) { + LOGGER.log(Level.SEVERE, "Case name not specified"); + System.out.println("Case name not specified"); + return; + } + + if (!(new File(dataSourcePath).exists())) { + LOGGER.log(Level.SEVERE, "Data source file not found {0}", dataSourcePath); + System.out.println("Data source file not found " + dataSourcePath); + return; + } + DataSource dataSource = new DataSource("", Paths.get(dataSourcePath)); + + // read options panel configuration + String rootOutputDir = UserPreferences.getCommandLineModeResultsFolder(); + if (!(new File(rootOutputDir).exists())) { + LOGGER.log(Level.SEVERE, "The output directory doesn't exist {0}", rootOutputDir); + System.out.println("The output directory doesn't exist " + rootOutputDir); + return; + } + rootOutputDirectory = Paths.get(rootOutputDir); + + // open case + Case caseForJob; + try { + caseForJob = openCase(baseCaseName); + } catch (CaseActionException ex) { + LOGGER.log(Level.SEVERE, "Error creating or opening case " + baseCaseName, ex); + return; + } + + if (caseForJob == null) { + LOGGER.log(Level.SEVERE, "Error creating or opening case {0}", baseCaseName); + return; + } + + try { + // run data source processor + runDataSourceProcessor(caseForJob, dataSource); + + // run ingest manager + analyze(dataSource); + + // generate CASE-UCO report + Long selectedDataSourceId = getDataSourceId(dataSource); + Path reportFolderPath = Paths.get(caseForJob.getReportDirectory(), "CASE-UCO", "Data_Source_ID_" + selectedDataSourceId.toString() + "_" + TimeStampUtils.createTimeStamp(), ReportCaseUco.getReportFileName()); //NON_NLS + ReportProgressPanel progressPanel = new ReportProgressPanel("CASE_UCO", rootOutputDir); // dummy progress panel + CaseUcoFormatExporter.generateReport(selectedDataSourceId, reportFolderPath.toString(), progressPanel); + } catch (InterruptedException | AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException | AnalysisStartupException ex) { + LOGGER.log(Level.SEVERE, "Unable to ingest data source " + baseCaseName + ". Exiting...", ex); + } finally { + try { + Case.closeCurrentCase(); + } catch (CaseActionException ex) { + LOGGER.log(Level.WARNING, "Exception while closing case", ex); + } + } + + } finally { + LOGGER.log(Level.INFO, "Job processing task stopped"); + + // shut down Autopsy + stop(); + } + } + + /** + * Provides object ID of the data source by reading it from Content + * object. + * + * @param dataSource DataSource object + * @return object ID + */ + private Long getDataSourceId(DataSource dataSource) { + Content content = dataSource.getContent().get(0); + return content.getId(); + } + + private Case openCase(String baseCaseName) throws CaseActionException { + + LOGGER.log(Level.INFO, "Opening case {0}", baseCaseName); + + Path caseDirectoryPath = findCaseDirectory(rootOutputDirectory, baseCaseName); + if (null != caseDirectoryPath) { + // found an existing case directory for same case name. the input case name must be unique. Exit. + LOGGER.log(Level.SEVERE, "Case {0} already exists. Case name must be unique. Exiting", baseCaseName); + throw new CaseActionException("Case " + baseCaseName + " already exists. Case name must be unique. Exiting"); + } else { + caseDirectoryPath = createCaseFolderPath(rootOutputDirectory, baseCaseName); + + // Create the case directory + Case.createCaseDirectory(caseDirectoryPath.toString(), Case.CaseType.SINGLE_USER_CASE); + + CaseDetails caseDetails = new CaseDetails(baseCaseName); + Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), caseDetails); + } + + Case caseForJob = Case.getCurrentCase(); + LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName()); + return caseForJob; + } + + /** + * Passes the data source for the current job through a data source + * processor that adds it to the case database. + * + * @param dataSource The data source. + * + * @throws + * AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException + * if there was a DSP processing error + * + * @throws InterruptedException if the thread running the job processing + * task is interrupted while blocked, i.e., if auto ingest is shutting + * down. + */ + private void runDataSourceProcessor(Case caseForJob, DataSource dataSource) throws InterruptedException, AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException { + + LOGGER.log(Level.INFO, "Adding data source {0} ", dataSource.getPath().toString()); + DataSourceProcessorProgressMonitor progressMonitor = new DoNothingDSPProgressMonitor(); + + // Get an ordered list of data source processors to try + List validDataSourceProcessors; + try { + validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath()); + } catch (AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException ex) { + LOGGER.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath()); + // rethrow the exception. + throw ex; + } + + // did we find a data source processor that can process the data source + if (validDataSourceProcessors.isEmpty()) { + // This should never happen. We should add all unsupported data sources as logical files. + LOGGER.log(Level.SEVERE, "Unsupported data source {0}", dataSource.getPath()); // NON-NLS + return; + } + + synchronized (ingestLock) { + // Try each DSP in decreasing order of confidence + for (AutoIngestDataSourceProcessor selectedProcessor : validDataSourceProcessors) { + UUID taskId = UUID.randomUUID(); + caseForJob.notifyAddingDataSource(taskId); + DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, ingestLock); + caseForJob.notifyAddingDataSource(taskId); + LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{dataSource.getPath(), selectedProcessor.getDataSourceType()}); + selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack); + ingestLock.wait(); + + // at this point we got the content object(s) from the current DSP. + // check whether the data source was processed successfully + if ((dataSource.getResultDataSourceProcessorResultCode() == CRITICAL_ERRORS) + || dataSource.getContent().isEmpty()) { + // move onto the the next DSP that can process this data source + logDataSourceProcessorResult(dataSource); + continue; + } + + logDataSourceProcessorResult(dataSource); + return; + } + // If we get to this point, none of the processors were successful + LOGGER.log(Level.SEVERE, "All data source processors failed to process {0}", dataSource.getPath()); + // Throw an exception. It will get caught & handled upstream and will result in AIM auto-pause. + throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors"); + } + } + + /** + * Logs the results of running a data source processor on the data + * source for the current job. + * + * @param dataSource The data source. + */ + private void logDataSourceProcessorResult(DataSource dataSource) { + + DataSourceProcessorCallback.DataSourceProcessorResult resultCode = dataSource.getResultDataSourceProcessorResultCode(); + if (null != resultCode) { + switch (resultCode) { + case NO_ERRORS: + LOGGER.log(Level.INFO, "Added data source to case"); + if (dataSource.getContent().isEmpty()) { + LOGGER.log(Level.SEVERE, "Data source failed to produce content"); + } + break; + + case NONCRITICAL_ERRORS: + for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) { + LOGGER.log(Level.WARNING, "Non-critical error running data source processor for {0}: {1}", new Object[]{dataSource.getPath(), errorMessage}); + } + LOGGER.log(Level.INFO, "Added data source to case"); + if (dataSource.getContent().isEmpty()) { + LOGGER.log(Level.SEVERE, "Data source failed to produce content"); + } + break; + + case CRITICAL_ERRORS: + for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) { + LOGGER.log(Level.SEVERE, "Critical error running data source processor for {0}: {1}", new Object[]{dataSource.getPath(), errorMessage}); + } + LOGGER.log(Level.SEVERE, "Failed to add data source to case"); + break; + } + } else { + LOGGER.log(Level.WARNING, "No result code for data source processor for {0}", dataSource.getPath()); + } + } + + /** + * Analyzes the data source content returned by the data source + * processor using the configured set of data source level and file + * level analysis modules. + * + * @param dataSource The data source to analyze. + * + * @throws AnalysisStartupException if there is an error analyzing the + * data source. + * @throws InterruptedException if the thread running the job processing + * task is interrupted while blocked, i.e., if auto ingest is shutting + * down. + */ + private void analyze(DataSource dataSource) throws AnalysisStartupException, InterruptedException { + + LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", dataSource.getPath()); + IngestJobEventListener ingestJobEventListener = new IngestJobEventListener(); + IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener); + try { + synchronized (ingestLock) { + IngestJobSettings ingestJobSettings = new IngestJobSettings(UserPreferences.getCommandLineModeIngestModuleContextString()); + List settingsWarnings = ingestJobSettings.getWarnings(); + if (settingsWarnings.isEmpty()) { + IngestJobStartResult ingestJobStartResult = IngestManager.getInstance().beginIngestJob(dataSource.getContent(), ingestJobSettings); + IngestJob ingestJob = ingestJobStartResult.getJob(); + if (null != ingestJob) { + /* + * Block until notified by the ingest job event + * listener or until interrupted because auto ingest + * is shutting down. + */ + ingestLock.wait(); + LOGGER.log(Level.INFO, "Finished ingest modules analysis for {0} ", dataSource.getPath()); + IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot(); + for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) { + if (!snapshot.isCancelled()) { + List cancelledModules = snapshot.getCancelledDataSourceIngestModules(); + if (!cancelledModules.isEmpty()) { + LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", dataSource.getPath())); + for (String module : snapshot.getCancelledDataSourceIngestModules()) { + LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, dataSource.getPath())); + } + } + LOGGER.log(Level.INFO, "Analysis of data source completed"); + } else { + LOGGER.log(Level.WARNING, "Analysis of data source cancelled"); + IngestJob.CancellationReason cancellationReason = snapshot.getCancellationReason(); + if (IngestJob.CancellationReason.NOT_CANCELLED != cancellationReason && IngestJob.CancellationReason.USER_CANCELLED != cancellationReason) { + throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), dataSource.getPath())); + } + } + } + } else if (!ingestJobStartResult.getModuleErrors().isEmpty()) { + for (IngestModuleError error : ingestJobStartResult.getModuleErrors()) { + LOGGER.log(Level.SEVERE, String.format("%s ingest module startup error for %s", error.getModuleDisplayName(), dataSource.getPath()), error.getThrowable()); + } + LOGGER.log(Level.SEVERE, "Failed to analyze data source due to ingest job startup error"); + throw new AnalysisStartupException(String.format("Error(s) during ingest module startup for %s", dataSource.getPath())); + } else { + LOGGER.log(Level.SEVERE, String.format("Ingest manager ingest job start error for %s", dataSource.getPath()), ingestJobStartResult.getStartupException()); + throw new AnalysisStartupException("Ingest manager error starting job", ingestJobStartResult.getStartupException()); + } + } else { + for (String warning : settingsWarnings) { + LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{dataSource.getPath(), warning}); + } + LOGGER.log(Level.SEVERE, "Failed to analyze data source due to settings errors"); + throw new AnalysisStartupException("Error(s) in ingest job settings"); + } + } + } finally { + IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventListener); + } + } + + /** + * Creates a case folder path. Does not create the folder described by + * the path. + * + * @param caseFoldersPath The root case folders path. + * @param caseName The name of the case. + * + * @return A case folder path with a time stamp suffix. + */ + Path createCaseFolderPath(Path caseFoldersPath, String caseName) { + String folderName = caseName + "_" + TimeStampUtils.createTimeStamp(); + return Paths.get(caseFoldersPath.toString(), folderName); + } + + /** + * Searches a given folder for the most recently modified case folder + * for a case. + * + * @param folderToSearch The folder to be searched. + * @param caseName The name of the case for which a case folder is to be + * found. + * + * @return The path of the case folder, or null if it is not found. + */ + Path findCaseDirectory(Path folderToSearch, String caseName) { + File searchFolder = new File(folderToSearch.toString()); + if (!searchFolder.isDirectory()) { + return null; + } + Path caseFolderPath = null; + String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName)); + long mostRecentModified = 0; + for (String candidateFolder : candidateFolders) { + File file = new File(candidateFolder); + if (file.lastModified() >= mostRecentModified) { + mostRecentModified = file.lastModified(); + caseFolderPath = Paths.get(folderToSearch.toString(), file.getPath()); + } + } + return caseFolderPath; + } + + /** + * An ingest job event listener that allows the job processing task to + * block until the analysis of a data source by the data source level + * and file level ingest modules is completed. + *

+ * Note that the ingest job can spawn "child" ingest jobs (e.g., if an + * embedded virtual machine is found), so the job processing task must + * remain blocked until ingest is no longer running. + */ + private class IngestJobEventListener implements PropertyChangeListener { + + /** + * Listens for local ingest job completed or cancelled events and + * notifies the job processing thread when such an event occurs and + * there are no "child" ingest jobs running. + * + * @param event + */ + @Override + public void propertyChange(PropertyChangeEvent event) { + if (AutopsyEvent.SourceType.LOCAL == ((AutopsyEvent) event).getSourceType()) { + String eventType = event.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { + synchronized (ingestLock) { + ingestLock.notify(); + } + } + } + } + }; + + /** + * A data source processor progress monitor does nothing. There is + * currently no mechanism for showing or recording data source processor + * progress during an ingest job. + */ + private class DoNothingDSPProgressMonitor implements DataSourceProcessorProgressMonitor { + + /** + * Does nothing. + * + * @param indeterminate + */ + @Override + public void setIndeterminate(final boolean indeterminate) { + } + + /** + * Does nothing. + * + * @param progress + */ + @Override + public void setProgress(final int progress) { + } + + /** + * Does nothing. + * + * @param text + */ + @Override + public void setProgressText(final String text) { + } + } + + /** + * Exception type thrown when there is a problem analyzing a data source + * with data source level and file level ingest modules for an ingest + * job. + */ + private final class AnalysisStartupException extends Exception { + + private static final long serialVersionUID = 1L; + + private AnalysisStartupException(String message) { + super(message); + } + + private AnalysisStartupException(String message, Throwable cause) { + super(message, cause); + } + } + } + + private static class CaseFolderFilter implements FilenameFilter { + + private final String caseName; + private final static String CASE_METADATA_EXT = CaseMetadata.getFileExtension(); + + CaseFolderFilter(String caseName) { + this.caseName = caseName; + } + + @Override + public boolean accept(File folder, String fileName) { + File file = new File(folder, fileName); + if (fileName.length() > TimeStampUtils.getTimeStampLength() && file.isDirectory()) { + if (TimeStampUtils.endsWithTimeStamp(fileName)) { + if (null != caseName) { + String fileNamePrefix = fileName.substring(0, fileName.length() - TimeStampUtils.getTimeStampLength()); + if (fileNamePrefix.equals(caseName)) { + return hasCaseMetadataFile(file); + } + } else { + return hasCaseMetadataFile(file); + } + } + } + return false; + } + + /** + * Determines whether or not there is a case metadata file in a given + * folder. + * + * @param folder The file object representing the folder to search. + * + * @return True or false. + */ + private static boolean hasCaseMetadataFile(File folder) { + for (File file : folder.listFiles()) { + if (file.getName().toLowerCase().endsWith(CASE_METADATA_EXT) && file.isFile()) { + return true; + } + } + return false; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.form new file mode 100755 index 0000000000..64e02664cc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.form @@ -0,0 +1,162 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.java new file mode 100755 index 0000000000..09aa18d258 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanel.java @@ -0,0 +1,395 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-2019 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.commandline; + +import java.awt.BorderLayout; +import java.awt.Cursor; +import java.io.File; +import java.nio.file.Files; +import java.util.List; +import javax.swing.JFileChooser; +import javax.swing.JOptionPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.ingest.IngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestJobSettingsPanel; +import java.nio.file.Paths; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * Configuration panel for auto ingest settings. + */ +@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives +public class CommandLineIngestSettingsPanel extends javax.swing.JPanel { + + private final CommandLineIngestSettingsPanelController controller; + private final JFileChooser fc = new JFileChooser(); + private static final long serialVersionUID = 1L; + private static final Logger logger = Logger.getLogger(CommandLineIngestSettingsPanel.class.getName()); + + /** + * Creates new form AutoIngestSettingsPanel + * + * @param theController Controller to notify of changes. + */ + public CommandLineIngestSettingsPanel(CommandLineIngestSettingsPanelController theController) { + controller = theController; + initComponents(); + + load(true); + outputPathTextField.getDocument().addDocumentListener(new MyDocumentListener()); + jLabelInvalidResultsFolder.setText(""); + } + + private class MyDocumentListener implements DocumentListener { + + @Override + public void changedUpdate(DocumentEvent e) { + valid(); + controller.changed(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + valid(); + controller.changed(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + valid(); + controller.changed(); + } + }; + + /** + * Load mode from persistent storage. + * + * @param inStartup True if we're doing the initial population of the UI + */ + final void load(boolean inStartup) { + + String results = org.sleuthkit.autopsy.commandline.UserPreferences.getCommandLineModeResultsFolder(); + if (results != null) { + outputPathTextField.setText(results); + } else { + outputPathTextField.setText(""); + } + + valid(); + } + + /** + * Save mode to persistent storage. + */ + void store() { + String resultsFolderPath = getNormalizedFolderPath(outputPathTextField.getText().trim()); + org.sleuthkit.autopsy.commandline.UserPreferences.setCommandLineModeResultsFolder(resultsFolderPath); + } + + /** + * Validate current panel settings. + */ + boolean valid() { + + if (validateResultsPath()) { + return true; + } + return false; + } + + /** + * Normalizes a path to make sure there are no "space" characters at the end + * + * @param path Path to a directory + * + * @return Path without "space" characters at the end + */ + String normalizePath(String path) { + + while (path.length() > 0) { + if (path.charAt(path.length() - 1) == ' ') { + path = path.substring(0, path.length() - 1); + } else { + break; + } + } + return path; + } + + /** + * Validates that a path is valid and points to a folder. + * + * @param path A path to be validated + * + * @return boolean returns true if valid and points to a folder, false + * otherwise + */ + boolean isFolderPathValid(String path) { + try { + File file = new File(normalizePath(path)); + + // check if it's a symbolic link + if (Files.isSymbolicLink(file.toPath())) { + return true; + } + + // local folder + if (file.exists() && file.isDirectory()) { + return true; + } + } catch (Exception ex) { + // Files.isSymbolicLink (and other "files" methods) throw exceptions on seemingly innocent inputs. + // For example, it will throw an exception when either " " is last character in path or + // a path starting with ":". + // We can just ignore these exceptions as they occur in process of user typing in the path. + return false; + } + return false; + } + + /** + * Returns a path that was normalized by file system. + * + * @param path A path to be normalized. Normalization occurs inside a call + * to new File(). + * + * @return String returns normalized OS path + */ + String getNormalizedFolderPath(String path) { + // removes "/", "\", and " " characters at the end of path string. + // normalizePath() removes spaces at the end of path and a call to "new File()" + // internally formats the path string to remove "/" and "\" characters at the end of path. + File file = new File(normalizePath(path)); + return file.getPath(); + } + + /** + * Validate results path. Display warnings if invalid. + */ + boolean validateResultsPath() { + + String outputPath = outputPathTextField.getText().trim(); + + if (outputPath.isEmpty()) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.ResultsDirectoryUnspecified")); + return false; + } + + if (!isFolderPathValid(outputPath)) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.PathInvalid")); + return false; + } + + if (false == permissionsAppropriate(outputPath)) { + jLabelInvalidResultsFolder.setVisible(true); + jLabelInvalidResultsFolder.setText(NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.CannotAccess") + + " " + outputPath + " " + + NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.CheckPermissions")); + return false; + } + + jLabelInvalidResultsFolder.setVisible(false); + return true; + } + + private void displayIngestJobSettingsPanel() { + this.getParent().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + IngestJobSettings ingestJobSettings = new IngestJobSettings(org.sleuthkit.autopsy.commandline.UserPreferences.getCommandLineModeIngestModuleContextString()); + showWarnings(ingestJobSettings); + IngestJobSettingsPanel ingestJobSettingsPanel = new IngestJobSettingsPanel(ingestJobSettings); + + add(ingestJobSettingsPanel, BorderLayout.PAGE_START); + + if (JOptionPane.showConfirmDialog(this, ingestJobSettingsPanel, "Ingest Module Configuration", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) == JOptionPane.OK_OPTION) { + // store the updated settings + ingestJobSettings = ingestJobSettingsPanel.getSettings(); + ingestJobSettings.save(); + showWarnings(ingestJobSettings); + } + + this.getParent().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + + private static void showWarnings(IngestJobSettings ingestJobSettings) { + List warnings = ingestJobSettings.getWarnings(); + if (warnings.isEmpty() == false) { + StringBuilder warningMessage = new StringBuilder(); + for (String warning : warnings) { + warningMessage.append(warning).append("\n"); + } + JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), warningMessage.toString()); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + nodeScrollPane = new javax.swing.JScrollPane(); + nodePanel = new javax.swing.JPanel(); + bnEditIngestSettings = new javax.swing.JButton(); + browseOutputFolderButton = new javax.swing.JButton(); + outputPathTextField = new javax.swing.JTextField(); + jLabelInvalidResultsFolder = new javax.swing.JLabel(); + jLabelSelectOutputFolder = new javax.swing.JLabel(); + + nodeScrollPane.setMinimumSize(new java.awt.Dimension(0, 0)); + + nodePanel.setMinimumSize(new java.awt.Dimension(100, 100)); + + org.openide.awt.Mnemonics.setLocalizedText(bnEditIngestSettings, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.bnEditIngestSettings.text")); // NOI18N + bnEditIngestSettings.setToolTipText(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.bnEditIngestSettings.toolTipText")); // NOI18N + bnEditIngestSettings.setActionCommand(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.bnEditIngestSettings.text")); // NOI18N + bnEditIngestSettings.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnEditIngestSettingsActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(browseOutputFolderButton, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.browseOutputFolderButton.text")); // NOI18N + browseOutputFolderButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseOutputFolderButtonActionPerformed(evt); + } + }); + + outputPathTextField.setText(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.outputPathTextField.text")); // NOI18N + outputPathTextField.setToolTipText(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.outputPathTextField.toolTipText")); // NOI18N + + jLabelInvalidResultsFolder.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(jLabelInvalidResultsFolder, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelInvalidResultsFolder.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(jLabelSelectOutputFolder, org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelSelectOutputFolder.text")); // NOI18N + jLabelSelectOutputFolder.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); + + javax.swing.GroupLayout nodePanelLayout = new javax.swing.GroupLayout(nodePanel); + nodePanel.setLayout(nodePanelLayout); + nodePanelLayout.setHorizontalGroup( + nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(nodePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(nodePanelLayout.createSequentialGroup() + .addComponent(outputPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 630, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseOutputFolderButton)) + .addComponent(bnEditIngestSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(nodePanelLayout.createSequentialGroup() + .addComponent(jLabelSelectOutputFolder) + .addGap(18, 18, 18) + .addComponent(jLabelInvalidResultsFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 544, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(355, Short.MAX_VALUE)) + ); + nodePanelLayout.setVerticalGroup( + nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(nodePanelLayout.createSequentialGroup() + .addGap(40, 40, 40) + .addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabelSelectOutputFolder, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabelInvalidResultsFolder)) + .addGap(1, 1, 1) + .addGroup(nodePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(browseOutputFolderButton) + .addComponent(outputPathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(25, 25, 25) + .addComponent(bnEditIngestSettings) + .addContainerGap(389, Short.MAX_VALUE)) + ); + + browseOutputFolderButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.browseOutputFolderButton.text")); // NOI18N + jLabelInvalidResultsFolder.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelInvalidResultsFolder.text")); // NOI18N + jLabelSelectOutputFolder.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(CommandLineIngestSettingsPanel.class, "CommandLineIngestSettingsPanel.jLabelSelectOutputFolder.text")); // NOI18N + + nodeScrollPane.setViewportView(nodePanel); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(nodeScrollPane, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 864, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(nodeScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 421, Short.MAX_VALUE) + ); + }// //GEN-END:initComponents + + private void browseOutputFolderButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseOutputFolderButtonActionPerformed + String oldText = outputPathTextField.getText().trim(); + // set the current directory of the FileChooser if the oldText is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + fc.setDialogTitle("Select case output folder:"); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + outputPathTextField.setText(path); + valid(); + controller.changed(); + } + }//GEN-LAST:event_browseOutputFolderButtonActionPerformed + + private void bnEditIngestSettingsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnEditIngestSettingsActionPerformed + displayIngestJobSettingsPanel(); + }//GEN-LAST:event_bnEditIngestSettingsActionPerformed + + boolean permissionsAppropriate(String path) { + return FileUtil.hasReadWriteAccess(Paths.get(path)); + } + + private void resetUI() { + load(true); + controller.changed(); + } + + void setEnabledState(boolean enabled) { + bnEditIngestSettings.setEnabled(enabled); + browseOutputFolderButton.setEnabled(enabled); + jLabelInvalidResultsFolder.setEnabled(enabled); + jLabelSelectOutputFolder.setEnabled(enabled); + outputPathTextField.setEnabled(enabled); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton bnEditIngestSettings; + private javax.swing.JButton browseOutputFolderButton; + private javax.swing.JLabel jLabelInvalidResultsFolder; + private javax.swing.JLabel jLabelSelectOutputFolder; + private javax.swing.JPanel nodePanel; + private javax.swing.JScrollPane nodeScrollPane; + private javax.swing.JTextField outputPathTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanelController.java b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanelController.java new file mode 100755 index 0000000000..a706df1cce --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineIngestSettingsPanelController.java @@ -0,0 +1,132 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-2019 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.commandline; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; + +@OptionsPanelController.TopLevelRegistration(categoryName = "#OptionsCategory_Name_Command_Line_Ingest", + iconBase = "org/sleuthkit/autopsy/images/command_line_icon.png", + position = 17, + keywords = "#OptionsCategory_Keywords_Command_Line_Ingest_Settings", + keywordsCategory = "Command Line Ingest") +public final class CommandLineIngestSettingsPanelController extends OptionsPanelController { + + private CommandLineIngestSettingsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + private static final Logger logger = Logger.getLogger(CommandLineIngestSettingsPanelController.class.getName()); + + @Override + public void update() { + getPanel().load(false); + changed = false; + } + + @Override + public void applyChanges() { + getPanel().store(); + changed = false; + } + + @Override + public void cancel() { + } + + @Override + public boolean isValid() { + return getPanel().valid(); + } + + @Override + public boolean isChanged() { + return changed; + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public JComponent getComponent(Lookup masterLookup) { + return getPanel(); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + if (pcs.getPropertyChangeListeners().length == 0) { + pcs.addPropertyChangeListener(l); + } + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + /** + * Note the NetBeans Framework does not appear to call this at all. We + * are using NetBeans 7.3.1 Build 201306052037. Perhaps in a future + * version of the Framework this will be resolved, but for now, simply + * don't unregister anything and add one time only in the + * addPropertyChangeListener() method above. + */ + } + + private CommandLineIngestSettingsPanel getPanel() { + if (panel == null) { + panel = new CommandLineIngestSettingsPanel(this); + panel.setSize(750, 600); //makes the panel large enough to hide the scroll bar + } + return panel; + } + + void changed() { + if (!changed) { + changed = true; + + try { + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } catch (Exception e) { + logger.log(Level.SEVERE, "GeneralOptionsPanelController listener threw exception", e); //NON-NLS + MessageNotifyUtil.Notify.show( + NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr"), + NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr.msg"), + MessageNotifyUtil.MessageType.ERROR); + } + } + + try { + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } catch (Exception e) { + logger.log(Level.SEVERE, "GeneralOptionsPanelController listener threw exception", e); //NON-NLS + MessageNotifyUtil.Notify.show( + NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr"), + NbBundle.getMessage(this.getClass(), "GeneralOptionsPanelController.moduleErr.msg"), + MessageNotifyUtil.MessageType.ERROR); + } + } +} + diff --git a/Core/src/org/sleuthkit/autopsy/commandline/CommandLineOptionProcessor.java b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineOptionProcessor.java new file mode 100755 index 0000000000..ae87872d90 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/commandline/CommandLineOptionProcessor.java @@ -0,0 +1,168 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014-2019 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.commandline; + +import java.io.File; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.netbeans.api.sendopts.CommandException; +import org.netbeans.spi.sendopts.Env; +import org.netbeans.spi.sendopts.Option; +import org.netbeans.spi.sendopts.OptionProcessor; +import org.openide.util.lookup.ServiceProvider; + +/** + * This class can be used to add command line options to Autopsy + */ +@ServiceProvider(service = OptionProcessor.class) +public class CommandLineOptionProcessor extends OptionProcessor { + + private static final Logger logger = Logger.getLogger(CommandLineOptionProcessor.class.getName()); + private final Option pathToDataSourceOption = Option.optionalArgument('l', "inputPath"); + private final Option caseNameOption = Option.optionalArgument('2', "caseName"); + private final Option runFromCommandLineOption = Option.optionalArgument('3', "runFromCommandLine"); + private String pathToDataSource; + private String baseCaseName; + private boolean runFromCommandLine = false; + + @Override + protected Set