mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-19 19:14:55 +00:00
Handle input image folders in case import
This commit is contained in:
parent
6a0a4fd4cb
commit
dd251cefa6
@ -253,7 +253,8 @@ SingleUserCaseImporter.BadDatabaseFileName=Database file does not exist!
|
|||||||
SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping
|
SingleUserCaseImporter.NonUniqueOutputFolder=Output folder not unique. Skipping
|
||||||
SingleUserCaseImporter.NonUniqueDatabaseName=Database name not unique. Skipping.
|
SingleUserCaseImporter.NonUniqueDatabaseName=Database name not unique. Skipping.
|
||||||
SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName=Unclear if database name unique. Moving ahead.
|
SingleUserCaseImporter.PotentiallyNonUniqueDatabaseName=Unclear if database name unique. Moving ahead.
|
||||||
SingleUserCaseImporter.ConvertedToMultiUser=This case was converted to a Multi-user collaborative case on
|
SingleUserCaseImporter.ConvertedToMultiUser=\nThis case was converted to a Multi-user collaborative case on
|
||||||
SingleUserCaseImporter.UnableToCopySourceImages=Unable to copy source images
|
SingleUserCaseImporter.UnableToCopySourceImages=Unable to copy source images
|
||||||
SingleUserCaseImporter.ConversionSuccessful=. Conversion successful:
|
SingleUserCaseImporter.ConversionSuccessful=. Conversion successful:
|
||||||
SingleUserCaseImporter.DeletingCase=Deleting original case folder
|
SingleUserCaseImporter.DeletingCase=Deleting original case folder
|
||||||
|
SingleUserCaseImporter.CanNotOpenDatabase=Unable to open database
|
@ -50,6 +50,7 @@ import org.sleuthkit.datamodel.CaseDbConnectionInfo;
|
|||||||
import org.sleuthkit.datamodel.SleuthkitCase;
|
import org.sleuthkit.datamodel.SleuthkitCase;
|
||||||
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
import org.sleuthkit.autopsy.coreutils.NetworkUtils;
|
||||||
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
|
import org.sleuthkit.autopsy.coreutils.TimeStampUtils;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.UNCPathUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert case(s) from single-user to multi-user. Recursively scans subfolders.
|
* Convert case(s) from single-user to multi-user. Recursively scans subfolders.
|
||||||
@ -72,10 +73,11 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
private final String caseOutputFolder;
|
private final String caseOutputFolder;
|
||||||
private final String imageInputFolder;
|
private final String imageInputFolder;
|
||||||
private final String imageOutputFolder;
|
private final String imageOutputFolder;
|
||||||
private final boolean copyImages;
|
private final boolean copySourceImages;
|
||||||
private final boolean deleteCase;
|
private final boolean deleteCase;
|
||||||
private final CaseDbConnectionInfo db;
|
private final CaseDbConnectionInfo db;
|
||||||
private final ConversionDoneCallback notifyOnComplete;
|
private final ConversionDoneCallback notifyOnComplete;
|
||||||
|
private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities();
|
||||||
private PrintWriter writer;
|
private PrintWriter writer;
|
||||||
private XMLCaseManagement oldXmlCaseManagement;
|
private XMLCaseManagement oldXmlCaseManagement;
|
||||||
private XMLCaseManagement newXmlCaseManagement;
|
private XMLCaseManagement newXmlCaseManagement;
|
||||||
@ -89,19 +91,19 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
* @param imageInput the folder that holds the images to copy over
|
* @param imageInput the folder that holds the images to copy over
|
||||||
* @param imageOutput the destination folder for the images
|
* @param imageOutput the destination folder for the images
|
||||||
* @param database the connection information to talk to the PostgreSQL db
|
* @param database the connection information to talk to the PostgreSQL db
|
||||||
* @param copyImages true if images should be copied
|
* @param copySourceImages true if images should be copied
|
||||||
* @param deleteCase true if the old version of the case should be deleted
|
* @param deleteCase true if the old version of the case should be deleted
|
||||||
* after import
|
* after import
|
||||||
* @param callback a callback from the calling panel for notification when
|
* @param callback a callback from the calling panel for notification when
|
||||||
* the conversion has completed. This is a Runnable on a different thread.
|
* the conversion has completed. This is a Runnable on a different thread.
|
||||||
*/
|
*/
|
||||||
public SingleUserCaseImporter(String caseInput, String caseOutput, String imageInput, String imageOutput, CaseDbConnectionInfo database,
|
public SingleUserCaseImporter(String caseInput, String caseOutput, String imageInput, String imageOutput, CaseDbConnectionInfo database,
|
||||||
boolean copyImages, boolean deleteCase, ConversionDoneCallback callback) {
|
boolean copySourceImages, boolean deleteCase, ConversionDoneCallback callback) {
|
||||||
this.caseInputFolder = caseInput;
|
this.caseInputFolder = caseInput;
|
||||||
this.caseOutputFolder = caseOutput;
|
this.caseOutputFolder = caseOutput;
|
||||||
this.imageInputFolder = imageInput;
|
this.imageInputFolder = imageInput;
|
||||||
this.imageOutputFolder = imageOutput;
|
this.imageOutputFolder = imageOutput;
|
||||||
this.copyImages = copyImages;
|
this.copySourceImages = copySourceImages;
|
||||||
this.deleteCase = deleteCase;
|
this.deleteCase = deleteCase;
|
||||||
this.db = database;
|
this.db = database;
|
||||||
this.notifyOnComplete = callback;
|
this.notifyOnComplete = callback;
|
||||||
@ -124,7 +126,7 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
try {
|
try {
|
||||||
log("Beginning to convert " + input.toString() + " to " + caseOutputFolder + "\\" + oldCaseFolder); //NON-NLS
|
log("Beginning to convert " + input.toString() + " to " + caseOutputFolder + "\\" + oldCaseFolder); //NON-NLS
|
||||||
|
|
||||||
if (copyImages) {
|
if (copySourceImages) {
|
||||||
checkInput(input.toFile(), new File(imageInputFolder));
|
checkInput(input.toFile(), new File(imageInputFolder));
|
||||||
} else {
|
} else {
|
||||||
checkInput(input.toFile(), null);
|
checkInput(input.toFile(), null);
|
||||||
@ -159,9 +161,11 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
|
|
||||||
copyResults(input, newCaseFolder, oldCaseName); // Copy items to new hostname folder structure
|
copyResults(input, newCaseFolder, oldCaseName); // Copy items to new hostname folder structure
|
||||||
dbName = convertDb(dbName, input, newCaseFolder); // Change from SQLite to PostgreSQL
|
dbName = convertDb(dbName, input, newCaseFolder); // Change from SQLite to PostgreSQL
|
||||||
if (copyImages) {
|
|
||||||
File imageSource = copyInputImages(imageInputFolder, oldCaseName, imageDestination); // Copy images over
|
File imageSource = findInputFolder(input, imageInputFolder, oldCaseName); // Find a folder for the input images
|
||||||
fixPaths(imageSource.toString(), imageDestination.toString(), dbName); // Update paths in DB
|
fixPaths(imageSource.toString(), imageDestination.toString(), dbName); // Update paths in DB
|
||||||
|
if (copySourceImages) {
|
||||||
|
copyImages(imageSource, imageDestination); // Copy images over
|
||||||
}
|
}
|
||||||
|
|
||||||
// create new XML config
|
// create new XML config
|
||||||
@ -194,6 +198,87 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a path to UNC. First go to UNC based on drive mappings, then try
|
||||||
|
* to put hostname in place of IP address.
|
||||||
|
*
|
||||||
|
* @param rawInput the String to sanitize
|
||||||
|
* @return Returns best-effort results.
|
||||||
|
*/
|
||||||
|
private String sanitizeToUNC(String rawInput) {
|
||||||
|
String uncWithIP = uncPathUtilities.mappedDriveToUNC(rawInput);
|
||||||
|
if (uncWithIP != null) {
|
||||||
|
rawInput = uncWithIP;
|
||||||
|
}
|
||||||
|
String uncWithHostName = uncPathUtilities.ipToHostName(rawInput);
|
||||||
|
if (uncWithHostName == null) {
|
||||||
|
uncWithHostName = rawInput;
|
||||||
|
}
|
||||||
|
return uncWithHostName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figure out what the name of the file source should be and return it. This
|
||||||
|
* could be one of three things: a portion of a name from the
|
||||||
|
* tsk_image_names table, the folder name passed in, or a child of the
|
||||||
|
* folder name passed in.
|
||||||
|
*
|
||||||
|
* @param sqliteDbFolder The path to the autopsy.db file
|
||||||
|
* @param rawInputFolder The path to the input folder
|
||||||
|
* @param oldCaseName The old name of the case
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private File findInputFolder(Path sqliteDbFolder, String rawInputFolder, String oldCaseName) {
|
||||||
|
|
||||||
|
String uncInputFolder = sanitizeToUNC(rawInputFolder);
|
||||||
|
// By this point, uncInputFolder is as sanitized as we can get it. If we
|
||||||
|
// could resolve a UNC path and hostname, it's a hostname-based UNC path
|
||||||
|
File uncInputFile = new File(uncInputFolder);
|
||||||
|
String uncParent = uncInputFile.getParent();
|
||||||
|
|
||||||
|
Path fullAttempt = Paths.get(uncInputFolder, oldCaseName);
|
||||||
|
Path partialAttempt = Paths.get(uncParent, oldCaseName);
|
||||||
|
|
||||||
|
if (fullAttempt.toFile().isDirectory()) {
|
||||||
|
/// we've found it, they are running a batch convert
|
||||||
|
return fullAttempt.toFile();
|
||||||
|
} else if (uncParent != null && !uncParent.isEmpty() && partialAttempt.toFile().isDirectory()) {
|
||||||
|
/// we've found it, they are running a specific convert
|
||||||
|
return partialAttempt.toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We still haven't found it. It must not be named the same as the output case. Search db.
|
||||||
|
try {
|
||||||
|
Class.forName("org.sqlite.JDBC"); //NON-NLS
|
||||||
|
Connection sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + sqliteDbFolder.resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS
|
||||||
|
Statement inputStatement = sqliteConnection.createStatement();
|
||||||
|
ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS
|
||||||
|
Path passedIn = Paths.get(uncInputFolder);
|
||||||
|
while (inputResultSet.next()) {
|
||||||
|
String sanitizedNameFromDatabase = sanitizeToUNC(inputResultSet.getString(2));
|
||||||
|
Path fromDatabase = Paths.get(sanitizedNameFromDatabase);
|
||||||
|
Path relative = passedIn.relativize(fromDatabase);
|
||||||
|
if (relative != null && relative.getNameCount() > 0) {
|
||||||
|
String databaseFolderName = relative.getName(0).toString();
|
||||||
|
fullAttempt = Paths.get(uncInputFolder, databaseFolderName);
|
||||||
|
partialAttempt = Paths.get(uncParent, databaseFolderName);
|
||||||
|
if (fullAttempt.toFile().isDirectory()) {
|
||||||
|
return fullAttempt.toFile();
|
||||||
|
} else if (uncParent != null && !uncParent.isEmpty() && partialAttempt.toFile().isDirectory()) {
|
||||||
|
return partialAttempt.toFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sqliteConnection.close();
|
||||||
|
} catch (ClassNotFoundException | SQLException ex) {
|
||||||
|
log(ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here without finding an appropriate folder, return the UNC
|
||||||
|
// resolved uncInputFolder as best effort.
|
||||||
|
return new File(uncInputFolder);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure the input source has an autopsy.db and exists.
|
* Ensure the input source has an autopsy.db and exists.
|
||||||
*
|
*
|
||||||
@ -289,16 +374,16 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Remove the single-user .aut file, database, Timeline database and log
|
// Remove the single-user .aut file, database, Timeline database and log
|
||||||
File oldDatabaseFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, caseName + DOTAUT).toFile();
|
File oldAutopsyFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, caseName + DOTAUT).toFile();
|
||||||
if (oldDatabaseFile.exists()) {
|
|
||||||
oldDatabaseFile.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
File oldAutopsyFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, AUTOPSY_DB_FILE).toFile();
|
|
||||||
if (oldAutopsyFile.exists()) {
|
if (oldAutopsyFile.exists()) {
|
||||||
oldAutopsyFile.delete();
|
oldAutopsyFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File oldDatabaseFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, AUTOPSY_DB_FILE).toFile();
|
||||||
|
if (oldDatabaseFile.exists()) {
|
||||||
|
oldDatabaseFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
File oldTimelineFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, TIMELINE_FILE).toFile();
|
File oldTimelineFile = Paths.get(caseOutputFolder, newCaseFolder, hostName, TIMELINE_FILE).toFile();
|
||||||
if (oldTimelineFile.exists()) {
|
if (oldTimelineFile.exists()) {
|
||||||
oldTimelineFile.delete();
|
oldTimelineFile.delete();
|
||||||
@ -984,34 +1069,19 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the images from the other case and place them in the central
|
* Get the images from the other case and place them in the central
|
||||||
* repository
|
* repository if the user chose to.
|
||||||
*
|
*
|
||||||
|
* @param imageSource the folder that the original images are in
|
||||||
|
* @param imageDestination the place to copy the images to
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private File copyInputImages(String rawInputFolder, String oldCaseName, File output) throws IOException {
|
private void copyImages(File imageSource, File imageDestination) throws IOException {
|
||||||
/// If we can find the input images, copy if needed, then update data source paths in tsk_image_names if needed.
|
// If we can find the input images, copy if needed.
|
||||||
|
if (imageSource != null && imageSource.exists() && imageDestination != null) {
|
||||||
File chosenInput = null;
|
FileUtils.copyDirectory(imageSource, imageDestination);
|
||||||
|
|
||||||
File rawInputFile = new File(rawInputFolder);
|
|
||||||
String rawParent = rawInputFile.getParent();
|
|
||||||
|
|
||||||
Path fullAttempt = Paths.get(rawInputFolder, oldCaseName);
|
|
||||||
Path partialAttempt = Paths.get(rawParent, oldCaseName);
|
|
||||||
|
|
||||||
if (fullAttempt.toFile().isDirectory()) {
|
|
||||||
/// we've found it, they are running a batch convert
|
|
||||||
chosenInput = fullAttempt.toFile();
|
|
||||||
} else if (rawParent != null && !rawParent.isEmpty() && partialAttempt.toFile().isDirectory()) {
|
|
||||||
/// we've found it, they are running a specific convert
|
|
||||||
chosenInput = partialAttempt.toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chosenInput != null && chosenInput.exists()) {
|
|
||||||
FileUtils.copyDirectory(chosenInput, output);
|
|
||||||
} else {
|
} else {
|
||||||
log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.UnableToCopySourceImages"));
|
log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.UnableToCopySourceImages"));
|
||||||
}
|
}
|
||||||
return chosenInput;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1020,8 +1090,8 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
*/
|
*/
|
||||||
private void fixPaths(String input, String output, String dbName) throws SQLException {
|
private void fixPaths(String input, String output, String dbName) throws SQLException {
|
||||||
/// Fix paths in reports, tsk_files_path, and tsk_image_names tables
|
/// Fix paths in reports, tsk_files_path, and tsk_image_names tables
|
||||||
|
|
||||||
Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS
|
Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS
|
||||||
|
if (postgresqlConnection != null) {
|
||||||
String hostName = NetworkUtils.getLocalHostName();
|
String hostName = NetworkUtils.getLocalHostName();
|
||||||
|
|
||||||
// add hostname to reports
|
// add hostname to reports
|
||||||
@ -1033,8 +1103,26 @@ public class SingleUserCaseImporter implements Runnable {
|
|||||||
updateStatement.executeUpdate("UPDATE tsk_files_path SET path=CONCAT('" + hostName + "\\', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS
|
updateStatement.executeUpdate("UPDATE tsk_files_path SET path=CONCAT('" + hostName + "\\', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS
|
||||||
|
|
||||||
// update path for images
|
// update path for images
|
||||||
|
Statement inputStatement = postgresqlConnection.createStatement();
|
||||||
|
ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS
|
||||||
|
Path lastPart = Paths.get(input).getFileName();
|
||||||
|
|
||||||
|
while (inputResultSet.next()) {
|
||||||
|
Path oldPath = Paths.get(inputResultSet.getString(2));
|
||||||
|
|
||||||
|
for (int x = 0; x < oldPath.getNameCount(); ++x) {
|
||||||
|
if (oldPath.getName(x).equals(lastPart)) {
|
||||||
|
Path newPath = Paths.get(output, oldPath.subpath(x + 1, oldPath.getNameCount()).toString());
|
||||||
updateStatement = postgresqlConnection.createStatement();
|
updateStatement = postgresqlConnection.createStatement();
|
||||||
updateStatement.executeUpdate("UPDATE tsk_image_names SET name=OVERLAY(name PLACING '" + output + "' FROM 1 FOR " + input.length() + ")"); //NON-NLS
|
updateStatement.executeUpdate("UPDATE tsk_image_names SET name='" + newPath.toString() + "' WHERE obj_id = " + inputResultSet.getInt(1)); //NON-NLS
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
postgresqlConnection.close();
|
||||||
|
} else {
|
||||||
|
log(NbBundle.getMessage(SingleUserCaseImporter.class, "SingleUserCaseImporter.CanNotOpenDatabase"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user