Autopsy  4.13.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
PortableCaseReportModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 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.report.modules.portablecase;
20 
22 import java.util.logging.Level;
23 import java.io.BufferedReader;
24 import java.io.File;
25 import java.io.InputStreamReader;
26 import java.io.IOException;
27 import java.nio.file.Paths;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Map;
35 import org.apache.commons.io.FileUtils;
36 import org.openide.modules.InstalledFileLocator;
37 import org.openide.util.NbBundle;
48 import org.sleuthkit.datamodel.AbstractFile;
49 import org.sleuthkit.datamodel.BlackboardArtifact;
50 import org.sleuthkit.datamodel.BlackboardArtifactTag;
51 import org.sleuthkit.datamodel.BlackboardAttribute;
52 import org.sleuthkit.datamodel.CaseDbAccessManager;
53 import org.sleuthkit.datamodel.Content;
54 import org.sleuthkit.datamodel.ContentTag;
55 import org.sleuthkit.datamodel.FileSystem;
56 import org.sleuthkit.datamodel.Image;
57 import org.sleuthkit.datamodel.LocalFilesDataSource;
58 import org.sleuthkit.datamodel.Pool;
59 import org.sleuthkit.datamodel.SleuthkitCase;
60 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
61 import org.sleuthkit.datamodel.TagName;
62 import org.sleuthkit.datamodel.TskCoreException;
63 import org.sleuthkit.datamodel.TskDataException;
64 import org.sleuthkit.datamodel.TskData;
65 import org.sleuthkit.datamodel.Volume;
66 import org.sleuthkit.datamodel.VolumeSystem;
67 
71 public class PortableCaseReportModule implements ReportModule {
72  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
73  private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
74  private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
75  private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids"; // NON-NLS
77 
78  // These are the types for the exported file subfolders
79  private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
81 
82  private Case currentCase = null;
83  private SleuthkitCase portableSkCase = null;
84  private String caseName = "";
85  private File caseFolder = null;
86  private File copiedFilesFolder = null;
87 
88  // Maps old object ID from current case to new object in portable case
89  private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
90 
91  // Maps new object ID to the new object
92  private final Map<Long, Content> newIdToContent = new HashMap<>();
93 
94  // Maps old TagName to new TagName
95  private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
96 
97  // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
98  private final Map<Integer, Integer> oldArtTypeIdToNewArtTypeId = new HashMap<>();
99 
100  // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
101  private final Map<Integer, BlackboardAttribute.Type> oldAttrTypeIdToNewAttrType = new HashMap<>();
102 
103  // Map of old artifact ID to new artifact
104  private final Map<Long, BlackboardArtifact> oldArtifactIdToNewArtifact = new HashMap<>();
105 
107  }
108 
109  @NbBundle.Messages({
110  "PortableCaseReportModule.getName.name=Portable Case"
111  })
112  @Override
113  public String getName() {
114  return Bundle.PortableCaseReportModule_getName_name();
115  }
116 
117  @NbBundle.Messages({
118  "PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared"
119  })
120  @Override
121  public String getDescription() {
122  return Bundle.PortableCaseReportModule_getDescription_description();
123  }
124 
125  @Override
126  public String getRelativeFilePath() {
127  try {
128  caseName = Case.getCurrentCaseThrows().getDisplayName() + " (Portable)"; // NON-NLS
129  } catch (NoCurrentCaseException ex) {
130  // a case may not be open yet
131  return "";
132  }
133  return caseName;
134  }
135 
141  private void handleCancellation(ReportProgressPanel progressPanel) {
142  logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
143  progressPanel.setIndeterminate(false);
145  cleanup();
146  }
147 
158  private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
159  if (ex == null) {
160  logger.log(Level.WARNING, logWarning);
161  } else {
162  logger.log(Level.SEVERE, logWarning, ex);
163  }
164  progressPanel.setIndeterminate(false);
165  progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, dialogWarning);
166  cleanup();
167  }
168 
169  @NbBundle.Messages({
170  "PortableCaseReportModule.generateReport.verifying=Verifying selected parameters...",
171  "PortableCaseReportModule.generateReport.creatingCase=Creating portable case database...",
172  "PortableCaseReportModule.generateReport.copyingTags=Copying tags...",
173  "# {0} - tag name",
174  "PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0}...",
175  "# {0} - tag name",
176  "PortableCaseReportModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...",
177  "# {0} - output folder",
178  "PortableCaseReportModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
179  "# {0} - output folder",
180  "PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
181  "PortableCaseReportModule.generateReport.caseClosed=Current case has been closed",
182  "PortableCaseReportModule.generateReport.interestingItemError=Error loading intersting items",
183  "PortableCaseReportModule.generateReport.errorReadingTags=Error while reading tags from case database",
184  "PortableCaseReportModule.generateReport.errorReadingSets=Error while reading interesting items sets from case database",
185  "PortableCaseReportModule.generateReport.noContentToCopy=No interesting files, results, or tagged items to copy",
186  "PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags",
187  "PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files",
188  "PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts",
189  "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files",
190  "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results",
191  "PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table",
192  "# {0} - attribute type name",
193  "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}",
194  "PortableCaseReportModule.generateReport.compressingCase=Compressing case...",
195  "PortableCaseReportModule.generateReport.errorCreatingReportFolder=Could not make report folder",
196  "PortableCaseReportModule.generateReport.errorGeneratingUCOreport=Problem while generating CASE-UCO report"
197  })
198 
199  public void generateReport(String reportPath, PortableCaseReportModuleSettings options, ReportProgressPanel progressPanel) {
200  this.settings = options;
201  progressPanel.setIndeterminate(true);
202  progressPanel.start();
203  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
204 
205  // Clear out any old values
206  cleanup();
207 
208  // Validate the input parameters
209  File outputDir = new File(reportPath);
210  if (! outputDir.exists()) {
211  handleError("Output folder " + outputDir.toString() + " does not exist",
212  Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
213  return;
214  }
215 
216  if (! outputDir.isDirectory()) {
217  handleError("Output folder " + outputDir.toString() + " is not a folder",
218  Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
219  return;
220  }
221 
222  // Save the current case object
223  try {
224  currentCase = Case.getCurrentCaseThrows();
225  caseName = currentCase.getDisplayName() + " (Portable)"; // NON-NLS
226  } catch (NoCurrentCaseException ex) {
227  handleError("Current case has been closed",
228  Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
229  return;
230  }
231 
232  // Check that there will be something to copy
233  List<TagName> tagNames;
234  if (options.areAllTagsSelected()) {
235  try {
237  } catch (NoCurrentCaseException | TskCoreException ex) {
238  handleError("Unable to get all tags",
239  Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS
240  return;
241  }
242  } else {
243  tagNames = options.getSelectedTagNames();
244  }
245 
246  List<String> setNames;
247  if (options.areAllSetsSelected()) {
248  try {
249  setNames = getAllInterestingItemsSets();
250  } catch (NoCurrentCaseException | TskCoreException ex) {
251  handleError("Unable to get all interesting items sets",
252  Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS
253  return;
254  }
255  } else {
256  setNames = options.getSelectedSetNames();
257  }
258 
259  if (tagNames.isEmpty() && setNames.isEmpty()) {
260  handleError("No content to copy",
261  Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
262  return;
263  }
264 
265  // Create the case.
266  // portableSkCase and caseFolder will be set here.
267  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
268  createCase(outputDir, progressPanel);
269  if (portableSkCase == null) {
270  // The error has already been handled
271  return;
272  }
273 
274  // Check for cancellation
275  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
276  handleCancellation(progressPanel);
277  return;
278  }
279 
280  // Set up the table for the image tags
281  try {
282  initializeImageTags(progressPanel);
283  } catch (TskCoreException ex) {
284  handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS
285  return;
286  }
287 
288  // Copy the selected tags
289  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
290  try {
291  for(TagName tagName:tagNames) {
292  TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
293  oldTagNameToNewTagName.put(tagName, newTagName);
294  }
295  } catch (TskCoreException ex) {
296  handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
297  return;
298  }
299 
300  // Set up tracking to support any custom artifact or attribute types
301  for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
302  oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
303  }
304  for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
305  try {
306  oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
307  } catch (TskCoreException ex) {
308  handleError("Error looking up attribute name " + type.getLabel(),
309  Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
310  ex, progressPanel); // NON-NLS
311  }
312  }
313 
314  // Copy the tagged files
315  try {
316  for(TagName tagName:tagNames) {
317  // Check for cancellation
318  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
319  handleCancellation(progressPanel);
320  return;
321  }
322  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
323  addFilesToPortableCase(tagName, progressPanel);
324 
325  // Check for cancellation
326  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
327  handleCancellation(progressPanel);
328  return;
329  }
330  }
331  } catch (TskCoreException ex) {
332  handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
333  return;
334  }
335 
336  // Copy the tagged artifacts and associated files
337  try {
338  for(TagName tagName:tagNames) {
339  // Check for cancellation
340  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
341  handleCancellation(progressPanel);
342  return;
343  }
344  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
345  addArtifactsToPortableCase(tagName, progressPanel);
346 
347  // Check for cancellation
348  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
349  handleCancellation(progressPanel);
350  return;
351  }
352  }
353  } catch (TskCoreException ex) {
354  handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
355  return;
356  }
357 
358  // Copy interesting files and results
359  if (! setNames.isEmpty()) {
360  try {
361  List<BlackboardArtifact> interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
362  for (BlackboardArtifact art:interestingFiles) {
363  // Check for cancellation
364  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
365  handleCancellation(progressPanel);
366  return;
367  }
368 
369  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
370  if (setNames.contains(setAttr.getValueString())) {
371  copyContentToPortableCase(art, progressPanel);
372  }
373  }
374  } catch (TskCoreException ex) {
375  handleError("Error copying interesting files", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingFiles(), ex, progressPanel); // NON-NLS
376  return;
377  }
378 
379  try {
380  List<BlackboardArtifact> interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
381  for (BlackboardArtifact art:interestingResults) {
382  // Check for cancellation
383  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
384  handleCancellation(progressPanel);
385  return;
386  }
387  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
388  if (setNames.contains(setAttr.getValueString())) {
389  copyContentToPortableCase(art, progressPanel);
390  }
391  }
392  } catch (TskCoreException ex) {
393  handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
394  return;
395  }
396  }
397 
398  // Check for cancellation
399  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
400  handleCancellation(progressPanel);
401  return;
402  }
403 
404  File reportsFolder = Paths.get(caseFolder.toString(), "Reports").toFile();
405  if(!reportsFolder.mkdir()) {
406  handleError("Could not make report folder", Bundle.PortableCaseReportModule_generateReport_errorCreatingReportFolder(), null, progressPanel); // NON-NLS
407  return;
408  }
409 
410  try {
411  CaseUcoFormatExporter.export(tagNames, setNames, reportsFolder, progressPanel);
412  } catch (IOException | SQLException | NoCurrentCaseException | TskCoreException ex) {
413  handleError("Problem while generating CASE-UCO report",
414  Bundle.PortableCaseReportModule_generateReport_errorGeneratingUCOreport(), ex, progressPanel); // NON-NLS
415  }
416 
417  // Compress the case (if desired)
418  if (options.shouldCompress()) {
419  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
420 
421  boolean success = compressCase(progressPanel);
422 
423  // Check for cancellation
424  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
425  handleCancellation(progressPanel);
426  return;
427  }
428 
429  if (! success) {
430  // Errors have been handled already
431  return;
432  }
433  }
434 
435  // Close the case connections and clear out the maps
436  cleanup();
437 
439 
440  }
441 
442  private List<String> getAllInterestingItemsSets() throws NoCurrentCaseException, TskCoreException {
443 
444  // Get the set names in use for the current case.
445  List<String> setNames = new ArrayList<>();
446  Map<String, Long> setCounts;
447 
448  // There may not be a case open when configuring report modules for Command Line execution
449  // Get all SET_NAMEs from interesting item artifacts
450  String innerSelect = "SELECT (value_text) AS set_name FROM blackboard_attributes WHERE (artifact_type_id = '"
451  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() + "' OR artifact_type_id = '"
452  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + "') AND attribute_type_id = '"
453  + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "'"; // NON-NLS
454 
455  // Get the count of each SET_NAME
456  String query = "set_name, count(1) AS set_count FROM (" + innerSelect + ") set_names GROUP BY set_name"; // NON-NLS
457 
459  Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
460  setCounts = callback.getSetCountMap();
461  setNames.addAll(setCounts.keySet());
462  return setNames;
463  }
464 
465 
473  @NbBundle.Messages({
474  "# {0} - case folder",
475  "PortableCaseReportModule.createCase.caseDirExists=Case folder {0} already exists",
476  "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
477  "# {0} - folder",
478  "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
479  "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
480  })
481  private void createCase(File outputDir, ReportProgressPanel progressPanel) {
482 
483  // Create the case folder
484  caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
485 
486  if (caseFolder.exists()) {
487  handleError("Case folder " + caseFolder.toString() + " already exists",
488  Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
489  return;
490  }
491 
492  // Create the case
493  try {
494  portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
495  } catch (TskCoreException ex) {
496  handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
497  Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
498  return;
499  }
500 
501  // Store the highest IDs
502  try {
503  saveHighestIds();
504  } catch (TskCoreException ex) {
505  handleError("Error storing maximum database IDs",
506  Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
507  return;
508  }
509 
510  // Create the base folder for the copied files
511  copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
512  if (! copiedFilesFolder.mkdir()) {
513  handleError("Error creating folder " + copiedFilesFolder.toString(),
514  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
515  return;
516  }
517 
518  // Create subfolders for the copied files
519  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
520  File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
521  if (! subFolder.mkdir()) {
522  handleError("Error creating folder " + subFolder.toString(),
523  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
524  return;
525  }
526  }
527  File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
528  if (! unknownTypeFolder.mkdir()) {
529  handleError("Error creating folder " + unknownTypeFolder.toString(),
530  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
531  return;
532  }
533 
534  }
535 
541  private void saveHighestIds() throws TskCoreException {
542 
543  CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
544 
545  String tableSchema = "( table_name TEXT PRIMARY KEY, "
546  + " max_id TEXT)"; // NON-NLS
547 
548  portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
549 
550  currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
551  currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags")); // NON-NLS
552  currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
553  currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
554  }
555 
563  private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException {
564 
565  // Create the image tags table in the portable case
566  CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
567  if (! portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
569  }
570  }
571 
580  private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
581 
582  // Get all the tags in the current case
583  List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
584 
585  // Copy the files into the portable case and tag
586  for (ContentTag tag : tags) {
587 
588  // Check for cancellation
589  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
590  return;
591  }
592 
593  Content content = tag.getContent();
594  if (content instanceof AbstractFile) {
595 
596  long newFileId = copyContentToPortableCase(content, progressPanel);
597 
598  // Tag the file
599  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
600  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
601  }
602  ContentTag newContentTag = portableSkCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
603 
604  // Get the image tag data associated with this tag (empty string if there is none)
605  // and save it if present
606  String appData = getImageTagDataForContentTag(tag);
607  if (! appData.isEmpty()) {
608  addImageTagToPortableCase(newContentTag, appData);
609  }
610  }
611  }
612  }
613 
623  private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException {
624 
625  GetImageTagCallback callback = new GetImageTagCallback();
626  String query = "* FROM " + ContentViewerTagManager.TABLE_NAME + " WHERE content_tag_id = " + tag.getId();
627  currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
628  return callback.getAppData();
629  }
630 
634  private static class GetImageTagCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
635 
636  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
637  private String appData = "";
638 
639  @Override
640  public void process(ResultSet rs) {
641  try {
642  while (rs.next()) {
643  try {
644  appData = rs.getString("app_data"); // NON-NLS
645  } catch (SQLException ex) {
646  logger.log(Level.WARNING, "Unable to get app_data from result set", ex); // NON-NLS
647  }
648  }
649  } catch (SQLException ex) {
650  logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS
651  }
652  }
653 
659  String getAppData() {
660  return appData;
661  }
662  }
663 
672  private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException {
673  String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')";
674  portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
675  }
676 
677 
686  private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
687 
688  List<BlackboardArtifactTag> tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
689 
690  // Copy the artifacts into the portable case along with their content and tag
691  for (BlackboardArtifactTag tag : tags) {
692 
693  // Check for cancellation
694  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
695  return;
696  }
697 
698  // Copy the source content
699  Content content = tag.getContent();
700  long newContentId = copyContentToPortableCase(content, progressPanel);
701 
702  // Copy the artifact
703  BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
704 
705  // Tag the artfiact
706  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
707  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
708  }
709  portableSkCase.addBlackboardArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
710  }
711  }
712 
723  private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
724 
725  if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
726  return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
727  }
728 
729  // First create the associated artifact (if present)
730  BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
731  List<BlackboardAttribute> newAttrs = new ArrayList<>();
732  if (oldAssociatedAttribute != null) {
733  BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
734  BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
735  newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
736  String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
737  }
738 
739  // Create the new artifact
740  int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
741  BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId);
742  List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
743 
744  // Copy over each attribute, making sure the type is in the new case.
745  for (BlackboardAttribute oldAttr:oldAttrs) {
746 
747  // The associated artifact has already been handled
748  if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
749  continue;
750  }
751 
752  BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
753  switch (oldAttr.getValueType()) {
754  case BYTE:
755  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
756  oldAttr.getValueBytes()));
757  break;
758  case DOUBLE:
759  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
760  oldAttr.getValueDouble()));
761  break;
762  case INTEGER:
763  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
764  oldAttr.getValueInt()));
765  break;
766  case DATETIME:
767  case LONG:
768  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
769  oldAttr.getValueLong()));
770  break;
771  case STRING:
772  case JSON:
773  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
774  oldAttr.getValueString()));
775  break;
776  default:
777  throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
778  }
779  }
780 
781  newArtifact.addAttributes(newAttrs);
782 
783  oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
784  return newArtifact;
785  }
786 
795  private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
796  if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
797  return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
798  }
799 
800  BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
801  try {
802  BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
803  oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID());
804  return newCustomType.getTypeID();
805  } catch (TskDataException ex) {
806  throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
807  }
808  }
809 
818  private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
819  BlackboardAttribute.Type oldAttrType = oldAttribute.getAttributeType();
820  if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
821  return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
822  }
823 
824  try {
825  BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(),
826  oldAttrType.getValueType(), oldAttrType.getDisplayName());
827  oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
828  return newCustomType;
829  } catch (TskDataException ex) {
830  throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS
831  }
832  }
833 
844  @NbBundle.Messages({
845  "# {0} - File name",
846  "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",
847  })
848  private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
849  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
850  return copyContent(content);
851  }
852 
862  private long copyContent(Content content) throws TskCoreException {
863 
864  // Check if we've already copied this content
865  if (oldIdToNewContent.containsKey(content.getId())) {
866  return oldIdToNewContent.get(content.getId()).getId();
867  }
868 
869  // Otherwise:
870  // - Make parent of this object (if applicable)
871  // - Copy this content
872  long parentId = 0;
873  if (content.getParent() != null) {
874  parentId = copyContent(content.getParent());
875  }
876 
877  Content newContent;
878  if (content instanceof BlackboardArtifact) {
879  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
880  newContent = copyArtifact(parentId, artifactToCopy);
881  } else {
882  CaseDbTransaction trans = portableSkCase.beginTransaction();
883  try {
884  if (content instanceof Image) {
885  Image image = (Image)content;
886  newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
887  new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
888  } else if (content instanceof VolumeSystem) {
889  VolumeSystem vs = (VolumeSystem)content;
890  newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
891  } else if (content instanceof Volume) {
892  Volume vs = (Volume)content;
893  newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
894  vs.getDescription(), vs.getFlags(), trans);
895  } else if (content instanceof Pool) {
896  Pool pool = (Pool)content;
897  newContent = portableSkCase.addPool(parentId, pool.getType(), trans);
898  } else if (content instanceof FileSystem) {
899  FileSystem fs = (FileSystem)content;
900  newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
901  fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
902  fs.getName(), trans);
903  } else if (content instanceof BlackboardArtifact) {
904  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
905  newContent = copyArtifact(parentId, artifactToCopy);
906  } else if (content instanceof AbstractFile) {
907  AbstractFile abstractFile = (AbstractFile)content;
908 
909  if (abstractFile instanceof LocalFilesDataSource) {
910  LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
911  newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
912  } else {
913  if (abstractFile.isDir()) {
914  newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
915  } else {
916  try {
917  // Copy the file
918  String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
919  String exportSubFolder = getExportSubfolder(abstractFile);
920  File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
921  File localFile = new File(exportFolder, fileName);
922  ContentUtils.writeToFile(abstractFile, localFile);
923 
924  // Get the new parent object in the portable case database
925  Content oldParent = abstractFile.getParent();
926  if (! oldIdToNewContent.containsKey(oldParent.getId())) {
927  throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
928  }
929  Content newParent = oldIdToNewContent.get(oldParent.getId());
930 
931  // Construct the relative path to the copied file
932  String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
933 
934  newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
935  abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
936  abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
937  true, TskData.EncodingType.NONE,
938  newParent, trans);
939  } catch (IOException ex) {
940  throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
941  + abstractFile.getId(), ex); // NON-NLS
942  }
943  }
944  }
945  } else {
946  throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
947  }
948  trans.commit();
949  } catch (TskCoreException ex) {
950  trans.rollback();
951  throw(ex);
952  }
953  }
954 
955  // Save the new object
956  oldIdToNewContent.put(content.getId(), newContent);
957  newIdToContent.put(newContent.getId(), newContent);
958  return oldIdToNewContent.get(content.getId()).getId();
959  }
960 
968  private String getExportSubfolder(AbstractFile abstractFile) {
969  if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
971  }
972 
973  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
974  if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
975  return cat.getDisplayName();
976  }
977  }
979  }
980 
984  private void cleanup() {
985  oldIdToNewContent.clear();
986  newIdToContent.clear();
987  oldTagNameToNewTagName.clear();
988  oldArtTypeIdToNewArtTypeId.clear();
990  oldArtifactIdToNewArtifact.clear();
991 
993 
994  currentCase = null;
995  caseFolder = null;
996  copiedFilesFolder = null;
997  }
998 
1002  private void closePortableCaseDatabase() {
1003  if (portableSkCase != null) {
1004  portableSkCase.close();
1005  portableSkCase = null;
1006  }
1007  }
1008 
1009  /*@Override
1010  public JPanel getConfigurationPanel() {
1011  configPanel = new CreatePortableCasePanel();
1012  return configPanel;
1013  } */
1014 
1015  private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1016 
1017  private final String tableName;
1018 
1019  StoreMaxIdCallback(String tableName) {
1020  this.tableName = tableName;
1021  }
1022 
1023  @Override
1024  public void process(ResultSet rs) {
1025 
1026  try {
1027  while (rs.next()) {
1028  try {
1029  Long maxId = rs.getLong("max_id"); // NON-NLS
1030  String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')"; // NON-NLS
1031  portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
1032 
1033  } catch (SQLException ex) {
1034  logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex); // NON-NLS
1035  } catch (TskCoreException ex) {
1036  logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
1037  }
1038 
1039  }
1040  } catch (SQLException ex) {
1041  logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
1042  }
1043  }
1044  }
1045 
1046  @NbBundle.Messages({
1047  "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
1048  "# {0} - Temp folder path",
1049  "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
1050  "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
1051  "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",
1052  })
1053  private boolean compressCase(ReportProgressPanel progressPanel) {
1054 
1055  // Close the portable case database (we still need some of the variables that would be cleared by cleanup())
1057 
1058  // Make a temporary folder for the compressed case
1059  File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS
1060  if (! tempZipFolder.mkdir()) {
1061  handleError("Error creating temporary folder " + tempZipFolder.toString(),
1062  Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
1063  return false;
1064  }
1065 
1066  // Find 7-Zip
1067  File sevenZipExe = locate7ZipExecutable();
1068  if (sevenZipExe == null) {
1069  handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
1070  return false;
1071  }
1072 
1073  // Create the chunk option
1074  String chunkOption = "";
1076  chunkOption = "-v" + settings.getChunkSize().getSevenZipParam();
1077  }
1078 
1079  File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
1080  ProcessBuilder procBuilder = new ProcessBuilder();
1081  procBuilder.command(
1082  sevenZipExe.getAbsolutePath(),
1083  "a", // Add to archive
1084  zipFile.getAbsolutePath(),
1085  caseFolder.getAbsolutePath(),
1086  chunkOption
1087  );
1088 
1089  try {
1090  Process process = procBuilder.start();
1091 
1092  while (process.isAlive()) {
1093  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
1094  process.destroy();
1095  return false;
1096  }
1097  Thread.sleep(200);
1098  }
1099  int exitCode = process.exitValue();
1100  if (exitCode != 0) {
1101  // Save any errors so they can be logged
1102  StringBuilder sb = new StringBuilder();
1103  try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
1104  String line;
1105  while ((line = br.readLine()) != null) {
1106  sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
1107  }
1108  }
1109 
1110  handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
1111  return false;
1112  }
1113  } catch (IOException | InterruptedException ex) {
1114  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1115  return false;
1116  }
1117 
1118  // Delete everything in the case folder then copy over the compressed file(s)
1119  try {
1120  FileUtils.cleanDirectory(caseFolder);
1121  FileUtils.copyDirectory(tempZipFolder, caseFolder);
1122  FileUtils.deleteDirectory(tempZipFolder);
1123  } catch (IOException ex) {
1124  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1125  return false;
1126  }
1127 
1128  return true;
1129  }
1130 
1136  private static File locate7ZipExecutable() {
1137  if (!PlatformUtil.isWindowsOS()) {
1138  return null;
1139  }
1140 
1141  String executableToFindName = Paths.get("7-Zip", "7z.exe").toString(); // NON-NLS
1142  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PortableCaseReportModule.class.getPackage().getName(), false);
1143  if (null == exeFile) {
1144  return null;
1145  }
1146 
1147  if (!exeFile.canExecute()) {
1148  return null;
1149  }
1150 
1151  return exeFile;
1152  }
1153 
1157  public static class GetInterestingItemSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1158 
1159  private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GetInterestingItemSetNamesCallback.class.getName());
1160  private final Map<String, Long> setCounts = new HashMap<>();
1161 
1162  @Override
1163  public void process(ResultSet rs) {
1164  try {
1165  while (rs.next()) {
1166  try {
1167  Long setCount = rs.getLong("set_count"); // NON-NLS
1168  String setName = rs.getString("set_name"); // NON-NLS
1169 
1170  setCounts.put(setName, setCount);
1171 
1172  } catch (SQLException ex) {
1173  logger.log(Level.WARNING, "Unable to get data_source_obj_id or value from result set", ex); // NON-NLS
1174  }
1175  }
1176  } catch (SQLException ex) {
1177  logger.log(Level.WARNING, "Failed to get next result for values by datasource", ex); // NON-NLS
1178  }
1179  }
1180 
1186  public Map<String, Long> getSetCountMap() {
1187  return setCounts;
1188  }
1189  }
1190 }
void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel)
long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel)
void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
Logger(String name, String resourceBundleName)
Definition: Logger.java:160
BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute)
void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel)
void generateReport(String reportPath, PortableCaseReportModuleSettings options, ReportProgressPanel progressPanel)
List< BlackboardArtifactTag > getBlackboardArtifactTagsByTagName(TagName tagName)
BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy)
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2057
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void export(List< TagName > tagTypes, List< String > interestingItemSets, File caseReportFolder, ReportProgressPanel progressPanel)
List< ContentTag > getContentTagsByTagName(TagName tagName)

Copyright © 2012-2019 Basis Technology. Generated on: Tue Jan 7 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.