Autopsy  4.9.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
SolrSearchService.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2015-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.keywordsearch;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.Reader;
24 import java.lang.reflect.InvocationTargetException;
25 import java.net.InetAddress;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.MissingResourceException;
29 import java.util.logging.Level;
30 import javax.swing.JDialog;
31 import javax.swing.JOptionPane;
32 import javax.swing.SwingUtilities;
33 import org.apache.commons.lang.math.NumberUtils;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.solr.client.solrj.SolrServerException;
36 import org.apache.solr.client.solrj.impl.HttpSolrServer;
37 import org.openide.util.NbBundle;
38 import org.openide.util.lookup.ServiceProvider;
39 import org.openide.util.lookup.ServiceProviders;
50 import org.sleuthkit.datamodel.BlackboardArtifact;
51 import org.sleuthkit.datamodel.Content;
52 import org.sleuthkit.datamodel.TskCoreException;
53 
58 @ServiceProviders(value = {
59  @ServiceProvider(service = KeywordSearchService.class)
60  ,
61  @ServiceProvider(service = AutopsyService.class)}
62 )
64 
65  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
66  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
67  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
68  private static final int LARGE_INDEX_SIZE_GB = 50;
69  private static final int GIANT_INDEX_SIZE_GB = 500;
70  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
71 
87  @Override
88  public void index(Content content) throws TskCoreException {
89  /*
90  * TODO (JIRA-1099): The following code has some issues that need to be
91  * resolved. For artifacts, it is assumed that the posting of artifacts
92  * is only occuring during an ingest job with an enabled keyword search
93  * ingest module handling index commits; it also assumes that the
94  * artifacts are only posted by modules in the either the file level
95  * ingest pipeline or the first stage data source level ingest pipeline,
96  * so that the artifacts will be searched during a periodic or final
97  * keyword search. It also assumes that the only other type of Content
98  * for which this API will be called are Reports generated at a time
99  * when doing a commit is required and desirable, i.e., in a context
100  * other than an ingest job.
101  */
102  if (content == null) {
103  return;
104  }
105  final Ingester ingester = Ingester.getDefault();
106  if (content instanceof BlackboardArtifact) {
107  BlackboardArtifact artifact = (BlackboardArtifact) content;
108  if (artifact.getArtifactID() > 0) {
109  /*
110  * Artifact indexing is only supported for artifacts that use
111  * negative artifact ids to avoid overlapping with the object
112  * ids of other types of Content.
113  */
114  return;
115  }
116  try {
117  Reader blackboardReader = TextReaders.getReader(content, null);
118  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
119  ingester.indexMetaDataOnly(artifact, sourceName);
120  ingester.indexText(blackboardReader, artifact.getArtifactID(), sourceName, content, null);
121  } catch (Ingester.IngesterException | TextReaders.NoTextReaderFound ex) {
122  throw new TskCoreException(ex.getCause().getMessage(), ex);
123  }
124  } else {
125  try {
126  Reader contentReader = TextReaders.getReader(content, null);
127  ingester.indexText(contentReader, content.getId(), content.getName(), content, null);
128  } catch (TextReaders.NoTextReaderFound | Ingester.IngesterException ex) {
129  try {
130  // Try the StringsTextExtractor if Tika extractions fails.
131  ingester.indexText(TextReaders.getStringsReader(content, null),content.getId(),content.getName(), content, null);
132  } catch (Ingester.IngesterException ex1) {
133  throw new TskCoreException(ex.getCause().getMessage(), ex1);
134  }
135  }
136  ingester.commit();
137  }
138  }
139 
148  @Override
149  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
150  HttpSolrServer solrServer = null;
151  if (host == null || host.isEmpty()) {
152  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
153  }
154  try {
155  solrServer = new HttpSolrServer("http://" + host + ":" + Integer.toString(port) + "/solr"); //NON-NLS
156  KeywordSearch.getServer().connectToSolrServer(solrServer);
157  } catch (SolrServerException ex) {
158  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS
159  } catch (IOException ex) {
160  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
161  String message = ex.getCause().getMessage().toLowerCase();
162  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
163  try {
164  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
165  // if we can reach the host, then it's probably port problem
166  result = Bundle.SolrConnectionCheck_Port();
167  } else {
168  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
169  }
170  } catch (IOException | MissingResourceException any) {
171  // it may be anything
172  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
173  }
174  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
175  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
176  }
177  throw new KeywordSearchServiceException(result);
178  } catch (NumberFormatException ex) {
179  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
180  } catch (IllegalArgumentException ex) {
181  throw new KeywordSearchServiceException(ex.getMessage());
182  } finally {
183  if (null != solrServer) {
184  solrServer.shutdown();
185  }
186  }
187  }
188 
194  @NbBundle.Messages({
195  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
196  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
197  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
198  })
199  @Override
201  String caseDirectory = metadata.getCaseDirectory();
202  IndexMetadata indexMetadata;
203  try {
204  indexMetadata = new IndexMetadata(caseDirectory);
205  } catch (IndexMetadata.TextIndexMetadataException ex) {
206  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
207  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
208  }
209  //find the index for the current version of solr (the one we are connected to) and delete its core using the index name
210  String currentSchema = IndexFinder.getCurrentSchemaVersion();
211  String currentSolr = IndexFinder.getCurrentSolrVersion();
212  for (Index index : indexMetadata.getIndexes()) {
213  if (index.getSolrVersion().equals(currentSolr) && index.getSchemaVersion().equals(currentSchema)) {
214  /*
215  * Unload/delete the core on the server and then delete the text
216  * index files.
217  */
218  KeywordSearch.getServer().deleteCore(index.getIndexName(), metadata);
219  if (!FileUtil.deleteDir(new File(index.getIndexPath()).getParentFile())) {
220  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
221  }
222  }
223  return; //only one core exists for each combination of solr and schema version
224  }
225 
226  //this code this code will only execute if an index for the current core was not found
227  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
228  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
229  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
230  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
231  }
232 
233  @Override
234  public void close() throws IOException {
235  }
236 
237  @Override
238  public String getServiceName() {
239  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
240  }
241 
250  @Override
251  @NbBundle.Messages({
252  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
253  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
254  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
255  "SolrSearch.creatingNewIndex.msg=Creating new text index",
256  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
257  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
258  "SolrSearch.openCore.msg=Opening text index",
259  "SolrSearch.openLargeCore.msg=Opening text index. This may take several minutes.",
260  "SolrSearch.openGiantCore.msg=Opening text index. Text index for this case is very large and may take long time to load.",
261  "SolrSearch.complete.msg=Text index successfully opened"})
263  if (context.cancelRequested()) {
264  return;
265  }
266 
267  ProgressIndicator progress = context.getProgressIndicator();
268  int totalNumProgressUnits = 7;
269  int progressUnitsCompleted = 0;
270 
271  String caseDirPath = context.getCase().getCaseDirectory();
272  Case theCase = context.getCase();
273  List<Index> indexes = new ArrayList<>();
274  progress.start(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
275  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
276  try {
277  // metadata file exists, get list of existing Solr cores for this case
278  progressUnitsCompleted++;
279  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
280  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
281  indexes = indexMetadata.getIndexes();
282  } catch (IndexMetadata.TextIndexMetadataException ex) {
283  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
284  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
285  }
286  } else {
287  // metadata file doesn't exist.
288  // do case subdirectory search to look for Solr 4 Schema 1.8 indexes
289  progressUnitsCompleted++;
290  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
291  Index oldIndex = IndexFinder.findOldIndexDir(theCase);
292  if (oldIndex != null) {
293  // add index to the list of indexes that exist for this case
294  indexes.add(oldIndex);
295  }
296  }
297 
298  if (context.cancelRequested()) {
299  return;
300  }
301 
302  // check if we found any existing indexes
303  Index currentVersionIndex = null;
304  if (indexes.isEmpty()) {
305  // new case that doesn't have an existing index. create new index folder
306  progressUnitsCompleted++;
307  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
308  currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
309  // add current index to the list of indexes that exist for this case
310  indexes.add(currentVersionIndex);
311  } else {
312  // check if one of the existing indexes is for latest Solr version and schema
313  progressUnitsCompleted++;
314  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
315  currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
316  if (currentVersionIndex == null) {
317  // found existing index(es) but none were for latest Solr version and schema version
318  progressUnitsCompleted++;
319  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
320  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
321  if (indexToUse == null) {
322  // unable to find index that can be used
323  throw new AutopsyServiceException("Unable to find index that can be used for this case");
324  }
325 
326  if (context.cancelRequested()) {
327  return;
328  }
329 
330  double currentSolrVersion = NumberUtils.toDouble(IndexFinder.getCurrentSolrVersion());
331  double indexSolrVersion = NumberUtils.toDouble(indexToUse.getSolrVersion());
332  if (indexSolrVersion == currentSolrVersion) {
333  // latest Solr version but schema not compatible. index should be used in read-only mode
334  if (!indexToUse.isCompatible(IndexFinder.getCurrentSchemaVersion()) && RuntimeProperties.runningWithGUI()) {
335  // pop up a message box to indicate the read-only restrictions.
336  JOptionPane optionPane = new JOptionPane(
337  NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.msg"),
338  JOptionPane.WARNING_MESSAGE,
339  JOptionPane.DEFAULT_OPTION);
340  try {
341  SwingUtilities.invokeAndWait(() -> {
342  JDialog dialog = optionPane.createDialog(NbBundle.getMessage(this.getClass(), "SolrSearchService.IndexReadOnlyDialog.title"));
343  dialog.setVisible(true);
344  });
345  } catch (InterruptedException ex) {
346  // Cancelled
347  return;
348  } catch (InvocationTargetException ex) {
349  throw new AutopsyServiceException("Error displaying limited search features warning dialog", ex);
350  }
351  }
352  // proceed with case open
353  currentVersionIndex = indexToUse;
354  } else {
355  // index needs to be upgraded to latest supported version of Solr
356  throw new AutopsyServiceException("Unable to find index to use for Case open");
357  }
358  }
359  }
360 
361  try {
362  // update text index metadata file
363  if (!indexes.isEmpty()) {
364  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
365  }
366  } catch (IndexMetadata.TextIndexMetadataException ex) {
367  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
368  }
369 
370  // open core
371  try {
372  // check text index size to gauge estimated time to open/load the index
373  long indexSizeInBytes = FileUtils.sizeOfDirectory(new File(currentVersionIndex.getIndexPath()));
374  long sizeInGb = indexSizeInBytes / 1000000000;
375  if (sizeInGb < LARGE_INDEX_SIZE_GB) {
376  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
377  } else if (sizeInGb >= LARGE_INDEX_SIZE_GB && sizeInGb < GIANT_INDEX_SIZE_GB) {
378  progress.switchToIndeterminate(Bundle.SolrSearch_openLargeCore_msg());
379  } else {
380  progress.switchToIndeterminate(Bundle.SolrSearch_openGiantCore_msg());
381  }
382 
383  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
384  } catch (KeywordSearchModuleException ex) {
385  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
386  }
387 
388  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
389  }
390 
399  @Override
401  /*
402  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
403  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
404  * in less than roughly two seconds. This stuff should be reworked using
405  * an ExecutorService and tasks with Futures.
406  */
407  AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
408  try {
409  Thread.sleep(2000);
410  } catch (InterruptedException ex) {
411  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
412  }
413 
414  try {
415  KeywordSearch.getServer().closeCore();
416  } catch (KeywordSearchModuleException ex) {
417  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
418  }
419  }
420 
430  @Deprecated
431  @Override
432  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
433  if (artifact == null) {
434  return;
435  }
436 
437  // We only support artifact indexing for Autopsy versions that use
438  // the negative range for artifact ids.
439  if (artifact.getArtifactID() > 0) {
440  return;
441  }
442  final Ingester ingester = Ingester.getDefault();
443 
444  try {
445  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
446  Reader contentSpecificReader = TextReaders.getReader((Content) artifact, null);
447  ingester.indexMetaDataOnly(artifact, sourceName);
448  ingester.indexText(contentSpecificReader, artifact.getId(), sourceName, artifact, null);
449  } catch (Ingester.IngesterException | TextReaders.NoTextReaderFound ex) {
450  throw new TskCoreException(ex.getCause().getMessage(), ex);
451  }
452  }
453 
454 }
void start(String message, int totalWorkUnits)
static Reader getStringsReader(Content content, Lookup context)
static Reader getReader(Content content, Lookup context)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:47

Copyright © 2012-2018 Basis Technology. Generated on: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.