Autopsy  4.11.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;
20 
21 import java.util.logging.Level;
22 import java.io.BufferedReader;
23 import java.io.File;
24 import java.io.InputStreamReader;
25 import java.io.IOException;
26 import java.nio.file.Paths;
27 import java.sql.ResultSet;
28 import java.sql.SQLException;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import org.apache.commons.io.FileUtils;
35 import org.openide.modules.InstalledFileLocator;
36 import org.openide.util.NbBundle;
45 import org.sleuthkit.datamodel.AbstractFile;
46 import org.sleuthkit.datamodel.BlackboardArtifact;
47 import org.sleuthkit.datamodel.BlackboardArtifactTag;
48 import org.sleuthkit.datamodel.BlackboardAttribute;
49 import org.sleuthkit.datamodel.CaseDbAccessManager;
50 import org.sleuthkit.datamodel.Content;
51 import org.sleuthkit.datamodel.ContentTag;
52 import org.sleuthkit.datamodel.FileSystem;
53 import org.sleuthkit.datamodel.Image;
54 import org.sleuthkit.datamodel.LocalFilesDataSource;
55 import org.sleuthkit.datamodel.SleuthkitCase;
56 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
57 import org.sleuthkit.datamodel.TagName;
58 import org.sleuthkit.datamodel.TskCoreException;
59 import org.sleuthkit.datamodel.TskDataException;
60 import org.sleuthkit.datamodel.TskData;
61 import org.sleuthkit.datamodel.Volume;
62 import org.sleuthkit.datamodel.VolumeSystem;
63 
67 class PortableCaseReportModule implements ReportModule {
68  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
69  private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
70  private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
71  private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids"; // NON-NLS
72  private PortableCaseOptions options;
73 
74  // These are the types for the exported file subfolders
75  private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
76  FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO);
77 
78  private Case currentCase = null;
79  private SleuthkitCase portableSkCase = null;
80  private final String caseName;
81  private File caseFolder = null;
82  private File copiedFilesFolder = null;
83 
84  // Maps old object ID from current case to new object in portable case
85  private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
86 
87  // Maps new object ID to the new object
88  private final Map<Long, Content> newIdToContent = new HashMap<>();
89 
90  // Maps old TagName to new TagName
91  private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
92 
93  // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
94  private final Map<Integer, Integer> oldArtTypeIdToNewArtTypeId = new HashMap<>();
95 
96  // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
97  private final Map<Integer, BlackboardAttribute.Type> oldAttrTypeIdToNewAttrType = new HashMap<>();
98 
99  // Map of old artifact ID to new artifact
100  private final Map<Long, BlackboardArtifact> oldArtifactIdToNewArtifact = new HashMap<>();
101 
102  PortableCaseReportModule() {
103  caseName = Case.getCurrentCase().getDisplayName() + " (Portable)"; // NON-NLS
104  }
105 
106  @NbBundle.Messages({
107  "PortableCaseReportModule.getName.name=Portable Case"
108  })
109  @Override
110  public String getName() {
111  return Bundle.PortableCaseReportModule_getName_name();
112  }
113 
114  @NbBundle.Messages({
115  "PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared"
116  })
117  @Override
118  public String getDescription() {
119  return Bundle.PortableCaseReportModule_getDescription_description();
120  }
121 
122  @Override
123  public String getRelativeFilePath() {
124  return caseName;
125  }
126 
132  private void handleCancellation(ReportProgressPanel progressPanel) {
133  logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
134  progressPanel.setIndeterminate(false);
135  progressPanel.complete(ReportProgressPanel.ReportStatus.CANCELED);
136  cleanup();
137  }
138 
149  private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
150  if (ex == null) {
151  logger.log(Level.WARNING, logWarning);
152  } else {
153  logger.log(Level.SEVERE, logWarning, ex);
154  }
155  MessageNotifyUtil.Message.error(dialogWarning);
156  progressPanel.setIndeterminate(false);
157  progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR);
158  cleanup();
159  }
160 
161  @NbBundle.Messages({
162  "PortableCaseReportModule.generateReport.verifying=Verifying selected parameters...",
163  "PortableCaseReportModule.generateReport.creatingCase=Creating portable case database...",
164  "PortableCaseReportModule.generateReport.copyingTags=Copying tags...",
165  "# {0} - tag name",
166  "PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0}...",
167  "# {0} - tag name",
168  "PortableCaseReportModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...",
169  "# {0} - output folder",
170  "PortableCaseReportModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
171  "# {0} - output folder",
172  "PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
173  "PortableCaseReportModule.generateReport.caseClosed=Current case has been closed",
174  "PortableCaseReportModule.generateReport.interestingItemError=Error loading intersting items",
175  "PortableCaseReportModule.generateReport.noContentToCopy=No interesting files, results, or tagged items to copy",
176  "PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags",
177  "PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files",
178  "PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts",
179  "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files",
180  "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results",
181  "# {0} - attribute type name",
182  "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}",
183  "PortableCaseReportModule.generateReport.compressingCase=Compressing case...",
184  })
185 
186  void generateReport(String reportPath, PortableCaseOptions options, ReportProgressPanel progressPanel) {
187  this.options = options;
188  progressPanel.setIndeterminate(true);
189  progressPanel.start();
190  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
191 
192  // Clear out any old values
193  cleanup();
194 
195  // Validate the input parameters
196  File outputDir = new File(reportPath);
197  if (! outputDir.exists()) {
198  handleError("Output folder " + outputDir.toString() + " does not exist",
199  Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
200  return;
201  }
202 
203  if (! outputDir.isDirectory()) {
204  handleError("Output folder " + outputDir.toString() + " is not a folder",
205  Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
206  return;
207  }
208 
209  // Save the current case object
210  try {
211  currentCase = Case.getCurrentCaseThrows();
212  } catch (NoCurrentCaseException ex) {
213  handleError("Current case has been closed",
214  Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
215  return;
216  }
217 
218  // Check that there will be something to copy
219  List<TagName> tagNames = options.getSelectedTagNames();
220  List<String> setNames = options.getSelectedSetNames();
221  if (tagNames.isEmpty() && setNames.isEmpty()) {
222  handleError("No content to copy",
223  Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
224  return;
225  }
226 
227  // Create the case.
228  // portableSkCase and caseFolder will be set here.
229  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
230  createCase(outputDir, progressPanel);
231  if (portableSkCase == null) {
232  // The error has already been handled
233  return;
234  }
235 
236  // Check for cancellation
237  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
238  handleCancellation(progressPanel);
239  return;
240  }
241 
242  // Copy the selected tags
243  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
244  try {
245  for(TagName tagName:tagNames) {
246  TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
247  oldTagNameToNewTagName.put(tagName, newTagName);
248  }
249  } catch (TskCoreException ex) {
250  handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
251  return;
252  }
253 
254  // Set up tracking to support any custom artifact or attribute types
255  for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
256  oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
257  }
258  for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
259  try {
260  oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
261  } catch (TskCoreException ex) {
262  handleError("Error looking up attribute name " + type.getLabel(),
263  Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
264  ex, progressPanel); // NON-NLS
265  }
266  }
267 
268  // Copy the tagged files
269  try {
270  for(TagName tagName:tagNames) {
271  // Check for cancellation
272  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
273  handleCancellation(progressPanel);
274  return;
275  }
276  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
277  addFilesToPortableCase(tagName, progressPanel);
278 
279  // Check for cancellation
280  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
281  handleCancellation(progressPanel);
282  return;
283  }
284  }
285  } catch (TskCoreException ex) {
286  handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
287  return;
288  }
289 
290  // Copy the tagged artifacts and associated files
291  try {
292  for(TagName tagName:tagNames) {
293  // Check for cancellation
294  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
295  handleCancellation(progressPanel);
296  return;
297  }
298  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
299  addArtifactsToPortableCase(tagName, progressPanel);
300 
301  // Check for cancellation
302  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
303  handleCancellation(progressPanel);
304  return;
305  }
306  }
307  } catch (TskCoreException ex) {
308  handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
309  return;
310  }
311 
312  // Copy interesting files and results
313  if (! setNames.isEmpty()) {
314  try {
315  List<BlackboardArtifact> interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
316  for (BlackboardArtifact art:interestingFiles) {
317  // Check for cancellation
318  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
319  handleCancellation(progressPanel);
320  return;
321  }
322 
323  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
324  if (setNames.contains(setAttr.getValueString())) {
325  copyContentToPortableCase(art, progressPanel);
326  }
327  }
328  } catch (TskCoreException ex) {
329  handleError("Error copying interesting files", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingFiles(), ex, progressPanel); // NON-NLS
330  return;
331  }
332 
333  try {
334  List<BlackboardArtifact> interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
335  for (BlackboardArtifact art:interestingResults) {
336  // Check for cancellation
337  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
338  handleCancellation(progressPanel);
339  return;
340  }
341  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
342  if (setNames.contains(setAttr.getValueString())) {
343  copyContentToPortableCase(art, progressPanel);
344  }
345  }
346  } catch (TskCoreException ex) {
347  handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
348  return;
349  }
350  }
351 
352  // Check for cancellation
353  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
354  handleCancellation(progressPanel);
355  return;
356  }
357 
358  // Compress the case (if desired)
359  if (options.shouldCompress()) {
360  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
361 
362  boolean success = compressCase(progressPanel);
363 
364  // Check for cancellation
365  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
366  handleCancellation(progressPanel);
367  return;
368  }
369 
370  if (! success) {
371  // Errors have been handled already
372  return;
373  }
374  }
375 
376  // Close the case connections and clear out the maps
377  cleanup();
378 
379  progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
380 
381  }
382 
390  @NbBundle.Messages({
391  "# {0} - case folder",
392  "PortableCaseReportModule.createCase.caseDirExists=Case folder {0} already exists",
393  "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
394  "# {0} - folder",
395  "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
396  "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
397  })
398  private void createCase(File outputDir, ReportProgressPanel progressPanel) {
399 
400  // Create the case folder
401  caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
402 
403  if (caseFolder.exists()) {
404  handleError("Case folder " + caseFolder.toString() + " already exists",
405  Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
406  return;
407  }
408 
409  // Create the case
410  try {
411  portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
412  } catch (TskCoreException ex) {
413  handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
414  Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
415  return;
416  }
417 
418  // Store the highest IDs
419  try {
420  saveHighestIds();
421  } catch (TskCoreException ex) {
422  handleError("Error storing maximum database IDs",
423  Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
424  return;
425  }
426 
427  // Create the base folder for the copied files
428  copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
429  if (! copiedFilesFolder.mkdir()) {
430  handleError("Error creating folder " + copiedFilesFolder.toString(),
431  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
432  return;
433  }
434 
435  // Create subfolders for the copied files
436  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
437  File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
438  if (! subFolder.mkdir()) {
439  handleError("Error creating folder " + subFolder.toString(),
440  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
441  return;
442  }
443  }
444  File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
445  if (! unknownTypeFolder.mkdir()) {
446  handleError("Error creating folder " + unknownTypeFolder.toString(),
447  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
448  return;
449  }
450 
451  }
452 
458  private void saveHighestIds() throws TskCoreException {
459 
460  CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
461 
462  String tableSchema = "( table_name TEXT PRIMARY KEY, "
463  + " max_id TEXT)"; // NON-NLS
464 
465  portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
466 
467  currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
468  currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags")); // NON-NLS
469  currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
470  currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
471  }
472 
481  private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
482 
483  // Get all the tags in the current case
484  List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
485 
486  // Copy the files into the portable case and tag
487  for (ContentTag tag : tags) {
488 
489  // Check for cancellation
490  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
491  return;
492  }
493 
494  Content content = tag.getContent();
495  if (content instanceof AbstractFile) {
496  long newFileId = copyContentToPortableCase(content, progressPanel);
497 
498  // Tag the file
499  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
500  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
501  }
502  portableSkCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
503  }
504  }
505  }
506 
515  private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
516 
517  List<BlackboardArtifactTag> tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
518 
519  // Copy the artifacts into the portable case along with their content and tag
520  for (BlackboardArtifactTag tag : tags) {
521 
522  // Check for cancellation
523  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
524  return;
525  }
526 
527  // Copy the source content
528  Content content = tag.getContent();
529  long newContentId = copyContentToPortableCase(content, progressPanel);
530 
531  // Copy the artifact
532  BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
533 
534  // Tag the artfiact
535  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
536  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
537  }
538  portableSkCase.addBlackboardArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
539  }
540  }
541 
552  private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
553 
554  if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
555  return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
556  }
557 
558  // First create the associated artifact (if present)
559  BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
560  List<BlackboardAttribute> newAttrs = new ArrayList<>();
561  if (oldAssociatedAttribute != null) {
562  BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
563  BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
564  newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
565  String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
566  }
567 
568  // Create the new artifact
569  int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
570  BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId);
571  List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
572 
573  // Copy over each attribute, making sure the type is in the new case.
574  for (BlackboardAttribute oldAttr:oldAttrs) {
575 
576  // The associated artifact has already been handled
577  if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
578  continue;
579  }
580 
581  BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
582  switch (oldAttr.getValueType()) {
583  case BYTE:
584  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
585  oldAttr.getValueBytes()));
586  break;
587  case DOUBLE:
588  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
589  oldAttr.getValueDouble()));
590  break;
591  case INTEGER:
592  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
593  oldAttr.getValueInt()));
594  break;
595  case DATETIME:
596  case LONG:
597  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
598  oldAttr.getValueLong()));
599  break;
600  case STRING:
601  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
602  oldAttr.getValueString()));
603  break;
604  default:
605  throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
606  }
607  }
608 
609  newArtifact.addAttributes(newAttrs);
610 
611  oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
612  return newArtifact;
613  }
614 
623  private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
624  if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
625  return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
626  }
627 
628  BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
629  try {
630  BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
631  oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID());
632  return newCustomType.getTypeID();
633  } catch (TskDataException ex) {
634  throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
635  }
636  }
637 
646  private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
647  BlackboardAttribute.Type oldAttrType = oldAttribute.getAttributeType();
648  if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
649  return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
650  }
651 
652  try {
653  BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(),
654  oldAttrType.getValueType(), oldAttrType.getDisplayName());
655  oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
656  return newCustomType;
657  } catch (TskDataException ex) {
658  throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS
659  }
660  }
661 
672  @NbBundle.Messages({
673  "# {0} - File name",
674  "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",
675  })
676  private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
677  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
678  return copyContent(content);
679  }
680 
690  private long copyContent(Content content) throws TskCoreException {
691 
692  // Check if we've already copied this content
693  if (oldIdToNewContent.containsKey(content.getId())) {
694  return oldIdToNewContent.get(content.getId()).getId();
695  }
696 
697  // Otherwise:
698  // - Make parent of this object (if applicable)
699  // - Copy this content
700  long parentId = 0;
701  if (content.getParent() != null) {
702  parentId = copyContent(content.getParent());
703  }
704 
705  Content newContent;
706  if (content instanceof BlackboardArtifact) {
707  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
708  newContent = copyArtifact(parentId, artifactToCopy);
709  } else {
710  CaseDbTransaction trans = portableSkCase.beginTransaction();
711  try {
712  if (content instanceof Image) {
713  Image image = (Image)content;
714  newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
715  new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
716  } else if (content instanceof VolumeSystem) {
717  VolumeSystem vs = (VolumeSystem)content;
718  newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
719  } else if (content instanceof Volume) {
720  Volume vs = (Volume)content;
721  newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
722  vs.getDescription(), vs.getFlags(), trans);
723  } else if (content instanceof FileSystem) {
724  FileSystem fs = (FileSystem)content;
725  newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
726  fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
727  fs.getName(), trans);
728  } else if (content instanceof BlackboardArtifact) {
729  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
730  newContent = copyArtifact(parentId, artifactToCopy);
731  } else if (content instanceof AbstractFile) {
732  AbstractFile abstractFile = (AbstractFile)content;
733 
734  if (abstractFile instanceof LocalFilesDataSource) {
735  LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
736  newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
737  } else {
738  if (abstractFile.isDir()) {
739  newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
740  } else {
741  try {
742  // Copy the file
743  String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
744  String exportSubFolder = getExportSubfolder(abstractFile);
745  File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
746  File localFile = new File(exportFolder, fileName);
747  ContentUtils.writeToFile(abstractFile, localFile);
748 
749  // Get the new parent object in the portable case database
750  Content oldParent = abstractFile.getParent();
751  if (! oldIdToNewContent.containsKey(oldParent.getId())) {
752  throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
753  }
754  Content newParent = oldIdToNewContent.get(oldParent.getId());
755 
756  // Construct the relative path to the copied file
757  String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
758 
759  newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
760  abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
761  abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
762  true, TskData.EncodingType.NONE,
763  newParent, trans);
764  } catch (IOException ex) {
765  throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
766  + abstractFile.getId(), ex); // NON-NLS
767  }
768  }
769  }
770  } else {
771  throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
772  }
773  trans.commit();
774  } catch (TskCoreException ex) {
775  trans.rollback();
776  throw(ex);
777  }
778  }
779 
780  // Save the new object
781  oldIdToNewContent.put(content.getId(), newContent);
782  newIdToContent.put(newContent.getId(), newContent);
783  return oldIdToNewContent.get(content.getId()).getId();
784  }
785 
793  private String getExportSubfolder(AbstractFile abstractFile) {
794  if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
795  return UNKNOWN_FILE_TYPE_FOLDER;
796  }
797 
798  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
799  if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
800  return cat.getDisplayName();
801  }
802  }
803  return UNKNOWN_FILE_TYPE_FOLDER;
804  }
805 
809  private void cleanup() {
810  oldIdToNewContent.clear();
811  newIdToContent.clear();
812  oldTagNameToNewTagName.clear();
813  oldArtTypeIdToNewArtTypeId.clear();
814  oldAttrTypeIdToNewAttrType.clear();
815  oldArtifactIdToNewArtifact.clear();
816 
817  closePortableCaseDatabase();
818 
819  currentCase = null;
820  caseFolder = null;
821  copiedFilesFolder = null;
822  }
823 
827  private void closePortableCaseDatabase() {
828  if (portableSkCase != null) {
829  portableSkCase.close();
830  portableSkCase = null;
831  }
832  }
833 
834  /*@Override
835  public JPanel getConfigurationPanel() {
836  configPanel = new CreatePortableCasePanel();
837  return configPanel;
838  } */
839 
840  private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
841 
842  private final String tableName;
843 
844  StoreMaxIdCallback(String tableName) {
845  this.tableName = tableName;
846  }
847 
848  @Override
849  public void process(ResultSet rs) {
850 
851  try {
852  while (rs.next()) {
853  try {
854  Long maxId = rs.getLong("max_id"); // NON-NLS
855  String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')"; // NON-NLS
856  portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
857 
858  } catch (SQLException ex) {
859  logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex); // NON-NLS
860  } catch (TskCoreException ex) {
861  logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
862  }
863 
864  }
865  } catch (SQLException ex) {
866  logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
867  }
868  }
869  }
870 
871  @NbBundle.Messages({
872  "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
873  "# {0} - Temp folder path",
874  "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
875  "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
876  "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",
877  })
878  private boolean compressCase(ReportProgressPanel progressPanel) {
879 
880  // Close the portable case database (we still need some of the variables that would be cleared by cleanup())
881  closePortableCaseDatabase();
882 
883  // Make a temporary folder for the compressed case
884  File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS
885  if (! tempZipFolder.mkdir()) {
886  handleError("Error creating temporary folder " + tempZipFolder.toString(),
887  Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
888  return false;
889  }
890 
891  // Find 7-Zip
892  File sevenZipExe = locate7ZipExecutable();
893  if (sevenZipExe == null) {
894  handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
895  return false;
896  }
897 
898  // Create the chunk option
899  String chunkOption = "";
900  if (options.getChunkSize() != ChunkSize.NONE) {
901  chunkOption = "-v" + options.getChunkSize().getSevenZipParam();
902  }
903 
904  File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
905  ProcessBuilder procBuilder = new ProcessBuilder();
906  procBuilder.command(
907  sevenZipExe.getAbsolutePath(),
908  "a", // Add to archive
909  zipFile.getAbsolutePath(),
910  caseFolder.getAbsolutePath(),
911  chunkOption
912  );
913 
914  try {
915  Process process = procBuilder.start();
916 
917  while (process.isAlive()) {
918  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
919  process.destroy();
920  return false;
921  }
922  Thread.sleep(200);
923  }
924  int exitCode = process.exitValue();
925  if (exitCode != 0) {
926  // Save any errors so they can be logged
927  StringBuilder sb = new StringBuilder();
928  try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
929  String line;
930  while ((line = br.readLine()) != null) {
931  sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
932  }
933  }
934 
935  handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
936  return false;
937  }
938  } catch (IOException | InterruptedException ex) {
939  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
940  return false;
941  }
942 
943  // Delete everything in the case folder then copy over the compressed file(s)
944  try {
945  FileUtils.cleanDirectory(caseFolder);
946  FileUtils.copyDirectory(tempZipFolder, caseFolder);
947  FileUtils.deleteDirectory(tempZipFolder);
948  } catch (IOException ex) {
949  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
950  return false;
951  }
952 
953  return true;
954  }
955 
961  private static File locate7ZipExecutable() {
962  if (!PlatformUtil.isWindowsOS()) {
963  return null;
964  }
965 
966  String executableToFindName = Paths.get("7-Zip", "7z.exe").toString(); // NON-NLS
967  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PortableCaseReportModule.class.getPackage().getName(), false);
968  if (null == exeFile) {
969  return null;
970  }
971 
972  if (!exeFile.canExecute()) {
973  return null;
974  }
975 
976  return exeFile;
977  }
978 
983  enum ChunkSize {
984 
985  NONE("Do not split", ""), // NON-NLS
986  DVD("4.5 GB (DVD)", "4500m"); // NON-NLS
987 
988  private final String displayName;
989  private final String sevenZipParam;
990 
997  private ChunkSize(String displayName, String sevenZipParam) {
998  this.displayName = displayName;
999  this.sevenZipParam = sevenZipParam;
1000  }
1001 
1002  String getDisplayName() {
1003  return displayName;
1004  }
1005 
1006  String getSevenZipParam() {
1007  return sevenZipParam;
1008  }
1009 
1010  @Override
1011  public String toString() {
1012  return displayName;
1013  }
1014  }
1015 
1019  static class PortableCaseOptions {
1020 
1021  private final List<TagName> tagNames = new ArrayList<>();
1022  private final List<String> setNames = new ArrayList<>();
1023  private boolean compress;
1024  private ChunkSize chunkSize;
1025 
1026  PortableCaseOptions(List<String> setNames, List<TagName> tagNames,
1027  boolean compress, ChunkSize chunkSize) {
1028  this.setNames.addAll(setNames);
1029  this.tagNames.addAll(tagNames);
1030  this.compress = compress;
1031  this.chunkSize = chunkSize;
1032  }
1033 
1034  PortableCaseOptions() {
1035  this.compress = false;
1036  this.chunkSize = ChunkSize.NONE;
1037  }
1038 
1039  void updateSetNames(List<String> setNames) {
1040  this.setNames.clear();
1041  this.setNames.addAll(setNames);
1042  }
1043 
1044  void updateTagNames(List<TagName> tagNames) {
1045  this.tagNames.clear();
1046  this.tagNames.addAll(tagNames);
1047  }
1048 
1049  void updateCompression(boolean compress, ChunkSize chunkSize) {
1050  this.compress = compress;
1051  this.chunkSize = chunkSize;
1052  }
1053 
1054  boolean isValid() {
1055  return (( !setNames.isEmpty()) || ( ! tagNames.isEmpty()));
1056  }
1057 
1058  List<String> getSelectedSetNames() {
1059  return new ArrayList<>(setNames);
1060  }
1061 
1062  List<TagName> getSelectedTagNames() {
1063  return new ArrayList<>(tagNames);
1064  }
1065 
1066  boolean shouldCompress() {
1067  return compress;
1068  }
1069 
1070  ChunkSize getChunkSize() {
1071  return chunkSize;
1072  }
1073  }
1074 }

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