diff --git a/Core/build.xml b/Core/build.xml index 0e5c90ef04..5d91ade7e1 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -74,6 +74,7 @@ + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index c269acaa98..0f00b980af 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -120,6 +120,7 @@ file.reference.okhttp-2.7.5-javadoc.jar=release/modules/ext/okhttp-2.7.5-javadoc file.reference.okhttp-2.7.5-sources.jar=release/modules/ext/okhttp-2.7.5-sources.jar file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar +file.reference.datcon.jar=release/modules/ext/DatCon.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index b2e17a7e32..476beadec8 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -802,6 +802,10 @@ ext/jutf7-1.0.0.jar release/modules/ext/jutf7-1.0.0.jar + + + ext/DatCon.jar + release/modules/ext/DatCon.jar diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED new file mode 100755 index 0000000000..f0e1fe823c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/Bundle.properties-MERGED @@ -0,0 +1,5 @@ +DATExtractor_process_message=Processing DJI DAT file: %s +DATFileExtractor_Extractor_Name=DAT File Extractor +DroneIngestModule_Description=Description +DroneIngestModule_Name=Drone +DroneIngestModule_process_start=Started {0} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DATDumper.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DATDumper.java new file mode 100755 index 0000000000..37d3c97920 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DATDumper.java @@ -0,0 +1,148 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import src.Files.ConvertDat; +import src.Files.CsvWriter; +import src.Files.DatFile; +import src.Files.DJIAssistantFile; +import src.Files.Exception.FileEnd; +import src.Files.Exception.NotDatFile; + +/** + * Dump DJI DAT files to csv file using the DatCon.jar. + * + */ +final class DATDumper { + + /** + * Construct a DATDumper. + */ + DATDumper() { + } + + /** + * Dump the DJI DAT file to a csv file. + * + * @param datFilePath Path to input DAT file + * @param outputFilePath Output file path + * @param overWriteExisting True to overwrite an existing csv file with + * outputFilePath + * + * @throws DroneIngestException + */ + void dumpDATFile(String datFilePath, String outputFilePath, boolean overWriteExisting) throws DroneIngestException { + // Validate the input and output file paths. + validateOutputFile(outputFilePath, overWriteExisting); + validateDATFile(datFilePath); + + DatFile datFile = null; + try (CsvWriter writer = new CsvWriter(outputFilePath)) { + // Creates a version specific DatFile object + datFile = DatFile.createDatFile(datFilePath); + datFile.reset(); + // preAnalyze does an inital pass of the DAT file to gather some + // information about the file. + datFile.preAnalyze(); + + // Creates a version specific ConvertDat object + ConvertDat convertDat = datFile.createConVertDat(); + + // The lower the sample rate the smaller the output csv file will be + // however the date will be less precise. 30 was recommended in the + // DatCon documentation as a good default. + convertDat.sampleRate = 30; + + // Setting the tickRangeLower and upper values reduces some of the + // noise invalid data in the output file. + if (datFile.gpsLockTick != -1) { + convertDat.tickRangeLower = datFile.gpsLockTick; + } + + if (datFile.lastMotorStopTick != -1) { + convertDat.tickRangeUpper = datFile.lastMotorStopTick; + } + + convertDat.setCsvWriter(writer); + convertDat.createRecordParsers(); + datFile.reset(); + + // Analyze does the work of parsing the data, everything prior was + // setup + convertDat.analyze(true); + + } catch (IOException | NotDatFile | FileEnd ex) { + throw new DroneIngestException(String.format("Failed to dump DAT file to csv. DAT = %s, CSV = %s", datFilePath, outputFilePath), ex); + } finally { + if (datFile != null) { + datFile.close(); + } + } + } + + /** + * Validate that if the given csv file exists that the overWriteExsiting + * param is true. Throws an exception if the file exists and + * overWriteExisting is false. + * + * @param outputFileName Absolute path for the output csv file + * @param overWriteExisting True to over write an existing file. + * + * @throws DroneIngestException Throws exception if overWriteExisting is + * true and outputFileName exists + */ + private void validateOutputFile(String outputFileName, boolean overWriteExisting) throws DroneIngestException { + File csvFile = new File(outputFileName); + + if (csvFile.exists()) { + if (overWriteExisting) { + csvFile.delete(); + } else { + throw new DroneIngestException(String.format("Unable to dump DAT file. overWriteExsiting is false and DAT output csv file exists: %s", outputFileName)); + } + } + } + + /** + * Validate that the DAT file exists and it is in a known format that can be + * dumped. + * + * @param datFilePath Absolute path to DAT file + * + * @throws DroneIngestException + */ + private void validateDATFile(String datFilePath) throws DroneIngestException { + File datFile = new File(datFilePath); + + if (!datFile.exists()) { + throw new DroneIngestException(String.format("Unable to dump DAT file DAT file does not exist: %s", datFilePath)); + } + + try { + if (!DatFile.isDatFile(datFilePath) && !DJIAssistantFile.isDJIDat(datFile)) { + throw new DroneIngestException(String.format("Unable to dump DAT file. File is not a DAT file: %s", datFilePath)); + } + } catch (FileNotFoundException ex) { + throw new DroneIngestException(String.format("Unable to dump DAT file. File not found %s", datFilePath), ex); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java new file mode 100755 index 0000000000..50db7723ba --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DATExtractor.java @@ -0,0 +1,318 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.time.temporal.TemporalAccessor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Extract drone position data from DJI Phantom drones. + * + * Module uses DatCon.jar to dump FLYXXX.DAT file to a CSV file which is stored + * in the case temp directory. Artifacts are created by parsing the csv file. + * + */ +final class DATExtractor extends DroneExtractor { + + private static final Logger logger = Logger.getLogger(DATExtractor.class.getName()); + + private static final String HEADER_LONG = "IMU_ATTI(0):Longitude"; + private static final String HEADER_LAT = "IMU_ATTI(0):Latitude"; + private static final String HEADER_VELOCITY = "IMU_ATTI(0):velComposite"; + private static final String HEADER_DATETILE = "GPS:dateTimeStamp"; + private static final String HEADER_ALTITUDE = "GPS(0):heightMSL"; + private static final String HEADER_DISTANCE_FROM_HP = "IMU_ATTI(0):distanceHP"; + private static final String HEADER_DISTANCE_TRAVELED = "IMU_ATTI(0):distanceTravelled"; + + /** + * Construct a DATExtractor. + * + * @throws DroneIngestException + */ + DATExtractor() throws DroneIngestException { + super(); + } + + + @Messages({ + "DATExtractor_process_message=Processing DJI DAT file: %s" + }) + @Override + void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) throws DroneIngestException { + List datFiles = findDATFiles(dataSource); + + DATDumper dumper = new DATDumper(); + + try { + for (AbstractFile DATFile : datFiles) { + if (context.dataSourceIngestIsCancelled()) { + break; + } + + progressBar.progress(String.format(Bundle.DATExtractor_process_message(), DATFile.getName())); + + // Copy the DAT file into the case temp folder + File tempDATFile = getTemporaryFile(context, DATFile); + + // Create a path for the csv file + String csvFilePath = getCSVPathForDAT(DATFile); + + try { + // Dump the DAT file to a csv file + dumper.dumpDATFile(tempDATFile.getAbsolutePath(), csvFilePath, true); + + if (context.dataSourceIngestIsCancelled()) { + break; + } + + // Process the csv file + processCSVFile(context, DATFile, csvFilePath); + } catch (DroneIngestException ex) { + logger.log(Level.WARNING, String.format("Exception thrown while processing DAT file %s", DATFile.getName()), ex); + } finally { + tempDATFile.delete(); + (new File(csvFilePath)).delete(); + } + } + } finally { + FileUtil.deleteDir(getExtractorTempPath().toFile()); + } + } + + @NbBundle.Messages({ + "DATFileExtractor_Extractor_Name=DAT File Extractor" + }) + + @Override + String getName() { + return Bundle.DATFileExtractor_Extractor_Name(); + } + + /** + * Find any files that have the file name FLYXXX.DAT where the X are digit + * characters. + * + * @param dataSource Data source to search + * + * @return List of found files or empty list if none where found + * + * @throws DroneIngestException + */ + private List findDATFiles(Content dataSource) throws DroneIngestException { + List fileList = new ArrayList<>(); + + FileManager fileManager = getCurrentCase().getServices().getFileManager(); + + // findFiles use the SQL wildcard # in the file name + try { + fileList = fileManager.findFiles(dataSource, "FLY___.DAT"); + } catch (TskCoreException ex) { + throw new DroneIngestException("Unable to find drone DAT files.", ex); + } + + return fileList; + } + + /** + * Return an absolute path for the given DAT file. + * + * @param file DAT file + * + * @return Absolute csv file path + */ + private String getCSVPathForDAT(AbstractFile file) { + String tempFileName = file.getName() + file.getId() + ".csv"; + return Paths.get(getExtractorTempPath().toString(), tempFileName).toString(); + } + + /** + * Process the csv dump of the drone DAT file. + * + * Create artifacts for all rows that have a valid longitude and latitude. + * + * @param context current case job context + * @param DATFile Original DAT file + * @param csvFilePath Path of csv file to process + * + * @throws DroneIngestException + */ + private void processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException { + try (BufferedReader reader = new BufferedReader(new FileReader(new File(csvFilePath)))) { + // First read in the header line and process + String line = reader.readLine(); + Map headerMap = makeHeaderMap(line.split(",")); + Collection artifacts = new ArrayList<>(); + + while ((line = reader.readLine()) != null) { + if (context.dataSourceIngestIsCancelled()) { + break; + } + + String[] values = line.split(","); + Collection attributes = buildAttributes(headerMap, values); + if (attributes != null) { + artifacts.add(makeWaypointArtifact(DATFile, attributes)); + } + } + + if (context.dataSourceIngestIsCancelled()) { + return; + } + + postArtifacts(artifacts); + + } catch (IOException ex) { + throw new DroneIngestException(String.format("Failed to read DAT csvFile %s", csvFilePath), ex); + } + } + + /** + * Create a lookup to quickly match the column header with its array index + * + * @param headers + * + * @return Map of column names with the column index + */ + private Map makeHeaderMap(String[] headers) { + Map map = new HashMap<>(); + + for (int index = 0; index < headers.length; index++) { + map.put(headers[index], index); + + } + + return map; + } + + /** + * Returns a list of BlackboardAttributes generated from the String array. + * + * If longitude and latitude are not valid, assume we the row is not + * interesting and return a null collection. + * + * @param columnLookup column header lookup map + * @param values Row data + * + * @return Collection of BlackboardAttributes for row or null collection if + * longitude or latitude was not valid + * + * @throws DroneIngestException + */ + private Collection buildAttributes(Map columnLookup, String[] values) throws DroneIngestException { + + Double latitude = getDoubleValue(columnLookup.get(HEADER_LAT), values); + Double longitude = getDoubleValue(columnLookup.get(HEADER_LONG), values); + + if (longitude == null || latitude == null) { + // Assume the row is not valid\has junk + return null; + } + + return makeWaypointAttributes(latitude, + longitude, + getDoubleValue(columnLookup.get(HEADER_ALTITUDE), values), + getDateTimeValue(columnLookup, values), + getDoubleValue(columnLookup.get(HEADER_VELOCITY), values), + getDoubleValue(columnLookup.get(HEADER_DISTANCE_FROM_HP), values), + getDoubleValue(columnLookup.get(HEADER_DISTANCE_TRAVELED), values)); + } + + /** + * Returns the waypoint timestamp in java/unix epoch seconds. + * + * The format of the date time string is 2016-09-26T18:26:19Z. + * + * @param headerMap + * @param values + * + * @return Epoch seconds or null if no dateTime value was found + */ + private Long getDateTimeValue(Map headerMap, String[] values) { + Integer index = headerMap.get(HEADER_DATETILE); + if (index == null || index == -1 || index > values.length) { + return null; + } + + String value = values[index]; + if (value == null || value.isEmpty()) { + return null; + } + + try { + ZonedDateTime zdt = ZonedDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); + return zdt.toLocalDateTime().toEpochSecond(ZoneOffset.UTC); + } catch (DateTimeParseException ex) { + return null; + } + } + + /** + * Returns the string value at the given index parsed as a double. + * + * @param index Index to string array + * @param values Array of string values + * + * @return Double value or null if the index is out of bounds of the string + * array or the string value at index was not a double. + */ + private Double getDoubleValue(Integer index, String[] values) { + if (index == null || index == -1 || index > values.length) { + return null; + } + + String value = values[index]; + if (value == null || value.isEmpty()) { + return null; + } + + try { + return Double.parseDouble(value); + } catch (NumberFormatException ex) { + return null; + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneExtractor.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneExtractor.java new file mode 100755 index 0000000000..d6e314c03e --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneExtractor.java @@ -0,0 +1,232 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Blackboard; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Abstract base class for all Drone file extractors. + */ +abstract class DroneExtractor { + + static private final String TEMP_FOLDER_NAME = "DroneExtractor"; + private final Case currentCase; + + /** + * Common constructor. Subclasses should call super in their constructor. + * + * @throws DroneIngestException + */ + protected DroneExtractor() throws DroneIngestException { + try { + currentCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + throw new DroneIngestException("Unable to create drone extractor, no open case.", ex); + } + } + + abstract void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) throws DroneIngestException; + + abstract String getName(); + + /** + * Return the current case object. + * + * @return Current case + */ + final protected Case getCurrentCase() { + return currentCase; + } + + /** + * Return the current SleuthkitCase. + * + * @return Current sleuthkit case + */ + final protected SleuthkitCase getSleuthkitCase() { + return currentCase.getSleuthkitCase(); + } + + /** + * Return the Blackboard object. + * + * @return Current Case blackboard object. + */ + final protected Blackboard getBlackboard() { + return currentCase.getSleuthkitCase().getBlackboard(); + } + + /** + * Method to post a list of BlackboardArtifacts to the blackboard. + * + * @param artifacts A list of artifacts. IF list is empty or null, the + * function will return. + */ + void postArtifacts(Collection artifacts) throws DroneIngestException { + if (artifacts == null || artifacts.isEmpty()) { + return; + } + + try { + getBlackboard().postArtifacts(artifacts, getName()); + } catch (Blackboard.BlackboardException ex) { + throw new DroneIngestException(String.format("Failed to post Drone artifacts to blackboard."), ex); + } + } + + /** + * Create a TSK_WAYPOINT artifact with the given list of attributes. + * + * @param DATFile DAT file + * @param attributes List of BlackboardAttributes + * + * @return TSK_WAYPOINT BlackboardArtifact + * + * @throws DroneIngestException + */ + protected BlackboardArtifact makeWaypointArtifact(AbstractFile DATFile, Collection attributes) throws DroneIngestException { + try { + BlackboardArtifact artifact = DATFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WAYPOINT); + artifact.addAttributes(attributes); + return artifact; + } catch (TskCoreException ex) { + throw new DroneIngestException(String.format("Failed to post Drone artifacts to blackboard."), ex); + } + } + + /** + * Returns a list of BlackboardAttributes for the given parameters. + * + * Throws exception of longitude or latitude are null. + * + * @param latitude Waypoint latitude, must be non-null + * @param longitude waypoint longitude, must be non-null + * @param altitude Waypoint altitude\height + * @param dateTime Timestamp the waypoint was created (Java epoch + * seconds) + * @param velocity Velocity + * @param distanceHP Distance from home point + * @param distanceTraveled Total distance the drone has traveled + * + * @return Collection of BlackboardAttributes + * + * @throws DroneIngestException + */ + protected Collection makeWaypointAttributes(Double latitude, Double longitude, Double altitude, Long dateTime, Double velocity, Double distanceHP, Double distanceTraveled) throws DroneIngestException { + Collection attributes = new ArrayList<>(); + + if (latitude == null || longitude == null) { + throw new DroneIngestException("Invalid list of waypoint attributes, longitude or latitude was null"); + } + + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, + DroneIngestModuleFactory.getModuleName(), latitude)); + + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, + DroneIngestModuleFactory.getModuleName(), longitude)); + + if (altitude != null) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, + DroneIngestModuleFactory.getModuleName(), altitude)); + } + + if (dateTime != null) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, + DroneIngestModuleFactory.getModuleName(), dateTime)); + } + + if (velocity != null) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_VELOCITY, + DroneIngestModuleFactory.getModuleName(), velocity)); + } + + if (distanceHP != null) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DRONE_HP_DISTANCE, + DroneIngestModuleFactory.getModuleName(), velocity)); + } + + if (distanceTraveled != null) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DRONE_DISTANCE_TRAVELED, + DroneIngestModuleFactory.getModuleName(), velocity)); + } + + return attributes; + } + + /** + * Build the temp path and create the directory if it does not currently + * exist. + * + * @param currentCase Currently open case + * @param extractorName Name of extractor + * + * @return Path of the temp directory for this module + */ + protected Path getExtractorTempPath() { + Path path = Paths.get(currentCase.getTempDirectory(), TEMP_FOLDER_NAME, this.getClass().getCanonicalName()); + File dir = path.toFile(); + if (!dir.exists()) { + dir.mkdirs(); + } + + return path; + } + + /** + * Create a copy of file in the case temp directory. + * + * @param context Current ingest context + * @param file File to be copied + * + * @return File copy. + * + * @throws DroneIngestException + */ + protected File getTemporaryFile(IngestJobContext context, AbstractFile file) throws DroneIngestException { + String tempFileName = file.getName() + file.getId() + file.getNameExtension(); + + Path tempFilePath = Paths.get(getExtractorTempPath().toString(), tempFileName); + + try { + ContentUtils.writeToFile(file, tempFilePath.toFile(), context::dataSourceIngestIsCancelled); + } catch (IOException ex) { + throw new DroneIngestException(String.format("Unable to create temp file %s for abstract file %s", tempFilePath.toString(), file.getName()), ex); + } + + return tempFilePath.toFile(); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestException.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestException.java new file mode 100755 index 0000000000..8807fe78db --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestException.java @@ -0,0 +1,48 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException; + +/** + * IngestModuleException wrapper class. + */ +public class DroneIngestException extends IngestModuleException { + + private static final long serialVersionUID = 1L; + + /** + * Create exception containing the error message + * + * @param msg the message + */ + public DroneIngestException(String msg) { + super(msg); + } + + /** + * Create exception containing the error message and cause exception + * + * @param msg the message + * @param ex cause exception + */ + public DroneIngestException(String msg, Exception ex) { + super(msg, ex); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModule.java new file mode 100755 index 0000000000..3265c4de4c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModule.java @@ -0,0 +1,93 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestMessage; +import org.sleuthkit.autopsy.ingest.IngestServices; +import org.sleuthkit.datamodel.Content; + +/** + * Drone file ingest module. + * + */ +public final class DroneIngestModule implements DataSourceIngestModule { + + private static final Logger logger = Logger.getLogger(DroneIngestModule.class.getName()); + final private List extractors; + + private IngestJobContext context; + + /** + * Construct a new drone ingest module. + */ + DroneIngestModule() { + extractors = new ArrayList<>(); + } + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + + extractors.add(new DATExtractor()); + } + + @Messages({ + "DroneIngestModule_process_start=Started {0}" + }) + @Override + public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) { + ProcessResult processResult = ProcessResult.OK; + + IngestServices services = IngestServices.getInstance(); + + services.postMessage(IngestMessage.createMessage( + IngestMessage.MessageType.INFO, + DroneIngestModuleFactory.getModuleName(), + Bundle.DroneIngestModule_process_start(dataSource.getName()))); + + progressBar.switchToIndeterminate(); + + for (DroneExtractor extractor : extractors) { + + if (context.dataSourceIngestIsCancelled()) { + logger.log(Level.INFO, "Drone ingest has been canceled, quitting before {0}", extractor.getName()); //NON-NLS + break; + } + + progressBar.progress(extractor.getName()); + + try { + extractor.process(dataSource, context, progressBar); + } catch (DroneIngestException ex) { + logger.log(Level.SEVERE, String.format("Exception thrown from drone extractor %s", extractor.getName()), ex); + } + } + + return processResult; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java new file mode 100755 index 0000000000..ca82222ee4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/drones/DroneIngestModuleFactory.java @@ -0,0 +1,72 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.drones; + +import org.openide.util.NbBundle.Messages; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.DataSourceIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * Drone file ingest module factory. + */ +@ServiceProvider(service = IngestModuleFactory.class) +public class DroneIngestModuleFactory extends IngestModuleFactoryAdapter { + + @Messages({ + "DroneIngestModule_Name=Drone", + "DroneIngestModule_Description=Description" + }) + + /** + * Helper function for returning the name of this module. + */ + static String getModuleName() { + return Bundle.DroneIngestModule_Name(); + } + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + @Override + public String getModuleDescription() { + return Bundle.DroneIngestModule_Description(); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean isDataSourceIngestModuleFactory() { + return true; + } + + @Override + public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings ingestJobOptions) { + return new DroneIngestModule(); + } + +} diff --git a/thirdparty/DatCon/3.6.9/DatCon.jar b/thirdparty/DatCon/3.6.9/DatCon.jar new file mode 100755 index 0000000000..3bcdca1cd9 Binary files /dev/null and b/thirdparty/DatCon/3.6.9/DatCon.jar differ