Autopsy  4.18.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.AnalysisResult;
66 import org.sleuthkit.datamodel.Blackboard.BlackboardException;
67 import org.sleuthkit.datamodel.BlackboardArtifact;
68 import org.sleuthkit.datamodel.BlackboardArtifactTag;
69 import org.sleuthkit.datamodel.BlackboardAttribute;
70 import org.sleuthkit.datamodel.CaseDbAccessManager;
71 import org.sleuthkit.datamodel.Content;
72 import org.sleuthkit.datamodel.ContentTag;
73 import org.sleuthkit.datamodel.DataArtifact;
74 import org.sleuthkit.datamodel.DataSource;
75 import org.sleuthkit.datamodel.FileSystem;
76 import org.sleuthkit.datamodel.Host;
77 import org.sleuthkit.datamodel.Image;
78 import org.sleuthkit.datamodel.LocalFilesDataSource;
79 import org.sleuthkit.datamodel.OsAccount;
80 import org.sleuthkit.datamodel.OsAccountManager;
81 import org.sleuthkit.datamodel.OsAccountManager.NotUserSIDException;
82 import org.sleuthkit.datamodel.OsAccountRealm;
83 import org.sleuthkit.datamodel.OsAccountRealmManager;
84 import org.sleuthkit.datamodel.Pool;
85 import org.sleuthkit.datamodel.Score;
86 import org.sleuthkit.datamodel.SleuthkitCase;
87 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
88 import org.sleuthkit.datamodel.TagName;
89 import org.sleuthkit.datamodel.TaggingManager.ContentTagChange;
90 import org.sleuthkit.datamodel.TskCoreException;
91 import org.sleuthkit.datamodel.TskData;
92 import org.sleuthkit.datamodel.Volume;
93 import org.sleuthkit.datamodel.VolumeSystem;
94 import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
95 import org.sleuthkit.datamodel.blackboardutils.attributes.BlackboardJsonAttrUtil;
96 import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
97 
101 public class PortableCaseReportModule implements ReportModule {
102 
104  private static final String FILE_FOLDER_NAME = "PortableCaseFiles"; // NON-NLS
105  private static final String UNKNOWN_FILE_TYPE_FOLDER = "Other"; // NON-NLS
106  private static final String MAX_ID_TABLE_NAME = "portable_case_max_ids"; // NON-NLS
107  private static final String CASE_UCO_FILE_NAME = "portable_CASE_UCO_output";
108  private static final String CASE_UCO_TMP_DIR = "case_uco_tmp";
110 
111  // These are the types for the exported file subfolders
112  private static final List<FileTypeCategory> FILE_TYPE_CATEGORIES = Arrays.asList(FileTypeCategory.AUDIO, FileTypeCategory.DOCUMENTS,
114 
115  // These are attribute types that have special handling and should not be copied
116  // into the new artifact directly.
117  private static final List<Integer> SPECIALLY_HANDLED_ATTRS = Arrays.asList(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID(),
118  BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS.getTypeID(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID());
119 
120  private Case currentCase = null;
121  private SleuthkitCase portableSkCase = null;
122  private String caseName = "";
123  private File caseFolder = null;
124  private File copiedFilesFolder = null;
125 
126  // Maps old object ID from current case to new object in portable case
127  private final Map<Long, Content> oldIdToNewContent = new HashMap<>();
128 
129  // Maps new object ID to the new object
130  private final Map<Long, Content> newIdToContent = new HashMap<>();
131 
132  // Maps old TagName to new TagName
133  private final Map<TagName, TagName> oldTagNameToNewTagName = new HashMap<>();
134 
135  // Map of old artifact type ID to new artifact type ID. There will only be changes if custom artifact types are present.
136  private final Map<Integer, Integer> oldArtTypeIdToNewArtTypeId = new HashMap<>();
137 
138  // Map of old attribute type ID to new attribute type ID. There will only be changes if custom attr types are present.
139  private final Map<Integer, BlackboardAttribute.Type> oldAttrTypeIdToNewAttrType = new HashMap<>();
140 
141  // Map of old artifact ID to new artifact
142  private final Map<Long, BlackboardArtifact> oldArtifactIdToNewArtifact = new HashMap<>();
143 
144  // Map of old OS account id to new OS account id
145  private final Map<Long, Long> oldOsAccountIdToNewOsAccountId = new HashMap<>();
146 
147  // Map of old OS account realm id to new OS account ream id
148  private final Map<Long, OsAccountRealm> oldRealmIdToNewRealm = new HashMap<>();
149 
151  }
152 
153  @NbBundle.Messages({
154  "PortableCaseReportModule.getName.name=Portable Case"
155  })
156  @Override
157  public String getName() {
158  return Bundle.PortableCaseReportModule_getName_name();
159  }
160 
161  @NbBundle.Messages({
162  "PortableCaseReportModule.getDescription.description=Copies selected items to a new single-user case that can be easily shared"
163  })
164  @Override
165  public String getDescription() {
166  return Bundle.PortableCaseReportModule_getDescription_description();
167  }
168 
169  @Override
170  public String getRelativeFilePath() {
171  try {
172  caseName = Case.getCurrentCaseThrows().getDisplayName() + " (Portable)"; // NON-NLS
173  } catch (NoCurrentCaseException ex) {
174  // a case may not be open yet
175  return "";
176  }
177  return caseName;
178  }
179 
185  private void handleCancellation(ReportProgressPanel progressPanel) {
186  logger.log(Level.INFO, "Portable case creation canceled by user"); // NON-NLS
187  progressPanel.setIndeterminate(false);
189  cleanup();
190  }
191 
202  private void handleError(String logWarning, String dialogWarning, Exception ex, ReportProgressPanel progressPanel) {
203  if (ex == null) {
204  logger.log(Level.WARNING, logWarning);
205  } else {
206  logger.log(Level.SEVERE, logWarning, ex);
207  }
208  progressPanel.setIndeterminate(false);
209  progressPanel.complete(ReportProgressPanel.ReportStatus.ERROR, dialogWarning);
210  cleanup();
211  }
212 
213  @NbBundle.Messages({
214  "PortableCaseReportModule.generateReport.verifying=Verifying selected parameters...",
215  "PortableCaseReportModule.generateReport.creatingCase=Creating portable case database...",
216  "PortableCaseReportModule.generateReport.copyingTags=Copying tags...",
217  "# {0} - tag name",
218  "PortableCaseReportModule.generateReport.copyingFiles=Copying files tagged as {0}...",
219  "# {0} - tag name",
220  "PortableCaseReportModule.generateReport.copyingArtifacts=Copying artifacts tagged as {0}...",
221  "# {0} - output folder",
222  "PortableCaseReportModule.generateReport.outputDirDoesNotExist=Output folder {0} does not exist",
223  "# {0} - output folder",
224  "PortableCaseReportModule.generateReport.outputDirIsNotDir=Output folder {0} is not a folder",
225  "PortableCaseReportModule.generateReport.caseClosed=Current case has been closed",
226  "PortableCaseReportModule.generateReport.interestingItemError=Error loading intersting items",
227  "PortableCaseReportModule.generateReport.errorReadingTags=Error while reading tags from case database",
228  "PortableCaseReportModule.generateReport.errorReadingSets=Error while reading interesting items sets from case database",
229  "PortableCaseReportModule.generateReport.noContentToCopy=No interesting files, results, or tagged items to copy",
230  "PortableCaseReportModule.generateReport.errorCopyingTags=Error copying tags",
231  "PortableCaseReportModule.generateReport.errorCopyingFiles=Error copying tagged files",
232  "PortableCaseReportModule.generateReport.errorCopyingArtifacts=Error copying tagged artifacts",
233  "PortableCaseReportModule.generateReport.errorCopyingInterestingFiles=Error copying interesting files",
234  "PortableCaseReportModule.generateReport.errorCopyingInterestingResults=Error copying interesting results",
235  "PortableCaseReportModule.generateReport.errorCreatingImageTagTable=Error creating image tags table",
236  "PortableCaseReportModule.generateReport.errorCopyingAutopsy=Error copying application",
237  "# {0} - attribute type name",
238  "PortableCaseReportModule.generateReport.errorLookingUpAttrType=Error looking up attribute type {0}",
239  "PortableCaseReportModule.generateReport.compressingCase=Compressing case...",
240  "PortableCaseReportModule_generateReport_copyingAutopsy=Copying application..."
241  })
242 
243  public void generateReport(String reportPath, PortableCaseReportModuleSettings options, ReportProgressPanel progressPanel) {
244  this.settings = options;
245  progressPanel.setIndeterminate(true);
246  progressPanel.start();
247  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_verifying());
248 
249  // Clear out any old values
250  cleanup();
251 
252  // Validate the input parameters
253  File outputDir = new File(reportPath);
254  if (!outputDir.exists()) {
255  handleError("Output folder " + outputDir.toString() + " does not exist",
256  Bundle.PortableCaseReportModule_generateReport_outputDirDoesNotExist(outputDir.toString()), null, progressPanel); // NON-NLS
257  return;
258  }
259 
260  if (!outputDir.isDirectory()) {
261  handleError("Output folder " + outputDir.toString() + " is not a folder",
262  Bundle.PortableCaseReportModule_generateReport_outputDirIsNotDir(outputDir.toString()), null, progressPanel); // NON-NLS
263  return;
264  }
265 
266  // Save the current case object
267  try {
268  currentCase = Case.getCurrentCaseThrows();
269  caseName = currentCase.getDisplayName() + " (Portable)"; // NON-NLS
270  } catch (NoCurrentCaseException ex) {
271  handleError("Current case has been closed",
272  Bundle.PortableCaseReportModule_generateReport_caseClosed(), null, progressPanel); // NON-NLS
273  return;
274  }
275 
276  // If the applciation is included add an extra level to the directory structure
277  if (options.includeApplication()) {
278  outputDir = Paths.get(outputDir.toString(), caseName).toFile();
279  }
280  // Check that there will be something to copy
281  List<TagName> tagNames;
282  if (options.areAllTagsSelected()) {
283  try {
285  } catch (NoCurrentCaseException | TskCoreException ex) {
286  handleError("Unable to get all tags",
287  Bundle.PortableCaseReportModule_generateReport_errorReadingTags(), ex, progressPanel); // NON-NLS
288  return;
289  }
290  } else {
291  tagNames = options.getSelectedTagNames();
292  }
293 
294  List<String> setNames;
295  if (options.areAllSetsSelected()) {
296  try {
297  setNames = getAllInterestingItemsSets();
298  } catch (NoCurrentCaseException | TskCoreException ex) {
299  handleError("Unable to get all interesting items sets",
300  Bundle.PortableCaseReportModule_generateReport_errorReadingSets(), ex, progressPanel); // NON-NLS
301  return;
302  }
303  } else {
304  setNames = options.getSelectedSetNames();
305  }
306 
307  if (tagNames.isEmpty() && setNames.isEmpty()) {
308  handleError("No content to copy",
309  Bundle.PortableCaseReportModule_generateReport_noContentToCopy(), null, progressPanel); // NON-NLS
310  return;
311  }
312 
313  // Create the case.
314  // portableSkCase and caseFolder will be set here.
315  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_creatingCase());
316  createCase(outputDir, progressPanel);
317  if (portableSkCase == null) {
318  // The error has already been handled
319  return;
320  }
321 
322  // Check for cancellation
323  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
324  handleCancellation(progressPanel);
325  return;
326  }
327 
328  // Set up the table for the image tags
329  try {
330  initializeImageTags(progressPanel);
331  } catch (TskCoreException ex) {
332  handleError("Error creating image tag table", Bundle.PortableCaseReportModule_generateReport_errorCreatingImageTagTable(), ex, progressPanel); // NON-NLS
333  return;
334  }
335 
336  // Copy the selected tags
337  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingTags());
338  try {
339  for (TagName tagName : tagNames) {
340  TagName newTagName = portableSkCase.addOrUpdateTagName(tagName.getDisplayName(), tagName.getDescription(), tagName.getColor(), tagName.getKnownStatus());
341  oldTagNameToNewTagName.put(tagName, newTagName);
342  }
343  } catch (TskCoreException ex) {
344  handleError("Error copying tags", Bundle.PortableCaseReportModule_generateReport_errorCopyingTags(), ex, progressPanel); // NON-NLS
345  return;
346  }
347 
348  // Set up tracking to support any custom artifact or attribute types
349  for (BlackboardArtifact.ARTIFACT_TYPE type : BlackboardArtifact.ARTIFACT_TYPE.values()) {
350  oldArtTypeIdToNewArtTypeId.put(type.getTypeID(), type.getTypeID());
351  }
352  for (BlackboardAttribute.ATTRIBUTE_TYPE type : BlackboardAttribute.ATTRIBUTE_TYPE.values()) {
353  try {
354  oldAttrTypeIdToNewAttrType.put(type.getTypeID(), portableSkCase.getAttributeType(type.getLabel()));
355  } catch (TskCoreException ex) {
356  handleError("Error looking up attribute name " + type.getLabel(),
357  Bundle.PortableCaseReportModule_generateReport_errorLookingUpAttrType(type.getLabel()),
358  ex, progressPanel); // NON-NLS
359  }
360  }
361 
362  // Copy the tagged files
363  try {
364  for (TagName tagName : tagNames) {
365  // Check for cancellation
366  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
367  handleCancellation(progressPanel);
368  return;
369  }
370  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingFiles(tagName.getDisplayName()));
371  addFilesToPortableCase(tagName, progressPanel);
372 
373  // Check for cancellation
374  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
375  handleCancellation(progressPanel);
376  return;
377  }
378  }
379  } catch (TskCoreException ex) {
380  handleError("Error copying tagged files", Bundle.PortableCaseReportModule_generateReport_errorCopyingFiles(), ex, progressPanel); // NON-NLS
381  return;
382  }
383 
384  // Copy the tagged artifacts and associated files
385  try {
386  for (TagName tagName : tagNames) {
387  // Check for cancellation
388  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
389  handleCancellation(progressPanel);
390  return;
391  }
392  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingArtifacts(tagName.getDisplayName()));
393  addArtifactsToPortableCase(tagName, progressPanel);
394 
395  // Check for cancellation
396  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
397  handleCancellation(progressPanel);
398  return;
399  }
400  }
401  } catch (TskCoreException ex) {
402  handleError("Error copying tagged artifacts", Bundle.PortableCaseReportModule_generateReport_errorCopyingArtifacts(), ex, progressPanel); // NON-NLS
403  return;
404  }
405 
406  // Copy interesting files and results
407  if (!setNames.isEmpty()) {
408  try {
409  List<AnalysisResult> interestingFiles = currentCase.getSleuthkitCase().getBlackboard().getAnalysisResultsByType(BlackboardArtifact.Type.TSK_INTERESTING_FILE_HIT.getTypeID());
410  for (AnalysisResult art : interestingFiles) {
411  // Check for cancellation
412  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
413  handleCancellation(progressPanel);
414  return;
415  }
416 
417  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
418  if (setNames.contains(setAttr.getValueString())) {
419  copyContentToPortableCase(art, progressPanel);
420  }
421  }
422  } catch (TskCoreException ex) {
423  handleError("Error copying interesting files", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingFiles(), ex, progressPanel); // NON-NLS
424  return;
425  }
426 
427  try {
428  List<AnalysisResult> interestingResults = currentCase.getSleuthkitCase().getBlackboard().getAnalysisResultsByType(BlackboardArtifact.Type.TSK_INTERESTING_ARTIFACT_HIT.getTypeID());
429  for (AnalysisResult art : interestingResults) {
430  // Check for cancellation
431  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
432  handleCancellation(progressPanel);
433  return;
434  }
435  BlackboardAttribute setAttr = art.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
436  if (setNames.contains(setAttr.getValueString())) {
437  copyContentToPortableCase(art, progressPanel);
438  }
439  }
440  } catch (TskCoreException ex) {
441  handleError("Error copying interesting results", Bundle.PortableCaseReportModule_generateReport_errorCopyingInterestingResults(), ex, progressPanel); // NON-NLS
442  return;
443  }
444  }
445 
446  // Check for cancellation
447  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
448  handleCancellation(progressPanel);
449  return;
450  }
451 
452  //Attempt to generate and included the CASE-UCO report.
453  generateCaseUcoReport(tagNames, setNames, progressPanel);
454 
455  if (options.includeApplication()) {
456  try {
457  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_copyingAutopsy());
458  copyApplication(getApplicationBasePath(), outputDir.getAbsolutePath());
459  createAppLaunchBatFile(outputDir.getAbsolutePath());
460  } catch (IOException ex) {
461  handleError("Error copying autopsy", Bundle.PortableCaseReportModule_generateReport_errorCopyingAutopsy(), ex, progressPanel); // NON-NLS
462  }
463  }
464 
465  // Compress the case (if desired)
466  if (options.shouldCompress()) {
467  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateReport_compressingCase());
468 
469  if(!compressCase(progressPanel, options.includeApplication() ? outputDir.getAbsolutePath() : caseFolder.getAbsolutePath())){
470  // Errors have been handled already
471  return;
472  }
473 
474  // Check for cancellation
475  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
476  handleCancellation(progressPanel);
477  return;
478  }
479  }
480 
481  // Close the case connections and clear out the maps
482  cleanup();
483 
485 
486  }
487 
499  @NbBundle.Messages({
500  "PortableCaseReportModule.generateCaseUcoReport.errorCreatingReportFolder=Could not make report folder",
501  "PortableCaseReportModule.generateCaseUcoReport.errorGeneratingCaseUcoReport=Problem while generating CASE-UCO report",
502  "PortableCaseReportModule.generateCaseUcoReport.startCaseUcoReportGeneration=Creating a CASE-UCO report of the portable case",
503  "PortableCaseReportModule.generateCaseUcoReport.successCaseUcoReportGeneration=Successfully created a CASE-UCO report of the portable case"
504  })
505  private void generateCaseUcoReport(List<TagName> tagNames, List<String> setNames, ReportProgressPanel progressPanel) {
506  //Create the 'Reports' directory to include a CASE-UCO report.
507  Path reportsDirectory = Paths.get(caseFolder.toString(), "Reports");
508  if (!reportsDirectory.toFile().mkdir()) {
509  logger.log(Level.SEVERE, "Could not make the report folder... skipping "
510  + "CASE-UCO report generation for the portable case");
511  return;
512  }
513 
514  Path reportFile = reportsDirectory.resolve(CASE_UCO_FILE_NAME);
515 
516  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_startCaseUcoReportGeneration());
517  try (OutputStream stream = new FileOutputStream(reportFile.toFile());
518  JsonWriter reportWriter = new JsonWriter(new OutputStreamWriter(stream, "UTF-8"))) {
519  Gson gson = new GsonBuilder().setPrettyPrinting().create();
520  reportWriter.setIndent(" ");
521  reportWriter.beginObject();
522  reportWriter.name("@graph");
523  reportWriter.beginArray();
524 
525  String caseTempDirectory = currentCase.getTempDirectory();
526  SleuthkitCase skCase = currentCase.getSleuthkitCase();
527  TagsManager tagsManager = currentCase.getServices().getTagsManager();
528 
529  //Create temp directory to filter out duplicate files.
530  //Clear out the old directory if it exists.
531  Path tmpDir = Paths.get(caseTempDirectory, CASE_UCO_TMP_DIR);
532  FileUtils.deleteDirectory(tmpDir.toFile());
533  Files.createDirectory(tmpDir);
534 
535  CaseUcoExporter exporter = new CaseUcoExporter(currentCase.getSleuthkitCase());
536  for (JsonElement element : exporter.exportSleuthkitCase()) {
537  gson.toJson(element, reportWriter);
538  }
539 
540  //Load all interesting BlackboardArtifacts that belong to the selected SET_NAMEs
541  //binned by data source id.
542  Multimap<Long, BlackboardArtifact> artifactsWithSetName = getInterestingArtifactsBySetName(skCase, setNames);
543 
544  //Search each data source looking for content tags and interesting
545  //items that match the selected tag names and set names.
546  for (DataSource dataSource : currentCase.getSleuthkitCase().getDataSources()) {
547  // Helper flag to ensure each data source is only written once in
548  // a report.
549  boolean dataSourceHasBeenIncluded = false;
550 
551  //Search content tags and artifact tags that match
552  for (TagName tagName : tagNames) {
553  for (ContentTag ct : tagsManager.getContentTagsByTagName(tagName, dataSource.getId())) {
554  dataSourceHasBeenIncluded |= addUniqueFile(ct.getContent(),
555  dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
556  }
557  for (BlackboardArtifactTag bat : tagsManager.getBlackboardArtifactTagsByTagName(tagName, dataSource.getId())) {
558  dataSourceHasBeenIncluded |= addUniqueFile(bat.getContent(),
559  dataSource, tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
560  }
561  }
562  //Search artifacts that this data source contains
563  for (BlackboardArtifact bArt : artifactsWithSetName.get(dataSource.getId())) {
564  Content sourceContent = bArt.getParent();
565  dataSourceHasBeenIncluded |= addUniqueFile(sourceContent, dataSource,
566  tmpDir, gson, exporter, reportWriter, dataSourceHasBeenIncluded);
567  }
568  }
569 
570  // Finish the report.
571  reportWriter.endArray();
572  reportWriter.endObject();
573  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_successCaseUcoReportGeneration());
574  } catch (IOException | TskCoreException ex) {
575  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_generateCaseUcoReport_errorGeneratingCaseUcoReport());
576  logger.log(Level.SEVERE, "Error encountered while trying to create "
577  + "CASE-UCO output for portable case.. the portable case will be "
578  + "completed without a CASE-UCO report.", ex);
579  }
580  }
581 
587  private Multimap<Long, BlackboardArtifact> getInterestingArtifactsBySetName(SleuthkitCase skCase, List<String> setNames) throws TskCoreException {
588  Multimap<Long, BlackboardArtifact> artifactsWithSetName = ArrayListMultimap.create();
589  if (!setNames.isEmpty()) {
590  List<BlackboardArtifact> allArtifacts = skCase.getBlackboardArtifacts(
591  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
592  allArtifacts.addAll(skCase.getBlackboardArtifacts(
593  BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT));
594 
595  for (BlackboardArtifact bArt : allArtifacts) {
596  BlackboardAttribute setAttr = bArt.getAttribute(
597  new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
598  if (setNames.contains(setAttr.getValueString())) {
599  artifactsWithSetName.put(bArt.getDataSource().getId(), bArt);
600  }
601  }
602  }
603  return artifactsWithSetName;
604  }
605 
626  private boolean addUniqueFile(Content content, DataSource dataSource,
627  Path tmpDir, Gson gson, CaseUcoExporter exporter, JsonWriter reportWriter,
628  boolean dataSourceHasBeenIncluded) throws IOException, TskCoreException {
629  if (content instanceof AbstractFile && !(content instanceof DataSource)) {
630  AbstractFile absFile = (AbstractFile) content;
631  Path filePath = tmpDir.resolve(Long.toString(absFile.getId()));
632  if (!absFile.isDir() && !Files.exists(filePath)) {
633  if (!dataSourceHasBeenIncluded) {
634  for (JsonElement element : exporter.exportDataSource(dataSource)) {
635  gson.toJson(element, reportWriter);
636  }
637  }
638  String subFolder = getExportSubfolder(absFile);
639  String fileName = absFile.getId() + "-" + FileUtil.escapeFileName(absFile.getName());
640  for (JsonElement element : exporter.exportAbstractFile(absFile, Paths.get(FILE_FOLDER_NAME, subFolder, fileName).toString())) {
641  gson.toJson(element, reportWriter);
642  }
643  Files.createFile(filePath);
644  return true;
645  }
646  }
647  return false;
648  }
649 
650  private List<String> getAllInterestingItemsSets() throws NoCurrentCaseException, TskCoreException {
651 
652  // Get the set names in use for the current case.
653  List<String> setNames = new ArrayList<>();
654  Map<String, Long> setCounts;
655 
656  // There may not be a case open when configuring report modules for Command Line execution
657  // Get all SET_NAMEs from interesting item artifacts
658  String innerSelect = "SELECT (value_text) AS set_name FROM blackboard_attributes WHERE (artifact_type_id = '"
659  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() + "' OR artifact_type_id = '"
660  + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID() + "') AND attribute_type_id = '"
661  + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() + "'"; // NON-NLS
662 
663  // Get the count of each SET_NAME
664  String query = "set_name, count(1) AS set_count FROM (" + innerSelect + ") set_names GROUP BY set_name"; // NON-NLS
665 
667  Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
668  setCounts = callback.getSetCountMap();
669  setNames.addAll(setCounts.keySet());
670  return setNames;
671  }
672 
680  @NbBundle.Messages({
681  "# {0} - case folder",
682  "PortableCaseReportModule.createCase.caseDirExists=Case folder {0} already exists",
683  "PortableCaseReportModule.createCase.errorCreatingCase=Error creating case",
684  "# {0} - folder",
685  "PortableCaseReportModule.createCase.errorCreatingFolder=Error creating folder {0}",
686  "PortableCaseReportModule.createCase.errorStoringMaxIds=Error storing maximum database IDs",})
687  private void createCase(File outputDir, ReportProgressPanel progressPanel) {
688 
689  // Create the case folder
690  caseFolder = Paths.get(outputDir.toString(), caseName).toFile();
691 
692  if (caseFolder.exists()) {
693  handleError("Case folder " + caseFolder.toString() + " already exists",
694  Bundle.PortableCaseReportModule_createCase_caseDirExists(caseFolder.toString()), null, progressPanel); // NON-NLS
695  return;
696  }
697 
698  // Create the case
699  try {
700  portableSkCase = currentCase.createPortableCase(caseName, caseFolder);
701  } catch (TskCoreException ex) {
702  handleError("Error creating case " + caseName + " in folder " + caseFolder.toString(),
703  Bundle.PortableCaseReportModule_createCase_errorCreatingCase(), ex, progressPanel); // NON-NLS
704  return;
705  }
706 
707  // Store the highest IDs
708  try {
709  saveHighestIds();
710  } catch (TskCoreException ex) {
711  handleError("Error storing maximum database IDs",
712  Bundle.PortableCaseReportModule_createCase_errorStoringMaxIds(), ex, progressPanel); // NON-NLS
713  return;
714  }
715 
716  // Create the base folder for the copied files
717  copiedFilesFolder = Paths.get(caseFolder.toString(), FILE_FOLDER_NAME).toFile();
718  if (!copiedFilesFolder.mkdir()) {
719  handleError("Error creating folder " + copiedFilesFolder.toString(),
720  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(copiedFilesFolder.toString()), null, progressPanel); // NON-NLS
721  return;
722  }
723 
724  // Create subfolders for the copied files
725  for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
726  File subFolder = Paths.get(copiedFilesFolder.toString(), cat.getDisplayName()).toFile();
727  if (!subFolder.mkdir()) {
728  handleError("Error creating folder " + subFolder.toString(),
729  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(subFolder.toString()), null, progressPanel); // NON-NLS
730  return;
731  }
732  }
733  File unknownTypeFolder = Paths.get(copiedFilesFolder.toString(), UNKNOWN_FILE_TYPE_FOLDER).toFile();
734  if (!unknownTypeFolder.mkdir()) {
735  handleError("Error creating folder " + unknownTypeFolder.toString(),
736  Bundle.PortableCaseReportModule_createCase_errorCreatingFolder(unknownTypeFolder.toString()), null, progressPanel); // NON-NLS
737  return;
738  }
739 
740  }
741 
747  private void saveHighestIds() throws TskCoreException {
748 
749  CaseDbAccessManager currentCaseDbManager = currentCase.getSleuthkitCase().getCaseDbAccessManager();
750 
751  String tableSchema = "( table_name TEXT PRIMARY KEY, "
752  + " max_id TEXT)"; // NON-NLS
753 
754  portableSkCase.getCaseDbAccessManager().createTable(MAX_ID_TABLE_NAME, tableSchema);
755 
756  currentCaseDbManager.select("max(obj_id) as max_id from tsk_objects", new StoreMaxIdCallback("tsk_objects")); // NON-NLS
757  currentCaseDbManager.select("max(tag_id) as max_id from content_tags", new StoreMaxIdCallback("content_tags")); // NON-NLS
758  currentCaseDbManager.select("max(tag_id) as max_id from blackboard_artifact_tags", new StoreMaxIdCallback("blackboard_artifact_tags")); // NON-NLS
759  currentCaseDbManager.select("max(examiner_id) as max_id from tsk_examiners", new StoreMaxIdCallback("tsk_examiners")); // NON-NLS
760  }
761 
769  private void initializeImageTags(ReportProgressPanel progressPanel) throws TskCoreException {
770 
771  // Create the image tags table in the portable case
772  CaseDbAccessManager portableDbAccessManager = portableSkCase.getCaseDbAccessManager();
773  if (!portableDbAccessManager.tableExists(ContentViewerTagManager.TABLE_NAME)) {
775  }
776  }
777 
786  private void addFilesToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
787 
788  // Get all the tags in the current case
789  List<ContentTag> tags = currentCase.getServices().getTagsManager().getContentTagsByTagName(oldTagName);
790 
791  // Copy the files into the portable case and tag
792  for (ContentTag tag : tags) {
793 
794  // Check for cancellation
795  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
796  return;
797  }
798 
799  Content content = tag.getContent();
800  if (content instanceof AbstractFile) {
801 
802  long newFileId = copyContentToPortableCase(content, progressPanel);
803 
804  // Tag the file
805  if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
806  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
807  }
808  ContentTagChange newContentTag = portableSkCase.getTaggingManager().addContentTag(newIdToContent.get(newFileId), oldTagNameToNewTagName.get(tag.getName()), tag.getComment(), tag.getBeginByteOffset(), tag.getEndByteOffset());
809 
810  // Get the image tag data associated with this tag (empty string if there is none)
811  // and save it if present
812  String appData = getImageTagDataForContentTag(tag);
813  if (!appData.isEmpty()) {
814  addImageTagToPortableCase(newContentTag.getAddedTag(), appData);
815  }
816  }
817  }
818  }
819 
830  private String getImageTagDataForContentTag(ContentTag tag) throws TskCoreException {
831 
832  GetImageTagCallback callback = new GetImageTagCallback();
833  String query = "* FROM " + ContentViewerTagManager.TABLE_NAME + " WHERE content_tag_id = " + tag.getId();
834  currentCase.getSleuthkitCase().getCaseDbAccessManager().select(query, callback);
835  return callback.getAppData();
836  }
837 
841  private static class GetImageTagCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
842 
843  private static final Logger logger = Logger.getLogger(PortableCaseReportModule.class.getName());
844  private String appData = "";
845 
846  @Override
847  public void process(ResultSet rs) {
848  try {
849  while (rs.next()) {
850  try {
851  appData = rs.getString("app_data"); // NON-NLS
852  } catch (SQLException ex) {
853  logger.log(Level.WARNING, "Unable to get app_data from result set", ex); // NON-NLS
854  }
855  }
856  } catch (SQLException ex) {
857  logger.log(Level.WARNING, "Failed to get next result for app_data", ex); // NON-NLS
858  }
859  }
860 
866  String getAppData() {
867  return appData;
868  }
869  }
870 
879  private void addImageTagToPortableCase(ContentTag newContentTag, String appData) throws TskCoreException {
880  String insert = "(content_tag_id, app_data) VALUES (" + newContentTag.getId() + ", '" + appData + "')";
881  portableSkCase.getCaseDbAccessManager().insert(ContentViewerTagManager.TABLE_NAME, insert);
882  }
883 
892  private void addArtifactsToPortableCase(TagName oldTagName, ReportProgressPanel progressPanel) throws TskCoreException {
893 
894  List<BlackboardArtifactTag> tags = currentCase.getServices().getTagsManager().getBlackboardArtifactTagsByTagName(oldTagName);
895 
896  // Copy the artifacts into the portable case along with their content and tag
897  for (BlackboardArtifactTag tag : tags) {
898 
899  // Check for cancellation
900  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
901  return;
902  }
903 
904  // Copy the source content
905  Content content = tag.getContent();
906  long newContentId = copyContentToPortableCase(content, progressPanel);
907 
908  // Copy the artifact
909  BlackboardArtifact newArtifact = copyArtifact(newContentId, tag.getArtifact());
910 
911  // Copy any attachments
912  copyAttachments(newArtifact, tag.getArtifact(), portableSkCase.getAbstractFileById(newContentId));
913 
914  // Copy any files associated with this artifact through the TSK_PATH_ID attribute
915  copyPathID(newArtifact, tag.getArtifact());
916 
917  // Tag the artfiact
918  if (!oldTagNameToNewTagName.containsKey(tag.getName())) {
919  throw new TskCoreException("TagName map is missing entry for ID " + tag.getName().getId() + " with display name " + tag.getName().getDisplayName()); // NON-NLS
920  }
921  portableSkCase.getTaggingManager().addArtifactTag(newArtifact, oldTagNameToNewTagName.get(tag.getName()), tag.getComment());
922  }
923  }
924 
937  private BlackboardArtifact copyArtifact(long newContentId, BlackboardArtifact artifactToCopy) throws TskCoreException {
938 
939  if (oldArtifactIdToNewArtifact.containsKey(artifactToCopy.getArtifactID())) {
940  return oldArtifactIdToNewArtifact.get(artifactToCopy.getArtifactID());
941  }
942 
943  // First create the associated artifact (if present)
944  BlackboardAttribute oldAssociatedAttribute = artifactToCopy.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
945  List<BlackboardAttribute> newAttrs = new ArrayList<>();
946  if (oldAssociatedAttribute != null) {
947  BlackboardArtifact oldAssociatedArtifact = currentCase.getSleuthkitCase().getBlackboardArtifact(oldAssociatedAttribute.getValueLong());
948  BlackboardArtifact newAssociatedArtifact = copyArtifact(newContentId, oldAssociatedArtifact);
949  newAttrs.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
950  String.join(",", oldAssociatedAttribute.getSources()), newAssociatedArtifact.getArtifactID()));
951  }
952 
953  List<BlackboardAttribute> oldAttrs = artifactToCopy.getAttributes();
954 
955  // Copy over each attribute, making sure the type is in the new case.
956  for (BlackboardAttribute oldAttr : oldAttrs) {
957 
958  // Skip attributes that are handled elsewhere
959  if (SPECIALLY_HANDLED_ATTRS.contains(oldAttr.getAttributeType().getTypeID())) {
960  continue;
961  }
962 
963  BlackboardAttribute.Type newAttributeType = getNewAttributeType(oldAttr);
964  switch (oldAttr.getValueType()) {
965  case BYTE:
966  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
967  oldAttr.getValueBytes()));
968  break;
969  case DOUBLE:
970  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
971  oldAttr.getValueDouble()));
972  break;
973  case INTEGER:
974  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
975  oldAttr.getValueInt()));
976  break;
977  case DATETIME:
978  case LONG:
979  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
980  oldAttr.getValueLong()));
981  break;
982  case STRING:
983  case JSON:
984  newAttrs.add(new BlackboardAttribute(newAttributeType, String.join(",", oldAttr.getSources()),
985  oldAttr.getValueString()));
986  break;
987  default:
988  throw new TskCoreException("Unexpected attribute value type found: " + oldAttr.getValueType().getLabel()); // NON-NLS
989  }
990  }
991  // Create the new artifact
992  int newArtifactTypeId = getNewArtifactTypeId(artifactToCopy);
993  BlackboardArtifact.Type newArtifactType = portableSkCase.getBlackboard().getArtifactType(newArtifactTypeId);
994  BlackboardArtifact newArtifact;
995 
996  // First, check if the artifact being copied is an AnalysisResult or a DataArtifact. If it
997  // is neither, attempt to reload it as the appropriate subclass.
998  if (!((artifactToCopy instanceof AnalysisResult) || (artifactToCopy instanceof DataArtifact))) {
999  try {
1000  if (newArtifactType.getCategory().equals(BlackboardArtifact.Category.ANALYSIS_RESULT)) {
1001  AnalysisResult ar = currentCase.getSleuthkitCase().getBlackboard().getAnalysisResultById(artifactToCopy.getId());
1002  if (ar != null) {
1003  artifactToCopy = ar;
1004  }
1005  } else {
1006  DataArtifact da = currentCase.getSleuthkitCase().getBlackboard().getDataArtifactById(artifactToCopy.getId());
1007  if (da != null) {
1008  artifactToCopy = da;
1009  }
1010  }
1011  } catch (TskCoreException ex) {
1012  // If the lookup failed, just use the orginal BlackboardArtifact
1013  }
1014  }
1015 
1016  try {
1017  if (artifactToCopy instanceof AnalysisResult) {
1018  AnalysisResult analysisResultToCopy = (AnalysisResult) artifactToCopy;
1019  newArtifact = portableSkCase.getBlackboard().newAnalysisResult(newArtifactType, newContentId,
1020  newIdToContent.get(newContentId).getDataSource().getId(), analysisResultToCopy.getScore(),
1021  analysisResultToCopy.getConclusion(), analysisResultToCopy.getConfiguration(),
1022  analysisResultToCopy.getJustification(), newAttrs).getAnalysisResult();
1023  } else if (artifactToCopy instanceof DataArtifact) {
1024  DataArtifact dataArtifactToCopy = (DataArtifact) artifactToCopy;
1025  Long newOsAccountId = null;
1026  if (dataArtifactToCopy.getOsAccountObjectId().isPresent()) {
1027  copyOsAccount(dataArtifactToCopy.getOsAccountObjectId().get());
1028  newOsAccountId = oldOsAccountIdToNewOsAccountId.get((dataArtifactToCopy.getOsAccountObjectId().get()));
1029  }
1030  newArtifact = portableSkCase.getBlackboard().newDataArtifact(newArtifactType, newContentId,
1031  newIdToContent.get(newContentId).getDataSource().getId(),
1032  newAttrs, newOsAccountId);
1033  } else {
1034  if (newArtifactType.getCategory().equals(BlackboardArtifact.Category.ANALYSIS_RESULT)) {
1035  newArtifact = portableSkCase.getBlackboard().newAnalysisResult(newArtifactType, newContentId,
1036  newIdToContent.get(newContentId).getDataSource().getId(), Score.SCORE_NONE,
1037  null, null, null, newAttrs).getAnalysisResult();
1038  } else {
1039  newArtifact = portableSkCase.getBlackboard().newDataArtifact(newArtifactType, newContentId,
1040  newIdToContent.get(newContentId).getDataSource().getId(),
1041  newAttrs, null);
1042  }
1043  }
1044  } catch (BlackboardException ex) {
1045  throw new TskCoreException("Error copying artifact with ID: " + artifactToCopy.getId());
1046  }
1047 
1048  oldArtifactIdToNewArtifact.put(artifactToCopy.getArtifactID(), newArtifact);
1049  return newArtifact;
1050  }
1051 
1061  private int getNewArtifactTypeId(BlackboardArtifact oldArtifact) throws TskCoreException {
1062  if (oldArtTypeIdToNewArtTypeId.containsKey(oldArtifact.getArtifactTypeID())) {
1063  return oldArtTypeIdToNewArtTypeId.get(oldArtifact.getArtifactTypeID());
1064  }
1065 
1066  BlackboardArtifact.Type oldCustomType = currentCase.getSleuthkitCase().getArtifactType(oldArtifact.getArtifactTypeName());
1067  try {
1068  BlackboardArtifact.Type newCustomType = portableSkCase.getBlackboard().getOrAddArtifactType(oldCustomType.getTypeName(), oldCustomType.getDisplayName());
1069  oldArtTypeIdToNewArtTypeId.put(oldArtifact.getArtifactTypeID(), newCustomType.getTypeID());
1070  return newCustomType.getTypeID();
1071  } catch (BlackboardException ex) {
1072  throw new TskCoreException("Error creating new artifact type " + oldCustomType.getTypeName(), ex); // NON-NLS
1073  }
1074  }
1075 
1085  private BlackboardAttribute.Type getNewAttributeType(BlackboardAttribute oldAttribute) throws TskCoreException {
1086  BlackboardAttribute.Type oldAttrType = oldAttribute.getAttributeType();
1087  if (oldAttrTypeIdToNewAttrType.containsKey(oldAttrType.getTypeID())) {
1088  return oldAttrTypeIdToNewAttrType.get(oldAttrType.getTypeID());
1089  }
1090 
1091  try {
1092  BlackboardAttribute.Type newCustomType = portableSkCase.getBlackboard().getOrAddAttributeType(oldAttrType.getTypeName(),
1093  oldAttrType.getValueType(), oldAttrType.getDisplayName());
1094  oldAttrTypeIdToNewAttrType.put(oldAttribute.getAttributeType().getTypeID(), newCustomType);
1095  return newCustomType;
1096  } catch (BlackboardException ex) {
1097  throw new TskCoreException("Error creating new attribute type " + oldAttrType.getTypeName(), ex); // NON-NLS
1098  }
1099  }
1100 
1111  @NbBundle.Messages({
1112  "# {0} - File name",
1113  "PortableCaseReportModule.copyContentToPortableCase.copyingFile=Copying file {0}",})
1114  private long copyContentToPortableCase(Content content, ReportProgressPanel progressPanel) throws TskCoreException {
1115  progressPanel.updateStatusLabel(Bundle.PortableCaseReportModule_copyContentToPortableCase_copyingFile(content.getUniquePath()));
1116  return copyContent(content);
1117  }
1118 
1128  private long copyContent(Content content) throws TskCoreException {
1129 
1130  // Check if we've already copied this content
1131  if (oldIdToNewContent.containsKey(content.getId())) {
1132  return oldIdToNewContent.get(content.getId()).getId();
1133  }
1134 
1135  // Otherwise:
1136  // - Make parent of this object (if applicable)
1137  // - Copy this content
1138  long parentId = 0;
1139  if (content.getParent() != null) {
1140  parentId = copyContent(content.getParent());
1141  }
1142 
1143  Content newContent;
1144  if (content instanceof BlackboardArtifact) {
1145  BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
1146  newContent = copyArtifact(parentId, artifactToCopy);
1147  } else {
1148 
1149  // Get or create the host (if needed) before beginning transaction.
1150  Host newHost = null;
1151  if (content instanceof DataSource) {
1152  Host oldHost = ((DataSource)content).getHost();
1153  newHost = portableSkCase.getHostManager().newHost(oldHost.getName());
1154  }
1155 
1156  // Copy the associated OS account (if needed) before beginning transaction.
1157  if (content instanceof AbstractFile) {
1158  AbstractFile file = (AbstractFile) content;
1159  if (file.getOsAccountObjectId().isPresent()) {
1160  copyOsAccount(file.getOsAccountObjectId().get());
1161  }
1162  }
1163 
1164  CaseDbTransaction trans = portableSkCase.beginTransaction();
1165  try {
1166  if (content instanceof Image) {
1167  Image image = (Image) content;
1168  newContent = portableSkCase.addImage(image.getType(), image.getSsize(), image.getSize(), image.getName(),
1169  new ArrayList<>(), image.getTimeZone(), image.getMd5(), image.getSha1(), image.getSha256(), image.getDeviceId(), newHost, trans);
1170  } else if (content instanceof VolumeSystem) {
1171  VolumeSystem vs = (VolumeSystem) content;
1172  newContent = portableSkCase.addVolumeSystem(parentId, vs.getType(), vs.getOffset(), vs.getBlockSize(), trans);
1173  } else if (content instanceof Volume) {
1174  Volume vs = (Volume) content;
1175  newContent = portableSkCase.addVolume(parentId, vs.getAddr(), vs.getStart(), vs.getLength(),
1176  vs.getDescription(), vs.getFlags(), trans);
1177  } else if (content instanceof Pool) {
1178  Pool pool = (Pool) content;
1179  newContent = portableSkCase.addPool(parentId, pool.getType(), trans);
1180  } else if (content instanceof FileSystem) {
1181  FileSystem fs = (FileSystem) content;
1182  newContent = portableSkCase.addFileSystem(parentId, fs.getImageOffset(), fs.getFsType(), fs.getBlock_size(),
1183  fs.getBlock_count(), fs.getRoot_inum(), fs.getFirst_inum(), fs.getLastInum(),
1184  fs.getName(), trans);
1185  } else if (content instanceof BlackboardArtifact) {
1186  BlackboardArtifact artifactToCopy = (BlackboardArtifact) content;
1187  newContent = copyArtifact(parentId, artifactToCopy);
1188  } else if (content instanceof AbstractFile) {
1189  AbstractFile abstractFile = (AbstractFile) content;
1190 
1191  if (abstractFile instanceof LocalFilesDataSource) {
1192  LocalFilesDataSource localFilesDS = (LocalFilesDataSource) abstractFile;
1193  newContent = portableSkCase.addLocalFilesDataSource(localFilesDS.getDeviceId(), localFilesDS.getName(), localFilesDS.getTimeZone(), newHost, trans);
1194  } else {
1195  if (abstractFile.isDir()) {
1196  newContent = portableSkCase.addLocalDirectory(parentId, abstractFile.getName(), trans);
1197  } else {
1198  try {
1199  // Copy the file
1200  String fileName = abstractFile.getId() + "-" + FileUtil.escapeFileName(abstractFile.getName());
1201  String exportSubFolder = getExportSubfolder(abstractFile);
1202  File exportFolder = Paths.get(copiedFilesFolder.toString(), exportSubFolder).toFile();
1203  File localFile = new File(exportFolder, fileName);
1204  ContentUtils.writeToFile(abstractFile, localFile);
1205 
1206  // Get the new parent object in the portable case database
1207  Content oldParent = abstractFile.getParent();
1208  if (!oldIdToNewContent.containsKey(oldParent.getId())) {
1209  throw new TskCoreException("Parent of file with ID " + abstractFile.getId() + " has not been created"); // NON-NLS
1210  }
1211  Content newParent = oldIdToNewContent.get(oldParent.getId());
1212 
1213  // Construct the relative path to the copied file
1214  String relativePath = FILE_FOLDER_NAME + File.separator + exportSubFolder + File.separator + fileName;
1215 
1216  Long newOsAccountId = null;
1217  if (abstractFile.getOsAccountObjectId().isPresent()) {
1218  newOsAccountId = oldOsAccountIdToNewOsAccountId.get(abstractFile.getOsAccountObjectId().get());
1219  }
1220 
1221  newContent = portableSkCase.addLocalFile(abstractFile.getName(), relativePath, abstractFile.getSize(),
1222  abstractFile.getCtime(), abstractFile.getCrtime(), abstractFile.getAtime(), abstractFile.getMtime(),
1223  abstractFile.getMd5Hash(), abstractFile.getSha256Hash(), abstractFile.getKnown(), abstractFile.getMIMEType(),
1224  true, TskData.EncodingType.NONE,
1225  newOsAccountId, abstractFile.getOwnerUid().orElse(null),
1226  newParent, trans);
1227  } catch (IOException ex) {
1228  throw new TskCoreException("Error copying file " + abstractFile.getName() + " with original obj ID "
1229  + abstractFile.getId(), ex); // NON-NLS
1230  }
1231  }
1232  }
1233  } else {
1234  throw new TskCoreException("Trying to copy unexpected Content type " + content.getClass().getName()); // NON-NLS
1235  }
1236  trans.commit();
1237  } catch (TskCoreException ex) {
1238  trans.rollback();
1239  throw (ex);
1240  }
1241  }
1242 
1243  // Save the new object
1244  oldIdToNewContent.put(content.getId(), newContent);
1245  newIdToContent.put(newContent.getId(), newContent);
1246  return oldIdToNewContent.get(content.getId()).getId();
1247  }
1248 
1255  private void copyOsAccount(Long oldOsAccountId) throws TskCoreException {
1256  // If it has already been copied, we're done.
1257  if (oldOsAccountIdToNewOsAccountId.containsKey(oldOsAccountId)) {
1258  return;
1259  }
1260 
1261  // Load the OS account from the current case.
1262  OsAccountManager oldOsAcctManager = currentCase.getSleuthkitCase().getOsAccountManager();
1263  OsAccount oldOsAccount = oldOsAcctManager.getOsAccountByObjectId(oldOsAccountId);
1264 
1265  // Load the realm associated with the OS account.
1266  OsAccountRealmManager oldRealmManager = currentCase.getSleuthkitCase().getOsAccountRealmManager();
1267  OsAccountRealm oldRealm = oldRealmManager.getRealmByRealmId(oldOsAccount.getRealmId());
1268 
1269  // Copy the realm to the portable case if necessary.
1270  if (!oldRealmIdToNewRealm.containsKey(oldOsAccount.getRealmId())) {
1271  OsAccountRealmManager newRealmManager = portableSkCase.getOsAccountRealmManager();
1272 
1273  Host host = null;
1274  if (oldRealm.getScopeHost().isPresent()) {
1275  host = oldRealm.getScopeHost().get();
1276  } else {
1277  if (oldRealm.getScope().equals(OsAccountRealm.RealmScope.DOMAIN)) {
1278  // This is a workaround to get around needing a new method for copying the realm.
1279  // The host won't be stored since it's a domain-scoped realm.
1280  List<Host> hosts = portableSkCase.getHostManager().getAllHosts();
1281  if (hosts.isEmpty()) {
1282  throw new TskCoreException("Failed to copy OsAccountRealm with ID=" + oldOsAccount.getRealmId() + " because there are no hosts in the case");
1283  }
1284  host = hosts.get(0);
1285  } else {
1286  throw new TskCoreException("Failed to copy OsAccountRealm with ID=" + oldOsAccount.getRealmId() + " because it is non-domain scoped but has no scope host");
1287  }
1288  }
1289 
1290  // We currently only support one realm name.
1291  String realmName = null;
1292  List<String> names = oldRealm.getRealmNames();
1293  if (!names.isEmpty()) {
1294  realmName = names.get(0);
1295  }
1296 
1297  try {
1298  OsAccountRealm newRealm = newRealmManager.newWindowsRealm(oldRealm.getRealmAddr().orElse(null), realmName, host, oldRealm.getScope());
1299  oldRealmIdToNewRealm.put(oldOsAccount.getRealmId(), newRealm);
1300  } catch (NotUserSIDException ex) {
1301  throw new TskCoreException("Failed to copy OsAccountRealm with ID=" + oldOsAccount.getRealmId(), ex);
1302  }
1303  }
1304 
1305  OsAccountManager newOsAcctManager = portableSkCase.getOsAccountManager();
1306  try {
1307  OsAccount newOsAccount = newOsAcctManager.newWindowsOsAccount(oldOsAccount.getAddr().orElse(null),
1308  oldOsAccount.getLoginName().orElse(null), oldRealmIdToNewRealm.get(oldOsAccount.getRealmId()));
1309  oldOsAccountIdToNewOsAccountId.put(oldOsAccountId, newOsAccount.getId());
1310  } catch (NotUserSIDException ex) {
1311  throw new TskCoreException("Failed to copy OsAccount with ID=" + oldOsAccount.getId(), ex);
1312  }
1313  }
1314 
1323  private void copyPathID(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact) throws TskCoreException {
1324  // Get the path ID attribute
1325  BlackboardAttribute oldPathIdAttr = oldArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID));
1326  if (oldPathIdAttr != null) {
1327  // Copy the file and remake the attribute if the path ID is valid
1328  long oldContentId = oldPathIdAttr.getValueLong();
1329  if (oldContentId > 0) {
1330  Content oldContent = currentCase.getSleuthkitCase().getContentById(oldContentId);
1331  long newContentId = copyContent(oldContent);
1332  newArtifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH_ID,
1333  String.join(",", oldPathIdAttr.getSources()), newContentId));
1334  }
1335  }
1336  }
1337 
1347  private void copyAttachments(BlackboardArtifact newArtifact, BlackboardArtifact oldArtifact, AbstractFile newFile) throws TskCoreException {
1348  // Get the attachments from TSK_ATTACHMENTS attribute.
1349  BlackboardAttribute attachmentsAttr = oldArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ATTACHMENTS));
1350  if (attachmentsAttr != null) {
1351  try {
1352  MessageAttachments msgAttachments = BlackboardJsonAttrUtil.fromAttribute(attachmentsAttr, MessageAttachments.class);
1353 
1354  Collection<MessageAttachments.FileAttachment> oldFileAttachments = msgAttachments.getFileAttachments();
1355  List<MessageAttachments.FileAttachment> newFileAttachments = new ArrayList<>();
1356  for (MessageAttachments.FileAttachment oldFileAttachment : oldFileAttachments) {
1357  long attachedFileObjId = oldFileAttachment.getObjectId();
1358  if (attachedFileObjId >= 0) {
1359  // Copy the attached file and save to the MessageAttachments object
1360  AbstractFile attachedFile = currentCase.getSleuthkitCase().getAbstractFileById(attachedFileObjId);
1361  if (attachedFile == null) {
1362  throw new TskCoreException("Error loading file with object ID " + attachedFileObjId + " from portable case");
1363  }
1364  long newFileID = copyContent(attachedFile);
1365  newFileAttachments.add(new MessageAttachments.FileAttachment(portableSkCase.getAbstractFileById(newFileID)));
1366  }
1367  }
1368 
1369  // Get the name of the module(s) that created the attachment
1370  String newSourceStr = "";
1371  List<String> oldSources = attachmentsAttr.getSources();
1372  if (! oldSources.isEmpty()) {
1373  newSourceStr = String.join(",", oldSources);
1374  }
1375 
1376  // Add the attachment. The account type specified in the constructor will not be used.
1377  CommunicationArtifactsHelper communicationArtifactsHelper = new CommunicationArtifactsHelper(currentCase.getSleuthkitCase(),
1378  newSourceStr, newFile, Account.Type.EMAIL);
1379  communicationArtifactsHelper.addAttachments(newArtifact, new MessageAttachments(newFileAttachments, msgAttachments.getUrlAttachments()));
1380  }
1381  catch (BlackboardJsonAttrUtil.InvalidJsonException ex) {
1382  throw new TskCoreException(String.format("Unable to parse json for MessageAttachments object in artifact: %s", oldArtifact.getName()), ex);
1383  }
1384  } else { // backward compatibility - email message attachments are derived files, children of the message.
1385  for (Content childContent : oldArtifact.getChildren()) {
1386  if (childContent instanceof AbstractFile) {
1387  copyContent(childContent);
1388  }
1389  }
1390  }
1391  }
1392 
1400  private String getExportSubfolder(AbstractFile abstractFile) {
1401  if (abstractFile.getMIMEType() == null || abstractFile.getMIMEType().isEmpty()) {
1402  return UNKNOWN_FILE_TYPE_FOLDER;
1403  }
1404 
1405  for (FileTypeCategory cat : FILE_TYPE_CATEGORIES) {
1406  if (cat.getMediaTypes().contains(abstractFile.getMIMEType())) {
1407  return cat.getDisplayName();
1408  }
1409  }
1410  return UNKNOWN_FILE_TYPE_FOLDER;
1411  }
1412 
1418  private Path getApplicationBasePath() {
1419  return getAutopsyExePath().getParent().getParent();
1420  }
1421 
1427  private Path getAutopsyExePath() {
1428  // If this is an installed version, there should be an <appName>64.exe file in the bin folder
1429  String exeName = getAutopsyExeName();
1430  String installPath = PlatformUtil.getInstallPath();
1431 
1432  return Paths.get(installPath, "bin", exeName);
1433  }
1434 
1440  private String getAutopsyExeName() {
1441  String appName = UserPreferences.getAppName();
1442  return appName + "64.exe";
1443  }
1444 
1453  private void copyApplication(Path sourceFolder, String destBaseFolder) throws IOException {
1454 
1455  // Create an appName folder in the destination
1456  Path destAppFolder = Paths.get(destBaseFolder, UserPreferences.getAppName());
1457  if (!destAppFolder.toFile().exists() && !destAppFolder.toFile().mkdirs()) {
1458  throw new IOException("Failed to create directory " + destAppFolder.toString());
1459  }
1460 
1461  // Now copy the files
1462  FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile());
1463  }
1464 
1472  private void createAppLaunchBatFile(String destBaseFolder) throws IOException {
1473  Path filePath = Paths.get(destBaseFolder, "open.bat");
1474  String appName = UserPreferences.getAppName();
1475  String exePath = "\"%~dp0" + appName + "\\bin\\" + getAutopsyExeName() + "\"";
1476  String casePath = "..\\" + caseName;
1477  try (FileWriter writer = new FileWriter(filePath.toFile())) {
1478  writer.write(exePath + " \"" + casePath + "\"");
1479  }
1480  }
1481 
1485  private void cleanup() {
1486  oldIdToNewContent.clear();
1487  newIdToContent.clear();
1488  oldTagNameToNewTagName.clear();
1489  oldArtTypeIdToNewArtTypeId.clear();
1491  oldArtifactIdToNewArtifact.clear();
1492  oldOsAccountIdToNewOsAccountId.clear();
1493  oldRealmIdToNewRealm.clear();
1494 
1496 
1497  currentCase = null;
1498  caseFolder = null;
1499  copiedFilesFolder = null;
1500  }
1501 
1505  private void closePortableCaseDatabase() {
1506  if (portableSkCase != null) {
1507  portableSkCase.close();
1508  portableSkCase = null;
1509  }
1510  }
1511 
1512  /*
1513  * @Override public JPanel getConfigurationPanel() { configPanel = new
1514  * CreatePortableCasePanel(); return configPanel; }
1515  */
1516  private class StoreMaxIdCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1517 
1518  private final String tableName;
1519 
1520  StoreMaxIdCallback(String tableName) {
1521  this.tableName = tableName;
1522  }
1523 
1524  @Override
1525  public void process(ResultSet rs) {
1526 
1527  try {
1528  while (rs.next()) {
1529  try {
1530  Long maxId = rs.getLong("max_id"); // NON-NLS
1531  String query = " (table_name, max_id) VALUES ('" + tableName + "', '" + maxId + "')"; // NON-NLS
1532  portableSkCase.getCaseDbAccessManager().insert(MAX_ID_TABLE_NAME, query);
1533 
1534  } catch (SQLException ex) {
1535  logger.log(Level.WARNING, "Unable to get maximum ID from result set", ex); // NON-NLS
1536  } catch (TskCoreException ex) {
1537  logger.log(Level.WARNING, "Unable to save maximum ID from result set", ex); // NON-NLS
1538  }
1539 
1540  }
1541  } catch (SQLException ex) {
1542  logger.log(Level.WARNING, "Failed to get maximum ID from result set", ex); // NON-NLS
1543  }
1544  }
1545  }
1546 
1547  @NbBundle.Messages({
1548  "PortableCaseReportModule.compressCase.errorFinding7zip=Could not locate 7-Zip executable",
1549  "# {0} - Temp folder path",
1550  "PortableCaseReportModule.compressCase.errorCreatingTempFolder=Could not create temporary folder {0}",
1551  "PortableCaseReportModule.compressCase.errorCompressingCase=Error compressing case",
1552  "PortableCaseReportModule.compressCase.canceled=Compression canceled by user",})
1553  private boolean compressCase(ReportProgressPanel progressPanel, String folderToCompress) {
1554 
1556 
1557  // Make a temporary folder for the compressed case
1558  Path dirToCompress = Paths.get(folderToCompress);
1559  File tempZipFolder = Paths.get(dirToCompress.getParent().toString(), "temp", "portableCase" + System.currentTimeMillis()).toFile();
1560  if (!tempZipFolder.mkdirs()) {
1561  handleError("Error creating temporary folder " + tempZipFolder.toString(),
1562  Bundle.PortableCaseReportModule_compressCase_errorCreatingTempFolder(tempZipFolder.toString()), null, progressPanel); // NON-NLS
1563  return false;
1564  }
1565 
1566  // Find 7-Zip
1567  File sevenZipExe = locate7ZipExecutable();
1568  if (sevenZipExe == null) {
1569  handleError("Error finding 7-Zip exectuable", Bundle.PortableCaseReportModule_compressCase_errorFinding7zip(), null, progressPanel); // NON-NLS
1570  return false;
1571  }
1572 
1573  // Create the chunk option
1574  String chunkOption = "";
1576  chunkOption = "-v" + settings.getChunkSize().getSevenZipParam();
1577  }
1578 
1579  File zipFile = Paths.get(tempZipFolder.getAbsolutePath(), caseName + ".zip").toFile(); // NON-NLS
1580  ProcessBuilder procBuilder = new ProcessBuilder();
1581  procBuilder.command(
1582  sevenZipExe.getAbsolutePath(),
1583  "a", // Add to archive
1584  zipFile.getAbsolutePath(),
1585  dirToCompress.toAbsolutePath().toString(),
1586  chunkOption
1587  );
1588 
1589  try {
1590  Process process = procBuilder.start();
1591 
1592  while (process.isAlive()) {
1593  if (progressPanel.getStatus() == ReportProgressPanel.ReportStatus.CANCELED) {
1594  process.destroy();
1595  return false;
1596  }
1597  Thread.sleep(200);
1598  }
1599  int exitCode = process.exitValue();
1600  if (exitCode != 0) {
1601  // Save any errors so they can be logged
1602  StringBuilder sb = new StringBuilder();
1603  try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
1604  String line;
1605  while ((line = br.readLine()) != null) {
1606  sb.append(line).append(System.getProperty("line.separator")); // NON-NLS
1607  }
1608  }
1609 
1610  handleError("Error compressing case\n7-Zip output: " + sb.toString(), Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), null, progressPanel); // NON-NLS
1611  return false;
1612  }
1613  } catch (IOException | InterruptedException ex) {
1614  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1615  return false;
1616  }
1617 
1618  // Delete everything in the case folder then copy over the compressed file(s)
1619  try {
1620  FileUtils.cleanDirectory(dirToCompress.toFile());
1621  FileUtils.copyDirectory(tempZipFolder, dirToCompress.toFile());
1622  FileUtils.deleteDirectory(new File(tempZipFolder.getParent()));
1623  } catch (IOException ex) {
1624  handleError("Error compressing case", Bundle.PortableCaseReportModule_compressCase_errorCompressingCase(), ex, progressPanel); // NON-NLS
1625  return false;
1626  }
1627 
1628  return true;
1629  }
1630 
1636  private static File locate7ZipExecutable() {
1637  if (!PlatformUtil.isWindowsOS()) {
1638  return null;
1639  }
1640 
1641  String executableToFindName = Paths.get("7-Zip", "7z.exe").toString(); // NON-NLS
1642  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PortableCaseReportModule.class.getPackage().getName(), false);
1643  if (null == exeFile) {
1644  return null;
1645  }
1646 
1647  if (!exeFile.canExecute()) {
1648  return null;
1649  }
1650 
1651  return exeFile;
1652  }
1653 
1657  public static class GetInterestingItemSetNamesCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
1658 
1659  private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(GetInterestingItemSetNamesCallback.class.getName());
1660  private final Map<String, Long> setCounts = new HashMap<>();
1661 
1662  @Override
1663  public void process(ResultSet rs) {
1664  try {
1665  while (rs.next()) {
1666  try {
1667  Long setCount = rs.getLong("set_count"); // NON-NLS
1668  String setName = rs.getString("set_name"); // NON-NLS
1669 
1670  setCounts.put(setName, setCount);
1671 
1672  } catch (SQLException ex) {
1673  logger.log(Level.WARNING, "Unable to get data_source_obj_id or value from result set", ex); // NON-NLS
1674  }
1675  }
1676  } catch (SQLException ex) {
1677  logger.log(Level.WARNING, "Failed to get next result for values by datasource", ex); // NON-NLS
1678  }
1679  }
1680 
1686  public Map<String, Long> getSetCountMap() {
1687  return setCounts;
1688  }
1689  }
1690 }
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:2468
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: Thu Jul 8 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.