Autopsy  4.17.0
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;
45 import org.sleuthkit.datamodel.Blackboard;
46 import org.sleuthkit.datamodel.BlackboardArtifact;
47 import org.sleuthkit.datamodel.Content;
48 import org.sleuthkit.datamodel.TskCoreException;
49 
54 @ServiceProviders(value = {
55  @ServiceProvider(service = KeywordSearchService.class),
56  @ServiceProvider(service = AutopsyService.class)
57 })
59 
60  private static final String BAD_IP_ADDRESS_FORMAT = "ioexception occurred when talking to server"; //NON-NLS
61  private static final String SERVER_REFUSED_CONNECTION = "server refused connection"; //NON-NLS
62  private static final int IS_REACHABLE_TIMEOUT_MS = 1000;
63  private static final Logger logger = Logger.getLogger(SolrSearchService.class.getName());
64 
80  @Override
81  public void index(Content content) throws TskCoreException {
82  /*
83  * TODO (JIRA-1099): The following code has some issues that need to be
84  * resolved. For artifacts, it is assumed that the posting of artifacts
85  * is only occuring during an ingest job with an enabled keyword search
86  * ingest module handling index commits; it also assumes that the
87  * artifacts are only posted by modules in the either the file level
88  * ingest pipeline or the first stage data source level ingest pipeline,
89  * so that the artifacts will be searched during a periodic or final
90  * keyword search. It also assumes that the only other type of Content
91  * for which this API will be called are Reports generated at a time
92  * when doing a commit is required and desirable, i.e., in a context
93  * other than an ingest job.
94  */
95  if (content == null) {
96  return;
97  }
98  final Ingester ingester = Ingester.getDefault();
99  if (content instanceof BlackboardArtifact) {
100  BlackboardArtifact artifact = (BlackboardArtifact) content;
101  if (artifact.getArtifactID() > 0) {
102  /*
103  * Artifact indexing is only supported for artifacts that use
104  * negative artifact ids to avoid overlapping with the object
105  * ids of other types of Content.
106  */
107  return;
108  }
109  try {
110  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor(content, null);
111  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
112  String sourceName = artifact.getDisplayName() + "_" + artifact.getArtifactID();
113  ingester.indexMetaDataOnly(artifact, sourceName);
114  ingester.indexText(blackboardExtractedTextReader, artifact.getArtifactID(), sourceName, content, null);
115  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
116  throw new TskCoreException("Error indexing artifact", ex);
117  }
118  } else {
119  try {
120  TextExtractor contentExtractor = TextExtractorFactory.getExtractor(content, null);
121  Reader contentExtractedTextReader = contentExtractor.getReader();
122  ingester.indexText(contentExtractedTextReader, content.getId(), content.getName(), content, null);
123  } catch (TextExtractorFactory.NoTextExtractorFound | Ingester.IngesterException | TextExtractor.InitReaderException ex) {
124  try {
125  // Try the StringsTextExtractor if Tika extractions fails.
126  TextExtractor stringsExtractor = TextExtractorFactory.getStringsExtractor(content, null);
127  Reader stringsExtractedTextReader = stringsExtractor.getReader();
128  ingester.indexStrings(stringsExtractedTextReader, content.getId(), content.getName(), content, null);
129  } catch (Ingester.IngesterException | TextExtractor.InitReaderException ex1) {
130  throw new TskCoreException("Error indexing content", ex1);
131  }
132  }
133  ingester.commit();
134  }
135  }
136 
145  @Override
146  public void tryConnect(String host, int port) throws KeywordSearchServiceException {
147  if (host == null || host.isEmpty()) {
148  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.MissingHostname")); //NON-NLS
149  }
150  try {
151  KeywordSearch.getServer().connectToSolrServer(host, Integer.toString(port));
152  } catch (SolrServerException ex) {
153  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
154  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort")); //NON-NLS*/
155  } catch (IOException ex) {
156  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
157  String result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
158  String message = ex.getCause().getMessage().toLowerCase();
159  if (message.startsWith(SERVER_REFUSED_CONNECTION)) {
160  try {
161  if (InetAddress.getByName(host).isReachable(IS_REACHABLE_TIMEOUT_MS)) {
162  // if we can reach the host, then it's probably port problem
163  result = Bundle.SolrConnectionCheck_Port();
164  } else {
165  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
166  }
167  } catch (IOException | MissingResourceException any) {
168  // it may be anything
169  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.HostnameOrPort"); //NON-NLS
170  }
171  } else if (message.startsWith(BAD_IP_ADDRESS_FORMAT)) {
172  result = NbBundle.getMessage(SolrSearchService.class, "SolrConnectionCheck.Hostname"); //NON-NLS
173  }
174  throw new KeywordSearchServiceException(result);
175  } catch (NumberFormatException ex) {
176  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
177  throw new KeywordSearchServiceException(Bundle.SolrConnectionCheck_Port());
178  } catch (IllegalArgumentException ex) {
179  logger.log(Level.SEVERE, "Unable to connect to Solr server. Host: " + host + ", port: " + port, ex);
180  throw new KeywordSearchServiceException(ex.getMessage());
181  }
182  }
183 
192  @Override
193  public void deleteDataSource(Long dataSourceId) throws KeywordSearchServiceException {
194 
195  try {
196  Server ddsServer = KeywordSearch.getServer();
197  ddsServer.deleteDataSource(dataSourceId);
198  } catch (IOException | KeywordSearchModuleException | NoOpenCoreException | SolrServerException ex) {
199  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
200  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.DeleteDataSource.msg", dataSourceId), ex);
201  }
202  }
203 
209  @NbBundle.Messages({
210  "# {0} - case directory", "SolrSearchService.exceptionMessage.noIndexMetadata=Unable to create IndexMetaData from case directory: {0}",
211  "SolrSearchService.exceptionMessage.noCurrentSolrCore=IndexMetadata did not contain a current Solr core so could not delete the case",
212  "# {0} - collection name", "SolrSearchService.exceptionMessage.unableToDeleteCollection=Unable to delete collection {0}",
213  "# {0} - index folder path", "SolrSearchService.exceptionMessage.failedToDeleteIndexFiles=Failed to delete text index files at {0}"
214  })
215  @Override
217  String caseDirectory = metadata.getCaseDirectory();
218  IndexMetadata indexMetadata;
219  try {
220  indexMetadata = new IndexMetadata(caseDirectory);
221  } catch (IndexMetadata.TextIndexMetadataException ex) {
222  logger.log(Level.WARNING, NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
223  throw new KeywordSearchServiceException(NbBundle.getMessage(SolrSearchService.class, "SolrSearchService.exceptionMessage.noIndexMetadata", caseDirectory), ex);
224  }
225 
226  if (indexMetadata.getIndexes().isEmpty()) {
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  // delete index(es) for this case
234  for (Index index : indexMetadata.getIndexes()) {
235  try {
236  // Unload/delete the collection on the server and then delete the text index files.
237  KeywordSearch.getServer().deleteCollection(index.getIndexName(), metadata);
238  } catch (KeywordSearchModuleException ex) {
239  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_unableToDeleteCollection(index.getIndexName()), ex);
240  }
241  File indexDir = new File(index.getIndexPath()).getParentFile();
242  if (indexDir.exists()) {
243  if (!FileUtil.deleteDir(indexDir)) {
244  throw new KeywordSearchServiceException(Bundle.SolrSearchService_exceptionMessage_failedToDeleteIndexFiles(index.getIndexPath()));
245  }
246  }
247  }
248  }
249 
250  @Override
251  public String getServiceName() {
252  return NbBundle.getMessage(this.getClass(), "SolrSearchService.ServiceName");
253  }
254 
263  @Override
264  @NbBundle.Messages({
265  "SolrSearch.lookingForMetadata.msg=Looking for text index metadata file",
266  "SolrSearch.readingIndexes.msg=Reading text index metadata file",
267  "SolrSearch.findingIndexes.msg=Looking for existing text index directories",
268  "SolrSearch.creatingNewIndex.msg=Creating new text index",
269  "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version",
270  "SolrSearch.indentifyingIndex.msg=Identifying text index to use",
271  "SolrSearch.openCore.msg=Opening text index. For large cases this may take several minutes.",
272  "SolrSearch.complete.msg=Text index successfully opened"})
274  if (context.cancelRequested()) {
275  return;
276  }
277 
278  ProgressIndicator progress = context.getProgressIndicator();
279  int totalNumProgressUnits = 7;
280  int progressUnitsCompleted = 0;
281 
282  String caseDirPath = context.getCase().getCaseDirectory();
283  Case theCase = context.getCase();
284  List<Index> indexes = new ArrayList<>();
285  progress.progress(Bundle.SolrSearch_lookingForMetadata_msg(), totalNumProgressUnits);
286  if (IndexMetadata.isMetadataFilePresent(caseDirPath)) {
287  try {
288  // metadata file exists, get list of existing Solr cores for this case
289  progressUnitsCompleted++;
290  progress.progress(Bundle.SolrSearch_findingIndexes_msg(), progressUnitsCompleted);
291  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath);
292  indexes = indexMetadata.getIndexes();
293  } catch (IndexMetadata.TextIndexMetadataException ex) {
294  logger.log(Level.SEVERE, String.format("Unable to read text index metadata file"), ex);
295  throw new AutopsyServiceException("Unable to read text index metadata file", ex);
296  }
297  }
298 
299  if (context.cancelRequested()) {
300  return;
301  }
302 
303  // check if we found any existing indexes
304  Index currentVersionIndex = null;
305  if (indexes.isEmpty()) {
306  // new case that doesn't have an existing index. create new index folder
307  progressUnitsCompleted++;
308  progress.progress(Bundle.SolrSearch_creatingNewIndex_msg(), progressUnitsCompleted);
309  currentVersionIndex = IndexFinder.createLatestVersionIndexDir(theCase);
310  // add current index to the list of indexes that exist for this case
311  indexes.add(currentVersionIndex);
312  } else {
313  // check if one of the existing indexes is for latest Solr version and schema
314  progressUnitsCompleted++;
315  progress.progress(Bundle.SolrSearch_checkingForLatestIndex_msg(), progressUnitsCompleted);
316  currentVersionIndex = IndexFinder.findLatestVersionIndexDir(indexes);
317  if (currentVersionIndex == null) {
318  // found existing index(es) but none were for latest Solr version and schema version
319  progressUnitsCompleted++;
320  progress.progress(Bundle.SolrSearch_indentifyingIndex_msg(), progressUnitsCompleted);
321  Index indexToUse = IndexFinder.identifyIndexToUse(indexes);
322  if (indexToUse == null) {
323  // unable to find index that can be used
324  throw new AutopsyServiceException("Unable to find index that can be used for this case");
325  }
326 
327  if (context.cancelRequested()) {
328  return;
329  }
330 
331  // check if schema is compatible
332  if (!indexToUse.isCompatible(IndexFinder.getCurrentSchemaVersion())) {
333  String msg = "Text index schema version " + indexToUse.getSchemaVersion() + " is not compatible with current schema";
334  logger.log(Level.WARNING, msg);
335  throw new AutopsyServiceException(msg);
336  }
337  // proceed with case open
338  currentVersionIndex = indexToUse;
339  }
340  }
341 
342  try {
343  // update text index metadata file
344  if (!indexes.isEmpty()) {
345  IndexMetadata indexMetadata = new IndexMetadata(caseDirPath, indexes);
346  }
347  } catch (IndexMetadata.TextIndexMetadataException ex) {
348  throw new AutopsyServiceException("Failed to save Solr core info in text index metadata file", ex);
349  }
350 
351  // open core
352  try {
353  progress.progress(Bundle.SolrSearch_openCore_msg(), totalNumProgressUnits - 1);
354  KeywordSearch.getServer().openCoreForCase(theCase, currentVersionIndex);
355  } catch (KeywordSearchModuleException ex) {
356  throw new AutopsyServiceException(String.format("Failed to open or create core for %s", caseDirPath), ex);
357  }
358  if (context.cancelRequested()) {
359  return;
360  }
361 
362  theCase.getSleuthkitCase().registerForEvents(this);
363 
364  progress.progress(Bundle.SolrSearch_complete_msg(), totalNumProgressUnits);
365  }
366 
375  @Override
377  /*
378  * TODO (JIRA 2525): The following code KeywordSearch.CaseChangeListener
379  * gambles that any BlackboardResultWriters (SwingWorkers) will complete
380  * in less than roughly two seconds. This stuff should be reworked using
381  * an ExecutorService and tasks with Futures.
382  */
383  AdHocSearchChildFactory.BlackboardResultWriter.stopAllWriters();
384  try {
385  Thread.sleep(2000);
386  } catch (InterruptedException ex) {
387  logger.log(Level.SEVERE, "Unexpected interrupt while waiting for BlackboardResultWriters to terminate", ex);
388  }
389 
390  try {
391  KeywordSearch.getServer().closeCore();
392  } catch (KeywordSearchModuleException ex) {
393  throw new AutopsyServiceException(String.format("Failed to close core for %s", context.getCase().getCaseDirectory()), ex);
394  }
395 
396  context.getCase().getSleuthkitCase().unregisterForEvents(this);
397  }
398 
404  @NbBundle.Messages("SolrSearchService.indexingError=Unable to index blackboard artifact.")
405  @Subscribe
406  void handleNewArtifacts(Blackboard.ArtifactsPostedEvent event) {
407  for (BlackboardArtifact artifact : event.getArtifacts()) {
408  if ((artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) && // don't index KWH bc it's based on existing indexed text
409  (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT.getTypeID())){ //don't index AO bc it has only an artifact ID - no useful text
410  try {
411  index(artifact);
412  } catch (TskCoreException ex) {
413  //TODO: is this the right error handling?
414  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
415  MessageNotifyUtil.Notify.error(Bundle.SolrSearchService_indexingError(), artifact.getDisplayName());
416  }
417  }
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  TextExtractor blackboardExtractor = TextExtractorFactory.getExtractor((Content) artifact, null);
447  Reader blackboardExtractedTextReader = blackboardExtractor.getReader();
448  ingester.indexMetaDataOnly(artifact, sourceName);
449  ingester.indexText(blackboardExtractedTextReader, artifact.getId(), sourceName, artifact, null);
450  } catch (Ingester.IngesterException | TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) {
451  throw new TskCoreException(ex.getCause().getMessage(), ex);
452  }
453  }
454 
455 }
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: Tue Jan 19 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.