23 package org.sleuthkit.autopsy.recentactivity;
 
   26 import java.io.IOException;
 
   27 import java.nio.ByteBuffer;
 
   28 import java.nio.ByteOrder;
 
   29 import java.nio.BufferUnderflowException;
 
   30 import java.nio.file.Files;
 
   31 import java.nio.file.Path;
 
   32 import java.nio.file.Paths;
 
   33 import java.util.ArrayList;
 
   34 import java.util.Arrays;
 
   35 import java.util.HashMap;
 
   36 import java.util.List;
 
   38 import java.util.Optional;
 
   39 import java.util.logging.Level;
 
   40 import org.joda.time.Instant;
 
   41 import org.openide.util.NbBundle.Messages;
 
   70 final class ExtractRecycleBin 
extends Extract {
 
   72     private static final Logger logger = Logger.getLogger(ExtractRecycleBin.class.getName());
 
   74     private static final String RECYCLE_BIN_ARTIFACT_NAME = 
"TSK_RECYCLE_BIN"; 
 
   76     private static final String RECYCLE_BIN_DIR_NAME = 
"$RECYCLE.BIN"; 
 
   78     private static final int V1_FILE_NAME_OFFSET = 24;
 
   79     private static final int V2_FILE_NAME_OFFSET = 28;
 
   80     private final IngestJobContext context;
 
   83         "ExtractRecycleBin_module_name=Recycle Bin Analyzer" 
   85     ExtractRecycleBin(IngestJobContext context) {
 
   86         super(Bundle.ExtractRecycleBin_module_name(), context);
 
   87         this.context = context;
 
   91     void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
 
   95             createRecycleBinArtifactType();
 
   96         } 
catch (TskCoreException ex) {
 
   97             logger.log(Level.WARNING, String.format(
"%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
 
  100         BlackboardArtifact.Type recycleBinArtifactType;
 
  103             recycleBinArtifactType = tskCase.getBlackboard().getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
 
  104         } 
catch (TskCoreException ex) {
 
  105             logger.log(Level.WARNING, String.format(
"Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); 
 
  111         Map<String, String> userNameMap;
 
  113             userNameMap = makeUserNameMap(dataSource);
 
  114         } 
catch (TskCoreException ex) {
 
  115             logger.log(Level.WARNING, 
"Unable to create OS Account user name map", ex);
 
  118             userNameMap = 
new HashMap<>();
 
  121         FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
 
  124         Map<String, List<AbstractFile>> rFileMap;
 
  126             rFileMap = makeRFileMap(dataSource);
 
  127         } 
catch (TskCoreException ex) {
 
  128             logger.log(Level.WARNING, String.format(
"Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
 
  133         List<AbstractFile> iFiles;
 
  135             iFiles = fileManager.findFiles(dataSource, 
"$I%", RECYCLE_BIN_DIR_NAME); 
 
  136         } 
catch (TskCoreException ex) {
 
  137             logger.log(Level.WARNING, 
"Unable to find recycle bin I files.", ex); 
 
  141         String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), 
"recyclebin", context.getJobId()); 
 
  144         for (AbstractFile iFile : iFiles) {
 
  146             if (context.dataSourceIngestIsCancelled()) {
 
  150             processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
 
  153         (
new File(tempRARecycleBinPath)).
delete();
 
  167     private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
 
  168         String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
 
  171                 ContentUtils.writeToFile(iFile, 
new File(tempFilePath));
 
  172             } 
catch (IOException ex) {
 
  173                 logger.log(Level.WARNING, String.format(
"Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); 
 
  180             RecycledFileMetaData metaData;
 
  182                 metaData = parseIFile(tempFilePath);
 
  183             } 
catch (IOException ex) {
 
  184                 logger.log(Level.WARNING, String.format(
"Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex); 
 
  190             String userID = getUserIDFromPath(iFile.getParentPath());
 
  191             String userName = 
"";
 
  192             if (!userID.isEmpty()) {
 
  193                 userName = userNameMap.get(userID);
 
  201             String rFileName = iFile.getName().replace(
"$I", 
"$R"); 
 
  202             List<AbstractFile> rFiles = rFileMap.get(rFileName);
 
  203             if (rFiles == null) {
 
  206             SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
 
  207             for (AbstractFile rFile : rFiles) {
 
  208                 if (context.dataSourceIngestIsCancelled()) {
 
  212                 if (iFile.getParentPath().equals(rFile.getParentPath())
 
  213                         && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
 
  215                         postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
 
  220                         if (rFile instanceof FsContent) {
 
  224                                 AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
 
  225                                 popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
 
  228                                 AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
 
  229                                 addFileSystemFile(skCase, (FsContent) rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
 
  232                     } 
catch (TskCoreException ex) {
 
  233                         logger.log(Level.WARNING, String.format(
"Unable to add attributes to artifact %s", rFile.getName()), ex); 
 
  238             (
new File(tempFilePath)).
delete();
 
  256     private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, 
long deletedTimeStamp) 
throws TskCoreException {
 
  257         if (recycledChildren == null) {
 
  261         for (Content child : recycledChildren) {
 
  262             if (child instanceof FsContent) {
 
  263                 FsContent fsContent = (FsContent) child;
 
  264                 if (fsContent.isFile()) {
 
  265                     addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
 
  266                 } 
else if (fsContent.isDir()) {
 
  267                     String newPath = parentPath + 
"\\" + fsContent.getName();
 
  268                     AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
 
  269                     popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
 
  309     private RecycledFileMetaData parseIFile(String iFilePath) 
throws IOException {
 
  311             byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
 
  313             ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
 
  314             byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 
  316             long version = byteBuffer.getLong();
 
  317             long fileSize = byteBuffer.getLong();
 
  318             long timestamp = byteBuffer.getLong();
 
  321             timestamp = Util.filetimeToMillis(timestamp) / 1000;
 
  326                 stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
 
  328                 int fileNameLength = byteBuffer.getInt() * 2; 
 
  329                 stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
 
  332             String fileName = 
new String(stringBytes, 
"UTF-16LE"); 
 
  334             return new RecycledFileMetaData(fileSize, timestamp, fileName);
 
  335         } 
catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
 
  336             throw new IOException(
"Error parsing $I File, file is corrupt or not a valid I$ file", ex);
 
  349     private Map<String, String> makeUserNameMap(Content dataSource) 
throws TskCoreException {
 
  350         Map<String, String> userNameMap = 
new HashMap<>();
 
  352         for (OsAccount account : tskCase.getOsAccountManager().getOsAccounts(((DataSource) dataSource).getHost())) {
 
  353             Optional<String> userName = account.getLoginName();
 
  354             userNameMap.put(account.getName(), userName.isPresent() ? userName.get() : 
"");
 
  369     private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) 
throws TskCoreException {
 
  370         FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
 
  371         List<AbstractFile> rFiles = fileManager.findFiles(dataSource, 
"$R%");
 
  372         Map<String, List<AbstractFile>> fileMap = 
new HashMap<>();
 
  374         for (AbstractFile rFile : rFiles) {
 
  375             String fileName = rFile.getName();
 
  376             List<AbstractFile> fileList = fileMap.get(fileName);
 
  378             if (fileList == null) {
 
  379                 fileList = 
new ArrayList<>();
 
  380                 fileMap.put(fileName, fileList);
 
  397     private String getUserIDFromPath(String iFileParentPath) {
 
  398         int index = iFileParentPath.indexOf(
'-') - 1;
 
  400             return (iFileParentPath.substring(index)).replace(
"/", 
"");
 
  416     private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
 
  417         return artifact.getAttribute(
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
 
  421         "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin" 
  428     private void createRecycleBinArtifactType() throws TskCoreException {
 
  430             tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); 
 
  431         } 
catch (BlackboardException ex) {
 
  432             throw new TskCoreException(String.format(
"An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
 
  450     private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, 
long dateTime) 
throws TskCoreException {
 
  451         List<BlackboardAttribute> attributes = 
new ArrayList<>();
 
  452         attributes.add(
new BlackboardAttribute(TSK_PATH, getDisplayName(), fileName));
 
  453         attributes.add(
new BlackboardAttribute(TSK_DATETIME_DELETED, getDisplayName(), dateTime));
 
  454         attributes.add(
new BlackboardAttribute(TSK_USER_NAME, getDisplayName(), userName == null || userName.isEmpty() ? 
"" : userName));
 
  455         return createArtifactWithAttributes(type, rFile, attributes);
 
  470     private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) 
throws TskCoreException {
 
  472         String parentPath = getParentPath(path);
 
  473         String folderName = getFileName(path);
 
  475         List<AbstractFile> files = null;
 
  476         if (parentPath != null) {
 
  477             if (!parentPath.equals(
"/")) {
 
  478                 parentPath = parentPath + 
"/";
 
  481             files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='%s' AND name='%s'",
 
  482                     dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : 
""));
 
  484             files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
 
  487         if (files == null || files.isEmpty()) {
 
  488             AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
 
  489             return skCase.addVirtualDirectory(parent.getId(), folderName);
 
  507     private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, 
long deletedTime) 
throws TskCoreException {
 
  508         skCase.addFileSystemFile(
 
  509                 recycleBinFile.getDataSourceObjectId(),
 
  510                 recycleBinFile.getFileSystemId(),
 
  512                 recycleBinFile.getMetaAddr(),
 
  513                 (int) recycleBinFile.getMetaSeq(),
 
  514                 recycleBinFile.getAttrType(),
 
  515                 recycleBinFile.getAttributeId(),
 
  516                 TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
 
  517                 (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
 
  518                 recycleBinFile.getSize(),
 
  519                 recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
 
  531     String normalizeFilePath(String pathString) {
 
  532         if (pathString == null || pathString.isEmpty()) {
 
  536         Path path = Paths.get(pathString);
 
  537         int nameCount = path.getNameCount();
 
  539             String rootless = 
"/" + path.subpath(0, nameCount);
 
  540             return rootless.replace(
"\\", 
"/");
 
  555     String getFileName(String filePath) {
 
  556         Path fileNamePath = Paths.get(filePath).getFileName();
 
  557         if (fileNamePath != null) {
 
  558             return fileNamePath.toString();
 
  570     String getParentPath(String path) {
 
  571         Path parentPath = Paths.get(path).getParent();
 
  572         if (parentPath != null) {
 
  573             return normalizeFilePath(parentPath.toString());
 
  581     final class RecycledFileMetaData {
 
  583         private final long fileSize;
 
  584         private final long deletedTimeStamp;
 
  585         private final String fileName;
 
  594         RecycledFileMetaData(Long fileSize, 
long deletedTimeStamp, String fileName) {
 
  595             this.fileSize = fileSize;
 
  596             this.deletedTimeStamp = deletedTimeStamp;
 
  597             this.fileName = fileName;
 
  614         long getDeletedTimeStamp() {
 
  615             return deletedTimeStamp;
 
  624         String getFullWindowsPath() {
 
  625             return fileName.trim();