diff --git a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java index 09e397c7a2f802b7bd780591de8e06de8e261674..e64010438c3006911324fa51961301dca0827577 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java +++ b/bindings/java/src/org/sleuthkit/datamodel/AbstractFile.java @@ -1055,7 +1055,7 @@ private synchronized void loadLocalFile() throws TskCoreException { } } else { // Copy the file from the server - localFile = getSleuthkitCase().loadFromFileService(this); + localFile = getSleuthkitCase().getFileRepositoryManager().loadFromFileRepository(this); } } diff --git a/bindings/java/src/org/sleuthkit/datamodel/FileRepositoryManager.java b/bindings/java/src/org/sleuthkit/datamodel/FileRepositoryManager.java new file mode 100644 index 0000000000000000000000000000000000000000..eb523c5570d8b70a32bfd57f91ff7b0f7a37f867 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/FileRepositoryManager.java @@ -0,0 +1,257 @@ +/* + * SleuthKit Java Bindings + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; + +/** + * + */ +public class FileRepositoryManager { + + private final static String FILE_PATH = "v1/files/"; + private final SleuthkitCase skCase; + private FileRepositorySettings settings; + private String fileDownloadFolder; + + // Create an unitialized file repository + FileRepositoryManager(SleuthkitCase skCase) { + this.skCase = skCase; + settings = null; + fileDownloadFolder = ""; + } + + public void initializeSettings(FileRepositorySettings settings, String fileDownloadPath) { + this.settings = settings; + this.fileDownloadFolder = fileDownloadPath; + } + + /** + * Check whether the file repository has been initialized. + */ + public boolean isEnabled() { + return settings != null; + } + + + public boolean caseUsesFileRepository() throws TskCoreException { + skCase.acquireSingleUserCaseReadLock(); + try (CaseDbConnection connection = skCase.getConnection(); + Statement statement = connection.createStatement(); + ResultSet rs = connection.executeQuery(statement, "SELECT COUNT(*) as count FROM tsk_files WHERE location=" + TskData.FileLocation.REPOSITORY.getValue());) { + int count = 0; + if (rs.next()) { + count = rs.getInt("count"); + } + return count > 0; + } catch (SQLException ex) { + throw new TskCoreException("Error querying case database for files stored in repository", ex); + } finally { + skCase.releaseSingleUserCaseReadLock(); + } + } + + private String getFilePath(AbstractFile abstractFile) { + return Paths.get(fileDownloadFolder, abstractFile.getSha256Hash()).toString(); + } + + public File loadFromFileRepository(AbstractFile abstractFile) throws TskCoreException { + if (!isEnabled()) { + throw new TskCoreException("File repository is not enabled"); + } + + if (! abstractFile.getFileLocation().equals(TskData.FileLocation.REPOSITORY)) { + throw new TskCoreException("File with object ID " + abstractFile.getId() + " is not stored in the file repository"); + } + + if (abstractFile.getSha256Hash() == null || abstractFile.getSha256Hash().isEmpty()) { + throw new TskCoreException("File with object ID " + abstractFile.getId() + " has no SHA-256 hash and can not be downloaded"); + } + + // Download the file if it's not already there. + String downloadPath = getFilePath(abstractFile); + if ( ! new File(downloadPath).exists()) { + downloadFileFromFileService(abstractFile, downloadPath); + } + + // Check that we got the file. + File tempFile = new File(downloadPath); + if (tempFile.exists()) { + return tempFile; + } else { + throw new TskCoreException("Failed to download file with object ID " + abstractFile.getId() + + " and SHA-256 hash " + abstractFile.getSha256Hash() + " from file repository"); + } + } + + private String makeUrl(AbstractFile abstractFile) { + return "http://" + settings.getAddress() + ":" + settings.getPort() + "/" + FILE_PATH + abstractFile.getSha256Hash(); + } + + private void downloadFileFromFileService(AbstractFile abstractFile, String downloadPath) throws TskCoreException { + + List<String> command = new ArrayList<>(); + command.add("curl"); + command.add("-X"); + command.add("GET"); + command.add(makeUrl(abstractFile)); + command.add("-H"); + command.add("accept: */*"); + command.add("--output"); + command.add(downloadPath); + + ProcessBuilder processBuilder = new ProcessBuilder(command).inheritIO(); + try { + Process process = processBuilder.start(); + process.waitFor(); + } catch (IOException | InterruptedException ex) { + throw new TskCoreException("Error downloading file with SHA-256 hash " + abstractFile.getSha256Hash() + " from file repository", ex); + } + } + + /** + * @param abstractFile + * @param trans + */ + public void saveToFileRepository(AbstractFile abstractFile, SleuthkitCase.CaseDbTransaction trans) throws TskCoreException { + + if (! isEnabled()) { + throw new TskCoreException("File repository is not enabled"); + } + + // Make sure the SHA-256 hash has been calculated + if (abstractFile.getSha256Hash() == null || abstractFile.getSha256Hash().isEmpty()) { + HashUtility.calculateHashes(abstractFile, Arrays.asList(HashUtility.HashType.SHA256)); + abstractFile.save(); + } + + String filePath = ""; + if (abstractFile.getLocalPath() == null || abstractFile.getLocalPath().isEmpty()) { + try { + filePath = extractFileToDisk(abstractFile); + } catch (IOException ex) { + throw new TskCoreException("Error writing temporary file to disk", ex); + } + } else { + filePath = abstractFile.getLocalAbsPath(); + } + + // Save the abstractFile data + saveLocalFileToFileService(filePath); + + // Update the file table entry + try { + SleuthkitCase.CaseDbConnection connection = trans.getConnection(); + + // Change the location to REPOSITORY + String updateLocation = "UPDATE tsk_files SET location = ? WHERE obj_id = ?"; // NON-NLS + PreparedStatement statement = connection.getPreparedStatement(updateLocation, Statement.NO_GENERATED_KEYS); + statement.clearParameters(); + statement.setLong(1, TskData.FileLocation.REPOSITORY.getValue()); + statement.setLong(2, abstractFile.getId()); + connection.executeUpdate(statement); + + // Remove entry for this file in tsk_files_path (if it exists) + String removePath = "DELETE FROM tsk_files_path WHERE obj_id = ?"; // NON-NLS + statement = connection.getPreparedStatement(removePath, Statement.NO_GENERATED_KEYS); + statement.clearParameters(); + statement.setLong(1, abstractFile.getId()); + connection.executeUpdate(statement); + + } catch (SQLException ex) { + throw new TskCoreException("Error updating database for move to file repository", ex); + } + } + + private void saveLocalFileToFileService(String pathToFile) throws TskCoreException { + File file = new File(pathToFile); + + // curl -X POST "http://localhost:8080/api/files" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "file=@Report.xml" + List<String> command = new ArrayList<>(); + command.add("curl"); + command.add("-X"); + command.add("POST"); + command.add("http://localhost:8080/v1/files"); + command.add("-H"); + command.add("accept: application/json"); + command.add("-H"); + command.add("Content-Type: multipart/form-data"); + command.add("-F"); + command.add("file=@" + file.getAbsolutePath()); + + ProcessBuilder processBuilder = new ProcessBuilder(command).inheritIO(); + try { + Process process = processBuilder.start(); + process.waitFor(); + } catch (IOException | InterruptedException ex) { + throw new TskCoreException("Error saving file at " + pathToFile + " to file repository", ex); + } + } + + private String extractFileToDisk(AbstractFile abstractFile) throws IOException { + String extractedPath = getFilePath(abstractFile); + + InputStream in = new ReadContentInputStream(abstractFile); + try (FileOutputStream out = new FileOutputStream(extractedPath, false)) { + byte[] buffer = new byte[0x2000]; + int len = in.read(buffer); + while (len != -1) { + out.write(buffer, 0, len); + len = in.read(buffer); + } + } catch (FileNotFoundException ex) { + throw new IOException("Error exporting file with object ID " + abstractFile.getId() + " to " + extractedPath, ex); + } finally { + in.close(); + } + return extractedPath; + } + + + static public class FileRepositorySettings { + String address; + String port; + + public FileRepositorySettings(String address, String port) { + this.address = address; + this.port = port; + } + + public String getAddress() { + return address; + } + + public String getPort() { + return port; + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index d8923b96da59ae55393556f65b1559b153b9c220..d601a67c86d4266a73838bd43c48a1651e0583c4 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -210,6 +210,7 @@ public class SleuthkitCase { private Blackboard blackboard; private CaseDbAccessManager dbAccessManager; private TaggingManager taggingMgr; + private FileRepositoryManager fileRepositoryManager; private final Map<String, Set<Long>> deviceIdToDatasourceObjIdMap = new HashMap<>(); @@ -388,6 +389,7 @@ private void init() throws Exception { timelineMgr = new TimelineManager(this); dbAccessManager = new CaseDbAccessManager(this); taggingMgr = new TaggingManager(this); + fileRepositoryManager = new FileRepositoryManager(this); } /** @@ -499,6 +501,15 @@ public synchronized CaseDbAccessManager getCaseDbAccessManager() throws TskCoreE public synchronized TaggingManager getTaggingManager() { return taggingMgr; } + + /** + * Get the case database FileRepositoryManager object. + * + * @return The per case FileRepositoryManager object. + */ + public synchronized FileRepositoryManager getFileRepositoryManager() { + return fileRepositoryManager; + } /** * Make sure the predefined artifact types are in the artifact types table. @@ -7018,49 +7029,7 @@ public LocalFile addLocalFile(String fileName, String localPath, String md5, String sha256, FileKnown known, String mimeType, boolean isFile, TskData.EncodingType encodingType, Content parent, CaseDbTransaction transaction) throws TskCoreException { - - return addLocalFile(fileName, localPath, - size, ctime, crtime, atime, mtime, - md5, sha256, known, mimeType, - isFile, encodingType, - parent, TskData.FileLocation.LOCAL, transaction); - } - - /** - * Adds a local/logical file to the case database. The database operations - * are done within a caller-managed transaction; the caller is responsible - * for committing or rolling back the transaction. - * - * @param fileName The name of the file. - * @param localPath The absolute path (including the file name) of the - * local/logical in secondary storage. - * @param size The size of the file in bytes. - * @param ctime The changed time of the file. - * @param crtime The creation time of the file. - * @param atime The accessed time of the file - * @param mtime The modified time of the file. - * @param md5 The MD5 hash of the file. - * @param sha256 The SHA-256 hash of the file. - * @param known The known status of the file (can be null) - * @param mimeType The MIME type of the file - * @param isFile True, unless the file is a directory. - * @param encodingType Type of encoding used on the file - * @param parent The parent of the file (e.g., a virtual directory) - * @param location The location of the file. - * @param transaction A caller-managed transaction within which the add - * file operations are performed. - * - * @return An object representing the local/logical file. - * - * @throws TskCoreException if there is an error completing a case database - * operation. - */ - LocalFile addLocalFile(String fileName, String localPath, - long size, long ctime, long crtime, long atime, long mtime, - String md5, String sha256, FileKnown known, String mimeType, - boolean isFile, TskData.EncodingType encodingType, - Content parent, TskData.FileLocation location, CaseDbTransaction transaction) throws TskCoreException { - + CaseDbConnection connection = transaction.getConnection(); Statement queryStatement = null; try { @@ -7120,7 +7089,7 @@ LocalFile addLocalFile(String fileName, String localPath, dataSourceObjId = getDataSourceObjectId(connection, parent.getId()); } statement.setString(19, parentPath); - statement.setLong(20, location.getValue()); + statement.setLong(20, TskData.FileLocation.LOCAL.getValue()); statement.setLong(21, dataSourceObjId); final String extension = extractExtension(fileName); statement.setString(22, extension); @@ -7431,132 +7400,6 @@ private void updateFilePath(CaseDbConnection connection, long objId, String path statement.setLong(3, objId); connection.executeUpdate(statement); } - - // TEMP TEMP - // Should be set at the same time as the file service server - File getFileServiceTempFolder() { - return new File("R:\\mytemp"); - } - - File loadFromFileService(AbstractFile abstractFile) throws TskCoreException { - if (! abstractFile.getFileLocation().equals(TskData.FileLocation.MICROSERVICE)) { - throw new TskCoreException("Not file service"); - } - - // TODO verify there's a hash (there should be) - - // TODO check existence first? - String downloadPath = Paths.get(getFileServiceTempFolder().toString(), abstractFile.getSha256Hash()).toString(); - downloadFileFromFileService(abstractFile, downloadPath); - - File tempFile = new File(downloadPath); - if (tempFile.exists()) { - // YAY! - return tempFile; - } else { - System.out.println("\n### oh no... " + tempFile.toString() + " does not exist"); - throw new TskCoreException("error"); - } - - } - - private void downloadFileFromFileService(AbstractFile file, String downloadPath) { - - // curl -X GET "http://localhost:8080/api/files/(hash value)" -H "accept: */*" --output downloadPath - List<String> command = new ArrayList<>(); - command.add("curl"); - command.add("-X"); - command.add("GET"); - command.add("http://localhost:8080/v1/files/" + file.getSha256Hash()); - command.add("-H"); - command.add("accept: */*"); - command.add("--output"); - command.add(downloadPath); - - System.out.println("### Process builder commands"); - for(String s:command){ - System.out.println(" " + s); - } - - ProcessBuilder processBuilder = new ProcessBuilder(command).inheritIO(); - try { - Process process = processBuilder.start(); - process.waitFor(); - } catch (IOException | InterruptedException ex) { - ex.printStackTrace(); - } - - - } - - /** - * TODO: should be abstractFile or content? - * @param abstractFile - */ - public void saveToFileService(AbstractFile abstractFile, CaseDbTransaction trans) throws TskCoreException { - - if (abstractFile.getLocalPath() == null || abstractFile.getLocalPath().isEmpty()) { - // TODO - System.out.println("Not supported yet\n"); - } else { - // Make sure the SHA-256 hash has been calculated - if (abstractFile.getSha256Hash() == null || abstractFile.getSha256Hash().isEmpty()) { - HashUtility.calculateHashes(abstractFile, Arrays.asList(HashUtility.HashType.SHA256)); - abstractFile.save(); - } - - // Save the abstractFile data - saveLocalFileToFileService(abstractFile); - - // Update the file table entry - try { - CaseDbConnection connection = trans.getConnection(); - PreparedStatement statement = connection.getPreparedStatement(PREPARED_STATEMENT.UPDATE_FILE_SERVICE_FILE); - statement.clearParameters(); - - statement.setLong(1, TskData.FileLocation.MICROSERVICE.getValue()); - statement.setLong(2, abstractFile.getId()); - - connection.executeUpdate(statement); - - // TODO remove local path - - } catch (SQLException ex) { - ex.printStackTrace(); - } - } - - } - - private void saveLocalFileToFileService(AbstractFile abstractFile) { - File file = new File(abstractFile.getLocalPath()); - - // curl -X POST "http://localhost:8080/api/files" -H "accept: application/json" -H "Content-Type: multipart/form-data" -F "file=@Report.xml" - List<String> command = new ArrayList<>(); - command.add("curl"); - command.add("-X"); - command.add("POST"); - command.add("http://localhost:8080/v1/files"); - command.add("-H"); - command.add("accept: application/json"); - command.add("-H"); - command.add("Content-Type: multipart/form-data"); - command.add("-F"); - command.add("file=@" + file.getAbsolutePath()); - - System.out.println("### Process builder commands"); - for(String s:command){ - System.out.println(" " + s); - } - - ProcessBuilder processBuilder = new ProcessBuilder(command).inheritIO(); - try { - Process process = processBuilder.start(); - process.waitFor(); - } catch (IOException | InterruptedException ex) { - ex.printStackTrace(); - } - } /** * Find all files in the data source, by name and parent @@ -11517,8 +11360,7 @@ private enum PREPARED_STATEMENT { INSERT_FS_INFO("INSERT INTO tsk_fs_info (obj_id, data_source_obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name)" + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"), SELECT_TAG_NAME_BY_ID("SELECT * FROM tag_names where tag_name_id = ?"), - SELECT_TAG_NAME_BY_NAME("SELECT * FROM tag_names where display_name = ?"), - UPDATE_FILE_SERVICE_FILE("UPDATE tsk_files SET location = ? WHERE obj_id = ?"); + SELECT_TAG_NAME_BY_NAME("SELECT * FROM tag_names where display_name = ?"); private final String sql; diff --git a/bindings/java/src/org/sleuthkit/datamodel/TskData.java b/bindings/java/src/org/sleuthkit/datamodel/TskData.java index e83310ba21e83e924bf31a0b6842ac65ad2fff72..b49e36a201a65d412e79358c85cf5d95d32153fe 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/TskData.java +++ b/bindings/java/src/org/sleuthkit/datamodel/TskData.java @@ -884,7 +884,7 @@ public static EncodingType valueOf(int type) { */ public enum FileLocation{ LOCAL(0), - MICROSERVICE(1); + REPOSITORY(1); private final int value;