diff --git a/Core/build.xml b/Core/build.xml index cd3b394f24..a8fa08cb0d 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -6,7 +6,8 @@ Builds, tests, and runs the project org.sleuthkit.autopsy.core - + + @@ -18,9 +19,7 @@ - - - + @@ -58,8 +57,8 @@ - + recentlyAddedCeArtifacts = new LinkedHashSet<>(); - private static int ceModuleInstanceCount = 0; + private static int correlationModuleInstanceCount; + private static boolean flagNotableItems; private final ExecutorService jobProcessingExecutor; private static final String INGEST_EVENT_THREAD_NAME = "Ingest-Event-Listener-%d"; private final PropertyChangeListener pcl1 = new IngestModuleEventListener(); @@ -88,21 +89,20 @@ public class IngestEventsListener { } /** - * Enable this IngestEventsListener to add contents to the Correlation - * Engine. - * + * Increase the number of IngestEventsListeners adding contents to the + * Correlation Engine. */ public synchronized static void incrementCorrelationEngineModuleCount() { - ceModuleInstanceCount++; //Should be called once in the Correlation Engine module's startup method. + correlationModuleInstanceCount++; //Should be called once in the Correlation Engine module's startup method. } /** - * Disable this IngestEventsListener from adding contents to the Correlation - * Engine. + * Decrease the number of IngestEventsListeners adding contents to the + * Correlation Engine. */ public synchronized static void decrementCorrelationEngineModuleCount() { if (getCeModuleInstanceCount() > 0) { //prevent it ingestJobCounter from going negative - ceModuleInstanceCount--; //Should be called once in the Correlation Engine module's shutdown method. + correlationModuleInstanceCount--; //Should be called once in the Correlation Engine module's shutdown method. } } @@ -111,17 +111,35 @@ public class IngestEventsListener { * is being run during injest to 0. */ synchronized static void resetCeModuleInstanceCount() { - ceModuleInstanceCount = 0; //called when a case is opened in case for some reason counter was not reset + correlationModuleInstanceCount = 0; //called when a case is opened in case for some reason counter was not reset } /** - * Wether or not the Correlation Engine Module is enabled for any of the + * Whether or not the Correlation Engine Module is enabled for any of the * currently running ingest jobs. * * @return boolean True for Correlation Engine enabled, False for disabled */ - private synchronized static int getCeModuleInstanceCount() { - return ceModuleInstanceCount; + public synchronized static int getCeModuleInstanceCount() { + return correlationModuleInstanceCount; + } + + /** + * Are notable items being flagged? + * + * @return True if flagging notable items; otherwise false. + */ + public synchronized static boolean isFlagNotableItems() { + return flagNotableItems; + } + + /** + * Configure the listener to flag notable items or not. + * + * @param value True to flag notable items; otherwise false. + */ + public synchronized static void setFlagNotableItems(boolean value) { + flagNotableItems = value; } @NbBundle.Messages({"IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)", @@ -174,7 +192,7 @@ public class IngestEventsListener { } switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) { case DATA_ADDED: { - jobProcessingExecutor.submit(new DataAddedTask(dbManager, evt)); + jobProcessingExecutor.submit(new DataAddedTask(dbManager, evt, isFlagNotableItems())); break; } } @@ -212,10 +230,12 @@ public class IngestEventsListener { private final EamDb dbManager; private final PropertyChangeEvent event; + private final boolean flagNotableItemsEnabled; - private DataAddedTask(EamDb db, PropertyChangeEvent evt) { + private DataAddedTask(EamDb db, PropertyChangeEvent evt, boolean flagNotableItemsEnabled) { dbManager = db; event = evt; + this.flagNotableItemsEnabled = flagNotableItemsEnabled; } @Override @@ -241,10 +261,12 @@ public class IngestEventsListener { // query db for artifact instances having this TYPE/VALUE and knownStatus = "Bad". // if gettKnownStatus() is "Unknown" and this artifact instance was marked bad in a previous case, // create TSK_INTERESTING_ARTIFACT_HIT artifact on BB. - List caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue()); - if (!caseDisplayNames.isEmpty()) { - postCorrelatedBadArtifactToBlackboard(bbArtifact, - caseDisplayNames); + if (flagNotableItemsEnabled) { + List caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue()); + if (!caseDisplayNames.isEmpty()) { + postCorrelatedBadArtifactToBlackboard(bbArtifact, + caseDisplayNames); + } } eamArtifacts.add(eamArtifact); } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties new file mode 100755 index 0000000000..a525713f7c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/Bundle.properties @@ -0,0 +1,2 @@ +IngestSettingsPanel.ingestSettingsLabel.text=Ingest Settings +IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text=Flag items previously tagged as notable diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java index 9657cdcd19..991da1ad58 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModule.java @@ -56,9 +56,11 @@ import org.sleuthkit.autopsy.centralrepository.eventlisteners.IngestEventsListen */ @Messages({"IngestModule.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)", "IngestModule.prevCaseComment.text=Previous Case: "}) -class IngestModule implements FileIngestModule { +final class IngestModule implements FileIngestModule { - private final static Logger LOGGER = Logger.getLogger(IngestModule.class.getName()); + static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = true; + + private final static Logger logger = Logger.getLogger(IngestModule.class.getName()); private final IngestServices services = IngestServices.getInstance(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private static final IngestModuleReferenceCounter warningMsgRefCounter = new IngestModuleReferenceCounter(); @@ -68,8 +70,19 @@ class IngestModule implements FileIngestModule { private Blackboard blackboard; private CorrelationAttribute.Type filesType; + private final boolean flagTaggedNotableItems; + + /** + * Instantiate the Correlation Engine ingest module. + * + * @param settings The ingest settings for the module instance. + */ + IngestModule(IngestSettings settings) { + flagTaggedNotableItems = settings.isFlagTaggedNotableItems(); + } + @Override - public ProcessResult process(AbstractFile af) { + public ProcessResult process(AbstractFile abstractFile) { if (EamDb.isEnabled() == false) { /* * Not signaling an error for now. This is a workaround for the way @@ -83,11 +96,11 @@ class IngestModule implements FileIngestModule { try { blackboard = Case.getOpenCase().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); return ProcessResult.ERROR; } - if (!EamArtifactUtil.isValidCentralRepoFile(af)) { + if (!EamArtifactUtil.isValidCentralRepoFile(abstractFile)) { return ProcessResult.OK; } @@ -95,7 +108,7 @@ class IngestModule implements FileIngestModule { try { dbManager = EamDb.getInstance(); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); + logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); return ProcessResult.ERROR; } @@ -105,21 +118,23 @@ class IngestModule implements FileIngestModule { } // get the hash because we're going to correlate it - String md5 = af.getMd5Hash(); + String md5 = abstractFile.getMd5Hash(); if ((md5 == null) || (HashUtility.isNoDataMd5(md5))) { return ProcessResult.OK; } - /* Search the central repo to see if this file was previously - * marked as being bad. Create artifact if it was. */ - if (af.getKnown() != TskData.FileKnown.KNOWN) { + /* + * Search the central repo to see if this file was previously marked as + * being bad. Create artifact if it was. + */ + if (abstractFile.getKnown() != TskData.FileKnown.KNOWN && flagTaggedNotableItems) { try { - List caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); - if (!caseDisplayNames.isEmpty()) { - postCorrelatedBadFileToBlackboard(af, caseDisplayNames); + List caseDisplayNamesList = dbManager.getListCasesHavingArtifactInstancesKnownBad(filesType, md5); + if (!caseDisplayNamesList.isEmpty()) { + postCorrelatedBadFileToBlackboard(abstractFile, caseDisplayNamesList); } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error searching database for artifact.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error searching database for artifact.", ex); // NON-NLS return ProcessResult.ERROR; } } @@ -130,14 +145,14 @@ class IngestModule implements FileIngestModule { CorrelationAttributeInstance cefi = new CorrelationAttributeInstance( eamCase, eamDataSource, - af.getParentPath() + af.getName(), + abstractFile.getParentPath() + abstractFile.getName(), null, - TskData.FileKnown.UNKNOWN // NOTE: Known status in the CR is based on tagging, not hashes like the Case Database. + TskData.FileKnown.UNKNOWN // NOTE: Known status in the CR is based on tagging, not hashes like the Case Database. ); eamArtifact.addInstance(cefi); dbManager.prepareBulkArtifact(eamArtifact); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS return ProcessResult.ERROR; } @@ -147,6 +162,7 @@ class IngestModule implements FileIngestModule { @Override public void shutDown() { IngestEventsListener.decrementCorrelationEngineModuleCount(); + if ((EamDb.isEnabled() == false) || (eamCase == null) || (eamDataSource == null)) { return; } @@ -154,19 +170,19 @@ class IngestModule implements FileIngestModule { try { dbManager = EamDb.getInstance(); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); + logger.log(Level.SEVERE, "Error connecting to Central Repository database.", ex); return; } try { dbManager.bulkInsertArtifacts(); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error doing bulk insert of artifacts.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error doing bulk insert of artifacts.", ex); // NON-NLS } try { Long count = dbManager.getCountArtifactInstancesByCaseDataSource(eamCase.getCaseUUID(), eamDataSource.getDeviceID()); - LOGGER.log(Level.INFO, "{0} artifacts in db for case: {1} ds:{2}", new Object[]{count, eamCase.getDisplayName(), eamDataSource.getName()}); // NON-NLS + logger.log(Level.INFO, "{0} artifacts in db for case: {1} ds:{2}", new Object[]{count, eamCase.getDisplayName(), eamDataSource.getName()}); // NON-NLS } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error counting artifacts.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error counting artifacts.", ex); // NON-NLS } // TODO: once we implement shared cache, if refCounter is 1, then submit data in bulk. @@ -181,6 +197,25 @@ class IngestModule implements FileIngestModule { @Override public void startUp(IngestJobContext context) throws IngestModuleException { IngestEventsListener.incrementCorrelationEngineModuleCount(); + + /* + * Tell the IngestEventsListener to flag notable items based on the + * current module's configuration. This is a work around for the lack of + * an artifacts pipeline. Note that this can be changed by another + * module instance. All modules are affected by the value. While not + * ideal, this will be good enough until a better solution can be + * posited. + * + * Note: Flagging cannot be disabled if any other instances of the + * Correlation Engine module are running. This restriction is to prevent + * missing results in the case where the first module is flagging + * notable items, and the proceeding module (with flagging disabled) + * causes the first to stop flagging. + */ + if (IngestEventsListener.getCeModuleInstanceCount() == 1 || !IngestEventsListener.isFlagNotableItems()) { + IngestEventsListener.setFlagNotableItems(flagTaggedNotableItems); + } + if (EamDb.isEnabled() == false) { /* * Not throwing the customary exception for now. This is a @@ -200,14 +235,14 @@ class IngestModule implements FileIngestModule { try { autopsyCase = Case.getOpenCase(); } catch (NoCurrentCaseException ex) { - LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); - throw new IngestModuleException("Exception while getting open case.", ex); + logger.log(Level.SEVERE, "Exception while getting open case.", ex); + throw new IngestModuleException("Exception while getting open case.", ex); } - + // Don't allow sqlite central repo databases to be used for multi user cases if ((autopsyCase.getCaseType() == Case.CaseType.MULTI_USER_CASE) && (EamDbPlatformEnum.getSelectedPlatform() == EamDbPlatformEnum.SQLITE)) { - LOGGER.log(Level.SEVERE, "Cannot run correlation engine on a multi-user case with a SQLite central repository."); + logger.log(Level.SEVERE, "Cannot run correlation engine on a multi-user case with a SQLite central repository."); throw new IngestModuleException("Cannot run on a multi-user case with a SQLite central repository."); // NON-NLS } jobId = context.getJobId(); @@ -216,14 +251,14 @@ class IngestModule implements FileIngestModule { try { centralRepoDb = EamDb.getInstance(); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error connecting to central repository database.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error connecting to central repository database.", ex); // NON-NLS throw new IngestModuleException("Error connecting to central repository database.", ex); // NON-NLS } try { filesType = centralRepoDb.getCorrelationTypeById(CorrelationAttribute.FILES_TYPE_ID); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting correlation type FILES in ingest module start up.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting correlation type FILES in ingest module start up.", ex); // NON-NLS throw new IngestModuleException("Error getting correlation type FILES in ingest module start up.", ex); // NON-NLS } @@ -237,15 +272,15 @@ class IngestModule implements FileIngestModule { try { eamCase = centralRepoDb.newCase(autopsyCase); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error creating new case in ingest module start up.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error creating new case in ingest module start up.", ex); // NON-NLS throw new IngestModuleException("Error creating new case in ingest module start up.", ex); // NON-NLS } } - + try { eamDataSource = CorrelationDataSource.fromTSKDataSource(eamCase, context.getDataSource()); } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error getting data source info.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error getting data source info.", ex); // NON-NLS throw new IngestModuleException("Error getting data source info.", ex); // NON-NLS } // TODO: once we implement a shared cache, load/init it here w/ syncronized and define reference counter @@ -259,7 +294,7 @@ class IngestModule implements FileIngestModule { centralRepoDb.newDataSource(eamDataSource); } } catch (EamDbException ex) { - LOGGER.log(Level.SEVERE, "Error adding data source to Central Repository.", ex); // NON-NLS + logger.log(Level.SEVERE, "Error adding data source to Central Repository.", ex); // NON-NLS throw new IngestModuleException("Error adding data source to Central Repository.", ex); // NON-NLS } @@ -282,7 +317,7 @@ class IngestModule implements FileIngestModule { // index the artifact for keyword search blackboard.indexArtifact(tifArtifact); } catch (Blackboard.BlackboardException ex) { - LOGGER.log(Level.SEVERE, "Unable to index blackboard artifact " + tifArtifact.getArtifactID(), ex); //NON-NLS + logger.log(Level.SEVERE, "Unable to index blackboard artifact " + tifArtifact.getArtifactID(), ex); //NON-NLS } // send inbox message @@ -291,9 +326,9 @@ class IngestModule implements FileIngestModule { // fire event to notify UI of this new artifact services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT)); } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Failed to create BlackboardArtifact.", ex); // NON-NLS + logger.log(Level.SEVERE, "Failed to create BlackboardArtifact.", ex); // NON-NLS } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "Failed to create BlackboardAttribute.", ex); // NON-NLS + logger.log(Level.SEVERE, "Failed to create BlackboardAttribute.", ex); // NON-NLS } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java index ed3d4f0915..6ef03ae00d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestModuleFactory.java @@ -1,7 +1,7 @@ /* * Central Repository * - * Copyright 2015-2017 Basis Technology Corp. + * Copyright 2015-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,9 @@ import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.centralrepository.optionspanel.GlobalSettingsPanel; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; +import org.sleuthkit.autopsy.ingest.NoIngestModuleIngestJobSettings; /** * Factory for Central Repository ingest modules @@ -34,8 +37,11 @@ import org.sleuthkit.autopsy.centralrepository.optionspanel.GlobalSettingsPanel; "IngestModuleFactory.ingestmodule.desc=Saves properties to the central repository for later correlation"}) public class IngestModuleFactory extends IngestModuleFactoryAdapter { - private static final String VERSION_NUMBER = "0.8.0"; - + /** + * Get the name of the module. + * + * @return The module name. + */ static String getModuleName() { return Bundle.IngestModuleFactory_ingestmodule_name(); } @@ -52,7 +58,7 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter { @Override public String getModuleVersionNumber() { - return VERSION_NUMBER; + return Version.getVersion(); } @Override @@ -61,8 +67,8 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter { } @Override - public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { - return new IngestModule(); + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + return new IngestModule((IngestSettings) settings); } @Override @@ -76,5 +82,30 @@ public class IngestModuleFactory extends IngestModuleFactoryAdapter { globalOptionsPanel.load(); return globalOptionsPanel; } + + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + return new IngestSettings(); + } + + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + if (settings instanceof IngestSettings) { + return new IngestSettingsPanel((IngestSettings) settings); + } + /* + * Compatibility check for older versions. + */ + if (settings instanceof NoIngestModuleIngestJobSettings) { + return new IngestSettingsPanel(new IngestSettings()); + } + + throw new IllegalArgumentException("Expected settings argument to be an instance of IngestSettings"); + } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java new file mode 100755 index 0000000000..32ab9e9f2d --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettings.java @@ -0,0 +1,71 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository.ingestmodule; + +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * Ingest job settings for the Correlation Engine module. + */ +final class IngestSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + + private boolean flagTaggedNotableItems; + + /** + * Instantiate the ingest job settings with default values. + */ + IngestSettings() { + this.flagTaggedNotableItems = IngestModule.DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS; + } + + /** + * Instantiate the ingest job settings. + * + * @param flagTaggedNotableItems Flag previously tagged notable items. + */ + IngestSettings(boolean flagTaggedNotableItems) { + this.flagTaggedNotableItems = flagTaggedNotableItems; + } + + @Override + public long getVersionNumber() { + return serialVersionUID; + } + + /** + * Are previously tagged notable items to be flagged? + * + * @return True if flagging; otherwise false. + */ + boolean isFlagTaggedNotableItems() { + return flagTaggedNotableItems; + } + + /** + * Flag or ignore previously identified notable items. + * + * @param ignorePreviousNotableItems Are previously tagged notable items to + * be flagged? + */ + void setFlagTaggedNotableItems(boolean flagTaggedNotableItems) { + this.flagTaggedNotableItems = flagTaggedNotableItems; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form new file mode 100755 index 0000000000..564031cb72 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.form @@ -0,0 +1,63 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java new file mode 100755 index 0000000000..57d4f0a098 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/ingestmodule/IngestSettingsPanel.java @@ -0,0 +1,97 @@ +/* + * Central Repository + * + * Copyright 2018 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.centralrepository.ingestmodule; + +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * Ingest job settings panel for the Correlation Engine module. + */ +final class IngestSettingsPanel extends IngestModuleIngestJobSettingsPanel { + + /** + * Creates new form IngestSettingsPanel + */ + public IngestSettingsPanel(IngestSettings settings) { + initComponents(); + customizeComponents(settings); + } + + /** + * Update components with values from the ingest job settings. + * + * @param settings The ingest job settings. + */ + private void customizeComponents(IngestSettings settings) { + flagTaggedNotableItemsCheckbox.setSelected(settings.isFlagTaggedNotableItems()); + } + + @Override + public IngestModuleIngestJobSettings getSettings() { + return new IngestSettings(flagTaggedNotableItemsCheckbox.isSelected()); + } + + /** + * 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() { + + ingestSettingsLabel = new javax.swing.JLabel(); + flagTaggedNotableItemsCheckbox = new javax.swing.JCheckBox(); + + ingestSettingsLabel.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(ingestSettingsLabel, org.openide.util.NbBundle.getMessage(IngestSettingsPanel.class, "IngestSettingsPanel.ingestSettingsLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(flagTaggedNotableItemsCheckbox, org.openide.util.NbBundle.getMessage(IngestSettingsPanel.class, "IngestSettingsPanel.flagTaggedNotableItemsCheckbox.text")); // NOI18N + + 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) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(flagTaggedNotableItemsCheckbox)) + .addComponent(ingestSettingsLabel)) + .addContainerGap(65, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(ingestSettingsLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(flagTaggedNotableItemsCheckbox) + .addContainerGap(245, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JCheckBox flagTaggedNotableItemsCheckbox; + private javax.swing.JLabel ingestSettingsLabel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form index ea06641799..50979ba938 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.form @@ -44,7 +44,7 @@ - + @@ -133,7 +133,7 @@ - + @@ -173,29 +173,29 @@ - + - + - + - + - + diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java index aaf6c6b4f7..55ea941d33 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/optionspanel/EamDbSettingsDialog.java @@ -243,7 +243,7 @@ public class EamDbSettingsDialog extends JDialog { .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addComponent(cbDatabaseType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(lbSingleUserSqLite, javax.swing.GroupLayout.DEFAULT_SIZE, 467, Short.MAX_VALUE) .addGap(9, 9, 9)) .addGroup(pnSQLiteSettingsLayout.createSequentialGroup() .addComponent(tfDatabasePath) @@ -273,25 +273,25 @@ public class EamDbSettingsDialog extends JDialog { .addComponent(lbDatabaseType, javax.swing.GroupLayout.Alignment.TRAILING)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(lbDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(lbDatabasePath) .addComponent(tfDatabasePath, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(bnDatabasePathFileOpen)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbHostname, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbHostName, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbHostName)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbPort, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbPort, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbPort)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(tbDbUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbUserName, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbUserName)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jpDbPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(lbUserPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(lbUserPassword)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(pnSQLiteSettingsLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(lbFullDbPath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) @@ -317,7 +317,7 @@ public class EamDbSettingsDialog extends JDialog { .addGroup(layout.createSequentialGroup() .addGap(10, 10, 10) .addComponent(pnSQLiteSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE) .addComponent(pnButtons, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(10, 10, 10)) ); diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java index 583c2aa157..99692ea633 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExplorerNodeActionVisitor.java @@ -23,12 +23,14 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.logging.Level; import javax.swing.AbstractAction; import javax.swing.Action; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; @@ -41,6 +43,7 @@ import org.sleuthkit.datamodel.LocalFile; import org.sleuthkit.datamodel.LocalDirectory; import org.sleuthkit.datamodel.VirtualDirectory; import org.sleuthkit.datamodel.Volume; +import org.sleuthkit.autopsy.coreutils.Logger; public class ExplorerNodeActionVisitor extends ContentVisitor.Default> { @@ -71,8 +74,12 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Image img) { List lst = new ArrayList<>(); //TODO lst.add(new ExtractAction("Extract Image", img)); - lst.add(new ExtractUnallocAction( + try { + lst.add(new ExtractUnallocAction( NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.extUnallocToSingleFiles"), img)); + } catch (NoCurrentCaseException ex) { + Logger.getLogger(ExplorerNodeActionVisitor.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + } return lst; } @@ -85,7 +92,8 @@ public class ExplorerNodeActionVisitor extends ContentVisitor.Default visit(final Volume vol) { List lst = new ArrayList<>(); lst.add(new ExtractUnallocAction( - NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.extUnallocToSingleFile"), vol)); + NbBundle.getMessage(this.getClass(), "ExplorerNodeActionVisitor.action.extUnallocToSingleFile"), vol)); + return lst; } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java index 1960089075..893bd94da4 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/ExtractUnallocAction.java @@ -69,14 +69,20 @@ final class ExtractUnallocAction extends AbstractAction { private long currentImage = 0L; private final boolean isImage; - public ExtractUnallocAction(String title, Volume volume) { + public ExtractUnallocAction(String title, Volume volume){ super(title); isImage = false; - OutputFileData outputFileData = new OutputFileData(volume); - filesToExtract.add(outputFileData); + try { + OutputFileData outputFileData = new OutputFileData(volume); + filesToExtract.add(outputFileData); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); + setEnabled(false); + } + } - public ExtractUnallocAction(String title, Image image) { + public ExtractUnallocAction(String title, Image image) throws NoCurrentCaseException { super(title); isImage = true; currentImage = image.getId(); @@ -595,15 +601,17 @@ final class ExtractUnallocAction extends AbstractAction { * Contingency constructor in event no VolumeSystem exists on an Image. * * @param img Image file to be analyzed + * + * @throws NoCurrentCaseException if there is no open case. */ - OutputFileData(Image img) { + OutputFileData(Image img) throws NoCurrentCaseException { this.layoutFiles = getUnallocFiles(img); Collections.sort(layoutFiles, new SortObjId()); this.volumeId = 0; this.imageId = img.getId(); this.imageName = img.getName(); this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + 0 + ".dat"; //NON-NLS - this.fileInstance = new File(Case.getCurrentCase().getExportDirectory() + File.separator + this.fileName); + this.fileInstance = new File(Case.getOpenCase().getExportDirectory() + File.separator + this.fileName); this.sizeInBytes = calcSizeInBytes(); } @@ -611,8 +619,10 @@ final class ExtractUnallocAction extends AbstractAction { * Default constructor for extracting info from Volumes. * * @param volume Volume file to be analyzed + * + * @throws NoCurrentCaseException if there is no open case. */ - OutputFileData(Volume volume) { + OutputFileData(Volume volume) throws NoCurrentCaseException { try { this.imageName = volume.getDataSource().getName(); this.imageId = volume.getDataSource().getId(); @@ -623,7 +633,7 @@ final class ExtractUnallocAction extends AbstractAction { this.imageId = 0; } this.fileName = this.imageName + "-Unalloc-" + this.imageId + "-" + volumeId + ".dat"; //NON-NLS - this.fileInstance = new File(Case.getCurrentCase().getExportDirectory() + File.separator + this.fileName); + this.fileInstance = new File(Case.getOpenCase().getExportDirectory() + File.separator + this.fileName); this.layoutFiles = getUnallocFiles(volume); Collections.sort(layoutFiles, new SortObjId()); this.sizeInBytes = calcSizeInBytes(); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index a41acd1c58..ebdfb6146f 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -518,7 +518,7 @@ final class DataSourceIngestJob { */ if (this.hasFirstStageDataSourceIngestPipeline() && this.hasFileIngestPipeline()) { logger.log(Level.INFO, "Scheduling first stage data source and file level analysis tasks for {0} (jobId={1})", new Object[]{dataSource.getName(), this.id}); //NON-NLS - DataSourceIngestJob.taskScheduler.scheduleIngestTasks(this, this.files); + DataSourceIngestJob.taskScheduler.scheduleIngestTasks(this); } else if (this.hasFirstStageDataSourceIngestPipeline()) { logger.log(Level.INFO, "Scheduling first stage data source level analysis tasks for {0} (jobId={1}), no file level analysis configured", new Object[]{dataSource.getName(), this.id}); //NON-NLS DataSourceIngestJob.taskScheduler.scheduleDataSourceIngestTask(this); @@ -827,7 +827,7 @@ final class DataSourceIngestJob { } /** - * Adds more files from the data source for this job to the job, i.e., adds + * Adds more files from the data source for this job to the job, e.g., adds * extracted or carved files. Not currently supported for the second stage * of the job. * @@ -835,9 +835,7 @@ final class DataSourceIngestJob { */ void addFiles(List files) { if (DataSourceIngestJob.Stages.FIRST == this.stage) { - for (AbstractFile file : files) { - DataSourceIngestJob.taskScheduler.scheduleFastTrackedFileIngestTask(this, file); - } + DataSourceIngestJob.taskScheduler.scheduleFileIngestTasks(this, files); } else { DataSourceIngestJob.logger.log(Level.SEVERE, "Adding files during second stage not supported"); //NON-NLS } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java index 65d0162d6d..218626985a 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestManager.java @@ -121,7 +121,7 @@ public class IngestManager { private final AtomicLong nextIngestManagerTaskId = new AtomicLong(0L); private final ExecutorService startIngestJobsExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-start-ingest-jobs-%d").build()); //NON-NLS; private final Map> startIngestJobFutures = new ConcurrentHashMap<>(); - private final Map ingestJobsById = new ConcurrentHashMap<>(); + private final Map ingestJobsById = new HashMap<>(); private final ExecutorService dataSourceLevelIngestJobTasksExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-data-source-ingest-%d").build()); //NON-NLS; private final ExecutorService fileLevelIngestJobTasksExecutor; private final ExecutorService eventPublishingExecutor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setNameFormat("IM-ingest-events-%d").build()); //NON-NLS; @@ -399,13 +399,17 @@ public class IngestManager { ingestMonitor.start(); } - ingestJobsById.put(job.getId(), job); + synchronized (ingestJobsById) { + ingestJobsById.put(job.getId(), job); + } errors = job.start(); if (errors.isEmpty()) { this.fireIngestJobStarted(job.getId()); IngestManager.logger.log(Level.INFO, "Ingest job {0} started", job.getId()); //NON-NLS } else { - this.ingestJobsById.remove(job.getId()); + synchronized (ingestJobsById) { + this.ingestJobsById.remove(job.getId()); + } for (IngestModuleError error : errors) { logger.log(Level.SEVERE, String.format("Error starting %s ingest module for job %d", error.getModuleDisplayName(), job.getId()), error.getThrowable()); //NON-NLS } @@ -438,7 +442,9 @@ public class IngestManager { */ void finishIngestJob(IngestJob job) { long jobId = job.getId(); - ingestJobsById.remove(jobId); + synchronized (ingestJobsById) { + ingestJobsById.remove(jobId); + } if (!job.isCancelled()) { IngestManager.logger.log(Level.INFO, "Ingest job {0} completed", jobId); //NON-NLS fireIngestJobCompleted(jobId); @@ -455,7 +461,9 @@ public class IngestManager { * @return True or false. */ public boolean isIngestRunning() { - return !ingestJobsById.isEmpty(); + synchronized (ingestJobsById) { + return !ingestJobsById.isEmpty(); + } } /** @@ -467,9 +475,11 @@ public class IngestManager { startIngestJobFutures.values().forEach((handle) -> { handle.cancel(true); }); - this.ingestJobsById.values().forEach((job) -> { - job.cancel(reason); - }); + synchronized (ingestJobsById) { + this.ingestJobsById.values().forEach((job) -> { + job.cancel(reason); + }); + } } /** @@ -770,9 +780,11 @@ public class IngestManager { */ List getIngestJobSnapshots() { List snapShots = new ArrayList<>(); - ingestJobsById.values().forEach((job) -> { - snapShots.addAll(job.getDataSourceIngestJobSnapshots()); - }); + synchronized (ingestJobsById) { + ingestJobsById.values().forEach((job) -> { + snapShots.addAll(job.getDataSourceIngestJobSnapshots()); + }); + } return snapShots; } @@ -808,7 +820,9 @@ public class IngestManager { public Void call() { try { if (Thread.currentThread().isInterrupted()) { - ingestJobsById.remove(job.getId()); + synchronized (ingestJobsById) { + ingestJobsById.remove(job.getId()); + } return null; } diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java index b9cee95687..36b09035b1 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestTasksScheduler.java @@ -21,12 +21,13 @@ package org.sleuthkit.autopsy.ingest; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; -import java.util.HashSet; +import java.util.Deque; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; -import java.util.Set; import java.util.TreeSet; import java.util.concurrent.BlockingDeque; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; @@ -40,64 +41,20 @@ import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; /** - * Creates ingest tasks for ingest jobs, queuing the tasks in priority order for - * execution by the ingest manager's ingest threads. + * Creates ingest tasks for data source ingest jobs, queueing the tasks in + * priority order for execution by the ingest manager's ingest threads. */ final class IngestTasksScheduler { - private static final Logger logger = Logger.getLogger(IngestTasksScheduler.class.getName()); private static final int FAT_NTFS_FLAGS = TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_FAT12.getValue() | TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_FAT16.getValue() | TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_FAT32.getValue() | TskData.TSK_FS_TYPE_ENUM.TSK_FS_TYPE_NTFS.getValue(); + private static final Logger logger = Logger.getLogger(IngestTasksScheduler.class.getName()); private static IngestTasksScheduler instance; - - /** - * Scheduling of data source ingest tasks is accomplished by putting them in - * a FIFO queue to be consumed by the ingest threads, so the queue is - * wrapped in a "dispenser" that implements the IngestTaskQueue interface - * and is exposed via a getter method. - */ - private final LinkedBlockingQueue pendingDataSourceTasks; - private final DataSourceIngestTaskQueue dataSourceTasksDispenser; - - /** - * Scheduling of file ingest tasks is accomplished by "shuffling" them - * through a sequence of internal queues that allows for the interleaving of - * tasks from different ingest jobs based on priority. These scheduling - * queues are: - * - * 1. Root directory tasks (priority queue) - * - * 2. Directory tasks (FIFO queue) - * - * 3. Pending file tasks (LIFO queue). - * - * The pending file tasks queue is LIFO to handle large numbers of files - * extracted from archive files. At least one image has been processed that - * had a folder full of archive files. The queue grew to have thousands of - * entries, as each successive archive file was expanded, so now extracted - * files get added to the front of the queue so that in such a scenario they - * would be processed before the expansion of the next archive file. - * - * Tasks in the pending file tasks queue are ready to be consumed by the - * ingest threads, so the queue is wrapped in a "dispenser" that implements - * the IngestTaskQueue interface and is exposed via a getter method. - */ - private final TreeSet rootDirectoryTasks; - private final List directoryTasks; - private final BlockingDeque pendingFileTasks; - private final FileIngestTaskQueue fileTasksDispenser; - - /** - * The ingest tasks scheduler allows ingest jobs to query it to see if there - * are any tasks in progress for the job. To make this possible, the ingest - * tasks scheduler needs to keep track not only of the tasks in its queues, - * but also of the tasks that have been handed out for processing by the - * ingest threads. Therefore all ingest tasks are added to this list when - * they are created and are not removed when an ingest thread takes an - * ingest task. Instead, the ingest thread calls back into the scheduler - * when the task is completed, at which time the task will be removed from - * this list. - */ - private final Set tasksInProgress; + private final DataSourceIngestTaskQueue dataSourceTaskQueueForIngestThreads; + private final List queuedAndRunningDataSourceTasks; + private final TreeSet rootFileTaskQueue; + private final Deque directoryFileTaskQueue; + private final FileIngestTaskQueue fileTaskQueueForIngestThreads; + private final List queuedAndRunningFileTasks; /** * Gets the ingest tasks scheduler singleton. @@ -113,52 +70,53 @@ final class IngestTasksScheduler { * Constructs an ingest tasks scheduler. */ private IngestTasksScheduler() { - this.pendingDataSourceTasks = new LinkedBlockingQueue<>(); - this.dataSourceTasksDispenser = new DataSourceIngestTaskQueue(); - this.rootDirectoryTasks = new TreeSet<>(new RootDirectoryTaskComparator()); - this.directoryTasks = new ArrayList<>(); - this.pendingFileTasks = new LinkedBlockingDeque<>(); - this.fileTasksDispenser = new FileIngestTaskQueue(); - this.tasksInProgress = new HashSet<>(); + this.queuedAndRunningDataSourceTasks = new LinkedList<>(); + this.dataSourceTaskQueueForIngestThreads = new DataSourceIngestTaskQueue(); + this.rootFileTaskQueue = new TreeSet<>(new RootDirectoryTaskComparator()); + this.directoryFileTaskQueue = new LinkedList<>(); + this.queuedAndRunningFileTasks = new LinkedList<>(); + this.fileTaskQueueForIngestThreads = new FileIngestTaskQueue(); } /** - * Gets this ingest task scheduler's implementation of the IngestTaskQueue - * interface for data source ingest tasks. + * Gets the data source level ingest tasks queue. This queue is a blocking + * queue intended for use by the ingest manager's data source ingest + * threads. * - * @return The data source ingest tasks queue. + * @return The queue. */ IngestTaskQueue getDataSourceIngestTaskQueue() { - return this.dataSourceTasksDispenser; + return this.dataSourceTaskQueueForIngestThreads; } /** - * Gets this ingest task scheduler's implementation of the IngestTaskQueue - * interface for file ingest tasks. + * Gets the file level ingest tasks queue. This queue is a blocking queue + * intended for use by the ingest manager's file ingest threads. * - * @return The file ingest tasks queue. + * @return The queue. */ IngestTaskQueue getFileIngestTaskQueue() { - return this.fileTasksDispenser; + return this.fileTaskQueueForIngestThreads; } /** * Schedules a data source level ingest task and file level ingest tasks for - * an ingest job. Either all of the files in the data source or a given - * subset of the files will be scheduled. + * a data source ingest job. * - * @param job The data source ingest job. - * @param files A subset of the files for the data source. + * @param job The data source ingest job. */ - synchronized void scheduleIngestTasks(DataSourceIngestJob job, List files) { + synchronized void scheduleIngestTasks(DataSourceIngestJob job) { if (!job.isCancelled()) { - // Scheduling of both a data source ingest task and file ingest tasks - // for a job must be an atomic operation. Otherwise, the data source - // task might be completed before the file tasks are scheduled, - // resulting in a potential false positive when another thread checks - // whether or not all the tasks for the job are completed. + /* + * Scheduling of both the data source ingest task and the initial + * file ingest tasks for a job must be an atomic operation. + * Otherwise, the data source task might be completed before the + * file tasks are scheduled, resulting in a potential false positive + * when another thread checks whether or not all the tasks for the + * job are completed. + */ this.scheduleDataSourceIngestTask(job); - this.scheduleFileIngestTasks(job, files); + this.scheduleFileIngestTasks(job); } } @@ -170,41 +128,29 @@ final class IngestTasksScheduler { synchronized void scheduleDataSourceIngestTask(DataSourceIngestJob job) { if (!job.isCancelled()) { DataSourceIngestTask task = new DataSourceIngestTask(job); - this.tasksInProgress.add(task); + this.queuedAndRunningDataSourceTasks.add(task); try { - this.pendingDataSourceTasks.put(task); + this.dataSourceTaskQueueForIngestThreads.add(task); } catch (InterruptedException ex) { - /** - * The current thread was interrupted while blocked on a full - * queue. Discard the task and reset the interrupted flag. - */ - this.tasksInProgress.remove(task); + IngestTasksScheduler.logger.log(Level.INFO, "Ingest cancelled while a data source ingest thread was blocked on a full queue", ex); + this.queuedAndRunningDataSourceTasks.remove(task); Thread.currentThread().interrupt(); } } } /** - * Schedules file level ingest tasks for a data source ingest job. Either - * all of the files in the data source or a given subset of the files will - * be scheduled. + * Schedules file level ingest tasks for a data source ingest job. * - * @param job The data source ingest job. - * @param files A subset of the files for the data source. + * @param job The data source ingest job. */ - synchronized void scheduleFileIngestTasks(DataSourceIngestJob job, List files) { + synchronized void scheduleFileIngestTasks(DataSourceIngestJob job) { if (!job.isCancelled()) { - List candidateFiles = new ArrayList<>(); - if (files.isEmpty()) { - getTopLevelFiles(job.getDataSource(), candidateFiles); - } else { - candidateFiles.addAll(files); - } - for (AbstractFile firstLevelFile : candidateFiles) { - FileIngestTask task = new FileIngestTask(job, firstLevelFile); + List candidateFiles = getTopLevelFiles(job.getDataSource()); + for (AbstractFile file : candidateFiles) { + FileIngestTask task = new FileIngestTask(job, file); if (IngestTasksScheduler.shouldEnqueueFileTask(task)) { - this.tasksInProgress.add(task); - this.rootDirectoryTasks.add(task); + this.rootFileTaskQueue.add(task); } } shuffleFileTaskQueues(); @@ -212,73 +158,119 @@ final class IngestTasksScheduler { } /** - * Schedules a file ingest task for a data source ingest job. The task that - * is created is added directly to the pending file tasks queues, i.e., it - * is "fast tracked." + * Schedules file level ingest tasks for a subset of the files for a data + * source ingest job. * - * @param job The data source ingest job. - * @param file A file. + * @param job The data source ingest job. + * @param files A subset of the files for the data source. */ - synchronized void scheduleFastTrackedFileIngestTask(DataSourceIngestJob job, AbstractFile file) { + synchronized void scheduleFileIngestTasks(DataSourceIngestJob job, Collection files) { if (!job.isCancelled()) { - FileIngestTask task = new FileIngestTask(job, file); - if (IngestTasksScheduler.shouldEnqueueFileTask(task)) { - this.tasksInProgress.add(task); - addToPendingFileTasksQueue(task); + List newTasksForFileIngestThreads = new LinkedList<>(); + for (AbstractFile file : files) { + /* + * Put the file directly into the queue for the file ingest + * threads, if it passes the filter for the job. The file is + * added to the queue for the ingest threads BEFORE the other + * queued tasks because the primary use case for this method is + * adding derived files from a higher priority task that + * preceded the tasks currently in the queue. + */ + FileIngestTask task = new FileIngestTask(job, file); + if (shouldEnqueueFileTask(task)) { + newTasksForFileIngestThreads.add(task); + } + + /* + * If the file or directory that was just queued has children, + * try to queue tasks for the children. Each child task will go + * into either the directory queue if it is a directory, or + * directly into the queue for the file ingest threads, if it + * passes the filter for the job. + */ + try { + for (Content child : file.getChildren()) { + if (child instanceof AbstractFile) { + AbstractFile childFile = (AbstractFile) child; + FileIngestTask childTask = new FileIngestTask(job, childFile); + if (childFile.hasChildren()) { + this.directoryFileTaskQueue.add(childTask); + } else if (shouldEnqueueFileTask(childTask)) { + newTasksForFileIngestThreads.add(task); + } + } + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, String.format("Error getting the children of %s (objId=%d)", file.getName(), file.getId()), ex); //NON-NLS + } + } + + /* + * The files are added to the queue for the ingest threads BEFORE + * the other queued tasks because the primary use case for this + * method is adding derived files from a higher priority task that + * preceded the tasks currently in the queue. + */ + for (FileIngestTask newTask : newTasksForFileIngestThreads) { + try { + this.queuedAndRunningFileTasks.add(newTask); + this.fileTaskQueueForIngestThreads.addFirst(newTask); + } catch (InterruptedException ex) { + this.queuedAndRunningFileTasks.remove(newTask); + IngestTasksScheduler.logger.log(Level.INFO, "Ingest cancelled while blocked on a full file ingest threads queue", ex); + Thread.currentThread().interrupt(); + break; + } } } } /** - * Allows an ingest thread to notify this ingest task scheduler that a task - * has been completed. + * Allows an ingest thread to notify this ingest task scheduler that a data + * source level task has been completed. * * @param task The completed task. */ - synchronized void notifyTaskCompleted(IngestTask task) { - tasksInProgress.remove(task); + synchronized void notifyTaskCompleted(DataSourceIngestTask task) { + this.queuedAndRunningDataSourceTasks.remove(task); } /** - * Queries the task scheduler to determine whether or not all current ingest - * tasks for an ingest job are completed. + * Allows an ingest thread to notify this ingest task scheduler that a file + * level task has been completed. * - * @param job The job for which the query is to be performed. + * @param task The completed task. + */ + synchronized void notifyTaskCompleted(FileIngestTask task) { + this.queuedAndRunningFileTasks.remove(task); + shuffleFileTaskQueues(); + } + + /** + * Queries the task scheduler to determine whether or not all of the ingest + * tasks for a data source ingest job have been completed. + * + * @param job The data source ingest job. * * @return True or false. */ synchronized boolean tasksForJobAreCompleted(DataSourceIngestJob job) { - for (IngestTask task : tasksInProgress) { - if (task.getIngestJob().getId() == job.getId()) { - return false; - } - } - return true; + return !hasTasksForJob(this.queuedAndRunningDataSourceTasks, job) + && !hasTasksForJob(this.rootFileTaskQueue, job) + && !hasTasksForJob(this.directoryFileTaskQueue, job) + && !hasTasksForJob(this.queuedAndRunningFileTasks, job); } /** - * Clears the "upstream" task scheduling queues for an ingest job, but does - * nothing about tasks that have already been shuffled into the concurrently - * accessed blocking queues shared with the ingest threads. Note that tasks - * in the "downstream" queues or already taken by the ingest threads will be - * flushed out when the ingest threads call back with their task completed - * notifications. + * Clears the "upstream" task scheduling queues for a data source ingest + * job, but does nothing about tasks that have already been moved into the + * queue that is consumed by the file ingest threads. * - * @param job The job for which the tasks are to to canceled. + * @param job The data source ingest job. */ synchronized void cancelPendingTasksForIngestJob(DataSourceIngestJob job) { - /** - * This code should not flush the blocking queues that are concurrently - * accessed by the ingest threads. This is because the "lock striping" - * and "weakly consistent" iterators of these collections make it so - * that this code could have a different view of the queues than the - * ingest threads. It does clean out the directory level tasks before - * they are exploded into file tasks. - */ - long jobId = job.getId(); - this.removeTasksForJob(this.rootDirectoryTasks, jobId); - this.removeTasksForJob(this.directoryTasks, jobId); - this.shuffleFileTaskQueues(); + this.removeTasksForJob(this.rootFileTaskQueue, job); + this.removeTasksForJob(this.directoryFileTaskQueue, job); } /** @@ -286,10 +278,12 @@ final class IngestTasksScheduler { * files and virtual directories for a data source. Used to create file * tasks to put into the root directories queue. * - * @param dataSource The data source. - * @param topLevelFiles The top level files are added to this list. + * @param dataSource The data source. + * + * @return The top level files. */ - private static void getTopLevelFiles(Content dataSource, List topLevelFiles) { + private static List getTopLevelFiles(Content dataSource) { + List topLevelFiles = new ArrayList<>(); Collection rootObjects = dataSource.accept(new GetRootDirectoryVisitor()); if (rootObjects.isEmpty() && dataSource instanceof AbstractFile) { // The data source is itself a file to be processed. @@ -317,74 +311,113 @@ final class IngestTasksScheduler { } } } + return topLevelFiles; } /** - * "Shuffles" the file task queues to ensure that there is at least one task - * in the pending file ingest tasks queue, as long as there are still file - * ingest tasks to be performed. + * Schedules file ingest tasks for the ingest manager's file ingest threads + * by "shuffling" them through a sequence of three queues that allows for + * the interleaving of tasks from different data source ingest jobs based on + * priority. The sequence of queues is: + * + * 1. The root file tasks priority queue, which contains file tasks for the + * root objects of the data sources that are being analyzed. For example, + * the root tasks for a disk image data source are typically the tasks for + * the contents of the root directories of the file systems. This queue is a + * priority queue that attempts to ensure that user directory content is + * analyzed before general file system content. It feeds into the directory + * tasks queue. + * + * 2. The directory file tasks queue, which contains root file tasks + * shuffled out of the root tasks queue, plus directory tasks discovered in + * the descent from the root tasks to the final leaf tasks in the content + * trees that are being analyzed for the data source ingest jobs. This queue + * is a FIFO queue. It feeds into the file tasks queue for the ingest + * manager's file ingest threads. + * + * 3. The file tasks queue for the ingest manager's file ingest threads. + * This queue is a blocking deque that is FIFO during a shuffle to maintain + * task prioritization, but LIFO when adding derived files to it directly + * during ingest. The reason for the LIFO additions is to give priority + * derived files of priority files. + * + * There is a fourth collection of file tasks, a "tracking" list, that keeps + * track of the file tasks that are either in the tasks queue for the file + * ingest threads, or are in the process of being analyzed in a file ingest + * thread. This queue is vital to the ingest task scheduler's ability to + * determine when all of the ingest tasks for a data source ingest job have + * been completed. It is also used to drive this shuffling algorithm - + * whenever this list is empty, the two "upstream" queues are "shuffled" to + * queue more tasks for the file ingest threads. */ synchronized private void shuffleFileTaskQueues() { - // This is synchronized because it is called both by synchronized - // methods of this ingest scheduler and an unsynchronized method of its - // file tasks "dispenser". - while (true) { - // Loop until either the pending file tasks queue is NOT empty - // or the upstream queues that feed into it ARE empty. - if (!this.pendingFileTasks.isEmpty()) { - // There are file tasks ready to be consumed, exit. - return; - } - if (this.directoryTasks.isEmpty()) { - if (this.rootDirectoryTasks.isEmpty()) { - // There are no root directory tasks to move into the - // directory queue, exit. - return; + List newTasksForFileIngestThreads = new LinkedList<>(); + while (this.queuedAndRunningFileTasks.isEmpty()) { + /* + * If the directory file task queue is empty, move the highest + * priority root file task, if there is one, into it. If both the + * root and the directory file task queues are empty, there is + * nothing left to shuffle, so exit. + */ + if (this.directoryFileTaskQueue.isEmpty()) { + if (!this.rootFileTaskQueue.isEmpty()) { + this.directoryFileTaskQueue.add(this.rootFileTaskQueue.pollFirst()); } else { - // Move the next root directory task into the - // directories queue. Note that the task was already - // added to the tasks in progress list when the task was - // created in scheduleFileIngestTasks(). - this.directoryTasks.add(this.rootDirectoryTasks.pollFirst()); + return; } } - // Try to add the most recently added directory from the - // directory tasks queue to the pending file tasks queue. - FileIngestTask directoryTask = this.directoryTasks.remove(this.directoryTasks.size() - 1); + /* + * Try to move the next task from the directory task queue into the + * queue for the file ingest threads, if it passes the filter for + * the job. The file is added to the queue for the ingest threads + * AFTER the higher priority tasks that preceded it. + */ + final FileIngestTask directoryTask = this.directoryFileTaskQueue.pollLast(); if (shouldEnqueueFileTask(directoryTask)) { - addToPendingFileTasksQueue(directoryTask); - } else { - this.tasksInProgress.remove(directoryTask); + newTasksForFileIngestThreads.add(directoryTask); + this.queuedAndRunningFileTasks.add(directoryTask); } - // If the directory contains subdirectories or files, try to - // enqueue tasks for them as well. + /* + * If the directory (or root level file) that was just queued has + * children, try to queue tasks for the children. Each child task + * will go into either the directory queue if it is a directory, or + * into the queue for the file ingest threads, if it passes the + * filter for the job. The file is added to the queue for the ingest + * threads AFTER the higher priority tasks that preceded it. + */ final AbstractFile directory = directoryTask.getFile(); try { for (Content child : directory.getChildren()) { if (child instanceof AbstractFile) { - AbstractFile file = (AbstractFile) child; - FileIngestTask childTask = new FileIngestTask(directoryTask.getIngestJob(), file); - if (file.hasChildren()) { - // Found a subdirectory, put the task in the - // pending directory tasks queue. Note the - // addition of the task to the tasks in progress - // list. This is necessary because this is the - // first appearance of this task in the queues. - this.tasksInProgress.add(childTask); - this.directoryTasks.add(childTask); + AbstractFile childFile = (AbstractFile) child; + FileIngestTask childTask = new FileIngestTask(directoryTask.getIngestJob(), childFile); + if (childFile.hasChildren()) { + this.directoryFileTaskQueue.add(childTask); } else if (shouldEnqueueFileTask(childTask)) { - // Found a file, put the task directly into the - // pending file tasks queue. - this.tasksInProgress.add(childTask); - addToPendingFileTasksQueue(childTask); + newTasksForFileIngestThreads.add(childTask); + this.queuedAndRunningFileTasks.add(childTask); } } } } catch (TskCoreException ex) { - String errorMessage = String.format("An error occurred getting the children of %s", directory.getName()); //NON-NLS - logger.log(Level.SEVERE, errorMessage, ex); + logger.log(Level.SEVERE, String.format("Error getting the children of %s (objId=%d)", directory.getName(), directory.getId()), ex); //NON-NLS + } + } + + /* + * The files are added to the queue for the ingest threads AFTER the + * higher priority tasks that preceded them. + */ + for (FileIngestTask newTask : newTasksForFileIngestThreads) { + try { + this.fileTaskQueueForIngestThreads.addFirst(newTask); + } catch (InterruptedException ex) { + this.queuedAndRunningFileTasks.remove(newTask); + IngestTasksScheduler.logger.log(Level.INFO, "Ingest cancelled while blocked on a full file ingest threads queue", ex); + Thread.currentThread().interrupt(); + break; } } } @@ -463,44 +496,44 @@ final class IngestTasksScheduler { } /** - * Adds a file ingest task to the blocking pending tasks queue. + * Checks whether or not a collection of ingest tasks includes a task for a + * given data source ingest job. * - * @param task The task to add. + * @param tasks The tasks. + * @param job The data source ingest job. + * + * @return True if there are no tasks for the job, false otherwise. */ - synchronized private void addToPendingFileTasksQueue(FileIngestTask task) { - try { - this.pendingFileTasks.putFirst(task); - } catch (InterruptedException ex) { - /** - * The current thread was interrupted while blocked on a full queue. - * Discard the task and reset the interrupted flag. - */ - this.tasksInProgress.remove(task); - Thread.currentThread().interrupt(); + synchronized private boolean hasTasksForJob(Collection tasks, DataSourceIngestJob job) { + long jobId = job.getId(); + for (IngestTask task : tasks) { + if (task.getIngestJob().getId() == jobId) { + return true; + } } + return false; } /** - * Removes all of the ingest tasks associated with an ingest job from a - * tasks queue. The task is removed from the the tasks in progress list as - * well. + * Removes all of the ingest tasks associated with a data source ingest job + * from a tasks collection. * - * @param taskQueue The queue from which to remove the tasks. - * @param jobId The id of the job for which the tasks are to be removed. + * @param tasks The collection from which to remove the tasks. + * @param job THe data source ingest job. */ - synchronized private void removeTasksForJob(Collection taskQueue, long jobId) { - Iterator iterator = taskQueue.iterator(); + synchronized private void removeTasksForJob(Collection tasks, DataSourceIngestJob job) { + long jobId = job.getId(); + Iterator iterator = tasks.iterator(); while (iterator.hasNext()) { IngestTask task = iterator.next(); if (task.getIngestJob().getId() == jobId) { - this.tasksInProgress.remove(task); iterator.remove(); } } } /** - * Counts the number of ingest tasks in a task queue for a given job. + * Counts the number of ingest tasks in a tasks collection for a given job. * * @param queue The queue for which to count tasks. * @param jobId The id of the job for which the tasks are to be counted. @@ -511,7 +544,7 @@ final class IngestTasksScheduler { Iterator iterator = queue.iterator(); int count = 0; while (iterator.hasNext()) { - IngestTask task = (IngestTask) iterator.next(); + IngestTask task = iterator.next(); if (task.getIngestJob().getId() == jobId) { count++; } @@ -549,8 +582,15 @@ final class IngestTasksScheduler { } } + /** + * Used to prioritize file ingest tasks in the root tasks queue so that + * user content is processed first. + */ private static class AbstractFilePriority { + private AbstractFilePriority() { + } + enum Priority { LAST, LOW, MEDIUM, HIGH @@ -642,28 +682,43 @@ final class IngestTasksScheduler { } /** - * Wraps access to pending data source ingest tasks in the interface - * required by the ingest threads. + * A blocking queue of data source ingest tasks for the ingest manager's + * data source ingest threads. */ private final class DataSourceIngestTaskQueue implements IngestTaskQueue { + private final BlockingQueue tasks = new LinkedBlockingQueue<>(); + + private void add(DataSourceIngestTask task) throws InterruptedException { + this.tasks.put(task); + } + @Override public IngestTask getNextTask() throws InterruptedException { - return IngestTasksScheduler.this.pendingDataSourceTasks.take(); + return tasks.take(); } + } /** - * Wraps access to pending file ingest tasks in the interface required by - * the ingest threads. + * A blocking, LIFO queue of data source ingest tasks for the ingest + * manager's data source ingest threads. */ private final class FileIngestTaskQueue implements IngestTaskQueue { + private final BlockingDeque tasks = new LinkedBlockingDeque<>(); + + private void addFirst(FileIngestTask task) throws InterruptedException { + this.tasks.putFirst(task); + } + + private void addLast(FileIngestTask task) throws InterruptedException { + this.tasks.putLast(task); + } + @Override public IngestTask getNextTask() throws InterruptedException { - FileIngestTask task = IngestTasksScheduler.this.pendingFileTasks.takeFirst(); - shuffleFileTaskQueues(); - return task; + return tasks.takeFirst(); } } @@ -674,10 +729,10 @@ final class IngestTasksScheduler { class IngestJobTasksSnapshot { private final long jobId; + private final long dsQueueSize; private final long rootQueueSize; private final long dirQueueSize; private final long fileQueueSize; - private final long dsQueueSize; private final long runningListSize; /** @@ -687,11 +742,11 @@ final class IngestTasksScheduler { */ IngestJobTasksSnapshot(long jobId) { this.jobId = jobId; - this.rootQueueSize = countTasksForJob(IngestTasksScheduler.this.rootDirectoryTasks, jobId); - this.dirQueueSize = countTasksForJob(IngestTasksScheduler.this.directoryTasks, jobId); - this.fileQueueSize = countTasksForJob(IngestTasksScheduler.this.pendingFileTasks, jobId); - this.dsQueueSize = countTasksForJob(IngestTasksScheduler.this.pendingDataSourceTasks, jobId); - this.runningListSize = countTasksForJob(IngestTasksScheduler.this.tasksInProgress, jobId); + this.rootQueueSize = countTasksForJob(IngestTasksScheduler.this.rootFileTaskQueue, jobId); + this.dirQueueSize = countTasksForJob(IngestTasksScheduler.this.directoryFileTaskQueue, jobId); + this.fileQueueSize = countTasksForJob(IngestTasksScheduler.this.fileTaskQueueForIngestThreads.tasks, jobId); + this.dsQueueSize = countTasksForJob(IngestTasksScheduler.this.dataSourceTaskQueueForIngestThreads.tasks, jobId); + this.runningListSize = countTasksForJob(IngestTasksScheduler.this.queuedAndRunningDataSourceTasks, jobId) + countTasksForJob(IngestTasksScheduler.this.queuedAndRunningFileTasks, jobId); } /** diff --git a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java index d322d1026f..4698b68006 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/runIngestModuleWizard/RunIngestModulesAction.java @@ -93,7 +93,7 @@ public final class RunIngestModulesAction extends AbstractAction { * Constructs an action that invokes the Run Ingest Modules wizard for the * children of a file. * - * @param file The file. + * @param parentFile The file. */ public RunIngestModulesAction(AbstractFile parentFile) { this.putValue(Action.NAME, Bundle.RunIngestModulesAction_name()); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java index 0dbb0300f2..0f8b009c52 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java @@ -547,7 +547,7 @@ public final class FilesSet implements Serializable { /** * Construct a meta-type condition. * - * @param metaType The meta-type to match, must. + * @param type The meta-type to match, must. */ public MetaTypeCondition(Type type) { this.type = type; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java index ea5b7b5ee3..d097882053 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestControlPanel.java @@ -38,7 +38,11 @@ import java.util.logging.Level; import javax.swing.DefaultListSelectionModel; import java.awt.Color; import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.File; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -149,6 +153,11 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private Color pendingTableBackground; private Color pendingTablelForeground; + /** + * Maintain a mapping of each service to it's last status update. + */ + private final ConcurrentHashMap statusByService; + /* * The enum is used in conjunction with the DefaultTableModel class to * provide table models for the JTables used to display a view of the @@ -235,6 +244,8 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { * controlling automated ingest for a single node within the cluster. */ private AutoIngestControlPanel() { + + this.statusByService = new ConcurrentHashMap<>(); //Disable the main window so they can only use the dashboard (if we used setVisible the taskBar icon would go away) WindowManager.getDefault().getMainWindow().setEnabled(false); @@ -248,6 +259,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); // Generated code. + statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down")); + statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down")); + statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); initPendingJobsTable(); initRunningJobsTable(); @@ -260,6 +274,25 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { UIManager.put("PopupMenu.consumeEventOnClose", false); } + /** + * Update status of the services on the dashboard + */ + private void displayServicesStatus() { + tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message", + statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); + String upStatus = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up"); + if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 + || statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()).compareTo(upStatus) != 0 + || statusByService.get(ServicesMonitor.Service.MESSAGING.toString()).compareTo(upStatus) != 0) { + tbServicesStatusMessage.setForeground(Color.RED); + } else { + tbServicesStatusMessage.setForeground(Color.BLACK); + } + } + /** * Queries the services monitor and sets the text for the services status * text box. @@ -274,15 +307,11 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { private void setServicesStatusMessage() { new SwingWorker() { - String caseDatabaseServerStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - String keywordSearchServiceStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - String messagingStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - @Override protected Void doInBackground() throws Exception { - caseDatabaseServerStatus = getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE); - keywordSearchServiceStatus = getServiceStatus(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH); - messagingStatus = getServiceStatus(ServicesMonitor.Service.MESSAGING); + statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE)); + statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH)); + statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), getServiceStatus(ServicesMonitor.Service.MESSAGING)); return null; } @@ -311,15 +340,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { @Override protected void done() { - tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message", caseDatabaseServerStatus, keywordSearchServiceStatus, keywordSearchServiceStatus, messagingStatus)); - String upStatus = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up"); - if (caseDatabaseServerStatus.compareTo(upStatus) != 0 - || keywordSearchServiceStatus.compareTo(upStatus) != 0 - || messagingStatus.compareTo(upStatus) != 0) { - tbServicesStatusMessage.setForeground(Color.RED); - } else { - tbServicesStatusMessage.setForeground(Color.BLACK); - } + displayServicesStatus(); } }.execute(); @@ -682,12 +703,33 @@ public final class AutoIngestControlPanel extends JPanel implements Observer { return; } - /* - * Subscribe to services monitor events. - */ - ServicesMonitor.getInstance().addSubscriber((PropertyChangeEvent evt) -> { - setServicesStatusMessage(); - }); + PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { + + String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); + String status = evt.getNewValue().toString(); + + if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { + status = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Up"); + } else if (status.equals(ServicesMonitor.ServiceStatus.DOWN.toString())) { + status = NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.tbServicesStatusMessage.Message.Down"); + SYS_LOGGER.log(Level.SEVERE, "Connection to {0} is down", serviceDisplayName); //NON-NLS + } + + // if the status update is for an existing service who's status hasn't changed - do nothing. + if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { + return; + } + + statusByService.put(serviceDisplayName, status); + displayServicesStatus(); + }; + + // Subscribe to all multi-user services in order to display their status + Set servicesList = new HashSet<>(); + servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); + servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); + servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); + ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); /* * Register with the AIM as an observer. diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java index 9324f782a3..3389a64a03 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestDashboard.java @@ -31,6 +31,10 @@ import java.util.logging.Level; import javax.swing.DefaultListSelectionModel; import java.awt.Color; import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.swing.JPanel; import javax.swing.JTable; import javax.swing.SwingWorker; @@ -82,6 +86,11 @@ final class AutoIngestDashboard extends JPanel implements Observer { private final DefaultTableModel runningTableModel; private final DefaultTableModel completedTableModel; private AutoIngestMonitor autoIngestMonitor; + + /** + * Maintain a mapping of each service to it's last status update. + */ + private final ConcurrentHashMap statusByService; /** * Creates a dashboard for monitoring an automated ingest cluster. @@ -105,6 +114,8 @@ final class AutoIngestDashboard extends JPanel implements Observer { * Constructs a panel for monitoring an automated ingest cluster. */ private AutoIngestDashboard() { + this.statusByService = new ConcurrentHashMap<>(); + pendingTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); runningTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); @@ -112,6 +123,9 @@ final class AutoIngestDashboard extends JPanel implements Observer { completedTableModel = new AutoIngestTableModel(JobsTableModelColumns.headers, 0); initComponents(); + statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); + statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); + statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down")); setServicesStatusMessage(); initPendingJobsTable(); initRunningJobsTable(); @@ -122,6 +136,25 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ UIManager.put("PopupMenu.consumeEventOnClose", false); } + + /** + * Update status of the services on the dashboard + */ + private void displayServicesStatus() { + tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", + statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()), + statusByService.get(ServicesMonitor.Service.MESSAGING.toString()))); + String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); + if (statusByService.get(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()).compareTo(upStatus) != 0 + || statusByService.get(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()).compareTo(upStatus) != 0 + || statusByService.get(ServicesMonitor.Service.MESSAGING.toString()).compareTo(upStatus) != 0) { + tbServicesStatusMessage.setForeground(Color.RED); + } else { + tbServicesStatusMessage.setForeground(Color.BLACK); + } + } /** * Queries the services monitor and sets the text for the services status @@ -129,15 +162,12 @@ final class AutoIngestDashboard extends JPanel implements Observer { */ private void setServicesStatusMessage() { new SwingWorker() { - String caseDatabaseServerStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - String keywordSearchServiceStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - String messagingStatus = ServicesMonitor.ServiceStatus.DOWN.toString(); - + @Override protected Void doInBackground() throws Exception { - caseDatabaseServerStatus = getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE); - keywordSearchServiceStatus = getServiceStatus(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH); - messagingStatus = getServiceStatus(ServicesMonitor.Service.MESSAGING); + statusByService.put(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_CASE_DATABASE)); + statusByService.put(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString(), getServiceStatus(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH)); + statusByService.put(ServicesMonitor.Service.MESSAGING.toString(), getServiceStatus(ServicesMonitor.Service.MESSAGING)); return null; } @@ -166,15 +196,7 @@ final class AutoIngestDashboard extends JPanel implements Observer { @Override protected void done() { - tbServicesStatusMessage.setText(NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message", caseDatabaseServerStatus, keywordSearchServiceStatus, keywordSearchServiceStatus, messagingStatus)); - String upStatus = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); - if (caseDatabaseServerStatus.compareTo(upStatus) != 0 - || keywordSearchServiceStatus.compareTo(upStatus) != 0 - || messagingStatus.compareTo(upStatus) != 0) { - tbServicesStatusMessage.setForeground(Color.RED); - } else { - tbServicesStatusMessage.setForeground(Color.BLACK); - } + displayServicesStatus(); } }.execute(); @@ -413,10 +435,38 @@ final class AutoIngestDashboard extends JPanel implements Observer { * auto ingest job tables. */ private void startUp() throws AutoIngestMonitor.AutoIngestMonitorException { - setServicesStatusMessage(); - ServicesMonitor.getInstance().addSubscriber((PropertyChangeEvent evt) -> { - setServicesStatusMessage(); - }); + + PropertyChangeListener propChangeListener = (PropertyChangeEvent evt) -> { + + String serviceDisplayName = ServicesMonitor.Service.valueOf(evt.getPropertyName()).toString(); + String status = evt.getNewValue().toString(); + + if (status.equals(ServicesMonitor.ServiceStatus.UP.toString())) { + status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Up"); + LOGGER.log(Level.INFO, "Connection to {0} is up", serviceDisplayName); //NON-NLS + } else if (status.equals(ServicesMonitor.ServiceStatus.DOWN.toString())) { + status = NbBundle.getMessage(AutoIngestDashboard.class, "AutoIngestDashboard.tbServicesStatusMessage.Message.Down"); + LOGGER.log(Level.SEVERE, "Connection to {0} is down", serviceDisplayName); //NON-NLS + } else { + LOGGER.log(Level.INFO, "Status for {0} is {1}", new Object[]{serviceDisplayName, status}); //NON-NLS + } + + // if the status update is for an existing service who's status hasn't changed - do nothing. + if (statusByService.containsKey(serviceDisplayName) && status.equals(statusByService.get(serviceDisplayName))) { + return; + } + + statusByService.put(serviceDisplayName, status); + displayServicesStatus(); + }; + + // Subscribe to all multi-user services in order to display their status + Set servicesList = new HashSet<>(); + servicesList.add(ServicesMonitor.Service.REMOTE_CASE_DATABASE.toString()); + servicesList.add(ServicesMonitor.Service.REMOTE_KEYWORD_SEARCH.toString()); + servicesList.add(ServicesMonitor.Service.MESSAGING.toString()); + ServicesMonitor.getInstance().addSubscriber(servicesList, propChangeListener); + autoIngestMonitor = new AutoIngestMonitor(); autoIngestMonitor.addObserver(this); autoIngestMonitor.startUp(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 23380b15ae..416f2b394f 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -874,7 +874,7 @@ public class Server { * if this does not exist then no server is recorded. * * Format of solrServerList.txt: - * , + * (host),(port) * Ex: 10.1.2.34,8983 * * @param rootOutputDirectory diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java index 4cb6eaf8e3..8ae8dc0261 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Extract.java @@ -41,17 +41,32 @@ import org.sleuthkit.datamodel.*; abstract class Extract { - protected Case currentCase = Case.getCurrentCase(); - protected SleuthkitCase tskCase = currentCase.getSleuthkitCase(); + protected Case currentCase; + protected SleuthkitCase tskCase; private final Logger logger = Logger.getLogger(this.getClass().getName()); private final ArrayList errorMessages = new ArrayList<>(); String moduleName = ""; boolean dataFound = false; - Extract() { + Extract() { } - void init() throws IngestModuleException { + final void init() throws IngestModuleException { + try { + currentCase = Case.getOpenCase(); + tskCase = currentCase.getSleuthkitCase(); + } catch (NoCurrentCaseException ex) { + throw new IngestModuleException(Bundle.Extract_indexError_message(), ex); + } + configExtractor(); + } + + /** + * Override to add any module-specific configuration + * + * @throws IngestModuleException + */ + void configExtractor() throws IngestModuleException { } abstract void process(Content dataSource, IngestJobContext context); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java index 4ef892ffc9..e5d93a91d4 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/SearchEngineURLQueryAnalyzer.java @@ -393,7 +393,7 @@ class SearchEngineURLQueryAnalyzer extends Extract { } @Override - void init() throws IngestModuleException { + void configExtractor() throws IngestModuleException { try { PlatformUtil.extractResourceToUserConfigDir(SearchEngineURLQueryAnalyzer.class, XMLFILE, true); } catch (IOException e) { diff --git a/TSKVersion.xml b/TSKVersion.xml new file mode 100644 index 0000000000..544d73ea7f --- /dev/null +++ b/TSKVersion.xml @@ -0,0 +1,3 @@ + + + diff --git a/build.xml b/build.xml index 0c45446ab2..9a8b8bd507 100644 --- a/build.xml +++ b/build.xml @@ -6,7 +6,7 @@ Builds the module suite Autopsy 4. - + @@ -79,6 +79,10 @@ + + + + - + + + + + + + + + + + + + + @@ -96,6 +113,7 @@ + @@ -279,10 +297,6 @@ - - - - diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java index 77345eb853..7aff9be4f3 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/MboxParser.java @@ -56,6 +56,7 @@ import org.apache.james.mime4j.stream.MimeConfig; import org.apache.tika.parser.txt.CharsetDetector; import org.apache.tika.parser.txt.CharsetMatch; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.EncodedFileOutputStream; @@ -267,8 +268,18 @@ class MboxParser { * @param email * @param e */ + @NbBundle.Messages ({"MboxParser.handleAttch.noOpenCase.errMsg=Exception while getting open case."}) private void handleAttachment(EmailMessage email, Entity e, long fileID, int index) { - String outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator; + String outputDirPath; + String relModuleOutputPath; + try { + outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator; + relModuleOutputPath = ThunderbirdMboxFileIngestModule.getRelModuleOutputPath() + File.separator; + } catch (NoCurrentCaseException ex) { + addErrorMessage(Bundle.MboxParser_handleAttch_noOpenCase_errMsg()); + logger.log(Level.SEVERE, Bundle.MboxParser_handleAttch_noOpenCase_errMsg(), ex); //NON-NLS + return; + } String filename = e.getFilename(); // sanitize name. Had an attachment with a Japanese encoded path that @@ -325,8 +336,7 @@ class MboxParser { EmailMessage.Attachment attach = new EmailMessage.Attachment(); attach.setName(filename); - attach.setLocalPath(ThunderbirdMboxFileIngestModule.getRelModuleOutputPath() - + File.separator + uniqueFilename); + attach.setLocalPath(relModuleOutputPath + uniqueFilename); attach.setSize(new File(outPath).length()); attach.setEncodingType(TskData.EncodingType.XOR1); email.addAttachment(attach); diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java index 541415e82b..a9a659b475 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/PstParser.java @@ -33,6 +33,8 @@ import java.util.List; import java.util.logging.Level; import org.sleuthkit.autopsy.coreutils.Logger; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; +import org.sleuthkit.autopsy.ingest.IngestModule; import org.sleuthkit.autopsy.ingest.IngestMonitor; import org.sleuthkit.autopsy.ingest.IngestServices; import static org.sleuthkit.autopsy.thunderbirdparser.ThunderbirdMboxFileIngestModule.getRelModuleOutputPath; @@ -204,9 +206,16 @@ class PstParser { * @param email * @param msg */ + @NbBundle.Messages({"PstParser.noOpenCase.errMsg=Exception while getting open case."}) private void extractAttachments(EmailMessage email, PSTMessage msg, long fileID) { int numberOfAttachments = msg.getNumberOfAttachments(); - String outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator; + String outputDirPath; + try { + outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator; + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return; + } for (int x = 0; x < numberOfAttachments; x++) { String filename = ""; try { @@ -246,6 +255,9 @@ class PstParser { NbBundle.getMessage(this.getClass(), "PstParser.extractAttch.errMsg.failedToExtractToDisk", filename)); logger.log(Level.WARNING, "Failed to extract attachment from pst file.", ex); //NON-NLS + } catch (NoCurrentCaseException ex) { + addErrorMessage(Bundle.PstParser_noOpenCase_errMsg()); + logger.log(Level.SEVERE, Bundle.PstParser_noOpenCase_errMsg(), ex); //NON-NLS } } } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 10dc2d31ce..1e439cb39d 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -31,6 +31,7 @@ import java.util.regex.Pattern; import org.openide.util.NbBundle; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.Logger; @@ -74,15 +75,26 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } @Override + @Messages ({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."}) public void startUp(IngestJobContext context) throws IngestModuleException { this.context = context; - fileManager = Case.getCurrentCase().getServices().getFileManager(); + try { + fileManager = Case.getOpenCase().getServices().getFileManager(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); + throw new IngestModuleException(Bundle.ThunderbirdMboxFileIngestModule_noOpenCase_errMsg(), ex); + } } @Override public ProcessResult process(AbstractFile abstractFile) { - blackboard = Case.getCurrentCase().getServices().getBlackboard(); + try { + blackboard = Case.getOpenCase().getServices().getBlackboard(); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); + return ProcessResult.ERROR; + } // skip known if (abstractFile.getKnown().equals(TskData.FileKnown.KNOWN)) { @@ -133,8 +145,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { */ @Messages({"ThunderbirdMboxFileIngestModule.processPst.indexError.message=Failed to index encryption detected artifact for keyword search."}) private ProcessResult processPst(AbstractFile abstractFile) { - String fileName = getTempPath() + File.separator + abstractFile.getName() + String fileName; + try { + fileName = getTempPath() + File.separator + abstractFile.getName() + "-" + String.valueOf(abstractFile.getId()); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return ProcessResult.ERROR; + } File file = new File(fileName); long freeSpace = services.getFreeDiskSpace(); @@ -159,8 +177,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { PstParser.ParseResult result = parser.parse(file, abstractFile.getId()); if (result == PstParser.ParseResult.OK) { - // parse success: Process email and add artifacts - processEmails(parser.getResults(), abstractFile); + try { + // parse success: Process email and add artifacts + processEmails(parser.getResults(), abstractFile); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return ProcessResult.ERROR; + } + } else if (result == PstParser.ParseResult.ENCRYPT) { // encrypted pst: Add encrypted file artifact try { @@ -225,8 +249,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { emailFolder = emailFolder + mboxFileName; emailFolder = emailFolder.replaceAll(".sbd", ""); //NON-NLS - String fileName = getTempPath() + File.separator + abstractFile.getName() + String fileName; + try { + fileName = getTempPath() + File.separator + abstractFile.getName() + "-" + String.valueOf(abstractFile.getId()); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return ProcessResult.ERROR; + } File file = new File(fileName); long freeSpace = services.getFreeDiskSpace(); @@ -249,7 +279,12 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { MboxParser parser = new MboxParser(services, emailFolder); List emails = parser.parse(file, abstractFile.getId()); - processEmails(emails, abstractFile); + try { + processEmails(emails, abstractFile); + } catch (NoCurrentCaseException ex) { + logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS + return ProcessResult.ERROR; + } if (file.delete() == false) { logger.log(Level.INFO, "Failed to delete temp file: {0}", file.getName()); //NON-NLS @@ -268,10 +303,11 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { /** * Get a path to a temporary folder. * - * @return + * @throws NoCurrentCaseException if there is no open case. + * @return the temporary folder */ - public static String getTempPath() { - String tmpDir = Case.getCurrentCase().getTempDirectory() + File.separator + public static String getTempPath() throws NoCurrentCaseException { + String tmpDir = Case.getOpenCase().getTempDirectory() + File.separator + "EmailParser"; //NON-NLS File dir = new File(tmpDir); if (dir.exists() == false) { @@ -280,8 +316,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return tmpDir; } - public static String getModuleOutputPath() { - String outDir = Case.getCurrentCase().getModuleDirectory() + File.separator + /** + * Get a module output folder. + * + * @throws NoCurrentCaseException if there is no open case. + * @return the module output folder + */ + public static String getModuleOutputPath() throws NoCurrentCaseException { + String outDir = Case.getOpenCase().getModuleDirectory() + File.separator + EmailParserModuleFactory.getModuleName(); File dir = new File(outDir); if (dir.exists() == false) { @@ -290,8 +332,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { return outDir; } - public static String getRelModuleOutputPath() { - return Case.getCurrentCase().getModuleOutputDirectoryRelativePath() + File.separator + /** + * Get a relative path of a module output folder. + * + * @throws NoCurrentCaseException if there is no open case. + * @return the relative path of the module output folder + */ + public static String getRelModuleOutputPath() throws NoCurrentCaseException { + return Case.getOpenCase().getModuleOutputDirectoryRelativePath() + File.separator + EmailParserModuleFactory.getModuleName(); } @@ -301,8 +349,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * * @param emails * @param abstractFile + * @throws NoCurrentCaseException if there is no open case. */ - private void processEmails(List emails, AbstractFile abstractFile) { + private void processEmails(List emails, AbstractFile abstractFile) throws NoCurrentCaseException { List derivedFiles = new ArrayList<>(); @@ -386,9 +435,10 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * * @param email * @param abstractFile + * @throws NoCurrentCaseException if there is no open case. */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) - private BlackboardArtifact addArtifact(EmailMessage email, AbstractFile abstractFile) { + private BlackboardArtifact addArtifact(EmailMessage email, AbstractFile abstractFile) throws NoCurrentCaseException { BlackboardArtifact bbart = null; List bbattributes = new ArrayList<>(); String to = email.getRecipients(); @@ -408,11 +458,14 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { String senderAddress; senderAddressList.addAll(findEmailAddresess(from)); - AccountFileInstance senderAccountInstance = null; + AccountFileInstance senderAccountInstance = null; + + Case openCase = Case.getOpenCase(); + if (senderAddressList.size() == 1) { senderAddress = senderAddressList.get(0); try { - senderAccountInstance = Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); + senderAccountInstance = openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress, EmailParserModuleFactory.getModuleName(), abstractFile); } catch(TskCoreException ex) { logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS @@ -431,7 +484,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { recipientAddresses.forEach((addr) -> { try { AccountFileInstance recipientAccountInstance = - Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, + openCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr, EmailParserModuleFactory.getModuleName(), abstractFile); recipientAccountInstances.add(recipientAccountInstance); } @@ -467,7 +520,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { bbart.addAttributes(bbattributes); // Add account relationships - Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); + openCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL); try { // index the artifact for keyword search diff --git a/unix_setup.sh b/unix_setup.sh new file mode 100755 index 0000000000..93a98ae1bc --- /dev/null +++ b/unix_setup.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +# Verifies programs are installed and copies native code into the Autopsy folder structure + +TSK_VERSION=4.6.0 + +# Verify PhotoRec was installed +photorec_filepath=/usr/bin/photorec +if [ -f "$photorec_filepath" ]; then + echo "$photorec_filepath found" +else + echo "ERROR: Photorec not found, please install the testdisk package" + exit 1 +fi + +# Verify Java was installed and configured +if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/bin/java" ]; then + echo "Java found in $JAVA_HOME" + else + echo "ERROR: Java was not found in $JAVA_HOME" + exit 1 + fi +else + echo "ERROR: JAVA_HOME environment variable must be defined" + exit 1 +fi + +# Verify Sleuth Kit Java was installed +sleuthkit_jar_filepath=/usr/share/java/sleuthkit-$TSK_VERSION.jar; +ext_jar_filepath=$PWD/autopsy/modules/ext/sleuthkit-postgresql-$TSK_VERSION.jar; +if [ -f "$sleuthkit_jar_filepath" ]; then + echo "$sleuthkit_jar_filepath found" + echo "Copying into the Autopsy directory" + rm $ext_jar_filepath; + if [ "$?" -gt 0 ]; then #checking if remove operation failed + echo "exiting .." + exit 1 + else + cp $sleuthkit_jar_filepath $ext_jar_filepath + if [ "$?" -ne 0 ]; then # checking copy operation was successful + echo "exiting..." + exit 1 + fi + fi +else + echo "ERROR: $sleuthkit_jar_filepath not found, please install the sleuthkit-java.deb file" + exit 1 +fi + +# make sure it is executable +chmod +x bin/autopsy + +echo "Autopsy is now configured. You can execute bin/autopsy to start it"