Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
InterestingItemDefsManager.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2014 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.interestingitems;
20 
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Observable;
32 import java.util.logging.Level;
33 import java.util.regex.Pattern;
34 import java.util.regex.PatternSyntaxException;
35 import org.openide.util.io.NbObjectInputStream;
36 import org.openide.util.io.NbObjectOutputStream;
40 import org.w3c.dom.Document;
41 import org.w3c.dom.Element;
42 import org.w3c.dom.NodeList;
43 
50 final class InterestingItemDefsManager extends Observable {
51 
52  private static final List<String> ILLEGAL_FILE_NAME_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", "/", ":", "*", "?", "\"", "<", ">")));
53  private static final List<String> ILLEGAL_FILE_PATH_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", ":", "*", "?", "\"", "<", ">")));
54  private static final String LEGACY_FILES_SET_DEFS_FILE_NAME = "InterestingFilesSetDefs.xml"; //NON-NLS
55  private static final String INTERESTING_FILES_SET_DEFS_SERIALIZATION_NAME = "InterestingFileSets.settings";
56  private static final String INTERESTING_FILES_SET_DEFS_SERIALIZATION_PATH = PlatformUtil.getUserConfigDirectory() + File.separator + INTERESTING_FILES_SET_DEFS_SERIALIZATION_NAME;
57  private static final String LEGACY_FILE_SET_DEFS_PATH = PlatformUtil.getUserConfigDirectory() + File.separator + LEGACY_FILES_SET_DEFS_FILE_NAME;
58  private static InterestingItemDefsManager instance;
59 
63  synchronized static InterestingItemDefsManager getInstance() {
64  if (instance == null) {
65  instance = new InterestingItemDefsManager();
66  }
67  return instance;
68  }
69 
75  static List<String> getIllegalFileNameChars() {
76  return InterestingItemDefsManager.ILLEGAL_FILE_NAME_CHARS;
77  }
78 
85  static List<String> getIllegalFilePathChars() {
86  return InterestingItemDefsManager.ILLEGAL_FILE_PATH_CHARS;
87  }
88 
95  synchronized Map<String, FilesSet> getInterestingFilesSets() throws InterestingItemDefsManagerException {
96  return FilesSetXML.readDefinitionsFile(LEGACY_FILE_SET_DEFS_PATH);
97  }
98 
106  synchronized void setInterestingFilesSets(Map<String, FilesSet> filesSets) throws InterestingItemDefsManagerException {
107  FilesSetXML.writeDefinitionsFile(INTERESTING_FILES_SET_DEFS_SERIALIZATION_PATH, filesSets);
108  this.setChanged();
109  this.notifyObservers();
110  }
111 
116  private final static class FilesSetXML {
117 
118  private static final Logger logger = Logger.getLogger(FilesSetXML.class.getName());
119  private static final String XML_ENCODING = "UTF-8"; //NON-NLS
120  private static final List<String> illegalFileNameChars = InterestingItemDefsManager.getIllegalFileNameChars();
121 
122  // The following tags and attributes are identical to those used in the
123  // TSK Framework interesting files set definitions file schema.
124  private static final String FILE_SETS_ROOT_TAG = "INTERESTING_FILE_SETS"; //NON-NLS
125  private static final String FILE_SET_TAG = "INTERESTING_FILE_SET"; //NON-NLS
126  private static final String NAME_RULE_TAG = "NAME"; //NON-NLS
127  private static final String EXTENSION_RULE_TAG = "EXTENSION"; //NON-NLS
128  private static final String NAME_ATTR = "name"; //NON-NLS
129  private static final String RULE_UUID_ATTR = "ruleUUID"; //NON-NLS
130  private static final String DESC_ATTR = "description"; //NON-NLS
131  private static final String IGNORE_KNOWN_FILES_ATTR = "ignoreKnown"; //NON-NLS
132  private static final String TYPE_FILTER_ATTR = "typeFilter"; //NON-NLS
133  private static final String PATH_FILTER_ATTR = "pathFilter"; //NON-NLS
134  private static final String TYPE_FILTER_VALUE_FILES = "file"; //NON-NLS
135  private static final String TYPE_FILTER_VALUE_DIRS = "dir"; //NON-NLS
136 
137  private static final String REGEX_ATTR = "regex"; //NON-NLS
138  private static final String PATH_REGEX_ATTR = "pathRegex"; //NON-NLS
139  private static final String TYPE_FILTER_VALUE_FILES_AND_DIRS = "files_and_dirs"; //NON-NLS
140  private static final String UNNAMED_LEGACY_RULE_PREFIX = "Unnamed Rule "; // NON-NLS
141  private static int unnamedLegacyRuleCounter;
142 
150  // Note: This method takes a file path to support the possibility of
151  // multiple intersting files set definition files, e.g., one for
152  // definitions that ship with Autopsy and one for user definitions.
153  static Map<String, FilesSet> readDefinitionsFile(String filePath) throws InterestingItemDefsManagerException {
154  Map<String, FilesSet> filesSets = readSerializedDefinitions();
155 
156  if (!filesSets.isEmpty()) {
157  return filesSets;
158  }
159  // Check if the legacy xml file exists.
160  File defsFile = new File(filePath);
161  if (!defsFile.exists()) {
162  return filesSets;
163  }
164 
165  // Check if the file can be read.
166  if (!defsFile.canRead()) {
167  logger.log(Level.SEVERE, "Interesting file sets definition file at {0} exists, but cannot be read", filePath); // NON-NLS
168  return filesSets;
169  }
170 
171  // Parse the XML in the file.
172  Document doc = XMLUtil.loadDoc(FilesSetXML.class, filePath);
173  if (doc == null) {
174  logger.log(Level.SEVERE, "Failed to parse interesting file sets definition file at {0}", filePath); // NON-NLS
175  return filesSets;
176  }
177 
178  // Get the root element.
179  Element root = doc.getDocumentElement();
180  if (root == null) {
181  logger.log(Level.SEVERE, "Failed to get root {0} element tag of interesting file sets definition file at {1}", new Object[]{FilesSetXML.FILE_SETS_ROOT_TAG, filePath}); // NON-NLS
182  return filesSets;
183  }
184 
185  // Read in the files set definitions.
186  NodeList setElems = root.getElementsByTagName(FILE_SET_TAG);
187  for (int i = 0; i < setElems.getLength(); ++i) {
188  readFilesSet((Element) setElems.item(i), filesSets, filePath);
189  }
190  return filesSets;
191  }
192 
201  private static Map<String, FilesSet> readSerializedDefinitions() throws InterestingItemDefsManagerException {
202  String filePath = INTERESTING_FILES_SET_DEFS_SERIALIZATION_PATH;
203  File fileSetFile = new File(filePath);
204  if (fileSetFile.exists()) {
205  try {
206  try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(filePath))) {
207  InterestingItemsFilesSetSettings filesSetsSettings = (InterestingItemsFilesSetSettings) in.readObject();
208  return filesSetsSettings.getFilesSets();
209  }
210  } catch (IOException | ClassNotFoundException ex) {
211  throw new InterestingItemDefsManagerException(String.format("Failed to read settings from %s", filePath), ex);
212  }
213  } else {
214  return new HashMap<String, FilesSet>();
215  }
216  }
217 
225  private static void readFilesSet(Element setElem, Map<String, FilesSet> filesSets, String filePath) {
226  // The file set must have a unique name.
227  String setName = setElem.getAttribute(FilesSetXML.NAME_ATTR);
228  if (setName.isEmpty()) {
229  logger.log(Level.SEVERE, "Found {0} element without required {1} attribute, ignoring malformed file set definition in interesting file sets definition file at {2}", new Object[]{FilesSetXML.FILE_SET_TAG, FilesSetXML.NAME_ATTR, filePath}); // NON-NLS
230  return;
231  }
232  if (filesSets.containsKey(setName)) {
233  logger.log(Level.SEVERE, "Found duplicate definition of set named {0} in interesting file sets definition file at {1}, discarding duplicate set", new Object[]{setName, filePath}); // NON-NLS
234  return;
235  }
236 
237  // The file set may have a description. The empty string is o.k.
238  String description = setElem.getAttribute(FilesSetXML.DESC_ATTR);
239 
240  // The file set may or may not ignore known files. The default behavior
241  // is to not ignore them.
242  String ignoreKnown = setElem.getAttribute(FilesSetXML.IGNORE_KNOWN_FILES_ATTR);
243  boolean ignoreKnownFiles = false;
244  if (!ignoreKnown.isEmpty()) {
245  ignoreKnownFiles = Boolean.parseBoolean(ignoreKnown);
246  }
247 
248  // Read file name set membership rules, if any.
250  Map<String, FilesSet.Rule> rules = new HashMap<>();
251  NodeList nameRuleElems = setElem.getElementsByTagName(FilesSetXML.NAME_RULE_TAG);
252  for (int j = 0; j < nameRuleElems.getLength(); ++j) {
253  Element elem = (Element) nameRuleElems.item(j);
254  FilesSet.Rule rule = FilesSetXML.readFileNameRule(elem);
255  if (rule != null) {
256  if (!rules.containsKey(rule.getUuid())) {
257  rules.put(rule.getUuid(), rule);
258  } else {
259  logger.log(Level.SEVERE, "Found duplicate rule {0} for set named {1} in interesting file sets definition file at {2}, discarding malformed set", new Object[]{rule.getUuid(), setName, filePath}); // NON-NLS
260  return;
261  }
262  } else {
263  logger.log(Level.SEVERE, "Found malformed rule for set named {0} in interesting file sets definition file at {1}, discarding malformed set", new Object[]{setName, filePath}); // NON-NLS
264  return;
265  }
266  }
267 
268  // Read file extension set membership rules, if any.
269  NodeList extRuleElems = setElem.getElementsByTagName(FilesSetXML.EXTENSION_RULE_TAG);
270  for (int j = 0; j < extRuleElems.getLength(); ++j) {
271  Element elem = (Element) extRuleElems.item(j);
272  FilesSet.Rule rule = FilesSetXML.readFileExtensionRule(elem);
273  if (rule != null) {
274  if (!rules.containsKey(rule.getUuid())) {
275  rules.put(rule.getUuid(), rule);
276  } else {
277  logger.log(Level.SEVERE, "Found duplicate rule {0} for set named {1} in interesting file sets definition file at {2}, discarding malformed set", new Object[]{rule.getUuid(), setName, filePath}); //NOI18N NON-NLS
278  return;
279  }
280  } else {
281  logger.log(Level.SEVERE, "Found malformed rule for set named {0} in interesting file sets definition file at {1}, discarding malformed set", new Object[]{setName, filePath}); //NOI18N NON-NLS
282  return;
283  }
284  }
285 
286  // Make the files set. Note that degenerate sets with no rules are
287  // allowed to facilitate the separation of set definition and rule
288  // definitions. A set without rules is simply the empty set.
289  FilesSet set = new FilesSet(setName, description, ignoreKnownFiles, rules);
290  filesSets.put(set.getName(), set);
291  }
292 
302  private static FilesSet.Rule readFileNameRule(Element elem) {
303  String ruleName = FilesSetXML.readRuleName(elem);
304 
305  // The content of the rule tag is a file name condition. It may be a
306  // regex, or it may be from a TSK Framework rule definition with a
307  // "*" globbing char, or it may be simple text.
308  String content = elem.getTextContent();
309  FilesSet.Rule.FullNameCondition nameCondition;
310  String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR);
311  if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON-NLS
312  Pattern pattern = compileRegex(content);
313  if (pattern != null) {
314  nameCondition = new FilesSet.Rule.FullNameCondition(pattern);
315  } else {
316  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.NAME_RULE_TAG + " regex, ignoring malformed '{0}' rule definition", ruleName); // NON-NLS
317  return null;
318  }
319  } else {
320  for (String illegalChar : illegalFileNameChars) {
321  if (content.contains(illegalChar)) {
322  logger.log(Level.SEVERE, FilesSetXML.NAME_RULE_TAG + " content has illegal chars, ignoring malformed '{0}' rule definition", new Object[]{FilesSetXML.NAME_RULE_TAG, ruleName}); // NON-NLS
323  return null;
324  }
325  }
326  nameCondition = new FilesSet.Rule.FullNameCondition(content);
327  }
328 
329  // Read in the type condition.
330  FilesSet.Rule.MetaTypeCondition metaTypeCondition = FilesSetXML.readMetaTypeCondition(elem);
331  if (metaTypeCondition == null) {
332  // Malformed attribute.
333  return null;
334  }
335 
336  // Read in the optional path condition. Null is o.k., but if the attribute
337  // is there, be sure it is not malformed.
338  FilesSet.Rule.ParentPathCondition pathCondition = null;
339  if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty()
340  || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) {
341  pathCondition = FilesSetXML.readPathCondition(elem);
342  if (pathCondition == null) {
343  // Malformed attribute.
344  return null;
345  }
346  }
347 
348  return new FilesSet.Rule(ruleName, nameCondition, metaTypeCondition, pathCondition, null, null);
349  }
350 
360  private static FilesSet.Rule readFileExtensionRule(Element elem) {
361  String ruleName = FilesSetXML.readRuleName(elem);
362 
363  // The content of the rule tag is a file name extension condition. It may
364  // be a regex, or it may be from a TSK Framework rule definition
365  // with a "*" globbing char.
366  String content = elem.getTextContent();
367  FilesSet.Rule.ExtensionCondition extCondition;
368  String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR);
369  if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON-NLS
370  Pattern pattern = compileRegex(content);
371  if (pattern != null) {
372  extCondition = new FilesSet.Rule.ExtensionCondition(pattern);
373  } else {
374  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.EXTENSION_RULE_TAG + " regex, ignoring malformed {0} rule definition", ruleName); // NON-NLS
375  return null;
376  }
377  } else {
378  for (String illegalChar : illegalFileNameChars) {
379  if (content.contains(illegalChar)) {
380  logger.log(Level.SEVERE, "{0} content has illegal chars, ignoring malformed {1} rule definition", ruleName); // NON-NLS
381  return null;
382  }
383  }
384  extCondition = new FilesSet.Rule.ExtensionCondition(content);
385  }
386 
387  // The rule must have a meta-type condition, unless a TSK Framework
388  // definitions file is being read.
389  FilesSet.Rule.MetaTypeCondition metaTypeCondition = null;
390  if (!elem.getAttribute(FilesSetXML.TYPE_FILTER_ATTR).isEmpty()) {
391  metaTypeCondition = FilesSetXML.readMetaTypeCondition(elem);
392  if (metaTypeCondition == null) {
393  // Malformed attribute.
394  return null;
395  }
396  } else {
397  metaTypeCondition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
398  }
399 
400  // The rule may have a path condition. Null is o.k., but if the attribute
401  // is there, it must not be malformed.
402  FilesSet.Rule.ParentPathCondition pathCondition = null;
403  if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty()
404  || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) {
405  pathCondition = FilesSetXML.readPathCondition(elem);
406  if (pathCondition == null) {
407  // Malformed attribute.
408  return null;
409  }
410  }
411 
412  return new FilesSet.Rule(ruleName, extCondition, metaTypeCondition, pathCondition, null, null);
413  }
414 
422  private static String readRuleName(Element elem) {
423  // The rule must have a name.
424  String ruleName = elem.getAttribute(FilesSetXML.NAME_ATTR);
425  return ruleName;
426  }
427 
435  private static Pattern compileRegex(String regex) {
436  try {
437  return Pattern.compile(regex);
438  } catch (PatternSyntaxException ex) {
439  logger.log(Level.SEVERE, "Error compiling rule regex: " + ex.getMessage(), ex); // NON-NLS
440  return null;
441  }
442  }
443 
453  private static FilesSet.Rule.MetaTypeCondition readMetaTypeCondition(Element ruleElement) {
454  FilesSet.Rule.MetaTypeCondition condition = null;
455  String conditionAttribute = ruleElement.getAttribute(FilesSetXML.TYPE_FILTER_ATTR);
456  if (!conditionAttribute.isEmpty()) {
457  switch (conditionAttribute) {
459  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
460  break;
462  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.DIRECTORIES);
463  break;
465  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES_AND_DIRECTORIES);
466  break;
467  default:
468  logger.log(Level.SEVERE, "Found {0} " + FilesSetXML.TYPE_FILTER_ATTR + " attribute with unrecognized value ''{0}'', ignoring malformed rule definition", conditionAttribute); // NON-NLS
469  break;
470  }
471  } else {
472  // Accept TSK Framework interesting files set definitions,
473  // default to files.
474  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
475  }
476  return condition;
477  }
478 
487  private static FilesSet.Rule.ParentPathCondition readPathCondition(Element ruleElement) {
488  FilesSet.Rule.ParentPathCondition condition = null;
489  String path = ruleElement.getAttribute(FilesSetXML.PATH_FILTER_ATTR);
490  String pathRegex = ruleElement.getAttribute(FilesSetXML.PATH_REGEX_ATTR);
491  if (!pathRegex.isEmpty() && path.isEmpty()) {
492  try {
493  Pattern pattern = Pattern.compile(pathRegex);
494  condition = new FilesSet.Rule.ParentPathCondition(pattern);
495  } catch (PatternSyntaxException ex) {
496  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.PATH_REGEX_ATTR + " regex, ignoring malformed path condition definition", ex); // NON-NLS
497  }
498  } else if (!path.isEmpty() && pathRegex.isEmpty()) {
499  condition = new FilesSet.Rule.ParentPathCondition(path);
500  }
501  return condition;
502  }
503 
513  // Note: This method takes a file path to support the possibility of
514  // multiple intersting files set definition files, e.g., one for
515  // definitions that ship with Autopsy and one for user definitions.
516  static boolean writeDefinitionsFile(String filePath, Map<String, FilesSet> interestingFilesSets) throws InterestingItemDefsManagerException {
517  try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(filePath))) {
518  out.writeObject(new InterestingItemsFilesSetSettings(interestingFilesSets));
519  } catch (IOException ex) {
520  throw new InterestingItemDefsManagerException(String.format("Failed to write settings to %s", filePath), ex);
521  }
522  return true;
523  }
524  }
525 
526  static class InterestingItemDefsManagerException extends Exception {
527 
528  InterestingItemDefsManagerException() {
529 
530  }
531 
532  InterestingItemDefsManagerException(String message) {
533  super(message);
534  }
535 
536  InterestingItemDefsManagerException(String message, Throwable cause) {
537  super(message, cause);
538  }
539 
540  InterestingItemDefsManagerException(Throwable cause) {
541  super(cause);
542  }
543  }
544 
545 }
static< T > Document loadDoc(Class< T > clazz, String xmlPath)
Definition: XMLUtil.java:228
static void readFilesSet(Element setElem, Map< String, FilesSet > filesSets, String filePath)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

Copyright © 2012-2016 Basis Technology. Generated on: Tue Oct 25 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.