/* * Autopsy Forensic Browser * * Copyright 2011 - 2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.modules.hashdatabase; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import javax.swing.JOptionPane; import org.apache.commons.io.FileUtils; import org.openide.util.NbBundle; import org.openide.util.io.NbObjectInputStream; import org.openide.util.io.NbObjectOutputStream; import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.XMLUtil; import org.sleuthkit.datamodel.TskCoreException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * Class to represent the settings to be serialized for hash lookup. */ final class HashLookupSettings implements Serializable { private static final String SERIALIZATION_FILE_NAME = "hashLookup.settings"; //NON-NLS private static final String SERIALIZATION_FILE_PATH = PlatformUtil.getUserConfigDirectory() + File.separator + SERIALIZATION_FILE_NAME; //NON-NLS private static final String SET_ELEMENT = "hash_set"; //NON-NLS private static final String SET_NAME_ATTRIBUTE = "name"; //NON-NLS private static final String SET_TYPE_ATTRIBUTE = "type"; //NON-NLS private static final String SEARCH_DURING_INGEST_ATTRIBUTE = "use_for_ingest"; //NON-NLS private static final String SEND_INGEST_MESSAGES_ATTRIBUTE = "show_inbox_messages"; //NON-NLS private static final String PATH_ELEMENT = "hash_set_path"; //NON-NLS private static final String LEGACY_PATH_NUMBER_ATTRIBUTE = "number"; //NON-NLS private static final String CONFIG_FILE_NAME = "hashsets.xml"; //NON-NLS private static final String configFilePath = PlatformUtil.getUserConfigDirectory() + File.separator + CONFIG_FILE_NAME; private static final Logger logger = Logger.getLogger(HashDbManager.class.getName()); private static final long serialVersionUID = 1L; private final List<HashDbInfo> hashDbInfoList; /** * Constructs a settings object to be serialized for hash lookups * * @param hashDbInfoList The list of hash db info. */ HashLookupSettings(List<HashDbInfo> hashDbInfoList) { this.hashDbInfoList = hashDbInfoList; } /** * Constructs a settings object to be serialized for hash lookups * * @param knownHashSets The list known hash sets for the settings. * @param knownBadHashSets The list of known bad hash sets for the settings. */ HashLookupSettings(List<HashDbManager.HashDb> knownHashSets, List<HashDbManager.HashDb> knownBadHashSets) throws HashLookupSettingsException { hashDbInfoList = new ArrayList<>(); this.addHashesToList(knownHashSets); this.addHashesToList(knownBadHashSets); } /** * Adds each HashDb to the settings. * * @param hashSetList The list of HashDb to add to the settings * * @throws * pacannot be obtained */ private void addHashesToList(List<HashDbManager.HashDb> hashSetList) throws HashLookupSettingsException { for (HashDbManager.HashDb hashDb : hashSetList) { try { String dbPath; if (hashDb.hasIndexOnly()) { dbPath = hashDb.getIndexPath(); } else { dbPath = hashDb.getDatabasePath(); } hashDbInfoList.add(new HashDbInfo(hashDb.getHashSetName(), hashDb.getKnownFilesType(), hashDb.getSearchDuringIngest(), hashDb.getSendIngestMessages(), dbPath)); } catch (TskCoreException ex) { throw new HashLookupSettingsException("Couldn't add hash database named: " + hashDb.getHashSetName(), ex); } } } /** * Gets the list of hash db info that this settings contains * * @return The list of hash databse info */ List<HashDbInfo> getHashDbInfo() { return hashDbInfoList; } /** * Reads the settings from the disk. * * @return The settings object representing what was read. * * @throws HashLookupSettingsException When there is a problem reading the * settings. */ static HashLookupSettings readSettings() throws HashLookupSettingsException { File fileSetFile = new File(SERIALIZATION_FILE_PATH); if (fileSetFile.exists()) { return readSerializedSettings(); } return readXmlSettings(); } /** * Reads the serialization settings from the disk * * @return Settings object representing what is saved in the serialization * file. * * @throws HashLookupSettingsException If there's a problem importing the * settings */ private static HashLookupSettings readSerializedSettings() throws HashLookupSettingsException { try { try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(SERIALIZATION_FILE_PATH))) { HashLookupSettings filesSetsSettings = (HashLookupSettings) in.readObject(); return filesSetsSettings; } } catch (IOException | ClassNotFoundException ex) { throw new HashLookupSettingsException("Could not read hash database settings.", ex); } } /** * Reads the xml settings from the disk * * @return Settings object representing what is saved in the xml file, or an * empty settings if there is no xml file. * * @throws HashLookupSettingsException If there's a problem importing the * settings */ private static HashLookupSettings readXmlSettings() throws HashLookupSettingsException { File xmlFile = new File(configFilePath); if (xmlFile.exists()) { boolean updatedSchema = false; // Open the XML document that implements the configuration file. final Document doc = XMLUtil.loadDoc(HashDbManager.class, configFilePath); if (doc == null) { throw new HashLookupSettingsException("Could not open xml document."); } // Get the root element. Element root = doc.getDocumentElement(); if (root == null) { throw new HashLookupSettingsException("Error loading hash sets: invalid file format."); } // Get the hash set elements. NodeList setsNList = root.getElementsByTagName(SET_ELEMENT); int numSets = setsNList.getLength(); // Create HashDbInfo objects for each hash set element. Throws on malformed xml. String attributeErrorMessage = "Missing %s attribute"; //NON-NLS String elementErrorMessage = "Empty %s element"; //NON-NLS List<String> hashSetNames = new ArrayList<>(); List<HashDbInfo> hashDbInfoList = new ArrayList<>(); for (int i = 0; i < numSets; ++i) { Element setEl = (Element) setsNList.item(i); String hashSetName = setEl.getAttribute(SET_NAME_ATTRIBUTE); if (hashSetName.isEmpty()) { throw new HashLookupSettingsException(String.format(attributeErrorMessage, SET_NAME_ATTRIBUTE)); } // Handle configurations saved before duplicate hash set names were not permitted. if (hashSetNames.contains(hashSetName)) { int suffix = 0; String newHashSetName; do { ++suffix; newHashSetName = hashSetName + suffix; } while (hashSetNames.contains(newHashSetName)); logger.log(Level.INFO, "Duplicate hash set name " + hashSetName + " found. Replacing with " + newHashSetName + "."); if (RuntimeProperties.coreComponentsAreActive()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.replacingDuplicateHashsetNameMsg", hashSetName, newHashSetName), NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.openHashDbErr"), JOptionPane.ERROR_MESSAGE); hashSetName = newHashSetName; } } String knownFilesType = setEl.getAttribute(SET_TYPE_ATTRIBUTE); if (knownFilesType.isEmpty()) { throw new HashLookupSettingsException(String.format(attributeErrorMessage, SET_TYPE_ATTRIBUTE)); } // Handle legacy known files types. if (knownFilesType.equals("NSRL")) { //NON-NLS knownFilesType = HashDbManager.HashDb.KnownFilesType.KNOWN.toString(); updatedSchema = true; } final String searchDuringIngest = setEl.getAttribute(SEARCH_DURING_INGEST_ATTRIBUTE); if (searchDuringIngest.isEmpty()) { throw new HashLookupSettingsException(String.format(attributeErrorMessage, SEND_INGEST_MESSAGES_ATTRIBUTE)); } Boolean searchDuringIngestFlag = Boolean.parseBoolean(searchDuringIngest); final String sendIngestMessages = setEl.getAttribute(SEND_INGEST_MESSAGES_ATTRIBUTE); if (searchDuringIngest.isEmpty()) { throw new HashLookupSettingsException(String.format(attributeErrorMessage, SEND_INGEST_MESSAGES_ATTRIBUTE)); } Boolean sendIngestMessagesFlag = Boolean.parseBoolean(sendIngestMessages); String dbPath; NodeList pathsNList = setEl.getElementsByTagName(PATH_ELEMENT); if (pathsNList.getLength() > 0) { Element pathEl = (Element) pathsNList.item(0); // Shouldn't be more than one. // Check for legacy path number attribute. String legacyPathNumber = pathEl.getAttribute(LEGACY_PATH_NUMBER_ATTRIBUTE); if (null != legacyPathNumber && !legacyPathNumber.isEmpty()) { updatedSchema = true; } dbPath = pathEl.getTextContent(); if (dbPath.isEmpty()) { throw new HashLookupSettingsException(String.format(elementErrorMessage, PATH_ELEMENT)); } } else { throw new HashLookupSettingsException(String.format(elementErrorMessage, PATH_ELEMENT)); } hashDbInfoList.add(new HashDbInfo(hashSetName, HashDbManager.HashDb.KnownFilesType.valueOf(knownFilesType), searchDuringIngestFlag, sendIngestMessagesFlag, dbPath)); hashSetNames.add(hashSetName); } if (updatedSchema) { String backupFilePath = configFilePath + ".v1_backup"; //NON-NLS String messageBoxTitle = NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.msgBoxTitle.confFileFmtChanged"); String baseMessage = NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.baseMessage.updatedFormatHashDbConfig"); try { FileUtils.copyFile(new File(configFilePath), new File(backupFilePath)); logger.log(Level.INFO, "Updated the schema, backup saved at: " + backupFilePath); if (RuntimeProperties.coreComponentsAreActive()) { JOptionPane.showMessageDialog(null, NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.savedBackupOfOldConfigMsg", baseMessage, backupFilePath), messageBoxTitle, JOptionPane.INFORMATION_MESSAGE); } } catch (IOException ex) { logger.log(Level.WARNING, "Failed to save backup of old format configuration file to " + backupFilePath, ex); //NON-NLS JOptionPane.showMessageDialog(null, baseMessage, messageBoxTitle, JOptionPane.INFORMATION_MESSAGE); } HashLookupSettings settings; settings = new HashLookupSettings(hashDbInfoList); HashLookupSettings.writeSettings(settings); } return new HashLookupSettings(hashDbInfoList); } else { return new HashLookupSettings(new ArrayList<>()); } } /** * Writes the given settings objects to the disk at the designated location * * @param settings The settings to be written * * @return Whether or not the settings were written successfully */ static boolean writeSettings(HashLookupSettings settings) { try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(SERIALIZATION_FILE_PATH))) { out.writeObject(settings); return true; } catch (Exception ex) { logger.log(Level.SEVERE, "Could not wtite hash database settings."); return false; } } /** * Represents the serializable information within a hash lookup in order to * be written to disk. Used to hand off information when loading and saving * hash lookups. */ static final class HashDbInfo implements Serializable { private static final long serialVersionUID = 1L; private final String hashSetName; private final HashDbManager.HashDb.KnownFilesType knownFilesType; private final boolean searchDuringIngest; private final boolean sendIngestMessages; private final String path; /** * Constructs a HashDbInfo object * * @param hashSetName The name of the hash set * @param knownFilesType The known files type * @param searchDuringIngest Whether or not the db is searched during * ingest * @param sendIngestMessages Whether or not ingest messages are sent * @param path The path to the db */ HashDbInfo(String hashSetName, HashDbManager.HashDb.KnownFilesType knownFilesType, boolean searchDuringIngest, boolean sendIngestMessages, String path) { this.hashSetName = hashSetName; this.knownFilesType = knownFilesType; this.searchDuringIngest = searchDuringIngest; this.sendIngestMessages = sendIngestMessages; this.path = path; } /** * Gets the hash set name. * * @return The hash set name. */ String getHashSetName() { return hashSetName; } /** * Gets the known files type setting. * * @return The known files type setting. */ HashDbManager.HashDb.KnownFilesType getKnownFilesType() { return knownFilesType; } /** * Gets the search during ingest setting. * * @return The search during ingest setting. */ boolean getSearchDuringIngest() { return searchDuringIngest; } /** * Gets the send ingest messages setting. * * @return The send ingest messages setting. */ boolean getSendIngestMessages() { return sendIngestMessages; } /** * Gets the path. * * @return The path. */ String getPath() { return path; } } /** * Used to translate more implementation-details-specific exceptions (which * are logged by this class) into more generic exceptions for propagation to * clients of the user-defined file types manager. */ static class HashLookupSettingsException extends Exception { private static final long serialVersionUID = 1L; HashLookupSettingsException(String message) { super(message); } HashLookupSettingsException(String message, Throwable throwable) { super(message, throwable); } } }