Autopsy  4.17.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-2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.report.modules.portablecase;
20 
21 import com.google.common.collect.ArrayListMultimap;
22 import com.google.common.collect.Multimap;
23 import com.google.gson.Gson;
24 import com.google.gson.GsonBuilder;
25 import com.google.gson.JsonElement;
26 import com.google.gson.stream.JsonWriter;
28 import java.util.logging.Level;
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileOutputStream;
32 import java.io.FileWriter;
33 import java.io.InputStreamReader;
34 import java.io.IOException;
35 import java.io.OutputStream;
36 import java.io.OutputStreamWriter;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import java.sql.ResultSet;
41 import java.sql.SQLException;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.Collection;
45 import java.util.HashMap;
46 import java.util.List;
47 import java.util.Map;
48 import org.apache.commons.io.FileUtils;
49 import org.openide.modules.InstalledFileLocator;
50 import org.openide.util.NbBundle;
62 import org.sleuthkit.caseuco.CaseUcoExporter;
63 import org.sleuthkit.datamodel.AbstractFile;
64 import org.sleuthkit.datamodel.Account;
65 import org.sleuthkit.datamodel.Blackboard.BlackboardException;
66 import org.sleuthkit.datamodel.BlackboardArtifact;
67 import org.sleuthkit.datamodel.BlackboardArtifactTag;
68 import org.sleuthkit.datamodel.BlackboardAttribute;
69 import org.sleuthkit.datamodel.CaseDbAccessManager;
70 import org.sleuthkit.datamodel.Content;
71 import org.sleuthkit.datamodel.ContentTag;
72 import org.sleuthkit.datamodel.DataSource;
73 import org.sleuthkit.datamodel.FileSystem;
74 import org.sleuthkit.datamodel.Image;
75 import org.sleuthkit.datamodel.LocalFilesDataSource;
76 import org.sleuthkit.datamodel.Pool;
77 import org.sleuthkit.datamodel.SleuthkitCase;
78 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
79 import org.sleuthkit.datamodel.TagName;
80 import org.sleuthkit.datamodel.TaggingManager.ContentTagChange;
81 import org.sleuthkit.datamodel.TskCoreException;
82 import org.sleuthkit.datamodel.TskData;
83 import org.sleuthkit.datamodel.Volume;
84 import org.sleuthkit.datamodel.VolumeSystem;
85 import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
86 import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
87 import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
88 
92 public class PortableCaseReportModule implements ReportModule {
93 
94  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
95  private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
96  private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
97  private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids"; // NON-NLS
98  private static final String CASE_UCO_FILE_NAME = "portable_CASE_UCO_output";
99  private static final String CASE_UCO_TMP_DIR = "case_uco_tmp";
101 
102  // These are the types for the exported file subfolders
103  private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
105 
106  // These are attribute types that have special handling and should not be copied
107  // into the new artifact directly.
108  private static final List<Integer> SPECIALLY_HANDLED_ATTRS = Arrays.asList(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID(),
109  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID());
110 
111  private Case currentCase = null;
112  private SleuthkitCase portableSkCase = null;
113  private String caseName = "";
114  private File caseFolder = null;
115  private File copiedFilesFolder = null;
116 
117  // Maps old object ID from current case to new object in portable case
118  private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
119 
120  // Maps new object ID to the new object
121  private final Map<Long, Content> newIdToContent = new HashMap<>();
122 
123  // Maps old TagName to new TagName
124  private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
125 
126  // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
127  private final Map<Integer, Integer> oldArtTypeIdToNewArtTypeId = new HashMap<>();
128 
129  // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
130  private final Map<Integer, BlackboardAttribute.Type> oldAttrTypeIdToNewAttrType = new HashMap<>();
131 
132  // Map of old artifact ID to new artifact
133  private final Map<Long, BlackboardArtifact> oldArtifactIdToNewArtifact = new HashMap<>();
134 
136  }
137 
138  @NbBundle.Messages({
139  "PortableCaseReportModule.getName.name=Portable Case"
140  })
141  @Override
142  public String getName() {
143  return Bundle.PortableCaseReportModule_getName_name();
144  }
145 
146  @NbBundle.Messages({
147  "PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared"
148  })
149  @Override
150  public String getDescription() {
151  return Bundle.PortableCaseReportModule_getDescription_description();
152  }
153 
154  @Override
155  public String getRelativeFilePath() {
156  try {
157  caseName = Case.getCurrentCaseThrows().getDisplayName() + " (Portable)"; // NON-NLS
158  } catch (NoCurrentCaseException ex) {
159  // a case may not be open yet
160  return "";
161  }
162  return caseName;
163  }
164 
170  private void handleCancellation(ReportProgressPanel progressPanel) {
171  logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
172  progressPanel.setIndeterminate(false);
174  cleanup();
175  }
176 
187  private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
188  if (ex == null) {
189  logger.log(Level.WARNING, logWarning);
190  } else {
191  logger.log(Level.SEVERE, logWarning, ex);
192  }
193  progressPanel.setIndeterminate(false);
194  progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, dialogWarning);
195  cleanup();
196  }
197 
198  @NbBundle.Messages({
199  "PortableCaseReportModule.generateReport.verifying=Verifying selected parameters...",
200  "PortableCaseReportModule.generateReport.creatingCase=Creating portable case database...",
201  "PortableCaseReportModule.generateReport.copyingTags=Copying tags...",
202  "# {0} - tag name",
203  "PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0}...",
204  "# {0} - tag name",
205  "PortableCaseReportModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...",
206  "# {0} - output folder",
207  "PortableCaseReportModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
208  "# {0} - output folder",
209  "PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
210  "PortableCaseReportModule.generateReport.caseClosed=Current case has been closed",
211  "PortableCaseReportModule.generateReport.interestingItemError=Error loading intersting items",
212  "PortableCaseReportModule.generateReport.errorReadingTags=Error while reading tags from case database",
213  "PortableCaseReportModule.generateReport.errorReadingSets=Error while reading interesting items sets from case database",
214  "PortableCaseReportModule.generateReport.noContentToCopy=No interesting files, results, or tagged items to copy",
215  "PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags",
216  "PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files",
217  "PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts",
218  "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files",
219  "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results",
220  "PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table",
221  "PortableCaseReportModule.generateReport.errorCopyingAutopsy=Error copying application",
222  "# {0} - attribute type name",
223  "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}",
224  "PortableCaseReportModule.generateReport.compressingCase=Compressing case...",
225  "PortableCaseReportModule_generateReport_copyingAutopsy=Copying application..."
226  })
227 
228  public void generateReport(String reportPath, PortableCaseReportModuleSettings options, ReportProgressPanel progressPanel) {
229  this.settings = options;
230  progressPanel.setIndeterminate(true);
231  progressPanel.start();
232  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
233 
234  // Clear out any old values
235  cleanup();
236 
237  // Validate the input parameters
238  File outputDir = new File(reportPath);
239  if (!outputDir.exists()) {
240  handleError("Output folder " + outputDir.toString() + " does not exist",
241  Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
242  return;
243  }
244 
245  if (!outputDir.isDirectory()) {
246  handleError("Output folder " + outputDir.toString() + " is not a folder",
247  Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
248  return;
249  }
250 
251  // Save the current case object
252  try {
253  currentCase = Case.getCurrentCaseThrows();
254  caseName = currentCase.getDisplayName() + " (Portable)"; // NON-NLS
255  } catch (NoCurrentCaseException ex) {
256  handleError("Current case has been closed",
257  Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
258  return;
259  }
260 
261  // If the applciation is included add an extra level to the directory structure
262  if (options.includeApplication()) {
263  outputDir = Paths.get(outputDir.toString(), caseName).toFile();
264  }
265  // Check that there will be something to copy
266  List<TagName> tagNames;
267  if (options.areAllTagsSelected()) {
268  try {
270  } catch (NoCurrentCaseException | TskCoreException ex) {
271  handleError("Unable to get all tags",
272  Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS
273  return;
274  }
275  } else {
276  tagNames = options.getSelectedTagNames();
277  }
278 
279  List<String> setNames;
280  if (options.areAllSetsSelected()) {
281  try {
282  setNames = getAllInterestingItemsSets();
283  } catch (NoCurrentCaseException | TskCoreException ex) {
284  handleError("Unable to get all interesting items sets",
285  Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS
286  return;
287  }
288  } else {
289  setNames = options.getSelectedSetNames();
290  }
291 
292  if (tagNames.isEmpty() && setNames.isEmpty()) {
293  handleError("No content to copy",
294  Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
295  return;
296  }
297 
298  // Create the case.
299  // portableSkCase and caseFolder will be set here.
300  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
301  createCase(outputDir, progressPanel);
302  if (portableSkCase == null) {
303  // The error has already been handled
304  return;
305  }
306 
307  // Check for cancellation
308  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
309  handleCancellation(progressPanel);
310  return;
311  }
312 
313  // Set up the table for the image tags
314  try {
315  initializeImageTags(progressPanel);
316  } catch (TskCoreException ex) {
317  handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS
318  return;
319  }
320 
321  // Copy the selected tags
322  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
323  try {
324  for (TagName tagName : tagNames) {
325  TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
326  oldTagNameToNewTagName.put(tagName, newTagName);
327  }
328  } catch (TskCoreException ex) {
329  handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
330  return;
331  }
332 
333  // Set up tracking to support any custom artifact or attribute types
334  for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) {
335  oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
336  }
337  for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
338  try {
339  oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
340  } catch (TskCoreException ex) {
341  handleError("Error looking up attribute name " + type.getLabel(),
342  Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
343  ex, progressPanel); // NON-NLS
344  }
345  }
346 
347  // Copy the tagged files
348  try {
349  for (TagName tagName : tagNames) {
350  // Check for cancellation
351  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
352  handleCancellation(progressPanel);
353  return;
354  }
355  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
356  addFilesToPortableCase(tagName, progressPanel);
357 
358  // Check for cancellation
359  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
360  handleCancellation(progressPanel);
361  return;
362  }
363  }
364  } catch (TskCoreException ex) {
365  handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
366  return;
367  }
368 
369  // Copy the tagged artifacts and associated files
370  try {
371  for (TagName tagName : tagNames) {
372  // Check for cancellation
373  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
374  handleCancellation(progressPanel);
375  return;
376  }
377  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
378  addArtifactsToPortableCase(tagName, progressPanel);
379 
380  // Check for cancellation
381  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
382  handleCancellation(progressPanel);
383  return;
384  }
385  }
386  } catch (TskCoreException ex) {
387  handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
388  return;
389  }
390 
391  // Copy interesting files and results
392  if (!setNames.isEmpty()) {
393  try {
394  List<BlackboardArtifact> interestingFiles = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
395  for (BlackboardArtifact art : interestingFiles) {
396  // Check for cancellation
397  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
398  handleCancellation(progressPanel);
399  return;
400  }
401 
402  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
403  if (setNames.contains(setAttr.getValueString())) {
404  copyContentToPortableCase(art, progressPanel);
405  }
406  }
407  } catch (TskCoreException ex) {
408  handleError("Error copying interesting files", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingFiles(), ex, progressPanel); // NON-NLS
409  return;
410  }
411 
412  try {
413  List<BlackboardArtifact> interestingResults = currentCase.getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT);
414  for (BlackboardArtifact art : interestingResults) {
415  // Check for cancellation
416  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
417  handleCancellation(progressPanel);
418  return;
419  }
420  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
421  if (setNames.contains(setAttr.getValueString())) {
422  copyContentToPortableCase(art, progressPanel);
423  }
424  }
425  } catch (TskCoreException ex) {
426  handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
427  return;
428  }
429  }
430 
431  // Check for cancellation
432  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
433  handleCancellation(progressPanel);
434  return;
435  }
436 
437  //Attempt to generate and included the CASE-UCO report.
438  generateCaseUcoReport(tagNames, setNames, progressPanel);
439 
440  if (options.includeApplication()) {
441  try {
442  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingAutopsy());
443  copyApplication(getApplicationBasePath(), outputDir.getAbsolutePath());
444  createAppLaunchBatFile(outputDir.getAbsolutePath());
445  } catch (IOException ex) {
446  handleError("Error copying autopsy", Bundle.PortableCaseReportModule_generateReport_errorCopyingAutopsy(), ex, progressPanel); // NON-NLS
447  }
448  }
449 
450  // Compress the case (if desired)
451  if (options.shouldCompress()) {
452  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
453 
454  if(!compressCase(progressPanel, options.includeApplication() ? outputDir.getAbsolutePath() : caseFolder.getAbsolutePath())){
455  // Errors have been handled already
456  return;
457  }
458 
459  // Check for cancellation
460  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
461  handleCancellation(progressPanel);
462  return;
463  }
464  }
465 
466  // Close the case connections and clear out the maps
467  cleanup();
468 
470 
471  }
472 
484  @NbBundle.Messages({
485  "PortableCaseReportModule.generateCaseUcoReport.errorCreatingReportFolder=Could not make report folder",
486  "PortableCaseReportModule.generateCaseUcoReport.errorGeneratingCaseUcoReport=Problem while generating CASE-UCO report",
487  "PortableCaseReportModule.generateCaseUcoReport.startCaseUcoReportGeneration=Creating a CASE-UCO report of the portable case",
488  "PortableCaseReportModule.generateCaseUcoReport.successCaseUcoReportGeneration=Successfully created a CASE-UCO report of the portable case"
489  })
490  private void generateCaseUcoReport(List<TagName> tagNames, List<String> setNames, ReportProgressPanel progressPanel) {
491  //Create the 'Reports' directory to include a CASE-UCO report.
492  Path reportsDirectory = Paths.get(caseFolder.toString(), "Reports");
493  if (!reportsDirectory.toFile().mkdir()) {
494  logger.log(Level.SEVERE, "Could not make the report folder... skipping "
495  + "CASE-UCO report generation for the portable case");
496  return;
497  }
498 
499  Path reportFile = reportsDirectory.resolve(CASE_UCO_FILE_NAME);
500 
501  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration());
502  try (OutputStream stream = new FileOutputStream(reportFile.toFile());
503  JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
504  Gson gson = new GsonBuilder().setPrettyPrinting().create();
505  reportWriter.setIndent(" ");
506  reportWriter.beginObject();
507  reportWriter.name("@graph");
508  reportWriter.beginArray();
509 
510  String caseTempDirectory = currentCase.getTempDirectory();
511  SleuthkitCase skCase = currentCase.getSleuthkitCase();
512  TagsManager tagsManager = currentCase.getServices().getTagsManager();
513 
514  //Create temp directory to filter out duplicate files.
515  //Clear out the old directory if it exists.
516  Path tmpDir = Paths.get(caseTempDirectory, CASE_UCO_TMP_DIR);
517  FileUtils.deleteDirectory(tmpDir.toFile());
518  Files.createDirectory(tmpDir);
519 
520  CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
521  for (JsonElement element : exporter.exportSleuthkitCase()) {
522  gson.toJson(element, reportWriter);
523  }
524 
525  //Load all interesting BlackboardArtifacts that belong to the selected SET_NAMEs
526  //binned by data source id.
527  Multimap<Long, BlackboardArtifact> artifactsWithSetName = getInterestingArtifactsBySetName(skCase, setNames);
528 
529  //Search each data source looking for content tags and interesting
530  //items that match the selected tag names and set names.
531  for (DataSource dataSource : currentCase.getSleuthkitCase().getDataSources()) {
532  // Helper flag to ensure each data source is only written once in
533  // a report.
534  boolean dataSourceHasBeenIncluded = false;
535 
536  //Search content tags and artifact tags that match
537  for (TagName tagName : tagNames) {
538  for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) {
539  dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(),
540  dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
541  }
542  for (BlackboardArtifactTag bat : tagsManager.getBlackboardArtifactTagsByTagName(tagName, dataSource.getId())) {
543  dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(),
544  dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
545  }
546  }
547  //Search artifacts that this data source contains
548  for (BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
549  Content sourceContent = bArt.getParent();
550  dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource,
551  tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
552  }
553  }
554 
555  // Finish the report.
556  reportWriter.endArray();
557  reportWriter.endObject();
558  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_successCaseUcoReportGeneration());
559  } catch (IOException | TskCoreException ex) {
560  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_errorGeneratingCaseUcoReport());
561  logger.log(Level.SEVERE, "Error encountered while trying to create "
562  + "CASE-UCO output for portable case.. the portable case will be "
563  + "completed without a CASE-UCO report.", ex);
564  }
565  }
566 
572  private Multimap<Long, BlackboardArtifact> getInterestingArtifactsBySetName(SleuthkitCase skCase, List<String> setNames) throws TskCoreException {
573  Multimap<Long, BlackboardArtifact> artifactsWithSetName = ArrayListMultimap.create();
574  if (!setNames.isEmpty()) {
575  List<BlackboardArtifact> allArtifacts = skCase.getBlackboardArtifacts(
576  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
577  allArtifacts.addAll(skCase.getBlackboardArtifacts(
578  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT));
579 
580  for (BlackboardArtifact bArt : allArtifacts) {
581  BlackboardAttribute setAttr = bArt.getAttribute(
582  new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
583  if (setNames.contains(setAttr.getValueString())) {
584  artifactsWithSetName.put(bArt.getDataSource().getId(), bArt);
585  }
586  }
587  }
588  return artifactsWithSetName;
589  }
590 
611  private boolean addUniqueFile(Content content, DataSource dataSource,
612  Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter,
613  boolean dataSourceHasBeenIncluded) throws IOException, TskCoreException {
614  if (content instanceof AbstractFile && !(content instanceof DataSource)) {
615  AbstractFile absFile = (AbstractFile) content;
616  Path filePath = tmpDir.resolve(Long.toString(absFile.getId()));
617  if (!absFile.isDir() && !Files.exists(filePath)) {
618  if (!dataSourceHasBeenIncluded) {
619  for (JsonElement element : exporter.exportDataSource(dataSource)) {
620  gson.toJson(element, reportWriter);
621  }
622  }
623  String subFolder = getExportSubfolder(absFile);
624  String fileName = absFile.getId() + "-" + FileUtil.escapeFileName(absFile.getName());
625  for (JsonElement element : exporter.exportAbstractFile(absFile, Paths.get(FILE_FOLDER_NAME, subFolder, fileName).toString())) {
626  gson.toJson(element, reportWriter);
627  }
628  Files.createFile(filePath);
629  return true;
630  }
631  }
632  return false;
633  }
634 
635  private List<String> getAllInterestingItemsSets() throws NoCurrentCaseException, TskCoreException {
636 
637  // Get the set names in use for the current case.
638  List<String> setNames = new ArrayList<>();
639  Map<String, Long> setCounts;
640 
641  // There may not be a case open when configuring report modules for Command Line execution
642  // Get all SET_NAMEs from interesting item artifacts
643  String innerSelect = "SELECT (value_text) AS set_name FROM blackboard_attributes WHERE (artifact_type_id = '"
644  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() + "' OR artifact_type_id = '"
645  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + "') AND attribute_type_id = '"
646  + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "'"; // NON-NLS
647 
648  // Get the count of each SET_NAME
649  String query = "set_name, count(1) AS set_count FROM (" + innerSelect + ") set_names GROUP BY set_name"; // NON-NLS
650 
652  Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
653  setCounts = callback.getSetCountMap();
654  setNames.addAll(setCounts.keySet());
655  return setNames;
656  }
657 
665  @NbBundle.Messages({
666  "# {0} - case folder",
667  "PortableCaseReportModule.createCase.caseDirExists=Case folder {0} already exists",
668  "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
669  "# {0} - folder",
670  "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
671  "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",})
672  private void createCase(File outputDir, ReportProgressPanel progressPanel) {
673 
674  // Create the case folder
675  caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
676 
677  if (caseFolder.exists()) {
678  handleError("Case folder " + caseFolder.toString() + " already exists",
679  Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
680  return;
681  }
682 
683  // Create the case
684  try {
685  portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
686  } catch (TskCoreException ex) {
687  handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
688  Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
689  return;
690  }
691 
692  // Store the highest IDs
693  try {
694  saveHighestIds();
695  } catch (TskCoreException ex) {
696  handleError("Error storing maximum database IDs",
697  Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
698  return;
699  }
700 
701  // Create the base folder for the copied files
702  copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
703  if (!copiedFilesFolder.mkdir()) {
704  handleError("Error creating folder " + copiedFilesFolder.toString(),
705  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
706  return;
707  }
708 
709  // Create subfolders for the copied files
710  for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
711  File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
712  if (!subFolder.mkdir()) {
713  handleError("Error creating folder " + subFolder.toString(),
714  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
715  return;
716  }
717  }
718  File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
719  if (!unknownTypeFolder.mkdir()) {
720  handleError("Error creating folder " + unknownTypeFolder.toString(),
721  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
722  return;
723  }
724 
725  }
726 
732  private void saveHighestIds() throws TskCoreException {
733 
734  CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
735 
736  String tableSchema = "( table_name TEXT PRIMARY KEY, "
737  + " max_id TEXT)"; // NON-NLS
738 
739  portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
740 
741  currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
742  currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags")); // NON-NLS
743  currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
744  currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
745  }
746 
754  private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException {
755 
756  // Create the image tags table in the portable case
757  CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
758  if (!portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
760  }
761  }
762 
771  private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
772 
773  // Get all the tags in the current case
774  List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
775 
776  // Copy the files into the portable case and tag
777  for (ContentTag tag : tags) {
778 
779  // Check for cancellation
780  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
781  return;
782  }
783 
784  Content content = tag.getContent();
785  if (content instanceof AbstractFile) {
786 
787  long newFileId = copyContentToPortableCase(content, progressPanel);
788 
789  // Tag the file
790  if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
791  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
792  }
793  ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
794 
795  // Get the image tag data associated with this tag (empty string if there is none)
796  // and save it if present
797  String appData = getImageTagDataForContentTag(tag);
798  if (!appData.isEmpty()) {
799  addImageTagToPortableCase(newContentTag.getAddedTag(), appData);
800  }
801  }
802  }
803  }
804 
815  private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException {
816 
817  GetImageTagCallback callback = new GetImageTagCallback();
818  String query = "* FROM " + ContentViewerTagManager.TABLE_NAME + " WHERE content_tag_id = " + tag.getId();
819  currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
820  return callback.getAppData();
821  }
822 
826  private static class GetImageTagCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
827 
828  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
829  private String appData = "";
830 
831  @Override
832  public void process(ResultSet rs) {
833  try {
834  while (rs.next()) {
835  try {
836  appData = rs.getString("app_data"); // NON-NLS
837  } catch (SQLException ex) {
838  logger.log(Level.WARNING, "Unable to get app_data from result set", ex); // NON-NLS
839  }
840  }
841  } catch (SQLException ex) {
842  logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS
843  }
844  }
845 
851  String getAppData() {
852  return appData;
853  }
854  }
855 
864  private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException {
865  String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')";
866  portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
867  }
868 
877  private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
878 
879  List<BlackboardArtifactTag> tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
880 
881  // Copy the artifacts into the portable case along with their content and tag
882  for (BlackboardArtifactTag tag : tags) {
883 
884  // Check for cancellation
885  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
886  return;
887  }
888 
889  // Copy the source content
890  Content content = tag.getContent();
891  long newContentId = copyContentToPortableCase(content, progressPanel);
892 
893  // Copy the artifact
894  BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
895 
896  // Copy any attachments
897  copyAttachments(newArtifact, tag.getArtifact(), portableSkCase.getAbstractFileById(newContentId));
898 
899  // Copy any files associated with this artifact through the TSK_PATH_ID attribute
900  copyPathID(newArtifact, tag.getArtifact());
901 
902  // Tag the artfiact
903  if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
904  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
905  }
906  portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
907  }
908  }
909 
922  private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
923 
924  if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
925  return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
926  }
927 
928  // First create the associated artifact (if present)
929  BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
930  List<BlackboardAttribute> newAttrs = new ArrayList<>();
931  if (oldAssociatedAttribute != null) {
932  BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
933  BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
934  newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
935  String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
936  }
937 
938  // Create the new artifact
939  int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
940  BlackboardArtifact newArtifact = portableSkCase.newBlackboardArtifact(newArtifactTypeId, newContentId);
941  List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
942 
943  // Copy over each attribute, making sure the type is in the new case.
944  for (BlackboardAttribute oldAttr : oldAttrs) {
945 
946  // Skip attributes that are handled elsewhere
947  if (SPECIALLY_HANDLED_ATTRS.contains(oldAttr.getAttributeType().getTypeID())) {
948  continue;
949  }
950 
951  BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
952  switch (oldAttr.getValueType()) {
953  case BYTE:
954  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
955  oldAttr.getValueBytes()));
956  break;
957  case DOUBLE:
958  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
959  oldAttr.getValueDouble()));
960  break;
961  case INTEGER:
962  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
963  oldAttr.getValueInt()));
964  break;
965  case DATETIME:
966  case LONG:
967  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
968  oldAttr.getValueLong()));
969  break;
970  case STRING:
971  case JSON:
972  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
973  oldAttr.getValueString()));
974  break;
975  default:
976  throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
977  }
978  }
979 
980  newArtifact.addAttributes(newAttrs);
981 
982  oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
983  return newArtifact;
984  }
985 
995  private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
996  if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
997  return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
998  }
999 
1000  BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
1001  try {
1002  BlackboardArtifact.Type newCustomType = portableSkCase.getBlackboard().getOrAddArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
1003  oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID());
1004  return newCustomType.getTypeID();
1005  } catch (BlackboardException ex) {
1006  throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
1007  }
1008  }
1009 
1019  private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
1020  BlackboardAttribute.Type oldAttrType = oldAttribute.getAttributeType();
1021  if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
1022  return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
1023  }
1024 
1025  try {
1026  BlackboardAttribute.Type newCustomType = portableSkCase.getBlackboard().getOrAddAttributeType(oldAttrType.getTypeName(),
1027  oldAttrType.getValueType(), oldAttrType.getDisplayName());
1028  oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
1029  return newCustomType;
1030  } catch (BlackboardException ex) {
1031  throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS
1032  }
1033  }
1034 
1045  @NbBundle.Messages({
1046  "# {0} - File name",
1047  "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",})
1048  private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
1049  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
1050  return copyContent(content);
1051  }
1052 
1062  private long copyContent(Content content) throws TskCoreException {
1063 
1064  // Check if we've already copied this content
1065  if (oldIdToNewContent.containsKey(content.getId())) {
1066  return oldIdToNewContent.get(content.getId()).getId();
1067  }
1068 
1069  // Otherwise:
1070  // - Make parent of this object (if applicable)
1071  // - Copy this content
1072  long parentId = 0;
1073  if (content.getParent() != null) {
1074  parentId = copyContent(content.getParent());
1075  }
1076 
1077  Content newContent;
1078  if (content instanceof BlackboardArtifact) {
1079  BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
1080  newContent = copyArtifact(parentId, artifactToCopy);
1081  } else {
1082  CaseDbTransaction trans = portableSkCase.beginTransaction();
1083  try {
1084  if (content instanceof Image) {
1085  Image image = (Image) content;
1086  newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
1087  new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), trans);
1088  } else if (content instanceof VolumeSystem) {
1089  VolumeSystem vs = (VolumeSystem) content;
1090  newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
1091  } else if (content instanceof Volume) {
1092  Volume vs = (Volume) content;
1093  newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
1094  vs.getDescription(), vs.getFlags(), trans);
1095  } else if (content instanceof Pool) {
1096  Pool pool = (Pool) content;
1097  newContent = portableSkCase.addPool(parentId, pool.getType(), trans);
1098  } else if (content instanceof FileSystem) {
1099  FileSystem fs = (FileSystem) content;
1100  newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
1101  fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
1102  fs.getName(), trans);
1103  } else if (content instanceof BlackboardArtifact) {
1104  BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
1105  newContent = copyArtifact(parentId, artifactToCopy);
1106  } else if (content instanceof AbstractFile) {
1107  AbstractFile abstractFile = (AbstractFile) content;
1108 
1109  if (abstractFile instanceof LocalFilesDataSource) {
1110  LocalFilesDataSource localFilesDS = (LocalFilesDataSource) abstractFile;
1111  newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), trans);
1112  } else {
1113  if (abstractFile.isDir()) {
1114  newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
1115  } else {
1116  try {
1117  // Copy the file
1118  String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
1119  String exportSubFolder = getExportSubfolder(abstractFile);
1120  File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
1121  File localFile = new File(exportFolder, fileName);
1122  ContentUtils.writeToFile(abstractFile, localFile);
1123 
1124  // Get the new parent object in the portable case database
1125  Content oldParent = abstractFile.getParent();
1126  if (!oldIdToNewContent.containsKey(oldParent.getId())) {
1127  throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
1128  }
1129  Content newParent = oldIdToNewContent.get(oldParent.getId());
1130 
1131  // Construct the relative path to the copied file
1132  String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
1133 
1134  newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
1135  abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
1136  abstractFile.getMd5Hash(), abstractFile.getSha256Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
1137  true, TskData.EncodingType.NONE,
1138  newParent, trans);
1139  } catch (IOException ex) {
1140  throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
1141  + abstractFile.getId(), ex); // NON-NLS
1142  }
1143  }
1144  }
1145  } else {
1146  throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
1147  }
1148  trans.commit();
1149  } catch (TskCoreException ex) {
1150  trans.rollback();
1151  throw (ex);
1152  }
1153  }
1154 
1155  // Save the new object
1156  oldIdToNewContent.put(content.getId(), newContent);
1157  newIdToContent.put(newContent.getId(), newContent);
1158  return oldIdToNewContent.get(content.getId()).getId();
1159  }
1160 
1169  private void copyPathID(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact) throws TskCoreException {
1170  // Get the path ID attribute
1171  BlackboardAttribute oldPathIdAttr = oldArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
1172  if (oldPathIdAttr != null) {
1173  // Copy the file and remake the attribute if the path ID is valid
1174  long oldContentId = oldPathIdAttr.getValueLong();
1175  if (oldContentId > 0) {
1176  Content oldContent = currentCase.getSleuthkitCase().getContentById(oldContentId);
1177  long newContentId = copyContent(oldContent);
1178  newArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
1179  String.join(",", oldPathIdAttr.getSources()), newContentId));
1180  }
1181  }
1182  }
1183 
1193  private void copyAttachments(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact, AbstractFile newFile) throws TskCoreException {
1194  // Get the attachments from TSK_ATTACHMENTS attribute.
1195  BlackboardAttribute attachmentsAttr = oldArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS));
1196  if (attachmentsAttr != null) {
1197  try {
1198  MessageAttachments msgAttachments = BlackboardJsonAttrUtil.fromAttribute(attachmentsAttr, MessageAttachments.class);
1199 
1200  Collection<MessageAttachments.FileAttachment> oldFileAttachments = msgAttachments.getFileAttachments();
1201  List<MessageAttachments.FileAttachment> newFileAttachments = new ArrayList<>();
1202  for (MessageAttachments.FileAttachment oldFileAttachment : oldFileAttachments) {
1203  long attachedFileObjId = oldFileAttachment.getObjectId();
1204  if (attachedFileObjId >= 0) {
1205  // Copy the attached file and save to the MessageAttachments object
1206  AbstractFile attachedFile = currentCase.getSleuthkitCase().getAbstractFileById(attachedFileObjId);
1207  if (attachedFile == null) {
1208  throw new TskCoreException("Error loading file with object ID " + attachedFileObjId + " from portable case");
1209  }
1210  long newFileID = copyContent(attachedFile);
1211  newFileAttachments.add(new MessageAttachments.FileAttachment(portableSkCase.getAbstractFileById(newFileID)));
1212  }
1213  }
1214 
1215  // Get the name of the module(s) that created the attachment
1216  String newSourceStr = "";
1217  List<String> oldSources = attachmentsAttr.getSources();
1218  if (! oldSources.isEmpty()) {
1219  newSourceStr = String.join(",", oldSources);
1220  }
1221 
1222  // Add the attachment. The account type specified in the constructor will not be used.
1223  CommunicationArtifactsHelper communicationArtifactsHelper = new CommunicationArtifactsHelper(currentCase.getSleuthkitCase(),
1224  newSourceStr, newFile, Account.Type.EMAIL);
1225  communicationArtifactsHelper.addAttachments(newArtifact, new MessageAttachments(newFileAttachments, msgAttachments.getUrlAttachments()));
1226  }
1227  catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
1228  throw new TskCoreException(String.format("Unable to parse json for MessageAttachments object in artifact: %s", oldArtifact.getName()), ex);
1229  }
1230  } else { // backward compatibility - email message attachments are derived files, children of the message.
1231  for (Content childContent : oldArtifact.getChildren()) {
1232  if (childContent instanceof AbstractFile) {
1233  copyContent(childContent);
1234  }
1235  }
1236  }
1237  }
1238 
1246  private String getExportSubfolder(AbstractFile abstractFile) {
1247  if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
1248  return UNKNOWN_FILE_TYPE_FOLDER;
1249  }
1250 
1251  for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
1252  if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
1253  return cat.getDisplayName();
1254  }
1255  }
1256  return UNKNOWN_FILE_TYPE_FOLDER;
1257  }
1258 
1264  private Path getApplicationBasePath() {
1265  return getAutopsyExePath().getParent().getParent();
1266  }
1267 
1273  private Path getAutopsyExePath() {
1274  // If this is an installed version, there should be an <appName>64.exe file in the bin folder
1275  String exeName = getAutopsyExeName();
1276  String installPath = PlatformUtil.getInstallPath();
1277 
1278  return Paths.get(installPath, "bin", exeName);
1279  }
1280 
1286  private String getAutopsyExeName() {
1287  String appName = UserPreferences.getAppName();
1288  return appName + "64.exe";
1289  }
1290 
1299  private void copyApplication(Path sourceFolder, String destBaseFolder) throws IOException {
1300 
1301  // Create an appName folder in the destination
1302  Path destAppFolder = Paths.get(destBaseFolder, UserPreferences.getAppName());
1303  if (!destAppFolder.toFile().exists() && !destAppFolder.toFile().mkdirs()) {
1304  throw new IOException("Failed to create directory " + destAppFolder.toString());
1305  }
1306 
1307  // Now copy the files
1308  FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile());
1309  }
1310 
1318  private void createAppLaunchBatFile(String destBaseFolder) throws IOException {
1319  Path filePath = Paths.get(destBaseFolder, "open.bat");
1320  String appName = UserPreferences.getAppName();
1321  String exePath = "\"%~dp0" + appName + "\\bin\\" + getAutopsyExeName() + "\"";
1322  String casePath = "..\\" + caseName;
1323  try (FileWriter writer = new FileWriter(filePath.toFile())) {
1324  writer.write(exePath + " \"" + casePath + "\"");
1325  }
1326  }
1327 
1331  private void cleanup() {
1332  oldIdToNewContent.clear();
1333  newIdToContent.clear();
1334  oldTagNameToNewTagName.clear();
1335  oldArtTypeIdToNewArtTypeId.clear();
1337  oldArtifactIdToNewArtifact.clear();
1338 
1340 
1341  currentCase = null;
1342  caseFolder = null;
1343  copiedFilesFolder = null;
1344  }
1345 
1349  private void closePortableCaseDatabase() {
1350  if (portableSkCase != null) {
1351  portableSkCase.close();
1352  portableSkCase = null;
1353  }
1354  }
1355 
1356  /*
1357  * @Override public JPanel getConfigurationPanel() { configPanel = new
1358  * CreatePortableCasePanel(); return configPanel; }
1359  */
1360  private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1361 
1362  private final String tableName;
1363 
1364  StoreMaxIdCallback(String tableName) {
1365  this.tableName = tableName;
1366  }
1367 
1368  @Override
1369  public void process(ResultSet rs) {
1370 
1371  try {
1372  while (rs.next()) {
1373  try {
1374  Long maxId = rs.getLong("max_id"); // NON-NLS
1375  String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')"; // NON-NLS
1376  portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
1377 
1378  } catch (SQLException ex) {
1379  logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex); // NON-NLS
1380  } catch (TskCoreException ex) {
1381  logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
1382  }
1383 
1384  }
1385  } catch (SQLException ex) {
1386  logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
1387  }
1388  }
1389  }
1390 
1391  @NbBundle.Messages({
1392  "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
1393  "# {0} - Temp folder path",
1394  "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
1395  "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
1396  "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",})
1397  private boolean compressCase(ReportProgressPanel progressPanel, String folderToCompress) {
1398 
1400 
1401  // Make a temporary folder for the compressed case
1402  Path dirToCompress = Paths.get(folderToCompress);
1403  File tempZipFolder = Paths.get(dirToCompress.getParent().toString(), "temp", "portableCase" + System.currentTimeMillis()).toFile();
1404  if (!tempZipFolder.mkdirs()) {
1405  handleError("Error creating temporary folder " + tempZipFolder.toString(),
1406  Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
1407  return false;
1408  }
1409 
1410  // Find 7-Zip
1411  File sevenZipExe = locate7ZipExecutable();
1412  if (sevenZipExe == null) {
1413  handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
1414  return false;
1415  }
1416 
1417  // Create the chunk option
1418  String chunkOption = "";
1420  chunkOption = "-v" + settings.getChunkSize().getSevenZipParam();
1421  }
1422 
1423  File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
1424  ProcessBuilder procBuilder = new ProcessBuilder();
1425  procBuilder.command(
1426  sevenZipExe.getAbsolutePath(),
1427  "a", // Add to archive
1428  zipFile.getAbsolutePath(),
1429  dirToCompress.toAbsolutePath().toString(),
1430  chunkOption
1431  );
1432 
1433  try {
1434  Process process = procBuilder.start();
1435 
1436  while (process.isAlive()) {
1437  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
1438  process.destroy();
1439  return false;
1440  }
1441  Thread.sleep(200);
1442  }
1443  int exitCode = process.exitValue();
1444  if (exitCode != 0) {
1445  // Save any errors so they can be logged
1446  StringBuilder sb = new StringBuilder();
1447  try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
1448  String line;
1449  while ((line = br.readLine()) != null) {
1450  sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
1451  }
1452  }
1453 
1454  handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
1455  return false;
1456  }
1457  } catch (IOException | InterruptedException ex) {
1458  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1459  return false;
1460  }
1461 
1462  // Delete everything in the case folder then copy over the compressed file(s)
1463  try {
1464  FileUtils.cleanDirectory(dirToCompress.toFile());
1465  FileUtils.copyDirectory(tempZipFolder, dirToCompress.toFile());
1466  FileUtils.deleteDirectory(new File(tempZipFolder.getParent()));
1467  } catch (IOException ex) {
1468  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1469  return false;
1470  }
1471 
1472  return true;
1473  }
1474 
1480  private static File locate7ZipExecutable() {
1481  if (!PlatformUtil.isWindowsOS()) {
1482  return null;
1483  }
1484 
1485  String executableToFindName = Paths.get("7-Zip", "7z.exe").toString(); // NON-NLS
1486  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PortableCaseReportModule.class.getPackage().getName(), false);
1487  if (null == exeFile) {
1488  return null;
1489  }
1490 
1491  if (!exeFile.canExecute()) {
1492  return null;
1493  }
1494 
1495  return exeFile;
1496  }
1497 
1501  public static class GetInterestingItemSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1502 
1503  private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GetInterestingItemSetNamesCallback.class.getName());
1504  private final Map<String, Long> setCounts = new HashMap<>();
1505 
1506  @Override
1507  public void process(ResultSet rs) {
1508  try {
1509  while (rs.next()) {
1510  try {
1511  Long setCount = rs.getLong("set_count"); // NON-NLS
1512  String setName = rs.getString("set_name"); // NON-NLS
1513 
1514  setCounts.put(setName, setCount);
1515 
1516  } catch (SQLException ex) {
1517  logger.log(Level.WARNING, "Unable to get data_source_obj_id or value from result set", ex); // NON-NLS
1518  }
1519  }
1520  } catch (SQLException ex) {
1521  logger.log(Level.WARNING, "Failed to get next result for values by datasource", ex); // NON-NLS
1522  }
1523  }
1524 
1530  public Map<String, Long> getSetCountMap() {
1531  return setCounts;
1532  }
1533  }
1534 }
void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel)
long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel)
void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel)
void copyAttachments(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact, AbstractFile newFile)
Multimap< Long, BlackboardArtifact > getInterestingArtifactsBySetName(SleuthkitCase skCase, List< String > setNames)
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 copyPathID(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact)
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)
void generateCaseUcoReport(List< TagName > tagNames, List< String > setNames, ReportProgressPanel progressPanel)
SleuthkitCase createPortableCase(String caseName, File portableCaseFolder)
Definition: Case.java:2220
boolean addUniqueFile(Content content, DataSource dataSource, Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter, boolean dataSourceHasBeenIncluded)
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
boolean compressCase(ReportProgressPanel progressPanel, String folderToCompress)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
List< ContentTag > getContentTagsByTagName(TagName tagName)

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