Autopsy  4.12.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;
47 import org.sleuthkit.datamodel.AbstractFile;
48 import org.sleuthkit.datamodel.BlackboardArtifact;
49 import org.sleuthkit.datamodel.BlackboardArtifactTag;
50 import org.sleuthkit.datamodel.BlackboardAttribute;
51 import org.sleuthkit.datamodel.CaseDbAccessManager;
52 import org.sleuthkit.datamodel.Content;
53 import org.sleuthkit.datamodel.ContentTag;
54 import org.sleuthkit.datamodel.FileSystem;
55 import org.sleuthkit.datamodel.Image;
56 import org.sleuthkit.datamodel.LocalFilesDataSource;
57 import org.sleuthkit.datamodel.SleuthkitCase;
58 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
59 import org.sleuthkit.datamodel.TagName;
60 import org.sleuthkit.datamodel.TskCoreException;
61 import org.sleuthkit.datamodel.TskDataException;
62 import org.sleuthkit.datamodel.TskData;
63 import org.sleuthkit.datamodel.Volume;
64 import org.sleuthkit.datamodel.VolumeSystem;
65 
69 class PortableCaseReportModule implements ReportModule {
70  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
71  private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
72  private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
73  private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids"; // NON-NLS
74  private PortableCaseOptions options;
75 
76  // These are the types for the exported file subfolders
77  private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
78  FileTypeCategory.EXECUTABLE, FileTypeCategory.IMAGE, FileTypeCategory.VIDEO);
79 
80  private Case currentCase = null;
81  private SleuthkitCase portableSkCase = null;
82  private final String caseName;
83  private File caseFolder = null;
84  private File copiedFilesFolder = null;
85 
86  // Maps old object ID from current case to new object in portable case
87  private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
88 
89  // Maps new object ID to the new object
90  private final Map<Long, Content> newIdToContent = new HashMap<>();
91 
92  // Maps old TagName to new TagName
93  private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
94 
95  // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
96  private final Map<Integer, Integer> oldArtTypeIdToNewArtTypeId = new HashMap<>();
97 
98  // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
99  private final Map<Integer, BlackboardAttribute.Type> oldAttrTypeIdToNewAttrType = new HashMap<>();
100 
101  // Map of old artifact ID to new artifact
102  private final Map<Long, BlackboardArtifact> oldArtifactIdToNewArtifact = new HashMap<>();
103 
104  PortableCaseReportModule() {
105  caseName = Case.getCurrentCase().getDisplayName() + " (Portable)"; // NON-NLS
106  }
107 
108  @NbBundle.Messages({
109  "PortableCaseReportModule.getName.name=Portable Case"
110  })
111  @Override
112  public String getName() {
113  return Bundle.PortableCaseReportModule_getName_name();
114  }
115 
116  @NbBundle.Messages({
117  "PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared"
118  })
119  @Override
120  public String getDescription() {
121  return Bundle.PortableCaseReportModule_getDescription_description();
122  }
123 
124  @Override
125  public String getRelativeFilePath() {
126  return caseName;
127  }
128 
134  private void handleCancellation(ReportProgressPanel progressPanel) {
135  logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
136  progressPanel.setIndeterminate(false);
137  progressPanel.complete(ReportProgressPanel.ReportStatus.CANCELED);
138  cleanup();
139  }
140 
151  private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
152  if (ex == null) {
153  logger.log(Level.WARNING, logWarning);
154  } else {
155  logger.log(Level.SEVERE, logWarning, ex);
156  }
157  MessageNotifyUtil.Message.error(dialogWarning);
158  progressPanel.setIndeterminate(false);
159  progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR);
160  cleanup();
161  }
162 
163  @NbBundle.Messages({
164  "PortableCaseReportModule.generateReport.verifying=Verifying selected parameters...",
165  "PortableCaseReportModule.generateReport.creatingCase=Creating portable case database...",
166  "PortableCaseReportModule.generateReport.copyingTags=Copying tags...",
167  "# {0} - tag name",
168  "PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0}...",
169  "# {0} - tag name",
170  "PortableCaseReportModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...",
171  "# {0} - output folder",
172  "PortableCaseReportModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
173  "# {0} - output folder",
174  "PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
175  "PortableCaseReportModule.generateReport.caseClosed=Current case has been closed",
176  "PortableCaseReportModule.generateReport.interestingItemError=Error loading intersting items",
177  "PortableCaseReportModule.generateReport.noContentToCopy=No interesting files, results, or tagged items to copy",
178  "PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags",
179  "PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files",
180  "PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts",
181  "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files",
182  "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results",
183  "PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table",
184  "# {0} - attribute type name",
185  "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}",
186  "PortableCaseReportModule.generateReport.compressingCase=Compressing case...",
187  "PortableCaseReportModule.generateReport.errorCreatingReportFolder=Could not make report folder",
188  "PortableCaseReportModule.generateReport.errorGeneratingUCOreport=Problem while generating CASE-UCO report"
189  })
190 
191  void generateReport(String reportPath, PortableCaseOptions options, ReportProgressPanel progressPanel) {
192  this.options = options;
193  progressPanel.setIndeterminate(true);
194  progressPanel.start();
195  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
196 
197  // Clear out any old values
198  cleanup();
199 
200  // Validate the input parameters
201  File outputDir = new File(reportPath);
202  if (! outputDir.exists()) {
203  handleError("Output folder " + outputDir.toString() + " does not exist",
204  Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
205  return;
206  }
207 
208  if (! outputDir.isDirectory()) {
209  handleError("Output folder " + outputDir.toString() + " is not a folder",
210  Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
211  return;
212  }
213 
214  // Save the current case object
215  try {
216  currentCase = Case.getCurrentCaseThrows();
217  } catch (NoCurrentCaseException ex) {
218  handleError("Current case has been closed",
219  Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
220  return;
221  }
222 
223  // Check that there will be something to copy
224  List<TagName> tagNames = options.getSelectedTagNames();
225  List<String> setNames = options.getSelectedSetNames();
226  if (tagNames.isEmpty() && setNames.isEmpty()) {
227  handleError("No content to copy",
228  Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
229  return;
230  }
231 
232  // Create the case.
233  // portableSkCase and caseFolder will be set here.
234  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
235  createCase(outputDir, progressPanel);
236  if (portableSkCase == null) {
237  // The error has already been handled
238  return;
239  }
240 
241  // Check for cancellation
242  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
243  handleCancellation(progressPanel);
244  return;
245  }
246 
247  // Set up the table for the image tags
248  try {
249  initializeImageTags(progressPanel);
250  } catch (TskCoreException ex) {
251  handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS
252  return;
253  }
254 
255  // Copy the selected tags
256  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
257  try {
258  for(TagName tagName:tagNames) {
259  TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
260  oldTagNameToNewTagName.put(tagName, newTagName);
261  }
262  } catch (TskCoreException ex) {
263  handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
264  return;
265  }
266 
267  // Set up tracking to support any custom artifact or attribute types
268  for (BlackboardArtifact.ARTIFACT_TYPE type:BlackboardArtifact.ARTIFACT_TYPE.values()) {
269  oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
270  }
271  for (BlackboardAttribute.ATTRIBUTE_TYPE type:BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
272  try {
273  oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
274  } catch (TskCoreException ex) {
275  handleError("Error looking up attribute name " + type.getLabel(),
276  Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
277  ex, progressPanel); // NON-NLS
278  }
279  }
280 
281  // Copy the tagged files
282  try {
283  for(TagName tagName:tagNames) {
284  // Check for cancellation
285  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
286  handleCancellation(progressPanel);
287  return;
288  }
289  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
290  addFilesToPortableCase(tagName, progressPanel);
291 
292  // Check for cancellation
293  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
294  handleCancellation(progressPanel);
295  return;
296  }
297  }
298  } catch (TskCoreException ex) {
299  handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
300  return;
301  }
302 
303  // Copy the tagged artifacts and associated files
304  try {
305  for(TagName tagName:tagNames) {
306  // Check for cancellation
307  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
308  handleCancellation(progressPanel);
309  return;
310  }
311  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
312  addArtifactsToPortableCase(tagName, progressPanel);
313 
314  // Check for cancellation
315  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
316  handleCancellation(progressPanel);
317  return;
318  }
319  }
320  } catch (TskCoreException ex) {
321  handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
322  return;
323  }
324 
325  // Copy interesting files and results
326  if (! setNames.isEmpty()) {
327  try {
328  List<BlackboardArtifact> interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
329  for (BlackboardArtifact art:interestingFiles) {
330  // Check for cancellation
331  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
332  handleCancellation(progressPanel);
333  return;
334  }
335 
336  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
337  if (setNames.contains(setAttr.getValueString())) {
338  copyContentToPortableCase(art, progressPanel);
339  }
340  }
341  } catch (TskCoreException ex) {
342  handleError("Error copying interesting files", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingFiles(), ex, progressPanel); // NON-NLS
343  return;
344  }
345 
346  try {
347  List<BlackboardArtifact> interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
348  for (BlackboardArtifact art:interestingResults) {
349  // Check for cancellation
350  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
351  handleCancellation(progressPanel);
352  return;
353  }
354  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
355  if (setNames.contains(setAttr.getValueString())) {
356  copyContentToPortableCase(art, progressPanel);
357  }
358  }
359  } catch (TskCoreException ex) {
360  handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
361  return;
362  }
363  }
364 
365  // Check for cancellation
366  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
367  handleCancellation(progressPanel);
368  return;
369  }
370 
371  File reportsFolder = Paths.get(caseFolder.toString(), "Reports").toFile();
372  if(!reportsFolder.mkdir()) {
373  handleError("Could not make report folder", Bundle.PortableCaseReportModule_generateReport_errorCreatingReportFolder(), null, progressPanel); // NON-NLS
374  return;
375  }
376 
377  try {
378  CaseUcoFormatExporter.export(tagNames, setNames, reportsFolder, progressPanel);
379  } catch (IOException | SQLException | NoCurrentCaseException | TskCoreException ex) {
380  handleError("Problem while generating CASE-UCO report",
381  Bundle.PortableCaseReportModule_generateReport_errorGeneratingUCOreport(), ex, progressPanel); // NON-NLS
382  }
383 
384  // Compress the case (if desired)
385  if (options.shouldCompress()) {
386  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
387 
388  boolean success = compressCase(progressPanel);
389 
390  // Check for cancellation
391  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
392  handleCancellation(progressPanel);
393  return;
394  }
395 
396  if (! success) {
397  // Errors have been handled already
398  return;
399  }
400  }
401 
402  // Close the case connections and clear out the maps
403  cleanup();
404 
405  progressPanel.complete(ReportProgressPanel.ReportStatus.COMPLETE);
406 
407  }
408 
416  @NbBundle.Messages({
417  "# {0} - case folder",
418  "PortableCaseReportModule.createCase.caseDirExists=Case folder {0} already exists",
419  "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
420  "# {0} - folder",
421  "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
422  "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",
423  })
424  private void createCase(File outputDir, ReportProgressPanel progressPanel) {
425 
426  // Create the case folder
427  caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
428 
429  if (caseFolder.exists()) {
430  handleError("Case folder " + caseFolder.toString() + " already exists",
431  Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
432  return;
433  }
434 
435  // Create the case
436  try {
437  portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
438  } catch (TskCoreException ex) {
439  handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
440  Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
441  return;
442  }
443 
444  // Store the highest IDs
445  try {
446  saveHighestIds();
447  } catch (TskCoreException ex) {
448  handleError("Error storing maximum database IDs",
449  Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
450  return;
451  }
452 
453  // Create the base folder for the copied files
454  copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
455  if (! copiedFilesFolder.mkdir()) {
456  handleError("Error creating folder " + copiedFilesFolder.toString(),
457  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
458  return;
459  }
460 
461  // Create subfolders for the copied files
462  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
463  File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
464  if (! subFolder.mkdir()) {
465  handleError("Error creating folder " + subFolder.toString(),
466  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
467  return;
468  }
469  }
470  File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
471  if (! unknownTypeFolder.mkdir()) {
472  handleError("Error creating folder " + unknownTypeFolder.toString(),
473  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
474  return;
475  }
476 
477  }
478 
484  private void saveHighestIds() throws TskCoreException {
485 
486  CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
487 
488  String tableSchema = "( table_name TEXT PRIMARY KEY, "
489  + " max_id TEXT)"; // NON-NLS
490 
491  portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
492 
493  currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
494  currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags")); // NON-NLS
495  currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
496  currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
497  }
498 
506  private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException {
507 
508  // Create the image tags table in the portable case
509  CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
510  if (! portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
511  portableDbAccessManager.createTable(ContentViewerTagManager.TABLE_NAME, ContentViewerTagManager.TABLE_SCHEMA_SQLITE);
512  }
513  }
514 
523  private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
524 
525  // Get all the tags in the current case
526  List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
527 
528  // Copy the files into the portable case and tag
529  for (ContentTag tag : tags) {
530 
531  // Check for cancellation
532  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
533  return;
534  }
535 
536  Content content = tag.getContent();
537  if (content instanceof AbstractFile) {
538 
539  long newFileId = copyContentToPortableCase(content, progressPanel);
540 
541  // Tag the file
542  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
543  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
544  }
545  ContentTag newContentTag = portableSkCase.addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
546 
547  // Get the image tag data associated with this tag (empty string if there is none)
548  // and save it if present
549  String appData = getImageTagDataForContentTag(tag);
550  if (! appData.isEmpty()) {
551  addImageTagToPortableCase(newContentTag, appData);
552  }
553  }
554  }
555  }
556 
566  private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException {
567 
568  GetImageTagCallback callback = new GetImageTagCallback();
569  String query = "* FROM " + ContentViewerTagManager.TABLE_NAME + " WHERE content_tag_id = " + tag.getId();
570  currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
571  return callback.getAppData();
572  }
573 
577  private static class GetImageTagCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
578 
579  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
580  private String appData = "";
581 
582  @Override
583  public void process(ResultSet rs) {
584  try {
585  while (rs.next()) {
586  try {
587  appData = rs.getString("app_data"); // NON-NLS
588  } catch (SQLException ex) {
589  logger.log(Level.WARNING, "Unable to get app_data from result set", ex); // NON-NLS
590  }
591  }
592  } catch (SQLException ex) {
593  logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS
594  }
595  }
596 
602  String getAppData() {
603  return appData;
604  }
605  }
606 
615  private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException {
616  String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')";
617  portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
618  }
619 
620 
629  private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
630 
631  List<BlackboardArtifactTag> tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
632 
633  // Copy the artifacts into the portable case along with their content and tag
634  for (BlackboardArtifactTag tag : tags) {
635 
636  // Check for cancellation
637  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
638  return;
639  }
640 
641  // Copy the source content
642  Content content = tag.getContent();
643  long newContentId = copyContentToPortableCase(content, progressPanel);
644 
645  // Copy the artifact
646  BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
647 
648  // Tag the artfiact
649  if (! oldTagNameToNewTagName.containsKey(tag.getName())) {
650  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
651  }
652  portableSkCase.addBlackboardArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
653  }
654  }
655 
666  private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
667 
668  if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
669  return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
670  }
671 
672  // First create the associated artifact (if present)
673  BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
674  List<BlackboardAttribute> newAttrs = new ArrayList<>();
675  if (oldAssociatedAttribute != null) {
676  BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
677  BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
678  newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
679  String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
680  }
681 
682  // Create the new artifact
683  int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
684  BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId);
685  List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
686 
687  // Copy over each attribute, making sure the type is in the new case.
688  for (BlackboardAttribute oldAttr:oldAttrs) {
689 
690  // The associated artifact has already been handled
691  if (oldAttr.getAttributeType().getTypeID() == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) {
692  continue;
693  }
694 
695  BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
696  switch (oldAttr.getValueType()) {
697  case BYTE:
698  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
699  oldAttr.getValueBytes()));
700  break;
701  case DOUBLE:
702  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
703  oldAttr.getValueDouble()));
704  break;
705  case INTEGER:
706  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
707  oldAttr.getValueInt()));
708  break;
709  case DATETIME:
710  case LONG:
711  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
712  oldAttr.getValueLong()));
713  break;
714  case STRING:
715  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
716  oldAttr.getValueString()));
717  break;
718  default:
719  throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
720  }
721  }
722 
723  newArtifact.addAttributes(newAttrs);
724 
725  oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
726  return newArtifact;
727  }
728 
737  private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
738  if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
739  return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
740  }
741 
742  BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
743  try {
744  BlackboardArtifact.Type newCustomType = portableSkCase.addBlackboardArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
745  oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID());
746  return newCustomType.getTypeID();
747  } catch (TskDataException ex) {
748  throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
749  }
750  }
751 
760  private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
761  BlackboardAttribute.Type oldAttrType = oldAttribute.getAttributeType();
762  if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
763  return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
764  }
765 
766  try {
767  BlackboardAttribute.Type newCustomType = portableSkCase.addArtifactAttributeType(oldAttrType.getTypeName(),
768  oldAttrType.getValueType(), oldAttrType.getDisplayName());
769  oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
770  return newCustomType;
771  } catch (TskDataException ex) {
772  throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS
773  }
774  }
775 
786  @NbBundle.Messages({
787  "# {0} - File name",
788  "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",
789  })
790  private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
791  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
792  return copyContent(content);
793  }
794 
804  private long copyContent(Content content) throws TskCoreException {
805 
806  // Check if we've already copied this content
807  if (oldIdToNewContent.containsKey(content.getId())) {
808  return oldIdToNewContent.get(content.getId()).getId();
809  }
810 
811  // Otherwise:
812  // - Make parent of this object (if applicable)
813  // - Copy this content
814  long parentId = 0;
815  if (content.getParent() != null) {
816  parentId = copyContent(content.getParent());
817  }
818 
819  Content newContent;
820  if (content instanceof BlackboardArtifact) {
821  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
822  newContent = copyArtifact(parentId, artifactToCopy);
823  } else {
824  CaseDbTransaction trans = portableSkCase.beginTransaction();
825  try {
826  if (content instanceof Image) {
827  Image image = (Image)content;
828  newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
829  new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
830  } else if (content instanceof VolumeSystem) {
831  VolumeSystem vs = (VolumeSystem)content;
832  newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
833  } else if (content instanceof Volume) {
834  Volume vs = (Volume)content;
835  newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
836  vs.getDescription(), vs.getFlags(), trans);
837  } else if (content instanceof FileSystem) {
838  FileSystem fs = (FileSystem)content;
839  newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
840  fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
841  fs.getName(), trans);
842  } else if (content instanceof BlackboardArtifact) {
843  BlackboardArtifact artifactToCopy = (BlackboardArtifact)content;
844  newContent = copyArtifact(parentId, artifactToCopy);
845  } else if (content instanceof AbstractFile) {
846  AbstractFile abstractFile = (AbstractFile)content;
847 
848  if (abstractFile instanceof LocalFilesDataSource) {
849  LocalFilesDataSource localFilesDS = (LocalFilesDataSource)abstractFile;
850  newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
851  } else {
852  if (abstractFile.isDir()) {
853  newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
854  } else {
855  try {
856  // Copy the file
857  String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
858  String exportSubFolder = getExportSubfolder(abstractFile);
859  File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
860  File localFile = new File(exportFolder, fileName);
861  ContentUtils.writeToFile(abstractFile, localFile);
862 
863  // Get the new parent object in the portable case database
864  Content oldParent = abstractFile.getParent();
865  if (! oldIdToNewContent.containsKey(oldParent.getId())) {
866  throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
867  }
868  Content newParent = oldIdToNewContent.get(oldParent.getId());
869 
870  // Construct the relative path to the copied file
871  String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
872 
873  newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
874  abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
875  abstractFile.getMd5Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
876  true, TskData.EncodingType.NONE,
877  newParent, trans);
878  } catch (IOException ex) {
879  throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
880  + abstractFile.getId(), ex); // NON-NLS
881  }
882  }
883  }
884  } else {
885  throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
886  }
887  trans.commit();
888  } catch (TskCoreException ex) {
889  trans.rollback();
890  throw(ex);
891  }
892  }
893 
894  // Save the new object
895  oldIdToNewContent.put(content.getId(), newContent);
896  newIdToContent.put(newContent.getId(), newContent);
897  return oldIdToNewContent.get(content.getId()).getId();
898  }
899 
907  private String getExportSubfolder(AbstractFile abstractFile) {
908  if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
909  return UNKNOWN_FILE_TYPE_FOLDER;
910  }
911 
912  for (FileTypeCategory cat:FILE_TYPE_CATEGORIES) {
913  if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
914  return cat.getDisplayName();
915  }
916  }
917  return UNKNOWN_FILE_TYPE_FOLDER;
918  }
919 
923  private void cleanup() {
924  oldIdToNewContent.clear();
925  newIdToContent.clear();
926  oldTagNameToNewTagName.clear();
927  oldArtTypeIdToNewArtTypeId.clear();
928  oldAttrTypeIdToNewAttrType.clear();
929  oldArtifactIdToNewArtifact.clear();
930 
931  closePortableCaseDatabase();
932 
933  currentCase = null;
934  caseFolder = null;
935  copiedFilesFolder = null;
936  }
937 
941  private void closePortableCaseDatabase() {
942  if (portableSkCase != null) {
943  portableSkCase.close();
944  portableSkCase = null;
945  }
946  }
947 
948  /*@Override
949  public JPanel getConfigurationPanel() {
950  configPanel = new CreatePortableCasePanel();
951  return configPanel;
952  } */
953 
954  private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
955 
956  private final String tableName;
957 
958  StoreMaxIdCallback(String tableName) {
959  this.tableName = tableName;
960  }
961 
962  @Override
963  public void process(ResultSet rs) {
964 
965  try {
966  while (rs.next()) {
967  try {
968  Long maxId = rs.getLong("max_id"); // NON-NLS
969  String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')"; // NON-NLS
970  portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
971 
972  } catch (SQLException ex) {
973  logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex); // NON-NLS
974  } catch (TskCoreException ex) {
975  logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
976  }
977 
978  }
979  } catch (SQLException ex) {
980  logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
981  }
982  }
983  }
984 
985  @NbBundle.Messages({
986  "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
987  "# {0} - Temp folder path",
988  "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
989  "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
990  "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",
991  })
992  private boolean compressCase(ReportProgressPanel progressPanel) {
993 
994  // Close the portable case database (we still need some of the variables that would be cleared by cleanup())
995  closePortableCaseDatabase();
996 
997  // Make a temporary folder for the compressed case
998  File tempZipFolder = Paths.get(currentCase.getTempDirectory(), "portableCase" + System.currentTimeMillis()).toFile(); // NON-NLS
999  if (! tempZipFolder.mkdir()) {
1000  handleError("Error creating temporary folder " + tempZipFolder.toString(),
1001  Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
1002  return false;
1003  }
1004 
1005  // Find 7-Zip
1006  File sevenZipExe = locate7ZipExecutable();
1007  if (sevenZipExe == null) {
1008  handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
1009  return false;
1010  }
1011 
1012  // Create the chunk option
1013  String chunkOption = "";
1014  if (options.getChunkSize() != ChunkSize.NONE) {
1015  chunkOption = "-v" + options.getChunkSize().getSevenZipParam();
1016  }
1017 
1018  File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
1019  ProcessBuilder procBuilder = new ProcessBuilder();
1020  procBuilder.command(
1021  sevenZipExe.getAbsolutePath(),
1022  "a", // Add to archive
1023  zipFile.getAbsolutePath(),
1024  caseFolder.getAbsolutePath(),
1025  chunkOption
1026  );
1027 
1028  try {
1029  Process process = procBuilder.start();
1030 
1031  while (process.isAlive()) {
1032  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
1033  process.destroy();
1034  return false;
1035  }
1036  Thread.sleep(200);
1037  }
1038  int exitCode = process.exitValue();
1039  if (exitCode != 0) {
1040  // Save any errors so they can be logged
1041  StringBuilder sb = new StringBuilder();
1042  try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
1043  String line;
1044  while ((line = br.readLine()) != null) {
1045  sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
1046  }
1047  }
1048 
1049  handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
1050  return false;
1051  }
1052  } catch (IOException | InterruptedException ex) {
1053  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1054  return false;
1055  }
1056 
1057  // Delete everything in the case folder then copy over the compressed file(s)
1058  try {
1059  FileUtils.cleanDirectory(caseFolder);
1060  FileUtils.copyDirectory(tempZipFolder, caseFolder);
1061  FileUtils.deleteDirectory(tempZipFolder);
1062  } catch (IOException ex) {
1063  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1064  return false;
1065  }
1066 
1067  return true;
1068  }
1069 
1075  private static File locate7ZipExecutable() {
1076  if (!PlatformUtil.isWindowsOS()) {
1077  return null;
1078  }
1079 
1080  String executableToFindName = Paths.get("7-Zip", "7z.exe").toString(); // NON-NLS
1081  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PortableCaseReportModule.class.getPackage().getName(), false);
1082  if (null == exeFile) {
1083  return null;
1084  }
1085 
1086  if (!exeFile.canExecute()) {
1087  return null;
1088  }
1089 
1090  return exeFile;
1091  }
1092 
1097  enum ChunkSize {
1098 
1099  NONE("Do not split", ""), // NON-NLS
1100  ONE_HUNDRED_MB("Split into 100 MB chunks", "100m"),
1101  CD("Split into 700 MB chunks (CD)", "700m"),
1102  ONE_GB("Split into 1 GB chunks", "1000m"),
1103  DVD("Split into 4.5 GB chunks (DVD)", "4500m"); // NON-NLS
1104 
1105  private final String displayName;
1106  private final String sevenZipParam;
1107 
1114  private ChunkSize(String displayName, String sevenZipParam) {
1115  this.displayName = displayName;
1116  this.sevenZipParam = sevenZipParam;
1117  }
1118 
1119  String getDisplayName() {
1120  return displayName;
1121  }
1122 
1123  String getSevenZipParam() {
1124  return sevenZipParam;
1125  }
1126 
1127  @Override
1128  public String toString() {
1129  return displayName;
1130  }
1131  }
1132 
1136  static class PortableCaseOptions {
1137 
1138  private final List<TagName> tagNames = new ArrayList<>();
1139  private final List<String> setNames = new ArrayList<>();
1140  private boolean compress;
1141  private ChunkSize chunkSize;
1142 
1143  PortableCaseOptions(List<String> setNames, List<TagName> tagNames,
1144  boolean compress, ChunkSize chunkSize) {
1145  this.setNames.addAll(setNames);
1146  this.tagNames.addAll(tagNames);
1147  this.compress = compress;
1148  this.chunkSize = chunkSize;
1149  }
1150 
1151  PortableCaseOptions() {
1152  this.compress = false;
1153  this.chunkSize = ChunkSize.NONE;
1154  }
1155 
1156  void updateSetNames(List<String> setNames) {
1157  this.setNames.clear();
1158  this.setNames.addAll(setNames);
1159  }
1160 
1161  void updateTagNames(List<TagName> tagNames) {
1162  this.tagNames.clear();
1163  this.tagNames.addAll(tagNames);
1164  }
1165 
1166  void updateCompression(boolean compress, ChunkSize chunkSize) {
1167  this.compress = compress;
1168  this.chunkSize = chunkSize;
1169  }
1170 
1171  boolean isValid() {
1172  return (( !setNames.isEmpty()) || ( ! tagNames.isEmpty()));
1173  }
1174 
1175  List<String> getSelectedSetNames() {
1176  return new ArrayList<>(setNames);
1177  }
1178 
1179  List<TagName> getSelectedTagNames() {
1180  return new ArrayList<>(tagNames);
1181  }
1182 
1183  boolean shouldCompress() {
1184  return compress;
1185  }
1186 
1187  ChunkSize getChunkSize() {
1188  return chunkSize;
1189  }
1190  }
1191 }
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

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