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;
 
   49 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
 
   52 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_DELETED;
 
   53 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
 
   54 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
 
   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;
 
   82         "ExtractRecycleBin_module_name=Recycle Bin" 
   85         super(Bundle.ExtractRecycleBin_module_name());
 
   89     void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
 
   93             createRecycleBinArtifactType();
 
   94         } 
catch (TskCoreException ex) {
 
   95             logger.log(Level.WARNING, String.format(
"%s may not have been created.", RECYCLE_BIN_ARTIFACT_NAME), ex);
 
   98         BlackboardArtifact.Type recycleBinArtifactType;
 
  101             recycleBinArtifactType = tskCase.getArtifactType(RECYCLE_BIN_ARTIFACT_NAME);
 
  102         } 
catch (TskCoreException ex) {
 
  103             logger.log(Level.WARNING, String.format(
"Unable to retrive custom artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex); 
 
  109         Map<String, String> userNameMap;
 
  111             userNameMap = makeUserNameMap(dataSource);
 
  112         } 
catch (TskCoreException ex) {
 
  113             logger.log(Level.WARNING, 
"Unable to create OS Account user name map", ex);
 
  116             userNameMap = 
new HashMap<>();
 
  119         FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
 
  122         Map<String, List<AbstractFile>> rFileMap;
 
  124             rFileMap = makeRFileMap(dataSource);
 
  125         } 
catch (TskCoreException ex) {
 
  126             logger.log(Level.WARNING, String.format(
"Unable to create $R file map for dataSource: %s", dataSource.getName()), ex);
 
  131         List<AbstractFile> iFiles;
 
  133             iFiles = fileManager.findFiles(dataSource, 
"$I%", RECYCLE_BIN_DIR_NAME); 
 
  134         } 
catch (TskCoreException ex) {
 
  135             logger.log(Level.WARNING, 
"Unable to find recycle bin I files.", ex); 
 
  139         String tempRARecycleBinPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), 
"recyclebin", context.getJobId()); 
 
  142         for (AbstractFile iFile : iFiles) {
 
  144             if (context.dataSourceIngestIsCancelled()) {
 
  148             processIFile(context, recycleBinArtifactType, iFile, userNameMap, rFileMap, tempRARecycleBinPath);
 
  151         (
new File(tempRARecycleBinPath)).
delete();
 
  164     private void processIFile(IngestJobContext context, BlackboardArtifact.Type recycleBinArtifactType, AbstractFile iFile, Map<String, String> userNameMap, Map<String, List<AbstractFile>> rFileMap, String tempRARecycleBinPath) {
 
  165         String tempFilePath = tempRARecycleBinPath + File.separator + Instant.now().getMillis() + iFile.getName();
 
  168                 ContentUtils.writeToFile(iFile, 
new File(tempFilePath));
 
  169             } 
catch (IOException ex) {
 
  170                 logger.log(Level.WARNING, String.format(
"Unable to write %s to temp directory. File name: %s", iFile.getName(), tempFilePath), ex); 
 
  177             RecycledFileMetaData metaData;
 
  179                 metaData = parseIFile(tempFilePath);
 
  180             } 
catch (IOException ex) {
 
  181                 logger.log(Level.WARNING, String.format(
"Unable to parse iFile %s", iFile.getParentPath() + iFile.getName()), ex); 
 
  187             String userID = getUserIDFromPath(iFile.getParentPath());
 
  188             String userName = 
"";
 
  189             if (!userID.isEmpty()) {
 
  190                 userName = userNameMap.get(userID);
 
  198             String rFileName = iFile.getName().replace(
"$I", 
"$R"); 
 
  199             List<AbstractFile> rFiles = rFileMap.get(rFileName);
 
  200             if (rFiles == null) {
 
  203             SleuthkitCase skCase = Case.getCurrentCase().getSleuthkitCase();
 
  204             for (AbstractFile rFile : rFiles) {
 
  205                 if (context.dataSourceIngestIsCancelled()) {
 
  209                 if (iFile.getParentPath().equals(rFile.getParentPath())
 
  210                         && iFile.getMetaFlagsAsString().equals(rFile.getMetaFlagsAsString())) {
 
  212                         postArtifact(createArtifact(rFile, recycleBinArtifactType, metaData.getFullWindowsPath(), userName, metaData.getDeletedTimeStamp()));
 
  217                         if (rFile instanceof FsContent) {
 
  221                                 AbstractFile directory = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile, metaData.getFullWindowsPath());
 
  222                                 popuplateDeletedDirectory(Case.getCurrentCase().getSleuthkitCase(), directory, rFile.getChildren(), metaData.getFullWindowsPath(), metaData.getDeletedTimeStamp());
 
  225                                 AbstractFile folder = getOrMakeFolder(Case.getCurrentCase().getSleuthkitCase(), (FsContent) rFile.getParent(), Paths.get(metaData.getFullWindowsPath()).getParent().toString());
 
  226                                 addFileSystemFile(skCase, (FsContent)rFile, folder, Paths.get(metaData.getFullWindowsPath()).getFileName().toString(), metaData.getDeletedTimeStamp());
 
  229                     } 
catch (TskCoreException ex) {
 
  230                         logger.log(Level.WARNING, String.format(
"Unable to add attributes to artifact %s", rFile.getName()), ex); 
 
  235             (
new File(tempFilePath)).
delete();
 
  253     private void popuplateDeletedDirectory(SleuthkitCase skCase, AbstractFile parentFolder, List<Content> recycledChildren, String parentPath, 
long deletedTimeStamp) 
throws TskCoreException {
 
  254         if (recycledChildren == null) {
 
  258         for (Content child : recycledChildren) {
 
  259             if (child instanceof FsContent) {
 
  260                 FsContent fsContent = (FsContent) child;
 
  261                 if (fsContent.isFile()) {
 
  262                     addFileSystemFile(skCase, fsContent, parentFolder, fsContent.getName(), deletedTimeStamp);
 
  263                 } 
else if (fsContent.isDir()) {
 
  264                     String newPath = parentPath + 
"\\" + fsContent.getName();
 
  265                     AbstractFile childFolder = getOrMakeFolder(skCase, fsContent, parentPath);
 
  266                     popuplateDeletedDirectory(skCase, childFolder, fsContent.getChildren(), newPath, deletedTimeStamp);
 
  297     private RecycledFileMetaData parseIFile(String iFilePath) 
throws IOException {
 
  299             byte[] allBytes = Files.readAllBytes(Paths.get(iFilePath));
 
  302         ByteBuffer byteBuffer = ByteBuffer.wrap(allBytes);
 
  303         byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
 
  305         long version = byteBuffer.getLong();
 
  306         long fileSize = byteBuffer.getLong();
 
  307         long timestamp = byteBuffer.getLong();
 
  310         timestamp = Util.filetimeToMillis(timestamp) / 1000;
 
  315             stringBytes = Arrays.copyOfRange(allBytes, V1_FILE_NAME_OFFSET, allBytes.length);
 
  317             int fileNameLength = byteBuffer.getInt() * 2; 
 
  318             stringBytes = Arrays.copyOfRange(allBytes, V2_FILE_NAME_OFFSET, V2_FILE_NAME_OFFSET + fileNameLength);
 
  321         String fileName = 
new String(stringBytes, 
"UTF-16LE"); 
 
  323         return new RecycledFileMetaData(fileSize, timestamp, fileName);
 
  324         } 
catch (IOException | BufferUnderflowException | IllegalArgumentException | ArrayIndexOutOfBoundsException ex) {
 
  325             throw new IOException(
"Error parsing $I File, file is corrupt or not a valid I$ file", ex);
 
  338     private Map<String, String> makeUserNameMap(Content dataSource) 
throws TskCoreException {
 
  339         Map<String, String> userNameMap = 
new HashMap<>();
 
  341          for(OsAccount account: tskCase.getOsAccountManager().getOsAccounts(((DataSource)dataSource).getHost())) {
 
  342             Optional<String> userName = account.getLoginName();
 
  343             userNameMap.put(account.getName(), userName.isPresent() ? userName.get() : 
"");
 
  358     private Map<String, List<AbstractFile>> makeRFileMap(Content dataSource) 
throws TskCoreException {
 
  359         FileManager fileManager = Case.getCurrentCase().getServices().getFileManager();
 
  360         List<AbstractFile> rFiles = fileManager.findFiles(dataSource, 
"$R%");
 
  361         Map<String, List<AbstractFile>> fileMap = 
new HashMap<>();
 
  363         for (AbstractFile rFile : rFiles) {
 
  364             String fileName = rFile.getName();
 
  365             List<AbstractFile> fileList = fileMap.get(fileName);
 
  367             if (fileList == null) {
 
  368                 fileList = 
new ArrayList<>();
 
  369                 fileMap.put(fileName, fileList);
 
  386     private String getUserIDFromPath(String iFileParentPath) {
 
  387         int index = iFileParentPath.indexOf(
'-') - 1;
 
  389             return (iFileParentPath.substring(index)).replace(
"/", 
"");
 
  405     private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
 
  406         return artifact.getAttribute(
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
 
  410         "ExtractRecycleBin_Recyle_Bin_Display_Name=Recycle Bin" 
  417     private void createRecycleBinArtifactType() throws TskCoreException {
 
  419             tskCase.getBlackboard().getOrAddArtifactType(RECYCLE_BIN_ARTIFACT_NAME, Bundle.ExtractRecycleBin_Recyle_Bin_Display_Name()); 
 
  420         } 
catch (BlackboardException ex) {
 
  421             throw new TskCoreException(String.format(
"An exception was thrown while defining artifact type %s", RECYCLE_BIN_ARTIFACT_NAME), ex);
 
  439     private BlackboardArtifact createArtifact(AbstractFile rFile, BlackboardArtifact.Type type, String fileName, String userName, 
long dateTime) 
throws TskCoreException {
 
  440         List<BlackboardAttribute> attributes = 
new ArrayList<>();
 
  441         attributes.add(
new BlackboardAttribute(TSK_PATH, getName(), fileName));
 
  442         attributes.add(
new BlackboardAttribute(TSK_DATETIME_DELETED, getName(), dateTime));
 
  443         attributes.add(
new BlackboardAttribute(TSK_USER_NAME, getName(), userName == null || userName.isEmpty() ? 
"" : userName));
 
  444         return createArtifactWithAttributes(type, rFile, attributes);
 
  459     private AbstractFile getOrMakeFolder(SleuthkitCase skCase, FsContent dataSource, String path) 
throws TskCoreException {
 
  461         String parentPath = getParentPath(path);
 
  462         String folderName = getFileName(path);
 
  464         List<AbstractFile> files = null;
 
  465         if (parentPath != null) {
 
  466             if (!parentPath.equals(
"/")) {
 
  467                 parentPath = parentPath + 
"/";
 
  470             files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='%s' AND name='%s'", 
 
  471                     dataSource.getFileSystemId(), SleuthkitCase.escapeSingleQuotes(parentPath), folderName != null ? SleuthkitCase.escapeSingleQuotes(folderName) : 
""));
 
  473             files = skCase.findAllFilesWhere(String.format(
"fs_obj_id=%s AND parent_path='/' AND name=''", dataSource.getFileSystemId()));
 
  476         if (files == null || files.isEmpty()) {
 
  477             AbstractFile parent = getOrMakeFolder(skCase, dataSource, parentPath);
 
  478             return skCase.addVirtualDirectory(parent.getId(), folderName);
 
  496     private void addFileSystemFile(SleuthkitCase skCase, FsContent recycleBinFile, Content parentDir, String fileName, 
long deletedTime) 
throws TskCoreException {
 
  497         skCase.addFileSystemFile(
 
  498                 recycleBinFile.getDataSourceObjectId(),
 
  499                 recycleBinFile.getFileSystemId(),
 
  501                 recycleBinFile.getMetaAddr(),
 
  502                 (int) recycleBinFile.getMetaSeq(),
 
  503                 recycleBinFile.getAttrType(),
 
  504                 recycleBinFile.getAttributeId(),
 
  505                 TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC,
 
  506                 (short) (TskData.TSK_FS_META_FLAG_ENUM.UNALLOC.getValue() | TskData.TSK_FS_META_FLAG_ENUM.USED.getValue()),
 
  507                 recycleBinFile.getSize(),
 
  508                 recycleBinFile.getCtime(), recycleBinFile.getCrtime(), recycleBinFile.getAtime(), deletedTime,
 
  520     String normalizeFilePath(String pathString) {
 
  521         if (pathString == null || pathString.isEmpty()) {
 
  525         Path path = Paths.get(pathString);
 
  526         int nameCount = path.getNameCount();
 
  528             String rootless = 
"/" + path.subpath(0, nameCount);
 
  529             return rootless.replace(
"\\", 
"/");
 
  544     String getFileName(String filePath) {
 
  545         Path fileNamePath = Paths.get(filePath).getFileName();
 
  546         if (fileNamePath != null) {
 
  547             return fileNamePath.toString();
 
  559     String getParentPath(String path) {
 
  560         Path parentPath = Paths.get(path).getParent();
 
  561         if (parentPath != null) {
 
  562             return normalizeFilePath(parentPath.toString());
 
  570     final class RecycledFileMetaData {
 
  572         private final long fileSize;
 
  573         private final long deletedTimeStamp;
 
  574         private final String fileName;
 
  583         RecycledFileMetaData(Long fileSize, 
long deletedTimeStamp, String fileName) {
 
  584             this.fileSize = fileSize;
 
  585             this.deletedTimeStamp = deletedTimeStamp;
 
  586             this.fileName = fileName;
 
  603         long getDeletedTimeStamp() {
 
  604             return deletedTimeStamp;
 
  613         String  getFullWindowsPath() {
 
  614             return fileName.trim();