diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index cc7ab91631..ba910c4391 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -219,6 +219,8 @@ MultiUserCasesPanel.searchLabel.text=Select any case and start typing to search MultiUserCasesPanel.cancelButton.text=Cancel ImageFilePanel.pathErrorLabel.text=Error Label ImageFilePanel.sectorSizeLabel.text=Sector size: +LocalDiskPanel.sectorSizeLabel.text=Sector Size: +LocalDiskPanel.refreshTableButton.text=Refresh Local Disks LocalFilesPanel.displayNameLabel.text=Logical File Set Display Name: Default LocalFilesPanel.errorLabel.text=Error Label LocalFilesPanel.selectedPaths.toolTipText= diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index b9665ab811..b9554e6367 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -806,7 +806,7 @@ public class Case { * * @throws CaseActionException throw if could not create the case dir */ - static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { + public static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException { File caseDirF = new File(caseDir); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java index 169c3d0f00..1df465e02a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskDSProcessor.java @@ -54,6 +54,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData */ private String deviceId; private String drivePath; + private int sectorSize; private String timeZone; private ImageWriterSettings imageWriterSettings; private boolean ignoreFatOrphanFiles; @@ -137,6 +138,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData if (!setDataSourceOptionsCalled) { deviceId = UUID.randomUUID().toString(); drivePath = configPanel.getContentPaths(); + sectorSize = configPanel.getSectorSize(); timeZone = configPanel.getTimeZone(); ignoreFatOrphanFiles = configPanel.getNoFatOrphans(); if (configPanel.getImageWriterEnabled()) { @@ -145,7 +147,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData imageWriterSettings = null; } } - addDiskTask = new AddImageTask(deviceId, drivePath, 0, timeZone, ignoreFatOrphanFiles, imageWriterSettings, progressMonitor, callback); + addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, imageWriterSettings, progressMonitor, callback); new Thread(addDiskTask).start(); } @@ -171,7 +173,33 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData * @param callback Callback to call when processing is done. */ public void run(String deviceId, String drivePath, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { - addDiskTask = new AddImageTask(deviceId, drivePath, 0, timeZone, ignoreFatOrphanFiles, imageWriterSettings, progressMonitor, callback); + run(deviceId, drivePath, 0, timeZone, ignoreFatOrphanFiles, progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the given settings instead of those provided by the + * selection and configuration panel. Returns as soon as the background task + * is started and uses the callback object to signal task completion and + * return results. + * + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is + * intended to be unique across multiple cases + * (e.g., a UUID). + * @param drivePath Path to the local drive. + * @param sectorSize The sector size (use '0' for autodetect). + * @param timeZone The time zone to use when processing dates + * and times for the image, obtained from + * java.util.TimeZone.getID. + * @param ignoreFatOrphanFiles Whether to parse orphans if the image has a + * FAT filesystem. + * @param progressMonitor Progress monitor for reporting progress + * during processing. + * @param callback Callback to call when processing is done. + */ + private void run(String deviceId, String drivePath, int sectorSize, String timeZone, boolean ignoreFatOrphanFiles, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + addDiskTask = new AddImageTask(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, imageWriterSettings, progressMonitor, callback); new Thread(addDiskTask).start(); } @@ -227,10 +255,11 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData public void process(String deviceId, Path dataSourcePath, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callBack) throws AutoIngestDataSourceProcessorException { this.deviceId = deviceId; this.drivePath = dataSourcePath.toString(); + this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); this.ignoreFatOrphanFiles = false; setDataSourceOptionsCalled = true; - run(deviceId, drivePath, timeZone, ignoreFatOrphanFiles, progressMonitor, callBack); + run(deviceId, drivePath, sectorSize, timeZone, ignoreFatOrphanFiles, progressMonitor, callBack); } /** @@ -250,6 +279,7 @@ public class LocalDiskDSProcessor implements DataSourceProcessor, AutoIngestData public void setDataSourceOptions(String drivePath, String timeZone, boolean ignoreFatOrphanFiles) { this.deviceId = UUID.randomUUID().toString(); this.drivePath = drivePath; + this.sectorSize = 0; this.timeZone = Calendar.getInstance().getTimeZone().getID(); this.ignoreFatOrphanFiles = ignoreFatOrphanFiles; setDataSourceOptionsCalled = true; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form index 1a6f40693a..b2f822bac9 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.form @@ -27,55 +27,52 @@ + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - @@ -85,27 +82,32 @@ - + - + - + + + + + + - - - + + + - + @@ -113,7 +115,7 @@ - + @@ -276,15 +278,32 @@ - + - + - + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java index 7da98049ab..0514159ed4 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/LocalDiskPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,8 +42,6 @@ import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; -@NbBundle.Messages({"LocalDiskPanel.refreshTablebutton.text=Refresh Local Disks" -}) /** * ImageTypePanel for adding a local disk or partition such as PhysicalDrive0 or * C:. @@ -51,6 +49,7 @@ import org.sleuthkit.autopsy.imagewriter.ImageWriterSettings; final class LocalDiskPanel extends JPanel { private static final Logger logger = Logger.getLogger(LocalDiskPanel.class.getName()); + private static final String[] SECTOR_SIZE_CHOICES = {"Auto Detect", "512", "1024", "2048", "4096"}; private static LocalDiskPanel instance; private static final long serialVersionUID = 1L; private List disks; @@ -68,6 +67,7 @@ final class LocalDiskPanel extends JPanel { initComponents(); customInit(); createTimeZoneList(); + createSectorSizeList(); refreshTable(); diskTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override @@ -115,7 +115,7 @@ final class LocalDiskPanel extends JPanel { diskTable.setEnabled(false); imageWriterErrorLabel.setVisible(false); imageWriterErrorLabel.setText(""); - if(! PlatformUtil.isWindowsOS()){ + if (!PlatformUtil.isWindowsOS()) { copyImageCheckbox.setSelected(false); copyImageCheckbox.setEnabled(false); } @@ -147,7 +147,9 @@ final class LocalDiskPanel extends JPanel { jLabel1 = new javax.swing.JLabel(); imageWriterErrorLabel = new javax.swing.JLabel(); changeDatabasePathCheckbox = new javax.swing.JCheckBox(); - refreshTablebutton = new javax.swing.JButton(); + refreshTableButton = new javax.swing.JButton(); + sectorSizeLabel = new javax.swing.JLabel(); + sectorSizeComboBox = new javax.swing.JComboBox<>(); setMinimumSize(new java.awt.Dimension(0, 420)); setPreferredSize(new java.awt.Dimension(485, 410)); @@ -205,13 +207,15 @@ final class LocalDiskPanel extends JPanel { org.openide.awt.Mnemonics.setLocalizedText(changeDatabasePathCheckbox, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.changeDatabasePathCheckbox.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(refreshTablebutton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.refreshTablebutton.text")); // NOI18N - refreshTablebutton.addActionListener(new java.awt.event.ActionListener() { + org.openide.awt.Mnemonics.setLocalizedText(refreshTableButton, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.refreshTableButton.text")); // NOI18N + refreshTableButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { - refreshTablebuttonActionPerformed(evt); + refreshTableButtonActionPerformed(evt); } }); + org.openide.awt.Mnemonics.setLocalizedText(sectorSizeLabel, org.openide.util.NbBundle.getMessage(LocalDiskPanel.class, "LocalDiskPanel.sectorSizeLabel.text")); // NOI18N + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( @@ -219,66 +223,73 @@ final class LocalDiskPanel extends JPanel { .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(copyImageCheckbox) - .addComponent(descLabel)) - .addGroup(layout.createSequentialGroup() - .addGap(21, 21, 21) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addGroup(layout.createSequentialGroup() - .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 342, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(browseButton, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(imageWriterErrorLabel) - .addComponent(jLabel1) - .addComponent(changeDatabasePathCheckbox)) - .addGap(0, 0, Short.MAX_VALUE)))) - .addComponent(noFatOrphansCheckbox)) - .addGap(0, 0, Short.MAX_VALUE)) - .addGroup(layout.createSequentialGroup() - .addComponent(timeZoneLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 216, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(timeZoneComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(diskLabel) + .addGap(0, 0, Short.MAX_VALUE)) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() .addGap(0, 0, Short.MAX_VALUE) - .addComponent(refreshTablebutton)) + .addComponent(refreshTableButton, javax.swing.GroupLayout.PREFERRED_SIZE, 129, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.Alignment.TRAILING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(errorLabel) - .addComponent(diskLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 146, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(copyImageCheckbox) + .addComponent(errorLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(sectorSizeLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 85, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 342, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseButton, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(noFatOrphansCheckbox) + .addGap(0, 0, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(timeZoneComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGap(21, 21, 21) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(jLabel1, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(changeDatabasePathCheckbox, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(imageWriterErrorLabel, javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(descLabel, javax.swing.GroupLayout.Alignment.LEADING)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(diskLabel) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(1, 1, 1) .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(refreshTablebutton) + .addComponent(refreshTableButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(timeZoneLabel) .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(noFatOrphansCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(descLabel) + .addGap(10, 10, 10) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(sectorSizeLabel) + .addComponent(sectorSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(copyImageCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(browseButton) - .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(changeDatabasePathCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel1) @@ -286,7 +297,7 @@ final class LocalDiskPanel extends JPanel { .addComponent(imageWriterErrorLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(errorLabel) - .addContainerGap(48, Short.MAX_VALUE)) + .addContainerGap(43, Short.MAX_VALUE)) ); }// //GEN-END:initComponents @@ -317,9 +328,9 @@ final class LocalDiskPanel extends JPanel { fireUpdateEvent(); }//GEN-LAST:event_browseButtonActionPerformed - private void refreshTablebuttonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshTablebuttonActionPerformed + private void refreshTableButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_refreshTableButtonActionPerformed refreshTable(); - }//GEN-LAST:event_refreshTablebuttonActionPerformed + }//GEN-LAST:event_refreshTableButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton browseButton; @@ -334,7 +345,9 @@ final class LocalDiskPanel extends JPanel { private javax.swing.JScrollPane jScrollPane1; private javax.swing.JCheckBox noFatOrphansCheckbox; private javax.swing.JTextField pathTextField; - private javax.swing.JButton refreshTablebutton; + private javax.swing.JButton refreshTableButton; + private javax.swing.JComboBox sectorSizeComboBox; + private javax.swing.JLabel sectorSizeLabel; private javax.swing.JComboBox timeZoneComboBox; private javax.swing.JLabel timeZoneLabel; // End of variables declaration//GEN-END:variables @@ -365,6 +378,21 @@ final class LocalDiskPanel extends JPanel { } } + /** + * Get the sector size. + * + * @return 0 if autodetect; otherwise the value selected. + */ + int getSectorSize() { + int sectorSizeSelectionIndex = sectorSizeComboBox.getSelectedIndex(); + + if (sectorSizeSelectionIndex == 0) { + return 0; + } + + return Integer.valueOf((String) sectorSizeComboBox.getSelectedItem()); + } + String getTimeZone() { String tz = timeZoneComboBox.getSelectedItem().toString(); return tz.substring(tz.indexOf(")") + 2).trim(); @@ -464,8 +492,8 @@ final class LocalDiskPanel extends JPanel { } /** - * Creates the drop down list for the time zones and then makes the local - * machine time zone to be selected. + * Creates the drop down list for the time zones and defaults the selection + * to the local machine time zone. */ public void createTimeZoneList() { // load and add all timezone @@ -500,6 +528,17 @@ final class LocalDiskPanel extends JPanel { } + /** + * Creates the drop down list for the sector size and defaults the selection + * to "Auto Detect". + */ + private void createSectorSizeList() { + for (String choice : SECTOR_SIZE_CHOICES) { + sectorSizeComboBox.addItem(choice); + } + sectorSizeComboBox.setSelectedIndex(0); + } + /** * Table model for displaing information from LocalDisk Objects in a table. */ diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index c1c766d139..c9fb9a7003 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -97,6 +97,8 @@ import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestJobStartResult; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestModuleError; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleException; +import org.sleuthkit.autopsy.keywordsearch.Server; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.SleuthkitCase; @@ -2250,6 +2252,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen Case.openAsCurrentCase(metadataFilePath.toString()); } else { caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); + + // Create the case directory now in case it is needed by selectSolrServerForCase + Case.createCaseDirectory(caseDirectoryPath.toString(), CaseType.MULTI_USER_CASE); + + // If a list of servers exists, choose one to use for this case + Server.selectSolrServerForCase(rootOutputDirectory, caseDirectoryPath); + CaseDetails caseDetails = new CaseDetails(caseName); Case.createAsCurrentCase(CaseType.MULTI_USER_CASE, caseDirectoryPath.toString(), caseDetails); /* @@ -2264,6 +2273,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen SYS_LOGGER.log(Level.INFO, "Opened case {0} for {1}", new Object[]{caseForJob.getName(), manifest.getFilePath()}); return caseForJob; + } catch (KeywordSearchModuleException ex) { + throw new CaseManagementException(String.format("Error creating solr settings file for case %s for %s", caseName, manifest.getFilePath()), ex); } catch (CaseActionException ex) { throw new CaseManagementException(String.format("Error creating or opening case %s for %s", caseName, manifest.getFilePath()), ex); } catch (IllegalStateException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java index a767e71cee..81ef0e614a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchIngestModule.java @@ -184,15 +184,16 @@ public final class KeywordSearchIngestModule implements FileIngestModule { if (Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) { // for multi-user cases need to verify connection to remore SOLR server KeywordSearchService kwsService = new SolrSearchService(); + Server.IndexingServerProperties properties = Server.getMultiUserServerProperties(Case.getCurrentCase().getCaseDirectory()); int port; try { - port = Integer.parseInt(UserPreferences.getIndexingServerPort()); + port = Integer.parseInt(properties.getPort()); } catch (NumberFormatException ex) { // if there is an error parsing the port number throw new IngestModuleException(Bundle.KeywordSearchIngestModule_init_badInitMsg() + " " + Bundle.SolrConnectionCheck_Port(), ex); } try { - kwsService.tryConnect(UserPreferences.getIndexingServerHost(), port); + kwsService.tryConnect(properties.getHost(), port); } catch (KeywordSearchServiceException ex) { throw new IngestModuleException(Bundle.KeywordSearchIngestModule_init_badInitMsg(), ex); } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java index 0e9b804757..23380b15ae 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Server.java @@ -39,7 +39,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Iterator; import java.util.List; +import java.util.Random; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Level; import javax.swing.AbstractAction; @@ -63,6 +65,7 @@ import org.openide.modules.Places; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.ModuleSettings; @@ -721,16 +724,15 @@ public class Server { */ @NbBundle.Messages({ "# {0} - core name", "Server.deleteCore.exception.msg=Failed to delete Solr core {0}",}) - void deleteCore(String coreName, Case.CaseType caseType) throws KeywordSearchServiceException { + void deleteCore(String coreName, CaseMetadata metadata) throws KeywordSearchServiceException { try { HttpSolrServer solrServer; - if (caseType == CaseType.SINGLE_USER_CASE) { + if (metadata.getCaseType() == CaseType.SINGLE_USER_CASE) { Integer localSolrServerPort = Integer.decode(ModuleSettings.getConfigSetting(PROPERTIES_FILE, PROPERTIES_CURRENT_SERVER_PORT)); solrServer = new HttpSolrServer("http://localhost:" + localSolrServerPort + "/solr"); //NON-NLS } else { - String host = UserPreferences.getIndexingServerHost(); - String port = UserPreferences.getIndexingServerPort(); - solrServer = new HttpSolrServer("http://" + host + ":" + port + "/solr"); //NON-NLS + IndexingServerProperties properties = getMultiUserServerProperties(metadata.getCaseDirectory()); + solrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } connectToSolrServer(solrServer); CoreAdminResponse response = CoreAdminRequest.getStatus(coreName, solrServer); @@ -768,9 +770,8 @@ public class Server { if (theCase.getCaseType() == CaseType.SINGLE_USER_CASE) { currentSolrServer = this.localSolrServer; } else { - String host = UserPreferences.getIndexingServerHost(); - String port = UserPreferences.getIndexingServerPort(); - currentSolrServer = new HttpSolrServer("http://" + host + ":" + port + "/solr"); //NON-NLS + IndexingServerProperties properties = getMultiUserServerProperties(theCase.getCaseDirectory()); + currentSolrServer = new HttpSolrServer("http://" + properties.getHost() + ":" + properties.getPort() + "/solr"); //NON-NLS } connectToSolrServer(currentSolrServer); @@ -829,6 +830,139 @@ public class Server { throw new KeywordSearchModuleException(NbBundle.getMessage(this.getClass(), "Server.openCore.exception.cantOpen.msg"), ex); } } + + /** + * Get the host and port for a multiuser case. + * If the file solrserver.txt exists, then use the values from that file. + * Otherwise use the settings from the properties file. + * + * @param caseDirectory Current case directory + * @return IndexingServerProperties containing the solr host/port for this case + */ + public static IndexingServerProperties getMultiUserServerProperties(String caseDirectory) { + + Path serverFilePath = Paths.get(caseDirectory, "solrserver.txt"); + if(serverFilePath.toFile().exists()){ + try{ + List lines = Files.readAllLines(serverFilePath); + if(lines.isEmpty()) { + logger.log(Level.SEVERE, "solrserver.txt file does not contain any data"); + } else if (! lines.get(0).contains(",")) { + logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); + } else { + String[] parts = lines.get(0).split(","); + if(parts.length != 2) { + logger.log(Level.SEVERE, "solrserver.txt file is corrupt - could not read host/port from " + lines.get(0)); + } else { + return new IndexingServerProperties(parts[0], parts[1]); + } + } + } catch (IOException ex) { + logger.log(Level.SEVERE, "solrserver.txt file could not be read", ex); + } + } + + // Default back to the user preferences if the solrserver.txt file was not found or if an error occurred + String host = UserPreferences.getIndexingServerHost(); + String port = UserPreferences.getIndexingServerPort(); + return new IndexingServerProperties(host, port); + } + + /** + * Pick a solr server to use for this case and record it in the case directory. + * Looks for a file named "solrServerList.txt" in the root output directory - + * if this does not exist then no server is recorded. + * + * Format of solrServerList.txt: + * , + * Ex: 10.1.2.34,8983 + * + * @param rootOutputDirectory + * @param caseDirectoryPath + * @throws KeywordSearchModuleException + */ + public static void selectSolrServerForCase(Path rootOutputDirectory, Path caseDirectoryPath) throws KeywordSearchModuleException { + // Look for the solr server list file + String serverListName = "solrServerList.txt"; + Path serverListPath = Paths.get(rootOutputDirectory.toString(), serverListName); + if(serverListPath.toFile().exists()){ + + // Read the list of solr servers + List lines; + try{ + lines = Files.readAllLines(serverListPath); + } catch (IOException ex){ + throw new KeywordSearchModuleException(serverListName + " could not be read", ex); + } + + // Remove any lines that don't contain a comma (these are likely just whitespace) + for (Iterator iterator = lines.iterator(); iterator.hasNext();) { + String line = iterator.next(); + if (! line.contains(",")) { + // Remove the current element from the iterator and the list. + iterator.remove(); + } + } + if(lines.isEmpty()) { + throw new KeywordSearchModuleException(serverListName + " had no valid server information"); + } + + // Choose which server to use + int rnd = new Random().nextInt(lines.size()); + String[] parts = lines.get(rnd).split(","); + if(parts.length != 2) { + throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); + } + + // Split it up just to do a sanity check on the data + String host = parts[0]; + String port = parts[1]; + if(host.isEmpty() || port.isEmpty()) { + throw new KeywordSearchModuleException("Invalid server data: " + lines.get(rnd)); + } + + // Write the server data to a file + Path serverFile = Paths.get(caseDirectoryPath.toString(), "solrserver.txt"); + try { + caseDirectoryPath.toFile().mkdirs(); + if (! caseDirectoryPath.toFile().exists()) { + throw new KeywordSearchModuleException("Case directory " + caseDirectoryPath.toString() + " does not exist"); + } + Files.write(serverFile, lines.get(rnd).getBytes()); + } catch (IOException ex){ + throw new KeywordSearchModuleException(serverFile.toString() + " could not be written", ex); + } + } + } + + /** + * Helper class to store the current server properties + */ + public static class IndexingServerProperties { + private final String host; + private final String port; + + IndexingServerProperties (String host, String port) { + this.host = host; + this.port = port; + } + + /** + * Get the host + * @return host + */ + public String getHost() { + return host; + } + + /** + * Get the port + * @return port + */ + public String getPort() { + return port; + } + } /** * Commits current core if it exists diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 52e5da17f1..3fea1a51ee 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -203,7 +203,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { * Unload/delete the core on the server and then delete the text * index files. */ - KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata.getCaseType()); + KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata); if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) { throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath())); }