From 99937bde7b34e6ca1afdf82ac0583fba4166acce Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Thu, 16 Jul 2015 16:35:31 -0400 Subject: [PATCH 1/7] Initial cut of case converter --- .../autopsy/casemodule/Bundle.properties | 12 + .../sleuthkit/autopsy/casemodule/Case.java | 7 +- .../autopsy/casemodule/CaseConverter.java | 1278 +++++++++++++++++ .../casemodule/ConversionDoneCallback.java | 5 + .../autopsy/casemodule/TimeStampUtils.java | 53 + .../autopsy/casemodule/XMLCaseManagement.java | 24 +- .../autopsy/core/UserPreferences.java | 2 +- .../autopsy/corecomponents/Bundle.properties | 2 +- .../MultiUserSettingsPanel.form | 58 +- .../MultiUserSettingsPanel.java | 70 +- 10 files changed, 1421 insertions(+), 90 deletions(-) create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/ConversionDoneCallback.java create mode 100644 Core/src/org/sleuthkit/autopsy/casemodule/TimeStampUtils.java diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 28b49a15c1..37f01bbb60 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -252,3 +252,15 @@ CollaborationMonitor.restoredSolrService.notify.msg=Connection to keyword search CollaborationMonitor.restoredMessageService.notify.msg=Connection to messaging server restored MissingImageDialog.lbWarning.text= MissingImageDialog.lbWarning.toolTipText= +CaseConverter.AlreadyMultiUser=Case is already multi-user! +CaseConverter.FinishedConverting=Finished converting +CaseConverter.To= to +CaseConverter.BadCaseSourceFolder=Case source folder does not exist! +CaseConverter.BadImageSourceFolder=Image source folder does not exist! +CaseConverter.BadDatabaseFileName=Database file does not exist! +CaseConverter.NonUniqueOutputFolder=Output folder not unique. Skipping +CaseConverter.NonUniqueDatabaseName=Database name not unique. Skipping. +CaseConverter.PotentiallyNonUniqueDatabaseName=Unclear if database name unique. Moving ahead. +CaseConverter.ConvertedToMultiUser=This case was converted to a Multi-user collaborative case on +CaseConverter.UnableToCopySourceImages=Unable to copy source images +CaseConverter.ConversionSuccessful=. Conversion successful: \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 1ac03b2859..f28acaae03 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -460,7 +460,7 @@ public class Case { * * @return the sanitized case name to use for Database, Solr, and ActiveMQ */ - private static String sanitizeCaseName(String caseName) { + public static String sanitizeCaseName(String caseName) { String result; @@ -1572,11 +1572,8 @@ public class Case { * Set the host name variable. Sometimes the network can be finicky, so the * answer returned by getHostName() could throw an exception or be null. * Have it read the environment variable if getHostName() is unsuccessful. - * Also note that some calls into the Case class are static via Case.*, so - * anywhere we use HOSTNAME prior to a Case class being instantiated, we - * must call getLocalHostName() first. */ - private static String getLocalHostName() { + public static String getLocalHostName() { if (HostName == null || HostName.isEmpty()) { try { HostName = java.net.InetAddress.getLocalHost().getHostName(); diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java new file mode 100644 index 0000000000..3ce0dc31c0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java @@ -0,0 +1,1278 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit 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.autopsy.casemodule; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Date; +import org.apache.commons.io.FileUtils; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.datamodel.CaseDbConnectionInfo; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * Convert case(s) from single-user to multi-user. Recursively scans subfolders. + */ +public class CaseConverter implements Runnable { + + private static final String AUTOPSY_DB_FILE = "autopsy.db"; //NON-NLS + private static final String DOTAUT = ".aut"; //NON-NLS + private static final String CACHE_FOLDER = "Cache"; //NON-NLS + private static final String EXPORT_FOLDER = "Export"; //NON-NLS + private static final String LOG_FOLDER = "Log"; //NON-NLS + private static final String MODULE_FOLDER = "ModuleOutput"; //NON-NLS + private static final String REPORTS_FOLDER = "Reports"; //NON-NLS + private static final String TEMP_FOLDER = "Temp"; //NON-NLS + private static final String TIMELINE_FOLDER = "Timeline"; //NON-NLS + private final static String AIM_LOG_FILE_NAME = "auto_ingest_log.txt"; //NON-NLS + private final static String TIMELINE_FILE = "events.db"; //NON-NLS + public static final String CASE_CONVERSION_LOG_FILE = "case_conversion.txt"; //NON-NLS + private static final String logDateFormat = "yyyy/MM/dd HH:mm:ss"; //NON-NLS + private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(logDateFormat); + private static final int MAX_DB_NAME_LENGTH = 63; + + private final String caseInputFolder; + private final String caseOutputFolder; + private final String imageInputFolder; + private final String imageOutputFolder; + private final CaseDbConnectionInfo db; + private final ConversionDoneCallback notifyOnComplete; + private PrintWriter writer; + private XMLCaseManagement oldXmlCaseManagement; + private XMLCaseManagement newXmlCaseManagement; + + /** + * CaseConverter constructor + * + * @param caseInput the folder to start our case search from. Will find + * valid cases from this folder down, and process them. + * @param caseOutput the folder to place processed cases into + * @param imageInput the folder that holds the images to copy over + * @param imageOutput the destination folder for the images + * @param database the connection information to talk to the PostgreSQL db + * @param callback a callback from the calling panel for notification when + * the conversion has completed. This is a Runnable on a different thread. + */ + public CaseConverter(String caseInput, String caseOutput, String imageInput, String imageOutput, CaseDbConnectionInfo database, ConversionDoneCallback callback) { + this.caseInputFolder = caseInput; + this.caseOutputFolder = caseOutput; + this.imageInputFolder = imageInput; + this.imageOutputFolder = imageOutput; + this.db = database; + this.notifyOnComplete = callback; + } + + /** + * Handles most of the heavy lifting for converting cases from single-user + * to multi-user. Creates new .aut file, moves folders to the right place, + * converts the database, and updates paths within the database. + * + * @param input the full path of the folder to process. + * @param oldCaseFolder the case folder holding the old case. This name will + * change if it conflicts with an existing case in the multi-user shared + * output folder + * @return true if successful, false if not + */ + private boolean processCase(Path input, String oldCaseFolder) { + boolean result = true; + + try { + log("Beginning to convert " + input.toString() + " to " + caseOutputFolder + "\\" + oldCaseFolder); //NON-NLS + + checkInput(input.toFile(), new File(imageInputFolder)); + String oldCaseName = oldCaseFolder; + if (TimeStampUtils.endsWithTimeStamp(oldCaseName)) { + oldCaseName = oldCaseName.substring(0, oldCaseName.length() - TimeStampUtils.getTimeStampLength()); + } + + oldXmlCaseManagement = new XMLCaseManagement(); + newXmlCaseManagement = new XMLCaseManagement(); + + // read old xml config + oldXmlCaseManagement.open(input.resolve(oldCaseName + DOTAUT).toString()); + if (oldXmlCaseManagement.getCaseType() == CaseType.MULTI_USER_CASE) { + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.AlreadyMultiUser")); + } + + String newCaseFolder = prepareOutput(caseOutputFolder, oldCaseFolder); + String caseName = newCaseFolder; // case name holds the deconflicted name of the case, and will not have a timestamp after the next 'if' clause runs + if (TimeStampUtils.endsWithTimeStamp(newCaseFolder)) { + caseName = newCaseFolder.substring(0, newCaseFolder.length() - TimeStampUtils.getTimeStampLength()); + } + + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS + Date date = new Date(); + String santizedDatabaseName = Case.sanitizeCaseName(caseName); + String dbName = santizedDatabaseName + "_" + dateFormat.format(date); //NON-NLS + String solrName = dbName; + + File imageDestination = Paths.get(imageOutputFolder, caseName).toFile(); + + copyResults(input, newCaseFolder); // Copy items to new hostname folder structure + dbName = convertDb(dbName, input, newCaseFolder); // Change from SQLite to PostgreSQL + solrName = fixUpSolr(solrName); // Hook Solr back up + File imageSource = copyInputImages(imageInputFolder, oldCaseName, imageDestination); // Copy images over + fixPaths(imageSource.toString(), imageDestination.toString(), dbName); // Update paths in DB + + // create new XML config + newXmlCaseManagement.create(Paths.get(caseOutputFolder, newCaseFolder).toString(), + caseName, + oldXmlCaseManagement.getCaseExaminer(), + oldXmlCaseManagement.getCaseNumber(), + CaseType.MULTI_USER_CASE, dbName, solrName); + + // Set created date. This calls writefile, no need to call it again + newXmlCaseManagement.setCreatedDate(oldXmlCaseManagement.getCreatedDate()); + + log(NbBundle.getMessage(CaseConverter.class, "CaseConverter.FinishedConverting") + + input.toString() + NbBundle.getMessage(CaseConverter.class, "CaseConverter.To") + + caseOutputFolder + File.separatorChar + newCaseFolder); + } catch (Exception exp) { + /// clean up here + log(exp.getMessage()); + result = false; + } + return result; + } + + /** + * Ensure the input source has an autopsy.db and exists. + * + * @param caseInput The folder containing a case to convert. + * @param imageInput The folder containing the images to copy. + * @throws Exception + */ + private void checkInput(File caseInput, File imageInput) throws Exception { + if (false == caseInput.exists()) { + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.BadCaseSourceFolder")); + } else if (false == imageInput.exists()) { + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.BadImageSourceFolder")); + } + Path path = Paths.get(caseInput.toString(), AUTOPSY_DB_FILE); + if (false == path.toFile().exists()) { + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.BadDatabaseFileName")); + } + } + + /** + * Handles case folder, PosgreSql database, and Solr core name deconfliction + * + * @param caseOutputFolder the case output folder + * @param caseFolder the case folder name, including timestamp + * @return the deconflicted caseFolder name to use. Includes timestamp. + * @throws Exception + */ + private String prepareOutput(String caseOutputFolder, String caseFolder) throws Exception { + // test for uniqueness + File specificOutputFolder = Paths.get(caseOutputFolder, caseFolder).toFile(); + String sanitizedCaseName = caseFolder; + if (specificOutputFolder.exists()) { + // not unique. add numbers before timestamp to specific case name + String timeStamp = ""; //NON-NLS + if (TimeStampUtils.endsWithTimeStamp(caseFolder)) { + sanitizedCaseName = caseFolder.substring(0, caseFolder.length() - TimeStampUtils.getTimeStampLength()); + timeStamp = caseFolder.substring(caseFolder.length() - TimeStampUtils.getTimeStampLength(), caseFolder.length()); + } + int number = 1; + String temp = ""; //NON-NLS + while (specificOutputFolder.exists()) { + if (number == Integer.MAX_VALUE) { + // oops. it never became unique. give up. + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.NonUniqueOutputFolder") + caseFolder); + } + temp = sanitizedCaseName + "_" + Integer.toString(number) + timeStamp; //NON-NLS + specificOutputFolder = Paths.get(caseOutputFolder, temp).toFile(); + ++number; + } + sanitizedCaseName = temp; + } + + // create output folders just in case + Paths.get(caseOutputFolder, sanitizedCaseName).toFile().mkdirs(); + return sanitizedCaseName; + } + + /** + * Copy all the folders at the base level to the new scheme involving + * hostname. Also take care of a few files such as logs, timeline db, etc. + * + * @param input the base path to the case folder to copy from + * @param newCaseFolder deconflicted case name for the new multi-user case + * @throws IOException + */ + private void copyResults(Path input, String newCaseFolder) throws IOException { + /// get hostname + String hostName = Case.getLocalHostName(); + Path source = input.resolve(CACHE_FOLDER); + Path destination; + + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, CACHE_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(EXPORT_FOLDER); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, EXPORT_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(LOG_FOLDER); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, LOG_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(MODULE_FOLDER); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, MODULE_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(REPORTS_FOLDER); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, REPORTS_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(TEMP_FOLDER); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, TEMP_FOLDER); + FileUtils.copyDirectory(source.toFile(), destination.toFile()); + } + + source = input.resolve(TIMELINE_FILE); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, hostName, MODULE_FOLDER, TIMELINE_FOLDER, TIMELINE_FILE); + FileUtils.copyFile(source.toFile(), destination.toFile()); + } + + source = input.resolve(AIM_LOG_FILE_NAME); + if (source.toFile().exists()) { + destination = Paths.get(caseOutputFolder, newCaseFolder, AIM_LOG_FILE_NAME); + FileUtils.copyFile(source.toFile(), destination.toFile()); + try (PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(destination.toString(), true)))) { + out.println(NbBundle.getMessage(CaseConverter.class, "CaseConverter.ConvertedToMultiUser") + new Date()); + } catch (IOException e) { + // if unable to log it, no problem + } + } + } + + /** + * Import the database from SQLite to PostgreSQL. Do not change any of the + * data while loading it over. Fixing paths is done once the database is + * completely imported. + * + * @param dbName the name of the database, could have name collision + * @param inputPath the path to the input case + * @param outputCaseName the name of the output case, could have extra + * digits to avoid name collisions + * @return the deconflicted name of the PostgreSQL database that was created + * @throws SQLException + * @throws ClassNotFoundException + */ + private String convertDb(String dbName, Path inputPath, String outputCaseName) throws SQLException, ClassNotFoundException, Exception { + // deconflict the database name + dbName = deconflictDatabaseName(db, dbName); + + // Create a new database via SleuthkitCase + SleuthkitCase newCase = SleuthkitCase.newCase(dbName, db, outputCaseName); + newCase.close(); + + /// Migrate from SQLite to PostgreSQL + Class.forName("org.sqlite.JDBC"); //NON-NLS + Connection sqliteConnection = DriverManager.getConnection("jdbc:sqlite:" + inputPath.resolve(AUTOPSY_DB_FILE).toString(), "", ""); //NON-NLS + + Connection postgresqlConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/" + dbName, db.getUserName(), db.getPassword()); //NON-NLS + + // blackboard_artifact_types + Statement inputStatement = sqliteConnection.createStatement(); + ResultSet inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifact_types"); //NON-NLS + Statement outputStatement; + Statement numberingPK; + long biggestPK = 0; + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + Statement check = postgresqlConnection.createStatement(); + ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_artifact_types WHERE artifact_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS + if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist + String sql = "INSERT INTO blackboard_artifact_types (artifact_type_id, type_name, display_name) VALUES (" + + value + ", '" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "'," + + " ? )"; //NON-NLS + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 3, 1); + pst.executeUpdate(); + } + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE blackboard_artifact_types_artifact_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // blackboard_attribute_types + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_attribute_types"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + Statement check = postgresqlConnection.createStatement(); + ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_attribute_types WHERE artifact_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS + if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist + String sql = "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name) VALUES (" + + value + ", '" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "'," + + " ? )"; //NON-NLS + + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 3, 1); + pst.executeUpdate(); + } + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE blackboard_attribute_types_attribute_type_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_objects + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_objects"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO tsk_objects (obj_id, par_obj_id, type) VALUES (" + + value + "," + + getNullableLong(inputResultSet, 2) + "," + + inputResultSet.getInt(3) + ")"); //NON-NLS + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_objects_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_image_names, no primary key + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_names"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + outputStatement.executeUpdate("INSERT INTO tsk_image_names (obj_id, name, sequence) VALUES (" + + inputResultSet.getLong(1) + ",'" + + inputResultSet.getString(2) + "'," + + inputResultSet.getInt(3) + ")"); //NON-NLS + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + + // tsk_image_info + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_image_info"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_image_info (obj_id, type, ssize, tzone, size, md5, display_name) VALUES (" + + value + "," + + getNullableInt(inputResultSet, 2) + "," + + getNullableInt(inputResultSet, 3) + "," + + " ? ," + + getNullableLong(inputResultSet, 5) + "," + + " ? ," + + " ? )"; //NON-NLS + + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 4, 1); + populateNullableString(pst, inputResultSet, 6, 2); + populateNullableString(pst, inputResultSet, 7, 3); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_image_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_fs_info + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_fs_info"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_fs_info (obj_id, img_offset, fs_type, block_size, block_count, root_inum, first_inum, last_inum, display_name) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getInt(3) + "," + + inputResultSet.getLong(4) + "," + + inputResultSet.getLong(5) + "," + + inputResultSet.getLong(6) + "," + + inputResultSet.getLong(7) + "," + + inputResultSet.getLong(8) + "," + + " ? )"; //NON-NLS + + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 9, 1); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_fs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_files_path + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_path"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO tsk_files_path (obj_id, path) VALUES (" + + value + ", '" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "')"); //NON-NLS + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_files_path_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_files + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_files (obj_id, fs_obj_id, attr_type, attr_id, name, meta_addr, meta_seq, type, has_layout, has_path, dir_type, meta_type, dir_flags, meta_flags, size, ctime, crtime, atime, mtime, mode, uid, gid, md5, known, parent_path) VALUES (" + + value + "," + + getNullableLong(inputResultSet, 2) + "," + + getNullableInt(inputResultSet, 3) + "," + + getNullableInt(inputResultSet, 4) + ",'" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(5)) + "'," + + getNullableLong(inputResultSet, 6) + "," + + getNullableLong(inputResultSet, 7) + "," + + getNullableInt(inputResultSet, 8) + "," + + getNullableInt(inputResultSet, 9) + "," + + getNullableInt(inputResultSet, 10) + "," + + getNullableInt(inputResultSet, 11) + "," + + getNullableInt(inputResultSet, 12) + "," + + getNullableInt(inputResultSet, 13) + "," + + getNullableInt(inputResultSet, 14) + "," + + getNullableLong(inputResultSet, 15) + "," + + getNullableLong(inputResultSet, 16) + "," + + getNullableLong(inputResultSet, 17) + "," + + getNullableLong(inputResultSet, 18) + "," + + getNullableLong(inputResultSet, 19) + "," + + getNullableInt(inputResultSet, 20) + "," + + getNullableInt(inputResultSet, 21) + "," + + getNullableInt(inputResultSet, 22) + "," + + " ? ," + + getNullableInt(inputResultSet, 24) + "," + + " ? )"; //NON-NLS + + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 23, 1); + populateNullableString(pst, inputResultSet, 25, 2); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_files_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_file_layout, no primary key + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_file_layout"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + outputStatement.executeUpdate("INSERT INTO tsk_file_layout (obj_id, byte_start, byte_len, sequence) VALUES (" + + inputResultSet.getLong(1) + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getLong(3) + "," + + inputResultSet.getInt(4) + ")"); //NON-NLS + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + + // tsk_db_info, no primary key + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_db_info"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + Statement check = postgresqlConnection.createStatement(); + ResultSet checkResult = check.executeQuery("SELECT * FROM tsk_db_info WHERE schema_ver=" + inputResultSet.getInt(1) + " AND tsk_ver=" + inputResultSet.getInt(2)); //NON-NLS + if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist + outputStatement.executeUpdate("INSERT INTO tsk_db_info (schema_ver, tsk_ver) VALUES (" + + getNullableInt(inputResultSet, 1) + "," + + getNullableInt(inputResultSet, 2) + ")"); //NON-NLS + } + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + + // tag_names + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tag_names"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tag_names (tag_name_id, display_name, description, color) VALUES (" + + value + "," + + " ? ,'" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(3)) + "','" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(4)) + "')"; //NON-NLS + + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 2, 1); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tag_names_tag_name_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // reports + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM reports"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO reports (report_id, path, crtime, src_module_name, report_name) VALUES (" + + value + ", '" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(2)) + "'," + + inputResultSet.getInt(3) + ",'" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(4)) + "','" + + SleuthkitCase.escapeSingleQuotes(inputResultSet.getString(5)) + "')"); //NON-NLS + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE reports_report_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // blackboard_artifacts + biggestPK = Long.MIN_VALUE; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifacts"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO blackboard_artifacts (artifact_id, obj_id, artifact_type_id) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getLong(3) + ")"); //NON-NLS + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + String seq; + if (biggestPK < 0) { + seq = Long.toString(biggestPK - 1); + } else { + seq = Long.toString(biggestPK + 1); + } + numberingPK.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq RESTART WITH " + seq); //NON-NLS + + // blackboard_attributes, no primary key + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_attributes"); //NON-NLS + + while (inputResultSet.next()) { + try { + String sql = "INSERT INTO blackboard_attributes (artifact_id, artifact_type_id, source, context, attribute_type_id, value_type, value_byte, value_text, value_int32, value_int64, value_double) VALUES (" + + inputResultSet.getLong(1) + "," + + inputResultSet.getLong(2) + "," + + " ? ," + + " ? ," + + inputResultSet.getLong(5) + "," + + inputResultSet.getInt(6) + "," + + " ? ," + + " ? ," + + getNullableInt(inputResultSet, 9) + "," + + getNullableLong(inputResultSet, 10) + "," + + " ? )"; //NON-NLS + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 3, 1); + populateNullableString(pst, inputResultSet, 4, 2); + populateNullableByteArray(pst, inputResultSet, 7, 3); + populateNullableString(pst, inputResultSet, 8, 4); + populateNullableNumeric(pst, inputResultSet, 11, 5); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + + // tsk_vs_parts + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_vs_parts"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_vs_parts (obj_id, addr, start, length, descr, flags) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getLong(3) + "," + + inputResultSet.getLong(4) + "," + + " ? ," + + inputResultSet.getInt(6) + ")"; //NON-NLS + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 5, 1); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_vs_parts_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_vs_info + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_vs_info"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO tsk_vs_info (obj_id, vs_type, img_offset, block_size) VALUES (" + + value + "," + + inputResultSet.getInt(2) + "," + + inputResultSet.getLong(3) + "," + + inputResultSet.getLong(4) + ")"); //NON-NLS + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_vs_info_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_files_derived + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_derived"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_files_derived (obj_id, derived_id, rederive) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + " ? )"; //NON-NLS + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 3, 1); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_files_derived_obj_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // tsk_files_derived_method + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM tsk_files_derived_method"); //NON-NLS + + while (inputResultSet.next()) { + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + String sql = "INSERT INTO tsk_files_derived_method (derived_id, tool_name, tool_version, other) VALUES (" + + value + ", '" + + inputResultSet.getString(2) + "','" + + inputResultSet.getString(3) + "'," + + " ? )"; //NON-NLS + PreparedStatement pst = postgresqlConnection.prepareStatement(sql); + populateNullableString(pst, inputResultSet, 4, 1); + pst.executeUpdate(); + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE tsk_files_derived_method_derived_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // content_tags + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM content_tags"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO content_tags (tag_id, obj_id, tag_name_id, comment, begin_byte_offset, end_byte_offset) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getLong(3) + ",'" + + inputResultSet.getString(4) + "'," + + inputResultSet.getLong(5) + "," + + inputResultSet.getLong(6) + ")"); //NON-NLS + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE content_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + // blackboard_artifact_tags + biggestPK = 0; + inputStatement = sqliteConnection.createStatement(); + inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifact_tags"); //NON-NLS + + while (inputResultSet.next()) { + outputStatement = postgresqlConnection.createStatement(); + try { + long value = inputResultSet.getLong(1); + if (value > biggestPK) { + biggestPK = value; + } + outputStatement.executeUpdate("INSERT INTO blackboard_artifact_tags (tag_id, artifact_id, tag_name_id, comment) VALUES (" + + value + "," + + inputResultSet.getLong(2) + "," + + inputResultSet.getLong(3) + ",'" + + inputResultSet.getString(4) + "')"); //NON-NLS + + } catch (SQLException ex) { + if (ex.getErrorCode() != 0) { // 0 if the entry already exists + throw new SQLException(ex); + } + } + } + numberingPK = postgresqlConnection.createStatement(); + numberingPK.execute("ALTER SEQUENCE blackboard_artifact_tags_tag_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS + + postgresqlConnection.close(); + + return dbName; + } + + /** + * Checks that our database name is unique. If it is not, attempts to add + * numbers to it until it is unique. Gives up if it goes through all + * positive integers without finding a unique name. + * + * @param db Database credentials + * @param baseDbName proposed name of the database to check for collisions + * @return name to use for the new database. Could be the name passed in. + * @throws ClassNotFoundException + * @throws SQLException + * @throws Exception + */ + private String deconflictDatabaseName(CaseDbConnectionInfo db, String baseDbName) throws ClassNotFoundException, SQLException, Exception { + + Class.forName("org.postgresql.Driver"); //NON-NLS + Connection dbNameConnection = DriverManager.getConnection("jdbc:postgresql://" + db.getHost() + ":" + db.getPort() + "/postgres", db.getUserName(), db.getPassword()); //NON-NLS + + int number = 1; + boolean unique = false; + String sanitizedDbName = baseDbName; + if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) { + sanitizedDbName = sanitizedDbName.substring(0, MAX_DB_NAME_LENGTH); + } + + if (dbNameConnection != null) { + while (unique == false) { + Statement st = dbNameConnection.createStatement(); + ResultSet answer = st.executeQuery("SELECT datname FROM pg_catalog.pg_database WHERE LOWER(datname) LIKE LOWER('" + sanitizedDbName + "%')"); //NON-NLS + + if (!answer.next()) { + unique = true; + } else { + // not unique. add numbers before dbName. + if (number == Integer.MAX_VALUE) { + // oops. it never became unique. give up. + throw new Exception(NbBundle.getMessage(CaseConverter.class, "CaseConverter.NonUniqueDatabaseName")); + } + sanitizedDbName = "_" + Integer.toString(number) + "_" + baseDbName; //NON-NLS + + // Chop full db name to 63 characters (max for PostgreSQL) + if (sanitizedDbName.length() > MAX_DB_NAME_LENGTH) { + sanitizedDbName = sanitizedDbName.substring(0, MAX_DB_NAME_LENGTH); + } + ++number; + } + } + dbNameConnection.close(); + } else { + // Could be caused by database credentials, using user accounts that + // can not check if other databases exist, so allow it to continue + log(NbBundle.getMessage(CaseConverter.class, "CaseConverter.PotentiallyNonUniqueDatabaseName")); + } + + return sanitizedDbName; + } + + /** + * Handle any Solr-specific plumbing to get it up and going on the new case. + * + * @param solrName + * @return The deconflicted name of the Solr core that should be stored in + * the .aut file + */ + private String fixUpSolr(String solrName) { + // KDM + /// What does Solr need to find the new core? + return solrName; + } + + /** + * Get the images from the other case and place them in the central + * repository + * + */ + private File copyInputImages(String rawInputFolder, String oldCaseName, File output) throws IOException { + /// If we can find the input images, copy if needed, then update data source paths in tsk_image_names if needed. + + File chosenInput = null; + + 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 { + log(NbBundle.getMessage(CaseConverter.class, "CaseConverter.UnableToCopySourceImages")); + } + return chosenInput; + } + + /** + * Fix up any paths in the database that refer to items that have moved. + * Candidates include events.db, input images, reports, file paths, etc. + */ + private void fixPaths(String input, String output, String dbName) throws SQLException { + /// 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 + String hostName = Case.getLocalHostName(); + + // add hostname to reports + Statement updateStatement = postgresqlConnection.createStatement(); + updateStatement.executeUpdate("UPDATE reports SET path=CONCAT('" + hostName + "/', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS + + // add hostname to tsk_files_path + updateStatement = postgresqlConnection.createStatement(); + updateStatement.executeUpdate("UPDATE tsk_files_path SET path=CONCAT('" + hostName + "\\', path) WHERE path IS NOT NULL AND path != ''"); //NON-NLS + + // update path for images + updateStatement = postgresqlConnection.createStatement(); + updateStatement.executeUpdate("UPDATE tsk_image_names SET name=OVERLAY(name PLACING '" + output + "' FROM 1 FOR " + input.length() + ")"); //NON-NLS + } + + /** + * Return an integer from the ResultSet converted to String or NULL, by + * checking ResultSet.wasNull() + * + * @param rs the ResultSet to work with + * @param index the index into the ResultSet to work with + * @return the proper value, the integer, or NULL + * @throws SQLException + */ + private String getNullableInt(ResultSet rs, int index) throws SQLException { + int value = rs.getInt(index); + if (rs.wasNull()) { + return "NULL"; //NON-NLS + } else { + return Integer.toString(value); + } + } + + /** + * Return a long from the ResultSet converted to String or NULL, by checking + * ResultSet.wasNull() + * + * @param rs the ResultSet to work with + * @param index the index into the ResultSet to work with + * @return the proper value, the long, or NULL + * @throws SQLException + */ + private String getNullableLong(ResultSet rs, int index) throws SQLException { + long value = rs.getLong(index); + if (rs.wasNull()) { + return "NULL"; //NON-NLS + } else { + return Long.toString(value); + } + } + + /** + * Place a NULL inside a prepared statement if needed, otherwise, place the + * String that was in the ResultSet. + * + * @param pst the prepared statement + * @param rs the ResultSet to work with + * @param rsIndex index for the result set + * @param psIndex index for the prepared statement + * @throws SQLException + */ + private void populateNullableString(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + String nullableString = rs.getString(rsIndex); + if (rs.wasNull()) { + pst.setNull(psIndex, java.sql.Types.NULL); + } else { + pst.setString(psIndex, SleuthkitCase.escapeSingleQuotes(nullableString)); + } + } + + /** + * Place a NULL inside a prepared statement if needed, otherwise, place the + * byte array that was in the ResultSet. + * + * @param pst the prepared statement + * @param rs the ResultSet to work with + * @param rsIndex index for the result set + * @param psIndex index for the prepared statement + * @throws SQLException + */ + private void populateNullableByteArray(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + byte[] nullableBytes = rs.getBytes(rsIndex); + if (rs.wasNull()) { + pst.setNull(psIndex, java.sql.Types.NULL); + } else { + pst.setBytes(psIndex, nullableBytes); + } + } + + /** + * Place a NULL inside a prepared statement if needed, otherwise, place the + * double that was in the ResultSet. + * + * @param pst the prepared statement + * @param rs the ResultSet to work with + * @param rsIndex index for the result set + * @param psIndex index for the prepared statement + * @throws SQLException + */ + private void populateNullableNumeric(PreparedStatement pst, ResultSet rs, int rsIndex, int psIndex) throws SQLException { + double nullableNumeric = rs.getDouble(rsIndex); + if (rs.wasNull()) { + pst.setNull(psIndex, java.sql.Types.NULL); + } else { + pst.setDouble(psIndex, nullableNumeric); + } + } + + /** + * This is the runnable's run method. It causes the iteration on all .aut + * files in the path, calling processCase for each one. + */ + @Override + public void run() { + openLog(); + boolean result = true; + + // iterate for .aut files + Path startingPoint = Paths.get(caseInputFolder); + FindDotAutFolders dotAutFolders = new FindDotAutFolders(); + try { + Path walked = Files.walkFileTree(startingPoint, dotAutFolders); + } catch (IOException ex) { + log(ex.getMessage()); + result = false; + } + + // feed .aut files in one by one + for (Path p : dotAutFolders.getTheList()) { + if (false == processCase(p, p.getFileName().toString())) { + result = false; + } + } + + closeLog(result); + if (notifyOnComplete != null) { + notifyOnComplete.conversionDoneCallback(result); + } + } + + /** + * Open the case conversion log in the base output folder. + * + */ + private void openLog() { + File temp = new File(caseOutputFolder); + temp.mkdirs(); + File logFile = Paths.get(caseOutputFolder, CASE_CONVERSION_LOG_FILE).toFile(); + try { + writer = new PrintWriter(new BufferedWriter(new FileWriter(logFile, logFile.exists())), true); + } catch (IOException ex) { + writer = null; + Exceptions.printStackTrace(ex); + } + } + + /** + * Log a message to the case conversion log in the base output folder. + * + * @param message the message to log. + */ + private void log(String message) { + if (writer != null) { + writer.println(String.format("%s %s", simpleDateFormat.format((Date.from(Instant.now()).getTime())), message)); //NON-NLS + } + } + + /** + * + * Close the case conversion log in the base output folder. + * + * @param result this informs the log if the end result was successful or + * not. True if all was successful, false otherwise. + */ + private void closeLog(boolean result) { + log(NbBundle.getMessage(CaseConverter.class, "CaseConverter.FinishedConverting") + + caseInputFolder + + NbBundle.getMessage(CaseConverter.class, "CaseConverter.To") + + caseOutputFolder + + NbBundle.getMessage(CaseConverter.class, "CaseConverter.ConversionSuccessful") + + result); + + if (writer != null) { + writer.close(); + + } + } + + /** + * This class extends SimpleFileVisitor to find all the cases to process + * based upon .aut files. + */ + private class FindDotAutFolders extends SimpleFileVisitor { + + private final ArrayList theList; + + public FindDotAutFolders() { + this.theList = new ArrayList<>(); + } + + /** + * Handle comparing .aut file and containing folder names without + * timestamps on either one. It strips them off if they exist. + * + * @param directory the directory we are currently visiting. + * @param attrs file attributes. + * + * @return Continue if we want to carry one, SKIP_SUBTREE if we've found + * a .aut file, precluding searching any deeper into this folder. + * @throws IOException + */ + @Override + public FileVisitResult preVisitDirectory(Path directory, BasicFileAttributes attrs) throws IOException { + // find all files that end in .aut + File[] dotAutFiles = directory.toFile().listFiles((File dir, String name) -> name.toLowerCase().endsWith(DOTAUT)); + + for (File specificFile : dotAutFiles) { + // if it ends in a timestamp, strip it off + String sanitizedCaseName = specificFile.getName(); + if (TimeStampUtils.endsWithTimeStamp(sanitizedCaseName)) { + sanitizedCaseName = sanitizedCaseName.substring(0, sanitizedCaseName.length() - TimeStampUtils.getTimeStampLength()); + } + + // if folder ends in a timestamp, strip it off + String sanitizedFolderName = directory.getFileName().toString(); + if (TimeStampUtils.endsWithTimeStamp(sanitizedFolderName)) { + sanitizedFolderName = sanitizedFolderName.substring(0, sanitizedFolderName.length() - TimeStampUtils.getTimeStampLength()); + } + + // If file and folder match, found leaf node case + if (sanitizedCaseName.toLowerCase().startsWith(sanitizedFolderName.toLowerCase())) { + theList.add(directory); + return FileVisitResult.SKIP_SUBTREE; + } + } + + // If no matching .aut files, traverse subfolders + return FileVisitResult.CONTINUE; + } + + /** + * This returns the list of folders we've found that need to be looked + * at for possible conversion to multi-user cases. + * + * @return the theList + */ + public ArrayList getTheList() { + return theList; + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/ConversionDoneCallback.java b/Core/src/org/sleuthkit/autopsy/casemodule/ConversionDoneCallback.java new file mode 100644 index 0000000000..f782f8104b --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/ConversionDoneCallback.java @@ -0,0 +1,5 @@ +package org.sleuthkit.autopsy.casemodule; + +public interface ConversionDoneCallback { + void conversionDoneCallback(boolean result); +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/TimeStampUtils.java b/Core/src/org/sleuthkit/autopsy/casemodule/TimeStampUtils.java new file mode 100644 index 0000000000..c7337fa2f0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/casemodule/TimeStampUtils.java @@ -0,0 +1,53 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 Basis Technology Corp. + * Contact: carrier sleuthkit 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.autopsy.casemodule; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class offers utility functions to identify and process time stamped folders. + */ +public final class TimeStampUtils { + + // Pattern to identify whether case name contains a time stamp generated by Auto Ingest. + // Sample case name created by auto-ingest: Case 1_2015_02_02_12_10_31 for case "Case 1" + private static final Pattern timeStampPattern = Pattern.compile("\\d{4}_\\d{2}_\\d{2}_\\d{2}_\\d{2}_\\d{2}$"); + private static final int LENGTH_OF_DATE_TIME_STAMP = 20; // length of the above time stamp + + /** + * Checks whether a string ends with a time stamp defined by pattern. + * + * @param inputString Input string + * @return true if string ends with a time stamp, false otherwise. + */ + public static boolean endsWithTimeStamp(String inputString) { + Matcher m = timeStampPattern.matcher(inputString); + return m.find(); + } + + /** + * Returns length of time stamp string. + * + * @return length of time stamp string. + */ + public static int getTimeStampLength() { + return LENGTH_OF_DATE_TIME_STAMP; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java b/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java index 530ce128a1..eec16fd912 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/XMLCaseManagement.java @@ -45,7 +45,7 @@ import org.xml.sax.SAXException; * * @author jantonius */ - class XMLCaseManagement implements CaseConfigFileInterface { +public class XMLCaseManagement implements CaseConfigFileInterface { final static String XSDFILE = "CaseSchema.xsd"; //NON-NLS final static String TOP_ROOT_NAME = "AutopsyCase"; //NON-NLS @@ -98,7 +98,7 @@ import org.xml.sax.SAXException; /** * The constructor */ - XMLCaseManagement() { + public XMLCaseManagement() { autopsySavedVersion = System.getProperty("netbeans.buildnumber"); } @@ -153,6 +153,22 @@ import org.xml.sax.SAXException; } + /** + * Sets the created date on the XML configuration file. This method is for + * preserving the created date when converting a case from single-user to + * multi-user. + * + * @param createdDate the date the case was originally created + * @throws org.sleuthkit.autopsy.casemodule.CaseActionException + */ + public void setCreatedDate(String createdDate) throws CaseActionException { + String newDate = dateFormat.format(new Date()); + Element rootEl = getRootElement(); + rootEl.getElementsByTagName(CREATED_DATE_NAME).item(0).setTextContent(createdDate); + rootEl.getElementsByTagName(MODIFIED_DATE_NAME).item(0).setTextContent(newDate); + writeFile(); + } + /** * Sets the examiner on the XML configuration file * @@ -369,7 +385,7 @@ import org.xml.sax.SAXException; * * @return createdDate the creation date of this case */ - protected String getCreatedDate() { + public String getCreatedDate() { if (doc != null) { Element crDateElement = (Element) getRootElement().getElementsByTagName(CREATED_DATE_NAME).item(0); return crDateElement.getTextContent(); @@ -533,7 +549,7 @@ import org.xml.sax.SAXException; * a Postgre db name. * @param textIndexName The name of the index where extracted text is stored. */ - protected void create(String dirPath, String caseName, String examiner, String caseNumber, CaseType caseType, String dbName, String textIndexName) throws CaseActionException { + public void create(String dirPath, String caseName, String examiner, String caseNumber, CaseType caseType, String dbName, String textIndexName) throws CaseActionException { clear(); // clear the previous data // set the case Name and Directory and the parent directory diff --git a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java index 702b91f7a5..cabe0700bc 100755 --- a/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java +++ b/Core/src/org/sleuthkit/autopsy/core/UserPreferences.java @@ -109,7 +109,7 @@ public final class UserPreferences { public static CaseDbConnectionInfo getDatabaseConnectionInfo() { DbType dbType; try { - dbType = DbType.valueOf(preferences.get(EXTERNAL_DATABASE_TYPE, "UNKOWN")); + dbType = DbType.valueOf(preferences.get(EXTERNAL_DATABASE_TYPE, "SQLITE")); } catch (Exception ex) { dbType = DbType.SQLITE; } diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index be9923ea0e..539b2354f9 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -150,7 +150,6 @@ FXVideoPanel.progress.errorWritingVideoToDisk=Error writing video to disk OptionsCategory_Name_Multi_User_Settings=Multi-user OptionsCategory_Keywords_Multi_User_Options=Multi-user Options MultiUserSettingsPanel.lbSolrSettings.text=Solr Settings -MultiUserSettingsPanel.lbOops.text= MultiUserSettingsPanel.tbPassword.toolTipText=Password MultiUserSettingsPanel.tbPassword.text= MultiUserSettingsPanel.tbUsername.toolTipText=User Name @@ -185,3 +184,4 @@ DataContentViewerHex.goToOffsetLabel.text=Jump to Offset DataContentViewerHex.goToOffsetTextField.text= DataContentViewerHex.goToOffsetTextField.msgDlg=Invalid Offset: {0} DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue=Cannot jump to the resultant offset +MultiUserSettingsPanel.tbOops.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form index 32bee7c818..ade40277b8 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.form @@ -36,8 +36,8 @@ - - + + @@ -50,18 +50,17 @@ - - - - + + + - + - + @@ -126,9 +125,6 @@ - - - @@ -242,9 +238,6 @@ - - - @@ -255,26 +248,9 @@ - - - - - - - - - - - - - - - - - @@ -396,6 +372,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java index 930b4a6b99..cf433b99b1 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/MultiUserSettingsPanel.java @@ -119,7 +119,6 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { lbSolrSettings = new javax.swing.JLabel(); tbIndexingServerHost = new javax.swing.JTextField(); tbIndexingServerPort = new javax.swing.JTextField(); - lbOops = new javax.swing.JLabel(); pnMessagingSettings = new javax.swing.JPanel(); lbMessagingSettings = new javax.swing.JLabel(); msgHostTextField = new javax.swing.JTextField(); @@ -127,17 +126,13 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { msgPortTextField = new javax.swing.JTextField(); msgPasswordField = new javax.swing.JPasswordField(); cbEnableMultiUser = new javax.swing.JCheckBox(); + tbOops = new javax.swing.JTextField(); pnDatabaseSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); tbHostnameOrIp.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N tbHostnameOrIp.setText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbHostnameOrIp.text")); // NOI18N tbHostnameOrIp.setToolTipText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbHostnameOrIp.toolTipText")); // NOI18N - tbHostnameOrIp.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - tbHostnameOrIpActionPerformed(evt); - } - }); tbPortNumber.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N tbPortNumber.setText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbPortNumber.text")); // NOI18N @@ -194,19 +189,9 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { tbIndexingServerHost.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N tbIndexingServerHost.setToolTipText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbIndexingServerHost.toolTipText")); // NOI18N - tbIndexingServerHost.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - tbIndexingServerHostActionPerformed(evt); - } - }); tbIndexingServerPort.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N tbIndexingServerPort.setToolTipText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbIndexingServerPort.toolTipText")); // NOI18N - tbIndexingServerPort.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - tbIndexingServerPortActionPerformed(evt); - } - }); javax.swing.GroupLayout pnSolrSettingsLayout = new javax.swing.GroupLayout(pnSolrSettings); pnSolrSettings.setLayout(pnSolrSettingsLayout); @@ -234,11 +219,6 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { .addGap(45, 45, 45)) ); - lbOops.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N - lbOops.setForeground(new java.awt.Color(255, 0, 0)); - org.openide.awt.Mnemonics.setLocalizedText(lbOops, org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.lbOops.text")); // NOI18N - lbOops.setVerticalAlignment(javax.swing.SwingConstants.BOTTOM); - pnMessagingSettings.setBorder(javax.swing.BorderFactory.createEtchedBorder()); lbMessagingSettings.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N @@ -299,6 +279,13 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { } }); + tbOops.setEditable(false); + tbOops.setBackground(new java.awt.Color(240, 240, 240)); + tbOops.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + tbOops.setForeground(new java.awt.Color(255, 0, 0)); + tbOops.setText(org.openide.util.NbBundle.getMessage(MultiUserSettingsPanel.class, "MultiUserSettingsPanel.tbOops.text")); // NOI18N + tbOops.setBorder(null); + javax.swing.GroupLayout pnOverallPanelLayout = new javax.swing.GroupLayout(pnOverallPanel); pnOverallPanel.setLayout(pnOverallPanelLayout); pnOverallPanelLayout.setHorizontalGroup( @@ -308,8 +295,8 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { .addGroup(pnOverallPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(pnOverallPanelLayout.createSequentialGroup() .addComponent(cbEnableMultiUser) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(lbOops, javax.swing.GroupLayout.PREFERRED_SIZE, 314, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(tbOops)) .addComponent(pnSolrSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pnDatabaseSettings, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(pnMessagingSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) @@ -318,17 +305,16 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { pnOverallPanelLayout.setVerticalGroup( pnOverallPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnOverallPanelLayout.createSequentialGroup() - .addContainerGap() - .addGroup(pnOverallPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(cbEnableMultiUser, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(lbOops, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) - .addGap(16, 16, 16) + .addGroup(pnOverallPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.CENTER) + .addComponent(tbOops, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(cbEnableMultiUser)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(pnDatabaseSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(pnSolrSettings, javax.swing.GroupLayout.PREFERRED_SIZE, 106, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(pnMessagingSettings, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(35, 35, 35)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); @@ -356,24 +342,12 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private void cbEnableMultiUserItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cbEnableMultiUserItemStateChanged if (!cbEnableMultiUser.isSelected()) { - lbOops.setText(""); + tbOops.setText(""); } enableMultiUserComponents(textBoxes, cbEnableMultiUser.isSelected()); controller.changed(); }//GEN-LAST:event_cbEnableMultiUserItemStateChanged - private void tbHostnameOrIpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbHostnameOrIpActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_tbHostnameOrIpActionPerformed - - private void tbIndexingServerHostActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbIndexingServerHostActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_tbIndexingServerHostActionPerformed - - private void tbIndexingServerPortActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tbIndexingServerPortActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_tbIndexingServerPortActionPerformed - void load() { CaseDbConnectionInfo dbInfo = UserPreferences.getDatabaseConnectionInfo(); tbHostnameOrIp.setText(dbInfo.getHost().trim()); @@ -451,7 +425,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { * @return true if it's okay, false otherwise. */ boolean valid() { - lbOops.setText(""); + tbOops.setText(""); if (cbEnableMultiUser.isSelected()) { return settingsAreComplete() && databaseSettingsAreValid() && indexingServerSettingsAreValid() && messageServiceSettingsAreValid(); } else { @@ -473,7 +447,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { || !messageServiceFieldsArePopulated()) { // We don't even have everything filled out result = false; - lbOops.setText(INCOMPLETE_SETTINGS_MSG); + tbOops.setText(INCOMPLETE_SETTINGS_MSG); } return result; } @@ -487,7 +461,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { if (portNumberIsValid(tbPortNumber.getText())) { return true; } else { - lbOops.setText(INVALID_DB_PORT_MSG); + tbOops.setText(INVALID_DB_PORT_MSG); return false; } } @@ -499,7 +473,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { */ boolean messageServiceSettingsAreValid() { if (!portNumberIsValid(msgPortTextField.getText())) { - lbOops.setText(INVALID_MESSAGE_SERVICE_PORT_MSG); + tbOops.setText(INVALID_MESSAGE_SERVICE_PORT_MSG); return false; } @@ -513,7 +487,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { */ boolean indexingServerSettingsAreValid() { if (!portNumberIsValid(tbIndexingServerPort.getText())) { - lbOops.setText(INVALID_INDEXING_SERVER_PORT_MSG); + tbOops.setText(INVALID_INDEXING_SERVER_PORT_MSG); return false; } @@ -543,7 +517,6 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private javax.swing.JCheckBox cbEnableMultiUser; private javax.swing.JLabel lbDatabaseSettings; private javax.swing.JLabel lbMessagingSettings; - private javax.swing.JLabel lbOops; private javax.swing.JLabel lbSolrSettings; private javax.swing.JTextField msgHostTextField; private javax.swing.JPasswordField msgPasswordField; @@ -556,6 +529,7 @@ public final class MultiUserSettingsPanel extends javax.swing.JPanel { private javax.swing.JTextField tbHostnameOrIp; private javax.swing.JTextField tbIndexingServerHost; private javax.swing.JTextField tbIndexingServerPort; + private javax.swing.JTextField tbOops; private javax.swing.JPasswordField tbPassword; private javax.swing.JTextField tbPortNumber; private javax.swing.JTextField tbUsername; From 5074684466fae8d2e5cc08eb29ea0ff351e698e7 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Fri, 17 Jul 2015 09:49:58 -0400 Subject: [PATCH 2/7] Prevent shutDown from being called on ingest pipelines that haven't started. --- .../autopsy/ingest/DataSourceIngestJob.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index 895aa63e06..aac5349381 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -386,9 +386,11 @@ final class DataSourceIngestJob { // errors are likely redundant. while (!this.fileIngestPipelinesQueue.isEmpty()) { pipeline = this.fileIngestPipelinesQueue.poll(); - List shutDownErrors = pipeline.shutDown(); - if (!shutDownErrors.isEmpty()) { - logIngestModuleErrors(shutDownErrors); + if(pipeline.isRunning()){ + List shutDownErrors = pipeline.shutDown(); + if (!shutDownErrors.isEmpty()) { + logIngestModuleErrors(shutDownErrors); + } } } break; @@ -565,7 +567,9 @@ final class DataSourceIngestJob { List errors = new ArrayList<>(); while (!this.fileIngestPipelinesQueue.isEmpty()) { FileIngestPipeline pipeline = fileIngestPipelinesQueue.poll(); - errors.addAll(pipeline.shutDown()); + if(pipeline.isRunning()){ + errors.addAll(pipeline.shutDown()); + } } if (!errors.isEmpty()) { logIngestModuleErrors(errors); From 07f66cbb62eda8203d288df97692bbb8c5f81b73 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Fri, 17 Jul 2015 10:13:32 -0400 Subject: [PATCH 3/7] artifact --> attribute --- .../sleuthkit/autopsy/casemodule/CaseConverter.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java index 3ce0dc31c0..1d5cb81c62 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java @@ -367,7 +367,7 @@ public class CaseConverter implements Runnable { biggestPK = value; } Statement check = postgresqlConnection.createStatement(); - ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_attribute_types WHERE artifact_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS + ResultSet checkResult = check.executeQuery("SELECT * FROM blackboard_attribute_types WHERE attribute_type_id=" + value + " AND type_name LIKE '" + inputResultSet.getString(2) + "' AND display_name LIKE '" + inputResultSet.getString(3) + "'"); //NON-NLS if (!checkResult.isBeforeFirst()) { // only insert if it doesn't exist String sql = "INSERT INTO blackboard_attribute_types (attribute_type_id, type_name, display_name) VALUES (" + value + ", '" @@ -676,7 +676,7 @@ public class CaseConverter implements Runnable { numberingPK.execute("ALTER SEQUENCE reports_report_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_artifacts - biggestPK = Long.MIN_VALUE; + biggestPK = Long.MIN_VALUE; // This table uses very large negative primary key values, so start at Long.MIN_VALUE inputStatement = sqliteConnection.createStatement(); inputResultSet = inputStatement.executeQuery("SELECT * FROM blackboard_artifacts"); //NON-NLS @@ -699,13 +699,7 @@ public class CaseConverter implements Runnable { } } numberingPK = postgresqlConnection.createStatement(); - String seq; - if (biggestPK < 0) { - seq = Long.toString(biggestPK - 1); - } else { - seq = Long.toString(biggestPK + 1); - } - numberingPK.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq RESTART WITH " + seq); //NON-NLS + numberingPK.execute("ALTER SEQUENCE blackboard_artifacts_artifact_id_seq RESTART WITH " + (biggestPK + 1)); //NON-NLS // blackboard_attributes, no primary key inputStatement = sqliteConnection.createStatement(); From 50c3aacb946dcac1788bcf72f22b02bf51a631f1 Mon Sep 17 00:00:00 2001 From: Karl Mortensen Date: Fri, 17 Jul 2015 10:35:29 -0400 Subject: [PATCH 4/7] remove solr comment --- .../autopsy/casemodule/CaseConverter.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java index 1d5cb81c62..9c27afe5ab 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseConverter.java @@ -146,7 +146,6 @@ public class CaseConverter implements Runnable { copyResults(input, newCaseFolder); // Copy items to new hostname folder structure dbName = convertDb(dbName, input, newCaseFolder); // Change from SQLite to PostgreSQL - solrName = fixUpSolr(solrName); // Hook Solr back up File imageSource = copyInputImages(imageInputFolder, oldCaseName, imageDestination); // Copy images over fixPaths(imageSource.toString(), imageDestination.toString(), dbName); // Update paths in DB @@ -966,19 +965,6 @@ public class CaseConverter implements Runnable { return sanitizedDbName; } - /** - * Handle any Solr-specific plumbing to get it up and going on the new case. - * - * @param solrName - * @return The deconflicted name of the Solr core that should be stored in - * the .aut file - */ - private String fixUpSolr(String solrName) { - // KDM - /// What does Solr need to find the new core? - return solrName; - } - /** * Get the images from the other case and place them in the central * repository From e0431dba77dc728e6c07fad22772b4983a2f6fd8 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 17 Jul 2015 15:09:11 -0400 Subject: [PATCH 5/7] Fix to have result count be updated whenever children are added or removed --- .../org/sleuthkit/autopsy/corecomponents/DataResultPanel.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 88d8706e71..3ca09b21b0 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -631,17 +631,16 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public void childrenAdded(final NodeMemberEvent nme) { Node[] delta = nme.getDelta(); + updateMatches(); if (load && containsReal(delta)) { load = false; if (SwingUtilities.isEventDispatchThread()) { setupTabs(nme.getNode()); - updateMatches(); } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { setupTabs(nme.getNode()); - updateMatches(); } }); } From 568e6de3b4b787bd22c4a400c43c8894f0245b21 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 17 Jul 2015 15:59:42 -0400 Subject: [PATCH 6/7] Renamed variables and added comments --- .../corecomponents/DataResultPanel.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 3ca09b21b0..346647f88e 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -65,7 +65,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C private DataContent customContentViewer; private boolean isMain; private String title; - private final DummyNodeListener dummyNodeListener = new DummyNodeListener(); + private final RootNodeListener dummyNodeListener = new RootNodeListener(); private static final Logger logger = Logger.getLogger(DataResultPanel.class.getName() ); private boolean listeningToTabbedPane = false; @@ -620,20 +620,26 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C } } - private class DummyNodeListener implements NodeListener { + private class RootNodeListener implements NodeListener { + + private volatile boolean waitingForData = true; - private volatile boolean load = true; - public void reset() { - load = true; + waitingForData = true; } - + @Override public void childrenAdded(final NodeMemberEvent nme) { Node[] delta = nme.getDelta(); updateMatches(); - if (load && containsReal(delta)) { - load = false; + + /* There is a known issue in this code whereby we will only + call setupTabs() once even though childrenAdded could be + called multiple times. That means that each panel may not + have access to all of the children when they decide if they + support the content */ + if (waitingForData && containsReal(delta)) { + waitingForData = false; if (SwingUtilities.isEventDispatchThread()) { setupTabs(nme.getNode()); } else { From 55c50d5f0d38030a47d59965fa517aa83a03dba1 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Fri, 17 Jul 2015 16:01:36 -0400 Subject: [PATCH 7/7] Renamed variable --- .../sleuthkit/autopsy/corecomponents/DataResultPanel.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java index 346647f88e..9ba152525c 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultPanel.java @@ -65,7 +65,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C private DataContent customContentViewer; private boolean isMain; private String title; - private final RootNodeListener dummyNodeListener = new RootNodeListener(); + private final RootNodeListener rootNodeListener = new RootNodeListener(); private static final Logger logger = Logger.getLogger(DataResultPanel.class.getName() ); private boolean listeningToTabbedPane = false; @@ -369,7 +369,7 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C @Override public void setNode(Node selectedNode) { if (this.rootNode != null) { - this.rootNode.removeNodeListener(dummyNodeListener); + this.rootNode.removeNodeListener(rootNodeListener); } // Deferring becoming a listener to the tabbed pane until this point // eliminates handling a superfluous stateChanged event during construction. @@ -380,8 +380,8 @@ public class DataResultPanel extends javax.swing.JPanel implements DataResult, C this.rootNode = selectedNode; if (this.rootNode != null) { - dummyNodeListener.reset(); - this.rootNode.addNodeListener(dummyNodeListener); + rootNodeListener.reset(); + this.rootNode.addNodeListener(rootNodeListener); } resetTabs(selectedNode);