diff --git a/Core/build.xml b/Core/build.xml
index bfde1a520b..f2b984763d 100644
--- a/Core/build.xml
+++ b/Core/build.xml
@@ -39,7 +39,11 @@
-
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
index 0a76b2a7fa..9a211c57a0 100644
--- a/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
+++ b/Core/src/org/sleuthkit/autopsy/coreutils/ExecUtil.java
@@ -196,6 +196,53 @@ public final class ExecUtil {
}
return process.exitValue();
}
+
+ /**
+ * Wait for the given process to finish, using the given ProcessTerminator.
+ *
+ * @param command The command that was used to start the process. Used
+ * only for logging purposes.
+ * @param process The process to wait for.
+ * @param terminator The ProcessTerminator used to determine if the process
+ * should be killed.
+ *
+ * @returnthe exit value of the process
+ *
+ * @throws SecurityException if a security manager exists and vetoes any
+ * aspect of running the process.
+ * @throws IOException if an I/o error occurs.
+ */
+ public static int waitForTermination(String command, Process process, ProcessTerminator terminator) throws SecurityException, IOException {
+ return ExecUtil.waitForTermination(command, process, ExecUtil.DEFAULT_TIMEOUT, ExecUtil.DEFAULT_TIMEOUT_UNITS, terminator);
+ }
+
+ private static int waitForTermination(String command, Process process, long timeOut, TimeUnit units, ProcessTerminator terminator) throws SecurityException, IOException {
+ try {
+ do {
+ process.waitFor(timeOut, units);
+ if (process.isAlive() && terminator.shouldTerminateProcess()) {
+ killProcess(process);
+ try {
+ process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning
+ } catch (InterruptedException exx) {
+ Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", command));
+ }
+ }
+ } while (process.isAlive());
+ } catch (InterruptedException ex) {
+ if (process.isAlive()) {
+ killProcess(process);
+ }
+ try {
+ process.waitFor(); //waiting to help ensure process is shutdown before calling interrupt() or returning
+ } catch (InterruptedException exx) {
+ Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, String.format("Wait for process termination following killProcess was interrupted for command %s", command));
+ }
+ Logger.getLogger(ExecUtil.class.getName()).log(Level.INFO, "Thread interrupted while running {0}", command); // NON-NLS
+ Thread.currentThread().interrupt();
+ }
+ return process.exitValue();
+ }
/**
* Kills a process and its children
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties
new file mode 100755
index 0000000000..9fe1026713
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties
@@ -0,0 +1,3 @@
+PlasoModuleSettingsPanel.winRegCheckBox.text=winreg: Parser for Windows NT Registry (REGF) files.
+PlasoModuleSettingsPanel.peCheckBox.text=pe: Parser for Portable Executable (PE) files.
+PlasoModuleSettingsPanel.plasoParserInfoTextArea.text=All plaso parsers except chrome_cache and the ones listed below are run. chrome_cache duplicates data collected by the RecentActivity module. The parsers below add significantly to the processing time and should only be enabled if the events they produce are needed.
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED
new file mode 100755
index 0000000000..e29304b633
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/Bundle.properties-MERGED
@@ -0,0 +1,25 @@
+# {0} - file that events are from
+PlasoIngestModule.artifact.progress=Adding events to case: {0}
+PlasoIngestModule.bad.imageFile=Cannot find image file name and path
+PlasoIngestModule.completed=Plaso Processing Completed
+PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation
+PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image.
+PlasoIngestModule.error.creating.output.dir=Error creating Plaso module output directory.
+PlasoIngestModule.error.running.log2timeline=Error running log2timeline, see log file.
+PlasoIngestModule.error.running.psort=Error running Psort, see log file.
+PlasoIngestModule.event.datetime=Event Date Time
+PlasoIngestModule.event.description=Event Description
+PlasoIngestModule.exception.posting.artifact=Exception Posting artifact.
+PlasoIngestModule.executable.not.found=Plaso Executable Not Found.
+PlasoIngestModule.has.run=Plaso Plugin has been run.
+PlasoIngestModule.log2timeline.cancelled=Log2timeline run was canceled
+PlasoIngestModule.psort.cancelled=psort run was canceled
+PlasoIngestModule.requires.windows=Plaso module requires windows.
+PlasoIngestModule.running.psort=Running Psort
+PlasoIngestModule.starting.log2timeline=Starting Log2timeline
+PlasoModuleFactory.ingestJobSettings.exception.msg=Expected settings argument to be instanceof PlasoModuleSettings
+PlasoModuleFactory_moduleDesc=Runs Plaso against a Data Source.
+PlasoModuleFactory_moduleName=Plaso
+PlasoModuleSettingsPanel.winRegCheckBox.text=winreg: Parser for Windows NT Registry (REGF) files.
+PlasoModuleSettingsPanel.peCheckBox.text=pe: Parser for Portable Executable (PE) files.
+PlasoModuleSettingsPanel.plasoParserInfoTextArea.text=All plaso parsers except chrome_cache and the ones listed below are run. chrome_cache duplicates data collected by the RecentActivity module. The parsers below add significantly to the processing time and should only be enabled if the events they produce are needed.
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java
new file mode 100755
index 0000000000..5fd4946a09
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoIngestModule.java
@@ -0,0 +1,458 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018-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.plaso;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import static java.util.Objects.nonNull;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import org.openide.modules.InstalledFileLocator;
+import org.openide.util.Cancellable;
+import org.openide.util.NbBundle;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.services.FileManager;
+import org.sleuthkit.autopsy.coreutils.ExecUtil;
+import org.sleuthkit.autopsy.coreutils.Logger;
+import org.sleuthkit.autopsy.coreutils.PlatformUtil;
+import org.sleuthkit.autopsy.coreutils.SQLiteDBConnect;
+import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
+import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator;
+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.AbstractFile;
+import org.sleuthkit.datamodel.Blackboard;
+import org.sleuthkit.datamodel.Blackboard.BlackboardException;
+import org.sleuthkit.datamodel.BlackboardArtifact;
+import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_TL_EVENT;
+import org.sleuthkit.datamodel.BlackboardAttribute;
+import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
+import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION;
+import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TL_EVENT_TYPE;
+import org.sleuthkit.datamodel.Content;
+import org.sleuthkit.datamodel.Image;
+import org.sleuthkit.datamodel.TskCoreException;
+import org.sleuthkit.datamodel.timeline.EventType;
+
+/**
+ * Data source ingest module that runs Plaso against the image.
+ */
+public class PlasoIngestModule implements DataSourceIngestModule {
+
+ private static final Logger logger = Logger.getLogger(PlasoIngestModule.class.getName());
+ private static final String MODULE_NAME = PlasoModuleFactory.getModuleName();
+
+ private static final String PLASO = "plaso"; //NON-NLS
+ private static final String PLASO64 = "plaso-20180818-amd64";//NON-NLS
+ private static final String PLASO32 = "plaso-20180818-win32";//NON-NLS
+ private static final String LOG2TIMELINE_EXECUTABLE = "Log2timeline.exe";//NON-NLS
+ private static final String PSORT_EXECUTABLE = "psort.exe";//NON-NLS
+ private static final String COOKIE = "cookie";//NON-NLS
+ private static final int LOG2TIMELINE_WORKERS = 2;
+
+ private File log2TimeLineExecutable;
+ private File psortExecutable;
+
+ private final PlasoModuleSettings settings;
+ private IngestJobContext context;
+ private Case currentCase;
+ private FileManager fileManager;
+
+ private Image image;
+ private AbstractFile previousFile = null; // cache used when looking up files in Autopsy DB
+
+ PlasoIngestModule(PlasoModuleSettings settings) {
+ this.settings = settings;
+ }
+
+ @NbBundle.Messages({
+ "PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
+ "PlasoIngestModule.requires.windows=Plaso module requires windows.",
+ "PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image."})
+ @Override
+ public void startUp(IngestJobContext context) throws IngestModuleException {
+ this.context = context;
+
+ if (false == PlatformUtil.isWindowsOS()) {
+ throw new IngestModuleException(Bundle.PlasoIngestModule_requires_windows());
+ }
+
+ try {
+ log2TimeLineExecutable = locateExecutable(LOG2TIMELINE_EXECUTABLE);
+ psortExecutable = locateExecutable(PSORT_EXECUTABLE);
+ } catch (FileNotFoundException exception) {
+ logger.log(Level.WARNING, "Plaso executable not found.", exception); //NON-NLS
+ throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception);
+ }
+
+ Content dataSource = context.getDataSource();
+ if (!(dataSource instanceof Image)) {
+ throw new IngestModuleException(Bundle.PlasoIngestModule_dataSource_not_an_image());
+ }
+ image = (Image) dataSource;
+ }
+
+ @NbBundle.Messages({
+ "PlasoIngestModule.error.running.log2timeline=Error running log2timeline, see log file.",
+ "PlasoIngestModule.error.running.psort=Error running Psort, see log file.",
+ "PlasoIngestModule.error.creating.output.dir=Error creating Plaso module output directory.",
+ "PlasoIngestModule.starting.log2timeline=Starting Log2timeline",
+ "PlasoIngestModule.running.psort=Running Psort",
+ "PlasoIngestModule.log2timeline.cancelled=Log2timeline run was canceled",
+ "PlasoIngestModule.psort.cancelled=psort run was canceled",
+ "PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
+ "PlasoIngestModule.completed=Plaso Processing Completed",
+ "PlasoIngestModule.has.run=Plaso Plugin has been run."})
+ @Override
+ public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
+ assert dataSource.equals(image);
+
+ statusHelper.switchToDeterminate(100);
+ currentCase = Case.getCurrentCase();
+ fileManager = currentCase.getServices().getFileManager();
+
+ String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
+ Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
+ try {
+ Files.createDirectories(moduleOutputPath);
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ // Run log2timeline
+ logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
+ statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
+ ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
+ try {
+ Process log2TimeLineProcess = log2TimeLineCommand.start();
+ try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
+ L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
+ new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
+ ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, new DataSourceIngestModuleProcessTerminator(context));
+ statusReader.cancel();
+ }
+
+ if (context.dataSourceIngestIsCancelled()) {
+ logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
+ return ProcessResult.OK;
+ }
+ if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
+ logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ // sort the output
+ statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
+ ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
+ ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
+
+ if (context.dataSourceIngestIsCancelled()) {
+ logger.log(Level.INFO, "psort run was canceled"); //NON-NLS
+ return ProcessResult.OK;
+ }
+ Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS
+ if (Files.notExists(plasoFile)) {
+ logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ // parse the output and make artifacts
+ createPlasoArtifacts(plasoFile.toString(), statusHelper);
+
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS
+ return ProcessResult.ERROR;
+ }
+
+ IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
+ Bundle.PlasoIngestModule_has_run(),
+ Bundle.PlasoIngestModule_completed());
+ IngestServices.getInstance().postMessage(message);
+ return ProcessResult.OK;
+ }
+
+ private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) {
+ //make a csv list of disabled parsers.
+ String parsersString = settings.getParsers().entrySet().stream()
+ .filter(entry -> entry.getValue() == false)
+ .map(entry -> "!" + entry.getKey()) // '!' prepended to parsername disables it. //NON-NLS
+ .collect(Collectors.joining(","));//NON-NLS
+
+ ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
+ "\"" + log2TimeLineExecutable + "\"", //NON-NLS
+ "--vss-stores", "all", //NON-NLS
+ "-z", image.getTimeZone(), //NON-NLS
+ "--partitions", "all", //NON-NLS
+ "--hasher_file_size_limit", "1", //NON-NLS
+ "--hashers", "none", //NON-NLS
+ "--parsers", "\"" + parsersString + "\"",//NON-NLS
+ "--no_dependencies_check", //NON-NLS
+ "--workers", String.valueOf(LOG2TIMELINE_WORKERS),//NON-NLS
+ moduleOutputPath.resolve(PLASO).toString(),
+ image.getPaths()[0]
+ );
+ processBuilder.redirectError(moduleOutputPath.resolve("log2timeline_err.txt").toFile()); //NON-NLS
+ return processBuilder;
+ }
+
+ static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
+ ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
+ /* Add an environment variable to force log2timeline/psort to run with
+ * the same permissions Autopsy uses. */
+ processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
+ return processBuilder;
+ }
+
+ private ProcessBuilder buildPsortCommand(Path moduleOutputPath) {
+ ProcessBuilder processBuilder = buildProcessWithRunAsInvoker(
+ "\"" + psortExecutable + "\"", //NON-NLS
+ "-o", "4n6time_sqlite", //NON-NLS
+ "-w", moduleOutputPath.resolve("plasodb.db3").toString(), //NON-NLS
+ moduleOutputPath.resolve(PLASO).toString()
+ );
+
+ processBuilder.redirectOutput(moduleOutputPath.resolve("psort_output.txt").toFile()); //NON-NLS
+ processBuilder.redirectError(moduleOutputPath.resolve("psort_err.txt").toFile()); //NON-NLS
+ return processBuilder;
+ }
+
+ private static File locateExecutable(String executableName) throws FileNotFoundException {
+ String architectureFolder = PlatformUtil.is64BitOS() ? PLASO64 : PLASO32;
+ String executableToFindName = Paths.get(PLASO, architectureFolder, executableName).toString();
+
+ File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PlasoIngestModule.class.getPackage().getName(), false);
+ if (null == exeFile || exeFile.canExecute() == false) {
+ throw new FileNotFoundException(executableName + " executable not found.");
+ }
+ return exeFile;
+ }
+
+ @NbBundle.Messages({
+ "PlasoIngestModule.exception.posting.artifact=Exception Posting artifact.",
+ "PlasoIngestModule.event.datetime=Event Date Time",
+ "PlasoIngestModule.event.description=Event Description",
+ "PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
+ "# {0} - file that events are from",
+ "PlasoIngestModule.artifact.progress=Adding events to case: {0}"})
+ private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) {
+ Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
+
+ String sqlStatement = "SELECT substr(filename,1) AS filename, "
+ + " strftime('%s', datetime) AS epoch_date, "
+ + " description, "
+ + " source, "
+ + " type, "
+ + " sourcetype "
+ + " FROM log2timeline "
+ + " WHERE source NOT IN ('FILE', "
+ + " 'WEBHIST') " // bad dates and duplicates with what we have.
+ + " AND sourcetype NOT IN ('UNKNOWN', "
+ + " 'PE Import Time');"; // lots of bad dates //NON-NLS
+ SQLiteDBConnect tempdbconnect = null;
+ ResultSet resultSet = null;
+ try {
+ tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS
+ resultSet = tempdbconnect.executeQry(sqlStatement);
+ while (resultSet.next()) {
+ if (context.dataSourceIngestIsCancelled()) {
+ logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS
+ return;
+ }
+
+ String currentFileName = resultSet.getString("filename"); //NON-NLS
+ statusHelper.progress(Bundle.PlasoIngestModule_artifact_progress(currentFileName), 66);
+ Content resolvedFile = getAbstractFile(currentFileName);
+ if (resolvedFile == null) {
+ logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS
+ resolvedFile = image;
+ }
+
+ Collection bbattributes = Arrays.asList(
+ new BlackboardAttribute(
+ TSK_DATETIME, MODULE_NAME,
+ resultSet.getLong("epoch_date")), //NON-NLS
+ new BlackboardAttribute(
+ TSK_DESCRIPTION, MODULE_NAME,
+ resultSet.getString("description")),//NON-NLS
+ new BlackboardAttribute(
+ TSK_TL_EVENT_TYPE, MODULE_NAME,
+ findEventSubtype(currentFileName, resultSet)));
+
+ try {
+ BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT);
+ bbart.addAttributes(bbattributes);
+ try {
+ /* Post the artifact which will index the artifact for
+ * keyword search, and fire an event to notify UI of
+ * this new artifact */
+ blackboard.postArtifact(bbart, MODULE_NAME);
+ } catch (BlackboardException ex) {
+ logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
+ } finally {
+ if(resultSet != null) {
+ try {
+ resultSet.close();
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Unable to close ResultSet", ex);
+ }
+ }
+
+ if(tempdbconnect != null) {
+ tempdbconnect.closeConnection();
+ }
+ }
+ }
+
+ private AbstractFile getAbstractFile(String file) {
+
+ Path path = Paths.get(file);
+ String fileName = path.getFileName().toString();
+ String filePath = path.getParent().toString().replaceAll("\\\\", "/");//NON-NLS
+ if (filePath.endsWith("/") == false) {//NON-NLS
+ filePath += "/";//NON-NLS
+ }
+
+ // check the cached file
+ //TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it?
+ if (previousFile != null
+ && previousFile.getName().equalsIgnoreCase(fileName)
+ && previousFile.getParentPath().equalsIgnoreCase(filePath)) {
+ return previousFile;
+
+ }
+ try {
+ List abstractFiles = fileManager.findFiles(fileName, filePath);
+ if (abstractFiles.size() == 1) {// TODO: why do we bother with this check. also we don't cache the file...
+ return abstractFiles.get(0);
+ }
+ for (AbstractFile resolvedFile : abstractFiles) {
+ // double check its an exact match
+ if (filePath.equalsIgnoreCase(resolvedFile.getParentPath())) {
+ // cache it for next time
+ previousFile = resolvedFile;
+ return resolvedFile;
+ }
+ }
+ } catch (TskCoreException ex) {
+ logger.log(Level.SEVERE, "Exception finding file.", ex);
+ }
+ return null;
+ }
+
+ /**
+ * Determine the event_type_id of the event from the plaso information.
+ *
+ * @param fileName The name of the file this event is from.
+ * @param row The row returned from the log2timeline table of th eplaso
+ * output.
+ *
+ * @return the event_type_id of the EventType of the given event.
+ *
+ * @throws SQLException
+ */
+ private long findEventSubtype(String fileName, ResultSet row) throws SQLException {
+ switch (row.getString("source")) {
+ case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case...
+ if (fileName.toLowerCase().contains(COOKIE)
+ || row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
+ return EventType.WEB_COOKIE.getTypeID();
+ } else {
+ return EventType.WEB_HISTORY.getTypeID();
+ }
+ case "EVT":
+ case "LOG":
+ return EventType.LOG_ENTRY.getTypeID();
+ case "REG":
+ switch (row.getString("sourcetype").toLowerCase()) {//NON-NLS
+ case "unknown : usb entries":
+ case "unknown : usbstor entries":
+ return EventType.DEVICES_ATTACHED.getTypeID();
+ default:
+ return EventType.REGISTRY.getTypeID();
+ }
+ default:
+ return EventType.OTHER.getTypeID();
+ }
+ }
+
+ /**
+ * Runs in a thread and reads the output of log2timeline. It redirectes the
+ * output both to a log file, and to the status message of the Plaso ingest
+ * module progress bar.
+ */
+ private static class L2TStatusProcessor implements Runnable, Cancellable {
+
+ private final BufferedReader log2TimeLineOutpout;
+ private final DataSourceIngestModuleProgress statusHelper;
+ volatile private boolean cancelled = false;
+ private final Path outputPath;
+
+ private L2TStatusProcessor(BufferedReader log2TimeLineOutpout, DataSourceIngestModuleProgress statusHelper, Path outputPath) throws IOException {
+ this.log2TimeLineOutpout = log2TimeLineOutpout;
+ this.statusHelper = statusHelper;
+ this.outputPath = outputPath;
+ }
+
+ @Override
+ public void run() {
+ try (BufferedWriter writer = Files.newBufferedWriter(outputPath.resolve("log2timeline_output.txt"));) {//NON-NLS
+ String line = log2TimeLineOutpout.readLine();
+ while (cancelled == false && nonNull(line)) {
+ statusHelper.progress(line);
+ writer.write(line);
+ writer.newLine();
+ line = log2TimeLineOutpout.readLine();
+ }
+ writer.flush();
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Error reading log2timeline output stream.", ex);//NON-NLS
+ }
+ }
+
+ @Override
+ public boolean cancel() {
+ cancelled = true;
+ return true;
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java
new file mode 100755
index 0000000000..4e082a0e6e
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleFactory.java
@@ -0,0 +1,112 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2018-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.plaso;
+
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.coreutils.Version;
+import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
+import org.sleuthkit.autopsy.ingest.FileIngestModule;
+import org.sleuthkit.autopsy.ingest.IngestModuleFactory;
+import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
+
+/**
+ * A factory that creates data source ingest modules that run Plaso against an
+ * image and saves the storage file to module output.
+ */
+@ServiceProvider(service = IngestModuleFactory.class)
+@NbBundle.Messages({"PlasoModuleFactory.ingestJobSettings.exception.msg=Expected settings argument to be instanceof PlasoModuleSettings"})
+public class PlasoModuleFactory implements IngestModuleFactory {
+
+ @NbBundle.Messages({"PlasoModuleFactory_moduleName=Plaso"})
+ static String getModuleName() {
+ return Bundle.PlasoModuleFactory_moduleName();
+ }
+
+ @Override
+ public String getModuleDisplayName() {
+ return getModuleName();
+ }
+
+ @NbBundle.Messages({"PlasoModuleFactory_moduleDesc=Runs Plaso against a Data Source."})
+ @Override
+ public String getModuleDescription() {
+ return Bundle.PlasoModuleFactory_moduleDesc();
+ }
+
+ @Override
+ public String getModuleVersionNumber() {
+ return Version.getVersion();
+ }
+
+ @Override
+ public boolean isDataSourceIngestModuleFactory() {
+ return true;
+ }
+
+ @Override
+ public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) {
+ assert settings instanceof PlasoModuleSettings;
+ if (settings instanceof PlasoModuleSettings) {
+ return new PlasoIngestModule((PlasoModuleSettings) settings);
+ }
+ throw new IllegalArgumentException(Bundle.PlasoModuleFactory_ingestJobSettings_exception_msg());
+ }
+
+ @Override
+ public boolean hasGlobalSettingsPanel() {
+ return false;
+ }
+
+ @Override
+ public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public IngestModuleIngestJobSettings getDefaultIngestJobSettings() {
+ return new PlasoModuleSettings();
+ }
+
+ @Override
+ public boolean hasIngestJobSettingsPanel() {
+ return true;
+ }
+
+ @Override
+ public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) {
+ assert settings instanceof PlasoModuleSettings;
+ if (settings instanceof PlasoModuleSettings) {
+ return new PlasoModuleSettingsPanel((PlasoModuleSettings) settings);
+ }
+ throw new IllegalArgumentException(Bundle.PlasoModuleFactory_ingestJobSettings_exception_msg());
+ }
+
+ @Override
+ public boolean isFileIngestModuleFactory() {
+ return false;
+ }
+
+ @Override
+ public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java
new file mode 100755
index 0000000000..2730f4365f
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettings.java
@@ -0,0 +1,92 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 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.plaso;
+
+import com.google.common.collect.ImmutableMap;
+import java.util.HashMap;
+import java.util.Map;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+
+/**
+ * Settings for the Plaso Ingest Module.
+ */
+public class PlasoModuleSettings implements IngestModuleIngestJobSettings {
+
+ private static final long serialVersionUID = 1L;
+
+ /** Map from parser name (or match pattern) to its enabled state. */
+ final Map parsers = new HashMap<>();
+
+ /**
+ * Get an immutable map from parser name to its enabled state. Parsers
+ * mapped to true or with no entry will be enabled. Parsers mapped to false,
+ * will be disabled.
+ */
+ Map getParsers() {
+ return ImmutableMap.copyOf(parsers);
+ }
+
+ /**
+ * Constructor. The PlasoModuleSettings will have the default parsers
+ * (winreg, pe, chrome, firefox, internet explorer) disabled.
+ */
+ public PlasoModuleSettings() {
+ parsers.put("winreg", false);
+ parsers.put("pe", false);
+
+ //chrome
+ parsers.put("chrome_preferences", false);
+ parsers.put("chrome_cache", false);
+ parsers.put("chrome_27_history", false);
+ parsers.put("chrome_8_history", false);
+ parsers.put("chrome_cookies", false);
+ parsers.put("chrome_extension_activity", false);
+
+ //firefox
+ parsers.put("firefox_cache", false);
+ parsers.put("firefox_cache2", false);
+ parsers.put("firefox_cookies", false);
+ parsers.put("firefox_downloads", false);
+ parsers.put("firefox_history", false);
+
+ //Internet Explorer
+ parsers.put("msiecf", false);
+ parsers.put("msie_webcache", false);
+ }
+
+ /**
+ * Gets the serialization version number.
+ *
+ * @return A serialization version number.
+ */
+ @Override
+ public long getVersionNumber() {
+ return serialVersionUID;
+ }
+
+ /**
+ * Set the given parser enabled/disabled
+ *
+ * @param parserName The name of the parser to enable/disable
+ * @param selected The new state (enabled/disabled) for the given parser.
+ */
+ void setParserEnabled(String parserName, boolean selected) {
+ parsers.put(parserName, selected);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form
new file mode 100755
index 0000000000..a7da26d3e5
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.form
@@ -0,0 +1,84 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java
new file mode 100755
index 0000000000..01937ebc2a
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/modules/plaso/PlasoModuleSettingsPanel.java
@@ -0,0 +1,115 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 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.plaso;
+
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
+import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
+
+/**
+ * Settings panel for the PlasoIngestModule.
+ */
+public class PlasoModuleSettingsPanel extends IngestModuleIngestJobSettingsPanel {
+
+ private final PlasoModuleSettings settings;
+
+ public PlasoModuleSettingsPanel(PlasoModuleSettings settings) {
+ this.settings = settings;
+ initComponents();
+ }
+
+ /** 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() {
+
+ winRegCheckBox = new javax.swing.JCheckBox();
+ peCheckBox = new javax.swing.JCheckBox();
+ plasoParserInfoTextArea = new javax.swing.JTextArea();
+
+ org.openide.awt.Mnemonics.setLocalizedText(winRegCheckBox, org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.winRegCheckBox.text")); // NOI18N
+ winRegCheckBox.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ winRegCheckBoxActionPerformed(evt);
+ }
+ });
+
+ org.openide.awt.Mnemonics.setLocalizedText(peCheckBox, org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.peCheckBox.text")); // NOI18N
+ peCheckBox.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ peCheckBoxActionPerformed(evt);
+ }
+ });
+
+ plasoParserInfoTextArea.setEditable(false);
+ plasoParserInfoTextArea.setBackground(javax.swing.UIManager.getDefaults().getColor("Panel.background"));
+ plasoParserInfoTextArea.setColumns(20);
+ plasoParserInfoTextArea.setLineWrap(true);
+ plasoParserInfoTextArea.setRows(5);
+ plasoParserInfoTextArea.setText(org.openide.util.NbBundle.getMessage(PlasoModuleSettingsPanel.class, "PlasoModuleSettingsPanel.plasoParserInfoTextArea.text")); // NOI18N
+ plasoParserInfoTextArea.setWrapStyleWord(true);
+ plasoParserInfoTextArea.setBorder(null);
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addComponent(plasoParserInfoTextArea)
+ .addComponent(peCheckBox)
+ .addComponent(winRegCheckBox))
+ .addContainerGap())
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addComponent(plasoParserInfoTextArea, javax.swing.GroupLayout.DEFAULT_SIZE, 188, Short.MAX_VALUE)
+ .addGap(18, 18, 18)
+ .addComponent(winRegCheckBox)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(peCheckBox)
+ .addContainerGap())
+ );
+ }// //GEN-END:initComponents
+
+ private void winRegCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_winRegCheckBoxActionPerformed
+ settings.setParserEnabled("winreg", winRegCheckBox.isSelected());
+ }//GEN-LAST:event_winRegCheckBoxActionPerformed
+
+ private void peCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_peCheckBoxActionPerformed
+ settings.setParserEnabled("pe", peCheckBox.isSelected());
+ }//GEN-LAST:event_peCheckBoxActionPerformed
+
+ @Override
+ public IngestModuleIngestJobSettings getSettings() {
+ return settings;
+ }
+
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JCheckBox peCheckBox;
+ private javax.swing.JTextArea plasoParserInfoTextArea;
+ private javax.swing.JCheckBox winRegCheckBox;
+ // End of variables declaration//GEN-END:variables
+}