Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
HashLookupSettings.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2018 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.modules.hashdatabase;
20 
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.Serializable;
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.logging.Level;
30 import javax.swing.JOptionPane;
31 import org.apache.commons.io.FileUtils;
32 import org.openide.util.NbBundle;
33 import org.openide.util.io.NbObjectInputStream;
34 import org.openide.util.io.NbObjectOutputStream;
35 import org.openide.windows.WindowManager;
42 import org.sleuthkit.datamodel.TskCoreException;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.NodeList;
47 
51 final class HashLookupSettings implements Serializable {
52 
53  private static final String SERIALIZATION_FILE_NAME = "hashLookup.settings"; //NON-NLS
54  private static final String SERIALIZATION_FILE_PATH = PlatformUtil.getUserConfigDirectory() + File.separator + SERIALIZATION_FILE_NAME; //NON-NLS
55  private static final String SET_ELEMENT = "hash_set"; //NON-NLS
56  private static final String SET_NAME_ATTRIBUTE = "name"; //NON-NLS
57  private static final String SET_TYPE_ATTRIBUTE = "type"; //NON-NLS
58  private static final String SEARCH_DURING_INGEST_ATTRIBUTE = "use_for_ingest"; //NON-NLS
59  private static final String SEND_INGEST_MESSAGES_ATTRIBUTE = "show_inbox_messages"; //NON-NLS
60  private static final String PATH_ELEMENT = "hash_set_path"; //NON-NLS
61  private static final String LEGACY_PATH_NUMBER_ATTRIBUTE = "number"; //NON-NLS
62  private static final String CONFIG_FILE_NAME = "hashsets.xml"; //NON-NLS
63  private static final String configFilePath = PlatformUtil.getUserConfigDirectory() + File.separator + CONFIG_FILE_NAME;
64  private static final Logger logger = Logger.getLogger(HashDbManager.class.getName());
65 
66  private static final String USER_DIR_PLACEHOLDER = "[UserConfigFolder]";
67  private static final String CURRENT_USER_DIR = PlatformUtil.getUserConfigDirectory();
68 
69  private static final long serialVersionUID = 1L;
70  private final List<HashDbInfo> hashDbInfoList;
71 
77  HashLookupSettings(List<HashDbInfo> hashDbInfoList) {
78  this.hashDbInfoList = hashDbInfoList;
79  }
80 
81  static List<HashDbInfo> convertHashSetList(List<HashDbManager.HashDb> hashSets) throws HashLookupSettingsException{
82  List<HashDbInfo> dbInfoList = new ArrayList<>();
83  for(HashDbManager.HashDb db:hashSets){
84  try{
85  dbInfoList.add(new HashDbInfo(db));
86  } catch (TskCoreException ex){
87  logger.log(Level.SEVERE, "Could not load hash set settings for {0}", db.getHashSetName());
88  }
89  }
90  return dbInfoList;
91  }
92 
98  List<HashDbInfo> getHashDbInfo() {
99  return hashDbInfoList;
100  }
101 
110  static HashLookupSettings readSettings() throws HashLookupSettingsException {
111  File fileSetFile = new File(SERIALIZATION_FILE_PATH);
112  if (fileSetFile.exists()) {
113  return readSerializedSettings();
114  }
115  return readXmlSettings();
116 
117  }
118 
128  private static HashLookupSettings readSerializedSettings() throws HashLookupSettingsException {
129  try {
130  try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(SERIALIZATION_FILE_PATH))) {
131  HashLookupSettings filesSetsSettings = (HashLookupSettings) in.readObject();
132 
133  /* NOTE: to support JIRA-4177, we need to check if any of the hash
134  database paths are in Windows user directory. If so, we replace the path
135  with USER_DIR_PLACEHOLDER before saving to disk. When reading from disk,
136  USER_DIR_PLACEHOLDER needs to be replaced with current user directory path.
137  */
138  convertPlaceholderToPath(filesSetsSettings);
139  return filesSetsSettings;
140  }
141  } catch (IOException | ClassNotFoundException ex) {
142  throw new HashLookupSettingsException("Could not read hash set settings.", ex);
143  }
144  }
145 
155  private static HashLookupSettings readXmlSettings() throws HashLookupSettingsException {
156  File xmlFile = new File(configFilePath);
157  if (xmlFile.exists()) {
158  boolean updatedSchema = false;
159 
160  // Open the XML document that implements the configuration file.
161  final Document doc = XMLUtil.loadDoc(HashDbManager.class, configFilePath);
162  if (doc == null) {
163  throw new HashLookupSettingsException("Could not open xml document.");
164  }
165 
166  // Get the root element.
167  Element root = doc.getDocumentElement();
168  if (root == null) {
169  throw new HashLookupSettingsException("Error loading hash sets: invalid file format.");
170  }
171 
172  // Get the hash set elements.
173  NodeList setsNList = root.getElementsByTagName(SET_ELEMENT);
174  int numSets = setsNList.getLength();
175 
176  // Create HashDbInfo objects for each hash set element. Throws on malformed xml.
177  String attributeErrorMessage = "Missing %s attribute"; //NON-NLS
178  String elementErrorMessage = "Empty %s element"; //NON-NLS
179  List<String> hashSetNames = new ArrayList<>();
180  List<HashDbInfo> hashDbInfoList = new ArrayList<>();
181  for (int i = 0; i < numSets; ++i) {
182  Element setEl = (Element) setsNList.item(i);
183 
184  String hashSetName = setEl.getAttribute(SET_NAME_ATTRIBUTE);
185  if (hashSetName.isEmpty()) {
186  throw new HashLookupSettingsException(String.format(attributeErrorMessage, SET_NAME_ATTRIBUTE));
187  }
188 
189  // Handle configurations saved before duplicate hash set names were not permitted.
190  if (hashSetNames.contains(hashSetName)) {
191  int suffix = 0;
192  String newHashSetName;
193  do {
194  ++suffix;
195  newHashSetName = hashSetName + suffix;
196  } while (hashSetNames.contains(newHashSetName));
197  logger.log(Level.INFO, "Duplicate hash set name " + hashSetName + " found. Replacing with " + newHashSetName + ".");
198  if (RuntimeProperties.runningWithGUI()) {
199  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
200  NbBundle.getMessage(HashLookupSettings.class,
201  "HashDbManager.replacingDuplicateHashsetNameMsg",
202  hashSetName, newHashSetName),
203  NbBundle.getMessage(HashLookupSettings.class, "HashDbManager.openHashDbErr"),
204  JOptionPane.ERROR_MESSAGE);
205  hashSetName = newHashSetName;
206  }
207  }
208 
209  String knownFilesType = setEl.getAttribute(SET_TYPE_ATTRIBUTE);
210  if (knownFilesType.isEmpty()) {
211  throw new HashLookupSettingsException(String.format(attributeErrorMessage, SET_TYPE_ATTRIBUTE));
212  }
213 
214  // Handle legacy known files types.
215  if (knownFilesType.equals("NSRL")) { //NON-NLS
216  knownFilesType = HashDbManager.HashDb.KnownFilesType.KNOWN.toString();
217  updatedSchema = true;
218  }
219 
220  final String searchDuringIngest = setEl.getAttribute(SEARCH_DURING_INGEST_ATTRIBUTE);
221  if (searchDuringIngest.isEmpty()) {
222  throw new HashLookupSettingsException(String.format(attributeErrorMessage, SEND_INGEST_MESSAGES_ATTRIBUTE));
223  }
224  Boolean searchDuringIngestFlag = Boolean.parseBoolean(searchDuringIngest);
225 
226  final String sendIngestMessages = setEl.getAttribute(SEND_INGEST_MESSAGES_ATTRIBUTE);
227  if (searchDuringIngest.isEmpty()) {
228  throw new HashLookupSettingsException(String.format(attributeErrorMessage, SEND_INGEST_MESSAGES_ATTRIBUTE));
229  }
230  Boolean sendIngestMessagesFlag = Boolean.parseBoolean(sendIngestMessages);
231 
232  String dbPath;
233  NodeList pathsNList = setEl.getElementsByTagName(PATH_ELEMENT);
234  if (pathsNList.getLength() > 0) {
235  Element pathEl = (Element) pathsNList.item(0); // Shouldn't be more than one.
236 
237  // Check for legacy path number attribute.
238  String legacyPathNumber = pathEl.getAttribute(LEGACY_PATH_NUMBER_ATTRIBUTE);
239  if (null != legacyPathNumber && !legacyPathNumber.isEmpty()) {
240  updatedSchema = true;
241  }
242 
243  dbPath = pathEl.getTextContent();
244  if (dbPath.isEmpty()) {
245  throw new HashLookupSettingsException(String.format(elementErrorMessage, PATH_ELEMENT));
246  }
247  } else {
248  throw new HashLookupSettingsException(String.format(elementErrorMessage, PATH_ELEMENT));
249  }
250  hashDbInfoList.add(new HashDbInfo(hashSetName, HashDbManager.HashDb.KnownFilesType.valueOf(knownFilesType),
251  searchDuringIngestFlag, sendIngestMessagesFlag, dbPath));
252  hashSetNames.add(hashSetName);
253  }
254 
255  if (updatedSchema) {
256  String backupFilePath = configFilePath + ".v1_backup"; //NON-NLS
257  String messageBoxTitle = NbBundle.getMessage(HashLookupSettings.class,
258  "HashDbManager.msgBoxTitle.confFileFmtChanged");
259  String baseMessage = NbBundle.getMessage(HashLookupSettings.class,
260  "HashDbManager.baseMessage.updatedFormatHashDbConfig");
261  try {
262  FileUtils.copyFile(new File(configFilePath), new File(backupFilePath));
263  logger.log(Level.INFO, "Updated the schema, backup saved at: " + backupFilePath);
264  if (RuntimeProperties.runningWithGUI()) {
265  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
266  NbBundle.getMessage(HashLookupSettings.class,
267  "HashDbManager.savedBackupOfOldConfigMsg",
268  baseMessage, backupFilePath),
269  messageBoxTitle,
270  JOptionPane.INFORMATION_MESSAGE);
271  }
272  } catch (IOException ex) {
273  logger.log(Level.WARNING, "Failed to save backup of old format configuration file to " + backupFilePath, ex); //NON-NLS
274  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(), baseMessage, messageBoxTitle, JOptionPane.INFORMATION_MESSAGE);
275  }
276  HashLookupSettings settings;
277  settings = new HashLookupSettings(hashDbInfoList);
278  HashLookupSettings.writeSettings(settings);
279  }
280  return new HashLookupSettings(hashDbInfoList);
281  } else {
282  return new HashLookupSettings(new ArrayList<>());
283  }
284  }
285 
293  static boolean writeSettings(HashLookupSettings settings) {
294 
295  /* NOTE: to support JIRA-4177, we need to check if any of the hash
296  database paths are in Windows user directory. If so, replace the path
297  with USER_DIR_PLACEHOLDER so that when it is read, it gets updated to be
298  the current user directory path.
299  */
300  convertPathToPlaceholder(settings);
301  try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(SERIALIZATION_FILE_PATH))) {
302  out.writeObject(settings);
303  // restore the paths, in case they are going to be used somewhere
304  convertPlaceholderToPath(settings);
305  return true;
306  } catch (Exception ex) {
307  logger.log(Level.SEVERE, "Could not write hash set settings.");
308  return false;
309  }
310  }
311 
319  static void convertPathToPlaceholder(HashLookupSettings settings) {
320  for (HashDbInfo hashDbInfo : settings.getHashDbInfo()) {
321  if (hashDbInfo.isFileDatabaseType()) {
322  String dbPath = hashDbInfo.getPath();
323  if (dbPath.startsWith(CURRENT_USER_DIR)) {
324  // replace the current user directory with place holder
325  String remainingPath = dbPath.substring(CURRENT_USER_DIR.length());
326  hashDbInfo.setPath(USER_DIR_PLACEHOLDER + remainingPath);
327  }
328  }
329  }
330  }
331 
339  static void convertPlaceholderToPath(HashLookupSettings settings) {
340  for (HashDbInfo hashDbInfo : settings.getHashDbInfo()) {
341  if (hashDbInfo.isFileDatabaseType()) {
342  String dbPath = hashDbInfo.getPath();
343  if (dbPath.startsWith(USER_DIR_PLACEHOLDER)) {
344  // replace the place holder with current user directory
345  String remainingPath = dbPath.substring(USER_DIR_PLACEHOLDER.length());
346  hashDbInfo.setPath(CURRENT_USER_DIR + remainingPath);
347  }
348  }
349  }
350  }
351 
352 
358  static final class HashDbInfo implements Serializable {
359 
360  enum DatabaseType{
361  FILE,
362  CENTRAL_REPOSITORY
363  };
364 
365  private static final long serialVersionUID = 1L;
366  private final String hashSetName;
367  private final HashDbManager.HashDb.KnownFilesType knownFilesType;
368  private boolean searchDuringIngest;
369  private final boolean sendIngestMessages;
370  private String path;
371  private final String version;
372  private final boolean readOnly;
373  private final int referenceSetID;
374  private DatabaseType dbType;
375 
386  HashDbInfo(String hashSetName, HashDbManager.HashDb.KnownFilesType knownFilesType, boolean searchDuringIngest, boolean sendIngestMessages, String path) {
387  this.hashSetName = hashSetName;
388  this.knownFilesType = knownFilesType;
389  this.searchDuringIngest = searchDuringIngest;
390  this.sendIngestMessages = sendIngestMessages;
391  this.path = path;
392  this.referenceSetID = -1;
393  this.version = "";
394  this.readOnly = false;
395  this.dbType = DatabaseType.FILE;
396  }
397 
398  HashDbInfo(String hashSetName, String version, int referenceSetID, HashDbManager.HashDb.KnownFilesType knownFilesType, boolean readOnly, boolean searchDuringIngest, boolean sendIngestMessages){
399  this.hashSetName = hashSetName;
400  this.version = version;
401  this.referenceSetID = referenceSetID;
402  this.knownFilesType = knownFilesType;
403  this.readOnly = readOnly;
404  this.searchDuringIngest = searchDuringIngest;
405  this.sendIngestMessages = sendIngestMessages;
406  this.path = "";
407  dbType = DatabaseType.CENTRAL_REPOSITORY;
408  }
409 
410  HashDbInfo(HashDbManager.HashDb db) throws TskCoreException{
411  if(db instanceof HashDbManager.SleuthkitHashSet){
412  HashDbManager.SleuthkitHashSet fileTypeDb = (HashDbManager.SleuthkitHashSet)db;
413  this.hashSetName = fileTypeDb.getHashSetName();
414  this.knownFilesType = fileTypeDb.getKnownFilesType();
415  this.searchDuringIngest = fileTypeDb.getSearchDuringIngest();
416  this.sendIngestMessages = fileTypeDb.getSendIngestMessages();
417  this.referenceSetID = -1;
418  this.version = "";
419  this.readOnly = false;
420  this.dbType = DatabaseType.FILE;
421  if (fileTypeDb.hasIndexOnly()) {
422  this.path = fileTypeDb.getIndexPath();
423  } else {
424  this.path = fileTypeDb.getDatabasePath();
425  }
426  } else {
427  HashDbManager.CentralRepoHashSet centralRepoDb = (HashDbManager.CentralRepoHashSet)db;
428  this.hashSetName = centralRepoDb.getHashSetName();
429  this.version = centralRepoDb.getVersion();
430  this.knownFilesType = centralRepoDb.getKnownFilesType();
431  this.readOnly = ! centralRepoDb.isUpdateable();
432  this.searchDuringIngest = centralRepoDb.getSearchDuringIngest();
433  this.sendIngestMessages = centralRepoDb.getSendIngestMessages();
434  this.path = "";
435  this.referenceSetID = centralRepoDb.getReferenceSetID();
436  this.dbType = DatabaseType.CENTRAL_REPOSITORY;
437  }
438  }
439 
445  String getHashSetName() {
446  return hashSetName;
447  }
448 
453  String getVersion(){
454  return version;
455  }
456 
461  boolean isReadOnly(){
462  return readOnly;
463  }
464 
470  HashDbManager.HashDb.KnownFilesType getKnownFilesType() {
471  return knownFilesType;
472  }
473 
479  boolean getSearchDuringIngest() {
480  return searchDuringIngest;
481  }
482 
487  void setSearchDuringIngest(boolean searchDuringIngest) {
488  this.searchDuringIngest = searchDuringIngest;
489  }
490 
496  boolean getSendIngestMessages() {
497  return sendIngestMessages;
498  }
499 
505  String getPath() {
506  return path;
507  }
508 
513  public void setPath(String path) {
514  this.path = path;
515  }
516 
517  int getReferenceSetID(){
518  return referenceSetID;
519  }
520 
525  boolean isFileDatabaseType(){
526  return dbType == DatabaseType.FILE;
527  }
528 
529  boolean isCentralRepoDatabaseType(){
530  return dbType == DatabaseType.CENTRAL_REPOSITORY;
531  }
532 
533  boolean matches(HashDb hashDb){
534  if(hashDb == null){
535  return false;
536  }
537 
538  if( ! this.knownFilesType.equals(hashDb.getKnownFilesType())){
539  return false;
540  }
541 
542  if((this.dbType == DatabaseType.CENTRAL_REPOSITORY) && (! (hashDb instanceof CentralRepoHashSet))
543  || (this.dbType == DatabaseType.FILE) && (! (hashDb instanceof SleuthkitHashSet))){
544  return false;
545  }
546 
547  if( ! this.hashSetName.equals(hashDb.getHashSetName())){
548  return false;
549  }
550 
551  if(hashDb instanceof CentralRepoHashSet){
552  CentralRepoHashSet crDb = (CentralRepoHashSet) hashDb;
553  if(this.referenceSetID != crDb.getReferenceSetID()){
554  return false;
555  }
556 
557  if(! version.equals(crDb.getVersion())){
558  return false;
559  }
560  }
561 
562  return true;
563  }
564 
565  @Override
566  public boolean equals(Object obj) {
567  if (obj == null) {
568  return false;
569  }
570 
571  if (getClass() != obj.getClass()) {
572  return false;
573  }
574 
575  final HashDbInfo other = (HashDbInfo) obj;
576 
577  if(! this.dbType.equals(other.dbType)){
578  return false;
579  }
580 
581  if(this.dbType.equals(DatabaseType.FILE)){
582  // For files, we expect the name and known type to match
583  return (this.hashSetName.equals(other.hashSetName)
584  && this.knownFilesType.equals(other.knownFilesType));
585  } else {
586  // For central repo, the name, index, and known files type should match
587  return (this.hashSetName.equals(other.hashSetName)
588  && (this.referenceSetID == other.referenceSetID)
589  && this.knownFilesType.equals(other.knownFilesType));
590  }
591  }
592 
593  @Override
594  public int hashCode() {
595  int hash = 5;
596  hash = 89 * hash + Objects.hashCode(this.hashSetName);
597  hash = 89 * hash + Objects.hashCode(this.knownFilesType);
598  hash = 89 * hash + Objects.hashCode(this.dbType);
599  if(this.dbType.equals(DatabaseType.CENTRAL_REPOSITORY)){
600  hash = 89 * hash + this.referenceSetID;
601  }
602 
603  return hash;
604  }
605 
613  private void readObject(java.io.ObjectInputStream stream)
614  throws IOException, ClassNotFoundException {
615  stream.defaultReadObject();
616 
617  if(dbType == null){
618  dbType = DatabaseType.FILE;
619  }
620  }
621  }
622 
628  static class HashLookupSettingsException extends Exception {
629 
630  private static final long serialVersionUID = 1L;
631 
632  HashLookupSettingsException(String message) {
633  super(message);
634  }
635 
636  HashLookupSettingsException(String message, Throwable throwable) {
637  super(message, throwable);
638  }
639  }
640 }

Copyright © 2012-2020 Basis Technology. Generated on: Wed Apr 8 2020
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.