Autopsy  4.19.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-2020 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 com.google.common.eventbus.Subscribe;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.Reader;
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 org.apache.solr.client.solrj.SolrServerException;
31 import org.openide.util.NbBundle;
32 import org.openide.util.lookup.ServiceProvider;
33 import org.openide.util.lookup.ServiceProviders;
46 import org.sleuthkit.datamodel.Blackboard;
47 import org.sleuthkit.datamodel.BlackboardArtifact;
48 import org.sleuthkit.datamodel.Content;
49 import org.sleuthkit.datamodel.TskCoreException;
50 
55 @ServiceProviders(value = {
56  @ServiceProvider(service = KeywordSearchService.class),
57  @ServiceProvider(service = AutopsyService.class)
58 })
60 
61  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
62  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
63  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
64  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
65 
81  @Override
82  public void index(Content content) throws TskCoreException {
83  /*
84  * TODO (JIRA-1099): The following code has some issues that need to be
85  * resolved. For artifacts, it is assumed that the posting of artifacts
86  * is only occuring during an ingest job with an enabled keyword search
87  * ingest module handling index commits; it also assumes that the
88  * artifacts are only posted by modules in the either the file level
89  * ingest pipeline or the first stage data source level ingest pipeline,
90  * so that the artifacts will be searched during a periodic or final
91  * keyword search. It also assumes that the only other type of Content
92  * for which this API will be called are Reports generated at a time
93  * when doing a commit is required and desirable, i.e., in a context
94  * other than an ingest job.
95  */
96  if (content == null) {
97  return;
98  }
99  final Ingester ingester = Ingester.getDefault();
100  if (content instanceof BlackboardArtifact) {
101  BlackboardArtifact artifact = (BlackboardArtifact) content;
102  if (artifact.getArtifactID() > 0) {
103  /*
104  * Artifact indexing is only supported for artifacts that use
105  * negative artifact ids to avoid overlapping with the object
106  * ids of other types of Content.
107  */
108  return;
109  }
110  try {
111  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor(content, null);
112  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
113  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
114  ingester.indexMetaDataOnly(artifact, sourceName);
115  ingester.indexText(blackboardExtractedTextReader, artifact.getArtifactID(), sourceName, content, null);
116  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
117  throw new TskCoreException("Error indexing artifact", ex);
118  }
119  } else {
120  try {
121  TextExtractor contentExtractor = TextExtractorFactory.getExtractor(content, null);
122  Reader contentExtractedTextReader = contentExtractor.getReader();
123  ingester.indexText(contentExtractedTextReader, content.getId(), content.getName(), content, null);
124  } catch (TextExtractorFactory.NoTextExtractorFound | Ingester.IngesterException | TextExtractor.InitReaderException ex) {
125  try {
126  // Try the StringsTextExtractor if Tika extractions fails.
127  TextExtractor stringsExtractor = TextExtractorFactory.getStringsExtractor(content, null);
128  Reader stringsExtractedTextReader = stringsExtractor.getReader();
129  ingester.indexStrings(stringsExtractedTextReader, content.getId(), content.getName(), content, null);
130  } catch (Ingester.IngesterException | TextExtractor.InitReaderException ex1) {
131  throw new TskCoreException("Error indexing content", ex1);
132  }
133  }
134  // only do a Solr commit if ingest is not running. If ingest is running, the changes will
135  // be committed via a periodic commit or via final commit after the ingest job has finished.
137  ingester.commit();
138  }
139  }
140  }
141 
150  @Override
151  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
152  if (host == null || host.isEmpty()) {
153  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
154  }
155  try {
156  KeywordSearch.getServer().connectToSolrServer(host, Integer.toString(port));
157  } catch (SolrServerException ex) {
158  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
159  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS*/
160  } catch (IOException ex) {
161  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
162  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
163  String message = ex.getCause().getMessage().toLowerCase();
164  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
165  try {
166  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
167  // if we can reach the host, then it's probably port problem
168  result = Bundle.SolrConnectionCheck_Port();
169  } else {
170  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
171  }
172  } catch (IOException | MissingResourceException any) {
173  // it may be anything
174  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
175  }
176  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
177  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
178  }
179  throw new KeywordSearchServiceException(result);
180  } catch (NumberFormatException ex) {
181  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
182  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
183  } catch (IllegalArgumentException ex) {
184  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
185  throw new KeywordSearchServiceException(ex.getMessage());
186  }
187  }
188 
197  @Override
198  public void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException {
199 
200  try {
201  Server ddsServer = KeywordSearch.getServer();
202  ddsServer.deleteDataSource(dataSourceId);
203  } catch (IOException | KeywordSearchModuleException | NoOpenCoreException | SolrServerException ex) {
204  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
205  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
206  }
207  }
208 
214  @NbBundle.Messages({
215  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
216  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
217  "# {0} - collection name", "SolrSearchService.exceptionMessage.unableToDeleteCollection=Unable to delete collection {0}",
218  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
219  })
220  @Override
222  String caseDirectory = metadata.getCaseDirectory();
223  IndexMetadata indexMetadata;
224  try {
225  indexMetadata = new IndexMetadata(caseDirectory);
226  } catch (IndexMetadata.TextIndexMetadataException ex) {
227  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
228  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
229  }
230 
231  if (indexMetadata.getIndexes().isEmpty()) {
232  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class,
233  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
234  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class,
235  "SolrSearchService.exceptionMessage.noCurrentSolrCore"));
236  }
237 
238  // delete index(es) for this case
239  for (Index index : indexMetadata.getIndexes()) {
240  try {
241  // Unload/delete the collection on the server and then delete the text index files.
242  KeywordSearch.getServer().deleteCollection(index.getIndexName(), metadata);
243  } catch (KeywordSearchModuleException ex) {
244  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_unableToDeleteCollection(index.getIndexName()), ex);
245  }
246  File indexDir = new File(index.getIndexPath()).getParentFile();
247  if (indexDir.exists()) {
248  if (!FileUtil.deleteDir(indexDir)) {
249  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
250  }
251  }
252  }
253  }
254 
255  @Override
256  public String getServiceName() {
257  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
258  }
259 
268  @Override
269  @NbBundle.Messages({
270  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
271  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
272  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
273  "SolrSearch.creatingNewIndex.msg=Creating new text index",
274  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
275  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
276  "SolrSearch.openCore.msg=Opening text index. For large cases this may take several minutes.",
277  "# {0} - futureVersion", "# {1} - currentVersion",
278  "SolrSearch.futureIndexVersion.msg=The text index for the case is for Solr {0}. This version of Autopsy is compatible with Solr {1}.",
279  "SolrSearch.unableToFindIndex.msg=Unable to find index that can be used for this case",
280  "SolrSearch.complete.msg=Text index successfully opened"})
282  if (context.cancelRequested()) {
283  return;
284  }
285 
286  ProgressIndicator progress = context.getProgressIndicator();
287  int totalNumProgressUnits = 7;
288  int progressUnitsCompleted = 0;
289 
290  String caseDirPath = context.getCase().getCaseDirectory();
291  Case theCase = context.getCase();
292  List<Index> indexes = new ArrayList<>();
293  progress.progress(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
294  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
295  try {
296  // metadata file exists, get list of existing Solr cores for this case
297  progressUnitsCompleted++;
298  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
299  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
300  indexes = indexMetadata.getIndexes();
301  } catch (IndexMetadata.TextIndexMetadataException ex) {
302  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
303  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
304  }
305  }
306 
307  if (context.cancelRequested()) {
308  return;
309  }
310 
311  // check if we found any existing indexes
312  Index currentVersionIndex = null;
313  if (indexes.isEmpty()) {
314  // new case that doesn't have an existing index. create new index folder
315  progressUnitsCompleted++;
316  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
317  currentVersionIndex = IndexFinder.createLatestVersionIndex(theCase);
318  // add current index to the list of indexes that exist for this case
319  indexes.add(currentVersionIndex);
320  } else {
321  // check if one of the existing indexes is for latest Solr version and schema
322  progressUnitsCompleted++;
323  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
324  currentVersionIndex = IndexFinder.findLatestVersionIndex(indexes);
325  if (currentVersionIndex == null) {
326  // found existing index(es) but none were for latest Solr version and schema version
327  progressUnitsCompleted++;
328  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
329  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
330  if (indexToUse == null) {
331  // unable to find index that can be used. check if the available index is for a "future" version of Solr,
332  // i.e. the user is using an "old/legacy" version of Autopsy to open cases created by later versions of Autopsy.
333  String futureIndexVersion = IndexFinder.isFutureIndexPresent(indexes);
334  if (!futureIndexVersion.isEmpty()) {
335  throw new AutopsyServiceException(Bundle.SolrSearch_futureIndexVersion_msg(futureIndexVersion, IndexFinder.getCurrentSolrVersion()));
336  }
337  throw new AutopsyServiceException(Bundle.SolrSearch_unableToFindIndex_msg());
338  }
339 
340  if (context.cancelRequested()) {
341  return;
342  }
343 
344  // check if schema is compatible
345  if (!indexToUse.isCompatible(IndexFinder.getCurrentSchemaVersion())) {
346  String msg = "Text index schema version " + indexToUse.getSchemaVersion() + " is not compatible with current schema";
347  logger.log(Level.WARNING, msg);
348  throw new AutopsyServiceException(msg);
349  }
350  // proceed with case open
351  currentVersionIndex = indexToUse;
352  }
353  }
354 
355  try {
356  // update text index metadata file
357  if (!indexes.isEmpty()) {
358  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
359  }
360  } catch (IndexMetadata.TextIndexMetadataException ex) {
361  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
362  }
363 
364  // open core
365  try {
366  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
367  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
368  } catch (KeywordSearchModuleException ex) {
369  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
370  }
371  if (context.cancelRequested()) {
372  return;
373  }
374 
375  theCase.getSleuthkitCase().registerForEvents(this);
376 
377  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
378  }
379 
388  @Override
390  /*
391  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
392  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
393  * in less than roughly two seconds. This stuff should be reworked using
394  * an ExecutorService and tasks with Futures.
395  */
396  AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
397  try {
398  Thread.sleep(2000);
399  } catch (InterruptedException ex) {
400  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
401  }
402 
403  try {
404  KeywordSearch.getServer().closeCore();
405  } catch (KeywordSearchModuleException ex) {
406  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
407  }
408 
409  if (context.getCase().getSleuthkitCase() != null) {
410  context.getCase().getSleuthkitCase().unregisterForEvents(this);
411  }
412  }
413 
419  @NbBundle.Messages("SolrSearchService.indexingError=Unable to index blackboard artifact.")
420  @Subscribe
421  void handleNewArtifacts(Blackboard.ArtifactsPostedEvent event) {
422  for (BlackboardArtifact artifact : event.getArtifacts()) {
423  if ((artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) && // don't index KWH bc it's based on existing indexed text
424  (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT.getTypeID())){ //don't index AO bc it has only an artifact ID - no useful text
425  try {
426  index(artifact);
427  } catch (TskCoreException ex) {
428  //TODO: is this the right error handling?
429  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
430  MessageNotifyUtil.Notify.error(Bundle.SolrSearchService_indexingError(), artifact.getDisplayName());
431  }
432  }
433  }
434  }
435 
445  @Deprecated
446  @Override
447  public void indexArtifact(BlackboardArtifact artifact) throws TskCoreException {
448  if (artifact == null) {
449  return;
450  }
451 
452  // We only support artifact indexing for Autopsy versions that use
453  // the negative range for artifact ids.
454  if (artifact.getArtifactID() > 0) {
455  return;
456  }
457  final Ingester ingester = Ingester.getDefault();
458 
459  try {
460  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
461  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor((Content) artifact, null);
462  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
463  ingester.indexMetaDataOnly(artifact, sourceName);
464  ingester.indexText(blackboardExtractedTextReader, artifact.getId(), sourceName, artifact, null);
465  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
466  throw new TskCoreException(ex.getCause().getMessage(), ex);
467  }
468  }
469 }
static synchronized IngestManager getInstance()
static TextExtractor getStringsExtractor(Content content, Lookup context)
static TextExtractor getExtractor(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-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.