Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CommandLineIngestManager.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019-2019 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.commandlineingest;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.io.File;
24 import java.io.FilenameFilter;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.List;
28 import java.util.UUID;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.logging.Level;
32 import org.netbeans.spi.sendopts.OptionProcessor;
33 import org.openide.LifecycleManager;
34 import org.openide.util.Lookup;
58 import org.sleuthkit.datamodel.Content;
59 
66 
67  private static final Logger LOGGER = Logger.getLogger(CommandLineIngestManager.class.getName());
68  private Path rootOutputDirectory;
69 
71  }
72 
73  public void start() {
74  new Thread(new JobProcessingTask()).start();
75  }
76 
77  public void stop() {
78  try {
79  // close current case if there is one open
81  } catch (CaseActionException ex) {
82  LOGGER.log(Level.WARNING, "Unable to close the case while shutting down command line ingest manager", ex); //NON-NLS
83  }
84 
85  // shut down Autopsy
86  LifecycleManager.getDefault().exit();
87  }
88 
89  private final class JobProcessingTask implements Runnable {
90 
91  private final Object ingestLock;
92 
93  private JobProcessingTask() {
94  ingestLock = new Object();
95  try {
97  LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false");
99  LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex);
100  }
101  }
102 
103  public void run() {
104  LOGGER.log(Level.INFO, "Job processing task started");
105 
106  try {
107  // read command line inputs
108  LOGGER.log(Level.INFO, "Autopsy is running from command line"); //NON-NLS
109  String dataSourcePath = "";
110  String baseCaseName = "";
111 
112  // first look up all OptionProcessors and get input data from CommandLineOptionProcessor
113  Collection<? extends OptionProcessor> optionProcessors = Lookup.getDefault().lookupAll(OptionProcessor.class);
114  Iterator<? extends OptionProcessor> optionsIterator = optionProcessors.iterator();
115  while (optionsIterator.hasNext()) {
116  // find CommandLineOptionProcessor
117  OptionProcessor processor = optionsIterator.next();
118  if (processor instanceof CommandLineOptionProcessor) {
119  // check if we are running from command line
120  dataSourcePath = ((CommandLineOptionProcessor) processor).getPathToDataSource();
121  baseCaseName = ((CommandLineOptionProcessor) processor).getBaseCaseName();
122  }
123  }
124 
125  LOGGER.log(Level.INFO, "Data source path = {0}", dataSourcePath); //NON-NLS
126  LOGGER.log(Level.INFO, "Case name = {0}", baseCaseName); //NON-NLS
127  System.out.println("Data source path = " + dataSourcePath);
128  System.out.println("Case name = " + baseCaseName);
129 
130  // verify inputs
131  if (dataSourcePath.isEmpty()) {
132  LOGGER.log(Level.SEVERE, "Data source path not specified");
133  System.out.println("Data source path not specified");
134  return;
135  }
136 
137  if (baseCaseName.isEmpty()) {
138  LOGGER.log(Level.SEVERE, "Case name not specified");
139  System.out.println("Case name not specified");
140  return;
141  }
142 
143  if (!(new File(dataSourcePath).exists())) {
144  LOGGER.log(Level.SEVERE, "Data source file not found {0}", dataSourcePath);
145  System.out.println("Data source file not found " + dataSourcePath);
146  return;
147  }
148 
149  // read options panel configuration
150  String rootOutputDir = UserPreferences.getCommandLineModeResultsFolder();
151  LOGGER.log(Level.INFO, "Output directory = {0}", rootOutputDir); //NON-NLS
152  System.out.println("Output directoryh = " + rootOutputDir);
153 
154  if (rootOutputDir.isEmpty()) {
155  LOGGER.log(Level.SEVERE, "Output directory not specified, please configure Command Line Options Panel (in Tools -> Options)");
156  System.out.println("Output directory not specified, please configure Command Line Options Panel (in Tools -> Options)");
157  return;
158  }
159 
160  if (!(new File(rootOutputDir).exists())) {
161  LOGGER.log(Level.SEVERE, "The output directory doesn't exist {0}", rootOutputDir);
162  System.out.println("The output directory doesn't exist " + rootOutputDir);
163  return;
164  }
165  rootOutputDirectory = Paths.get(rootOutputDir);
166 
167  // open case
168  Case caseForJob;
169  try {
170  caseForJob = openCase(baseCaseName);
171  } catch (CaseActionException ex) {
172  LOGGER.log(Level.SEVERE, "Error creating or opening case " + baseCaseName, ex);
173  System.out.println("Error creating or opening case " + baseCaseName);
174  return;
175  }
176 
177  if (caseForJob == null) {
178  LOGGER.log(Level.SEVERE, "Error creating or opening case {0}", baseCaseName);
179  System.out.println("Error creating or opening case " + baseCaseName);
180  return;
181  }
182 
183  AutoIngestDataSource dataSource = new AutoIngestDataSource("", Paths.get(dataSourcePath));
184  try {
185  // run data source processor
186  runDataSourceProcessor(caseForJob, dataSource);
187 
188  // run ingest manager
189  analyze(dataSource);
190 
191  // generate CASE-UCO report
192  Long selectedDataSourceId = getDataSourceId(dataSource);
193  Path reportFolderPath = Paths.get(caseForJob.getReportDirectory(), "CASE-UCO", "Data_Source_ID_" + selectedDataSourceId.toString() + "_" + TimeStampUtils.createTimeStamp(), ReportCaseUco.getReportFileName()); //NON_NLS
194  ReportProgressPanel progressPanel = new ReportProgressPanel("CASE_UCO", rootOutputDir); // dummy progress panel
195  CaseUcoFormatExporter.generateReport(selectedDataSourceId, reportFolderPath.toString(), progressPanel);
197  LOGGER.log(Level.SEVERE, "Unable to ingest data source " + dataSourcePath + ". Exiting...", ex);
198  System.out.println("Unable to ingest data source " + dataSourcePath + ". Exiting...");
199  } catch (Throwable ex) {
200  /*
201  * Unexpected runtime exceptions firewall. This task is designed to
202  * be able to be run in an executor service thread pool without
203  * calling get() on the task's Future<Void>, so this ensures that
204  * such errors get logged.
205  */
206  LOGGER.log(Level.SEVERE, "Unexpected error while ingesting data source " + dataSourcePath, ex);
207  System.out.println("Unexpected error while ingesting data source " + dataSourcePath + ". Exiting...");
208 
209  } finally {
210  try {
212  } catch (CaseActionException ex) {
213  LOGGER.log(Level.WARNING, "Exception while closing case", ex);
214  System.out.println("Exception while closing case");
215  }
216  }
217 
218  } finally {
219  LOGGER.log(Level.INFO, "Job processing task finished");
220  System.out.println("Job processing task finished");
221 
222  // shut down Autopsy
223  stop();
224  }
225  }
226 
234  private Long getDataSourceId(AutoIngestDataSource dataSource) {
235  Content content = dataSource.getContent().get(0);
236  return content.getId();
237  }
238 
239  private Case openCase(String baseCaseName) throws CaseActionException {
240 
241  LOGGER.log(Level.INFO, "Opening case {0}", baseCaseName);
242 
243  Path caseDirectoryPath = findCaseDirectory(rootOutputDirectory, baseCaseName);
244  if (null != caseDirectoryPath) {
245  // found an existing case directory for same case name. the input case name must be unique. Exit.
246  LOGGER.log(Level.SEVERE, "Case {0} already exists. Case name must be unique. Exiting", baseCaseName);
247  throw new CaseActionException("Case " + baseCaseName + " already exists. Case name must be unique. Exiting");
248  } else {
249  caseDirectoryPath = createCaseFolderPath(rootOutputDirectory, baseCaseName);
250 
251  // Create the case directory
252  Case.createCaseDirectory(caseDirectoryPath.toString(), Case.CaseType.SINGLE_USER_CASE);
253 
254  CaseDetails caseDetails = new CaseDetails(baseCaseName);
255  Case.createAsCurrentCase(Case.CaseType.SINGLE_USER_CASE, caseDirectoryPath.toString(), caseDetails);
256  }
257 
258  Case caseForJob = Case.getCurrentCase();
259  LOGGER.log(Level.INFO, "Opened case {0}", caseForJob.getName());
260  return caseForJob;
261  }
262 
278 
279  LOGGER.log(Level.INFO, "Adding data source {0} ", dataSource.getPath().toString());
280 
281  // Get an ordered list of data source processors to try
282  List<AutoIngestDataSourceProcessor> validDataSourceProcessors;
283  try {
284  validDataSourceProcessors = DataSourceProcessorUtility.getOrderedListOfDataSourceProcessors(dataSource.getPath());
286  LOGGER.log(Level.SEVERE, "Exception while determining best data source processor for {0}", dataSource.getPath());
287  // rethrow the exception.
288  throw ex;
289  }
290 
291  // did we find a data source processor that can process the data source
292  if (validDataSourceProcessors.isEmpty()) {
293  // This should never happen. We should add all unsupported data sources as logical files.
294  LOGGER.log(Level.SEVERE, "Unsupported data source {0}", dataSource.getPath()); // NON-NLS
295  return;
296  }
297 
299  synchronized (ingestLock) {
300  // Try each DSP in decreasing order of confidence
301  for (AutoIngestDataSourceProcessor selectedProcessor : validDataSourceProcessors) {
302  UUID taskId = UUID.randomUUID();
303  caseForJob.notifyAddingDataSource(taskId);
304  DataSourceProcessorCallback callBack = new AddDataSourceCallback(caseForJob, dataSource, taskId, ingestLock);
305  caseForJob.notifyAddingDataSource(taskId);
306  LOGGER.log(Level.INFO, "Identified data source type for {0} as {1}", new Object[]{dataSource.getPath(), selectedProcessor.getDataSourceType()});
307  selectedProcessor.process(dataSource.getDeviceId(), dataSource.getPath(), progressMonitor, callBack);
308  ingestLock.wait();
309 
310  // at this point we got the content object(s) from the current DSP.
311  // check whether the data source was processed successfully
312  if ((dataSource.getResultDataSourceProcessorResultCode() == CRITICAL_ERRORS)
313  || dataSource.getContent().isEmpty()) {
314  // move onto the the next DSP that can process this data source
315  logDataSourceProcessorResult(dataSource);
316  continue;
317  }
318 
319  logDataSourceProcessorResult(dataSource);
320  return;
321  }
322  // If we get to this point, none of the processors were successful
323  LOGGER.log(Level.SEVERE, "All data source processors failed to process {0}", dataSource.getPath());
324  // Throw an exception. It will get caught & handled upstream and will result in AIM auto-pause.
325  throw new AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException("Failed to process " + dataSource.getPath() + " with all data source processors");
326  }
327  }
328 
336 
338  if (null != resultCode) {
339  switch (resultCode) {
340  case NO_ERRORS:
341  LOGGER.log(Level.INFO, "Added data source to case");
342  if (dataSource.getContent().isEmpty()) {
343  LOGGER.log(Level.SEVERE, "Data source failed to produce content");
344  }
345  break;
346 
347  case NONCRITICAL_ERRORS:
348  for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) {
349  LOGGER.log(Level.WARNING, "Non-critical error running data source processor for {0}: {1}", new Object[]{dataSource.getPath(), errorMessage});
350  }
351  LOGGER.log(Level.INFO, "Added data source to case");
352  if (dataSource.getContent().isEmpty()) {
353  LOGGER.log(Level.SEVERE, "Data source failed to produce content");
354  }
355  break;
356 
357  case CRITICAL_ERRORS:
358  for (String errorMessage : dataSource.getDataSourceProcessorErrorMessages()) {
359  LOGGER.log(Level.SEVERE, "Critical error running data source processor for {0}: {1}", new Object[]{dataSource.getPath(), errorMessage});
360  }
361  LOGGER.log(Level.SEVERE, "Failed to add data source to case");
362  break;
363  }
364  } else {
365  LOGGER.log(Level.WARNING, "No result code for data source processor for {0}", dataSource.getPath());
366  }
367  }
368 
382  private void analyze(AutoIngestDataSource dataSource) throws AnalysisStartupException, InterruptedException {
383 
384  LOGGER.log(Level.INFO, "Starting ingest modules analysis for {0} ", dataSource.getPath());
385  IngestJobEventListener ingestJobEventListener = new IngestJobEventListener();
386  IngestManager.getInstance().addIngestJobEventListener(ingestJobEventListener);
387  try {
388  synchronized (ingestLock) {
390  List<String> settingsWarnings = ingestJobSettings.getWarnings();
391  if (settingsWarnings.isEmpty()) {
392  IngestJobStartResult ingestJobStartResult = IngestManager.getInstance().beginIngestJob(dataSource.getContent(), ingestJobSettings);
393  IngestJob ingestJob = ingestJobStartResult.getJob();
394  if (null != ingestJob) {
395  /*
396  * Block until notified by the ingest job event
397  * listener or until interrupted because auto ingest
398  * is shutting down.
399  */
400  ingestLock.wait();
401  LOGGER.log(Level.INFO, "Finished ingest modules analysis for {0} ", dataSource.getPath());
402  IngestJob.ProgressSnapshot jobSnapshot = ingestJob.getSnapshot();
403  for (IngestJob.ProgressSnapshot.DataSourceProcessingSnapshot snapshot : jobSnapshot.getDataSourceSnapshots()) {
404  if (!snapshot.isCancelled()) {
405  List<String> cancelledModules = snapshot.getCancelledDataSourceIngestModules();
406  if (!cancelledModules.isEmpty()) {
407  LOGGER.log(Level.WARNING, String.format("Ingest module(s) cancelled for %s", dataSource.getPath()));
408  for (String module : snapshot.getCancelledDataSourceIngestModules()) {
409  LOGGER.log(Level.WARNING, String.format("%s ingest module cancelled for %s", module, dataSource.getPath()));
410  }
411  }
412  LOGGER.log(Level.INFO, "Analysis of data source completed");
413  } else {
414  LOGGER.log(Level.WARNING, "Analysis of data source cancelled");
415  IngestJob.CancellationReason cancellationReason = snapshot.getCancellationReason();
416  if (IngestJob.CancellationReason.NOT_CANCELLED != cancellationReason && IngestJob.CancellationReason.USER_CANCELLED != cancellationReason) {
417  throw new AnalysisStartupException(String.format("Analysis cancelled due to %s for %s", cancellationReason.getDisplayName(), dataSource.getPath()));
418  }
419  }
420  }
421  } else if (!ingestJobStartResult.getModuleErrors().isEmpty()) {
422  for (IngestModuleError error : ingestJobStartResult.getModuleErrors()) {
423  LOGGER.log(Level.SEVERE, String.format("%s ingest module startup error for %s", error.getModuleDisplayName(), dataSource.getPath()), error.getThrowable());
424  }
425  LOGGER.log(Level.SEVERE, "Failed to analyze data source due to ingest job startup error");
426  throw new AnalysisStartupException(String.format("Error(s) during ingest module startup for %s", dataSource.getPath()));
427  } else {
428  LOGGER.log(Level.SEVERE, String.format("Ingest manager ingest job start error for %s", dataSource.getPath()), ingestJobStartResult.getStartupException());
429  throw new AnalysisStartupException("Ingest manager error starting job", ingestJobStartResult.getStartupException());
430  }
431  } else {
432  for (String warning : settingsWarnings) {
433  LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{dataSource.getPath(), warning});
434  }
435  LOGGER.log(Level.SEVERE, "Failed to analyze data source due to settings errors");
436  throw new AnalysisStartupException("Error(s) in ingest job settings");
437  }
438  }
439  } finally {
440  IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventListener);
441  }
442  }
443 
453  Path createCaseFolderPath(Path caseFoldersPath, String caseName) {
454  String folderName = caseName + "_" + TimeStampUtils.createTimeStamp();
455  return Paths.get(caseFoldersPath.toString(), folderName);
456  }
457 
468  Path findCaseDirectory(Path folderToSearch, String caseName) {
469  File searchFolder = new File(folderToSearch.toString());
470  if (!searchFolder.isDirectory()) {
471  return null;
472  }
473  Path caseFolderPath = null;
474  String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName));
475  long mostRecentModified = 0;
476  for (String candidateFolder : candidateFolders) {
477  File file = new File(candidateFolder);
478  if (file.lastModified() >= mostRecentModified) {
479  mostRecentModified = file.lastModified();
480  caseFolderPath = Paths.get(folderToSearch.toString(), file.getPath());
481  }
482  }
483  return caseFolderPath;
484  }
485 
495  private class IngestJobEventListener implements PropertyChangeListener {
496 
504  @Override
505  public void propertyChange(PropertyChangeEvent event) {
506  if (AutopsyEvent.SourceType.LOCAL == ((AutopsyEvent) event).getSourceType()) {
507  String eventType = event.getPropertyName();
508  if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
509  synchronized (ingestLock) {
510  ingestLock.notify();
511  }
512  }
513  }
514  }
515  };
516 
523 
529  @Override
530  public void setIndeterminate(final boolean indeterminate) {
531  }
532 
538  @Override
539  public void setProgress(final int progress) {
540  }
541 
547  @Override
548  public void setProgressText(final String text) {
549  }
550  }
551 
557  private final class AnalysisStartupException extends Exception {
558 
559  private static final long serialVersionUID = 1L;
560 
561  private AnalysisStartupException(String message) {
562  super(message);
563  }
564 
565  private AnalysisStartupException(String message, Throwable cause) {
566  super(message, cause);
567  }
568  }
569  }
570 
571  private static class CaseFolderFilter implements FilenameFilter {
572 
573  private final String caseName;
574  private final static String CASE_METADATA_EXT = CaseMetadata.getFileExtension();
575 
576  CaseFolderFilter(String caseName) {
577  this.caseName = caseName;
578  }
579 
580  @Override
581  public boolean accept(File folder, String fileName) {
582  File file = new File(folder, fileName);
583  if (fileName.length() > TimeStampUtils.getTimeStampLength() && file.isDirectory()) {
584  if (TimeStampUtils.endsWithTimeStamp(fileName)) {
585  if (null != caseName) {
586  String fileNamePrefix = fileName.substring(0, fileName.length() - TimeStampUtils.getTimeStampLength());
587  if (fileNamePrefix.equals(caseName)) {
588  return hasCaseMetadataFile(file);
589  }
590  } else {
591  return hasCaseMetadataFile(file);
592  }
593  }
594  }
595  return false;
596  }
597 
606  private static boolean hasCaseMetadataFile(File folder) {
607  for (File file : folder.listFiles()) {
608  if (file.getName().toLowerCase().endsWith(CASE_METADATA_EXT) && file.isFile()) {
609  return true;
610  }
611  }
612  return false;
613  }
614  }
615 
616 }
static List< AutoIngestDataSourceProcessor > getOrderedListOfDataSourceProcessors(Path dataSourcePath)
static synchronized IngestManager getInstance()
static void generateReport(Long selectedDataSourceId, String reportOutputPath, ReportProgressPanel progressPanel)
static void createCaseDirectory(String caseDirPath, CaseType caseType)
Definition: Case.java:906
IngestJobStartResult beginIngestJob(Collection< Content > dataSources, IngestJobSettings settings)
synchronized DataSourceProcessorResult getResultDataSourceProcessorResultCode()
static boolean endsWithTimeStamp(String inputString)
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)
static synchronized void setRunningWithGUI(boolean runningWithGUI)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void createAsCurrentCase(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:527
IngestManager.IngestManagerException getStartupException()

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.