Handle input image folders in case import

This commit is contained in:
Karl Mortensen 2015-07-31 09:41:28 -04:00
parent 6a0a4fd4cb
commit dd251cefa6
2 changed files with 138 additions and 49 deletions

View File

@ -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

View File

@ -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"));
}
} }
/** /**