Autopsy  3.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
PhotoRecCarverFileIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014 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.modules.photoreccarver;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.lang.ProcessBuilder.Redirect;
24 import java.nio.file.DirectoryStream;
25 import java.nio.file.FileAlreadyExistsException;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.text.DateFormat;
30 import java.text.SimpleDateFormat;
31 import java.util.ArrayList;
32 import java.util.Date;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.concurrent.ConcurrentHashMap;
36 import java.util.logging.Level;
37 import org.openide.modules.InstalledFileLocator;
38 import org.openide.util.NbBundle;
60 
64 final class PhotoRecCarverFileIngestModule implements FileIngestModule {
65 
66  private static final String PHOTOREC_DIRECTORY = "photorec_exec"; //NON-NLS
67  private static final String PHOTOREC_EXECUTABLE = "photorec_win.exe"; //NON-NLS
68  private static final String PHOTOREC_RESULTS_BASE = "results"; //NON-NLS
69  private static final String PHOTOREC_RESULTS_EXTENDED = "results.1"; //NON-NLS
70  private static final String PHOTOREC_REPORT = "report.xml"; //NON-NLS
71  private static final String LOG_FILE = "run_log.txt"; //NON-NLS
72  private static final String TEMP_DIR_NAME = "temp"; // NON-NLS
73  private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName());
74  private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
75  private static final Map<Long, WorkingPaths> pathsByJob = new ConcurrentHashMap<>();
76  private IngestJobContext context;
77  private Path rootOutputDirPath;
78  private File executableFile;
79  private IngestServices services;
80 
84  @Override
85  public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
86  this.context = context;
87  this.services = IngestServices.getInstance();
88 
89  // If the global unallocated space processing setting and the module
90  // process unallocated space only setting are not in sych, throw an
91  // exception. Although the result would not be incorrect, it would be
92  // unfortunate for the user to get an accidental no-op for this module.
93  if (!this.context.processingUnallocatedSpace()) {
94  throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "unallocatedSpaceProcessingSettingsError.message"));
95  }
96 
97  this.rootOutputDirPath = PhotoRecCarverFileIngestModule.createModuleOutputDirectoryForCase();
98 
99  Path execName = Paths.get(PHOTOREC_DIRECTORY, PHOTOREC_EXECUTABLE);
100  executableFile = locateExecutable(execName.toString());
101 
102  if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.context.getJobId()) == 1) {
103  try {
104  // The first instance creates an output subdirectory with a date and time stamp
105  DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS"); // NON-NLS
106  Date date = new Date();
107  String folder = this.context.getDataSource().getId() + "_" + dateFormat.format(date);
108  Path outputDirPath = Paths.get(this.rootOutputDirPath.toAbsolutePath().toString(), folder);
109  Files.createDirectories(outputDirPath);
110 
111  // A temp subdirectory is also created as a location for writing unallocated space files to disk.
112  Path tempDirPath = Paths.get(outputDirPath.toString(), PhotoRecCarverFileIngestModule.TEMP_DIR_NAME);
113  Files.createDirectory(tempDirPath);
114 
115  // Save the directories for the current job.
116  PhotoRecCarverFileIngestModule.pathsByJob.put(this.context.getJobId(), new WorkingPaths(outputDirPath, tempDirPath));
117  }
118  catch (SecurityException | IOException | UnsupportedOperationException ex) {
119  throw new IngestModule.IngestModuleException(NbBundle.getMessage(this.getClass(), "cannotCreateOutputDir.message", ex.getLocalizedMessage()));
120  }
121  }
122  }
123 
127  @Override
128  public IngestModule.ProcessResult process(AbstractFile file) {
129  // Skip everything except unallocated space files.
132  }
133 
134  Path tempFilePath = null;
135  try {
136  long id = getRootId(file);
137  // make sure we have a valid systemID
138  if (id == -1) {
139  return ProcessResult.ERROR;
140  }
141 
142  // Verify initialization succeeded.
143  if (null == this.executableFile) {
144  logger.log(Level.SEVERE, "PhotoRec carver called after failed start up"); // NON-NLS
146  }
147 
148  // Check that we have roughly enough disk space left to complete the operation
149  long freeDiskSpace = IngestServices.getInstance().getFreeDiskSpace();
150  // Some network drives always return -1 for free disk space.
151  // In this case, expect enough space and move on.
152 
153  if ((freeDiskSpace != -1) && ((file.getSize() * 1.2) > freeDiskSpace)) {
154  logger.log(Level.SEVERE, "PhotoRec error processing {0} with {1} Not enough space on primary disk to save unallocated space.", // NON-NLS
155  new Object[]{file.getName(), PhotoRecCarverIngestModuleFactory.getModuleName()}); // NON-NLS
156  MessageNotifyUtil.Notify.error(NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.UnableToCarve", file.getName()),
157  NbBundle.getMessage(this.getClass(), "PhotoRecIngestModule.NotEnoughDiskSpace"));
158 
160  }
161 
162  // Write the file to disk.
163  WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.get(this.context.getJobId());
164  tempFilePath = Paths.get(paths.getTempDirPath().toString(), file.getName());
165  ContentUtils.writeToFile(file, tempFilePath.toFile());
166 
167  // Create a subdirectory for this file.
168  Path outputDirPath = Paths.get(paths.getOutputDirPath().toString(), file.getName());
169  Files.createDirectory(outputDirPath);
170  File log = new File(Paths.get(outputDirPath.toString(), LOG_FILE).toString()); //NON-NLS
171 
172  // Scan the file with Unallocated Carver.
173  ProcessBuilder processAndSettings = new ProcessBuilder(
174  "\"" + executableFile + "\"",
175  "/d", // NON-NLS
176  "\"" + outputDirPath.toAbsolutePath() + File.separator + PHOTOREC_RESULTS_BASE + "\"",
177  "/cmd", // NON-NLS
178  "\"" + tempFilePath.toFile() + "\"",
179  "search"); // NON_NLS
180 
181  // Add environment variable to force PhotoRec to run with the same permissions Autopsy uses
182  processAndSettings.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
183  processAndSettings.redirectErrorStream(true);
184  processAndSettings.redirectOutput(Redirect.appendTo(log));
185 
186  int exitValue = ExecUtil.execute(processAndSettings, new FileIngestModuleProcessTerminator(this.context));
187 
188  if (this.context.fileIngestIsCancelled() == true) {
189  // if it was cancelled by the user, result is OK
190  cleanup(outputDirPath, tempFilePath);
191  logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS
193  } else if (0 != exitValue) {
194  // if it failed or was cancelled by timeout, result is ERROR
195  cleanup(outputDirPath, tempFilePath);
196  logger.log(Level.SEVERE, "PhotoRec carver returned error exit value = {0} when scanning {1}", // NON-NLS
197  new Object[]{exitValue, file.getName()}); // NON-NLS
199  }
200 
201  // Move carver log file to avoid placement into Autopsy results. PhotoRec appends ".1" to the folder name.
202  java.io.File oldAuditFile = new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_RESULTS_EXTENDED, PHOTOREC_REPORT).toString()); //NON-NLS
203  java.io.File newAuditFile = new java.io.File(Paths.get(outputDirPath.toString(), PHOTOREC_REPORT).toString()); //NON-NLS
204  oldAuditFile.renameTo(newAuditFile);
205 
206  Path pathToRemove = Paths.get(outputDirPath.toAbsolutePath().toString());
207  try (DirectoryStream<Path> stream = Files.newDirectoryStream(pathToRemove)) {
208  for (Path entry : stream) {
209  if (Files.isDirectory(entry)) {
210  FileUtil.deleteDir(new File(entry.toString()));
211  }
212  }
213  }
214 
215  // Now that we've cleaned up the folders and data files, parse the xml output file to add carved items into the database
216  PhotoRecCarverOutputParser parser = new PhotoRecCarverOutputParser(outputDirPath);
217  List<LayoutFile> theList = parser.parse(newAuditFile, id, file);
218  if (theList != null) { // if there were any results from carving, add the unallocated carving event to the reports list.
219  context.addFilesToJob(new ArrayList<>(theList));
220  services.fireModuleContentEvent(new ModuleContentEvent(theList.get(0))); // fire an event to update the tree
221  }
222  }
223  catch (IOException ex) {
224  logger.log(Level.SEVERE, "Error processing " + file.getName() + " with PhotoRec carver", ex); // NON-NLS
226  }
227 
228  finally {
229  if (null != tempFilePath && Files.exists(tempFilePath)) {
230  // Get rid of the unallocated space file.
231  tempFilePath.toFile().delete();
232  }
233  }
235 
236  }
237 
238  private void cleanup(Path outputDirPath, Path tempFilePath) {
239  // cleanup the output path
240  FileUtil.deleteDir(new File(outputDirPath.toString()));
241  if (null != tempFilePath && Files.exists(tempFilePath)) {
242  tempFilePath.toFile().delete();
243  }
244  }
245 
246 
250  @Override
251  public void shutDown() {
252  if (this.context != null && refCounter.decrementAndGet(this.context.getJobId()) == 0) {
253  try {
254  // The last instance of this module for an ingest job cleans out
255  // the working paths map entry for the job and deletes the temp dir.
256  WorkingPaths paths = PhotoRecCarverFileIngestModule.pathsByJob.remove(this.context.getJobId());
257  FileUtil.deleteDir(new File(paths.getTempDirPath().toString()));
258  }
259  catch (SecurityException ex) {
260  logger.log(Level.SEVERE, "Error shutting down PhotoRec carver module", ex); // NON-NLS
261  }
262  }
263  }
264 
265  private static final class WorkingPaths {
266 
267  private final Path outputDirPath;
268  private final Path tempDirPath;
269 
270  WorkingPaths(Path outputDirPath, Path tempDirPath) {
271  this.outputDirPath = outputDirPath;
272  this.tempDirPath = tempDirPath;
273  }
274 
275  Path getOutputDirPath() {
276  return this.outputDirPath;
277  }
278 
279  Path getTempDirPath() {
280  return this.tempDirPath;
281  }
282  }
283 
290  synchronized static Path createModuleOutputDirectoryForCase() throws IngestModule.IngestModuleException {
291  Path path = Paths.get(Case.getCurrentCase().getModulesOutputDirAbsPath(), PhotoRecCarverIngestModuleFactory.getModuleName());
292  try {
293  Files.createDirectory(path);
294  }
295  catch (FileAlreadyExistsException ex) {
296  // No worries.
297  }
298  catch (IOException | SecurityException | UnsupportedOperationException ex) {
299  throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotCreateOutputDir.message", ex.getLocalizedMessage()));
300  }
301  return path;
302  }
303 
310  private static long getRootId(AbstractFile file) {
311  long id = -1;
312  Content parent = null;
313  try {
314  parent = file.getParent();
315  while (parent != null) {
316  if (parent instanceof Volume || parent instanceof Image) {
317  id = parent.getId();
318  break;
319  }
320  parent = parent.getParent();
321  }
322  }
323  catch (TskCoreException ex) {
324  logger.log(Level.SEVERE, "PhotoRec carver exception while trying to get parent of AbstractFile.", ex); //NON-NLS
325  }
326  return id;
327  }
328 
336  public static File locateExecutable(String executableToFindName) throws IngestModule.IngestModuleException {
337  // Must be running under a Windows operating system.
338  if (!PlatformUtil.isWindowsOS()) {
339  throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "unsupportedOS.message"));
340  }
341 
342  File exeFile = InstalledFileLocator.getDefault().locate(executableToFindName, PhotoRecCarverFileIngestModule.class.getPackage().getName(), false);
343  if (null == exeFile) {
344  throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "missingExecutable.message"));
345  }
346 
347  if (!exeFile.canExecute()) {
348  throw new IngestModule.IngestModuleException(NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "cannotRunExecutable.message"));
349  }
350 
351  return exeFile;
352  }
353 
354 }
static< T, V > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, SwingWorker< T, V > worker, boolean source)
static int execute(ProcessBuilder processBuilder)
Definition: ExecUtil.java:97
TskData.TSK_DB_FILES_TYPE_ENUM getType()
void addFilesToJob(List< AbstractFile > files)
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
static boolean deleteDir(File dirPath)
Definition: FileUtil.java:40
static Logger getLogger(String name)
Definition: Logger.java:131
static synchronized IngestServices getInstance()

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