Autopsy  4.0
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 
304  private static FilesSet.Rule readFileNameRule(Element elem) {
305  String ruleName = FilesSetXML.readRuleName(elem);
306 
307  // The content of the rule tag is a file name condition. It may be a
308  // regex, or it may be from a TSK Framework rule definition with a
309  // "*" globbing char, or it may be simple text.
310  String content = elem.getTextContent();
311  FilesSet.Rule.FullNameCondition nameCondition;
312  String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR);
313  if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON-NLS
314  Pattern pattern = compileRegex(content);
315  if (pattern != null) {
316  nameCondition = new FilesSet.Rule.FullNameCondition(pattern);
317  } else {
318  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.NAME_RULE_TAG + " regex, ignoring malformed '{0}' rule definition", ruleName); // NON-NLS
319  return null;
320  }
321  } else {
322  for (String illegalChar : illegalFileNameChars) {
323  if (content.contains(illegalChar)) {
324  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
325  return null;
326  }
327  }
328  nameCondition = new FilesSet.Rule.FullNameCondition(content);
329  }
330 
331  // Read in the type condition.
332  FilesSet.Rule.MetaTypeCondition metaTypeCondition = FilesSetXML.readMetaTypeCondition(elem);
333  if (metaTypeCondition == null) {
334  // Malformed attribute.
335  return null;
336  }
337 
338  // Read in the optional path condition. Null is o.k., but if the attribute
339  // is there, be sure it is not malformed.
340  FilesSet.Rule.ParentPathCondition pathCondition = null;
341  if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty()
342  || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) {
343  pathCondition = FilesSetXML.readPathCondition(elem);
344  if (pathCondition == null) {
345  // Malformed attribute.
346  return null;
347  }
348  }
349 
350  return new FilesSet.Rule(ruleName, nameCondition, metaTypeCondition, pathCondition, null, null);
351  }
352 
362  private static FilesSet.Rule readFileExtensionRule(Element elem) {
363  String ruleName = FilesSetXML.readRuleName(elem);
364 
365  // The content of the rule tag is a file name extension condition. It may
366  // be a regex, or it may be from a TSK Framework rule definition
367  // with a "*" globbing char.
368  String content = elem.getTextContent();
369  FilesSet.Rule.ExtensionCondition extCondition;
370  String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR);
371  if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON-NLS
372  Pattern pattern = compileRegex(content);
373  if (pattern != null) {
374  extCondition = new FilesSet.Rule.ExtensionCondition(pattern);
375  } else {
376  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.EXTENSION_RULE_TAG + " regex, ignoring malformed {0} rule definition", ruleName); // NON-NLS
377  return null;
378  }
379  } else {
380  for (String illegalChar : illegalFileNameChars) {
381  if (content.contains(illegalChar)) {
382  logger.log(Level.SEVERE, "{0} content has illegal chars, ignoring malformed {1} rule definition", ruleName); // NON-NLS
383  return null;
384  }
385  }
386  extCondition = new FilesSet.Rule.ExtensionCondition(content);
387  }
388 
389  // The rule must have a meta-type condition, unless a TSK Framework
390  // definitions file is being read.
391  FilesSet.Rule.MetaTypeCondition metaTypeCondition = null;
392  if (!elem.getAttribute(FilesSetXML.TYPE_FILTER_ATTR).isEmpty()) {
393  metaTypeCondition = FilesSetXML.readMetaTypeCondition(elem);
394  if (metaTypeCondition == null) {
395  // Malformed attribute.
396  return null;
397  }
398  } else {
399  metaTypeCondition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
400  }
401 
402  // The rule may have a path condition. Null is o.k., but if the attribute
403  // is there, it must not be malformed.
404  FilesSet.Rule.ParentPathCondition pathCondition = null;
405  if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty()
406  || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) {
407  pathCondition = FilesSetXML.readPathCondition(elem);
408  if (pathCondition == null) {
409  // Malformed attribute.
410  return null;
411  }
412  }
413 
414  return new FilesSet.Rule(ruleName, extCondition, metaTypeCondition, pathCondition, null, null);
415  }
416 
424  private static String readRuleName(Element elem) {
425  // The rule must have a name.
426  String ruleName = elem.getAttribute(FilesSetXML.NAME_ATTR);
427  return ruleName;
428  }
429 
437  private static Pattern compileRegex(String regex) {
438  try {
439  return Pattern.compile(regex);
440  } catch (PatternSyntaxException ex) {
441  logger.log(Level.SEVERE, "Error compiling rule regex: " + ex.getMessage(), ex); // NON-NLS
442  return null;
443  }
444  }
445 
455  private static FilesSet.Rule.MetaTypeCondition readMetaTypeCondition(Element ruleElement) {
456  FilesSet.Rule.MetaTypeCondition condition = null;
457  String conditionAttribute = ruleElement.getAttribute(FilesSetXML.TYPE_FILTER_ATTR);
458  if (!conditionAttribute.isEmpty()) {
459  switch (conditionAttribute) {
461  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
462  break;
464  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.DIRECTORIES);
465  break;
467  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES_AND_DIRECTORIES);
468  break;
469  default:
470  logger.log(Level.SEVERE, "Found {0} " + FilesSetXML.TYPE_FILTER_ATTR + " attribute with unrecognized value ''{0}'', ignoring malformed rule definition", conditionAttribute); // NON-NLS
471  break;
472  }
473  } else {
474  // Accept TSK Framework interesting files set definitions,
475  // default to files.
476  condition = new FilesSet.Rule.MetaTypeCondition(FilesSet.Rule.MetaTypeCondition.Type.FILES);
477  }
478  return condition;
479  }
480 
489  private static FilesSet.Rule.ParentPathCondition readPathCondition(Element ruleElement) {
490  FilesSet.Rule.ParentPathCondition condition = null;
491  String path = ruleElement.getAttribute(FilesSetXML.PATH_FILTER_ATTR);
492  String pathRegex = ruleElement.getAttribute(FilesSetXML.PATH_REGEX_ATTR);
493  if (!pathRegex.isEmpty() && path.isEmpty()) {
494  try {
495  Pattern pattern = Pattern.compile(pathRegex);
496  condition = new FilesSet.Rule.ParentPathCondition(pattern);
497  } catch (PatternSyntaxException ex) {
498  logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.PATH_REGEX_ATTR + " regex, ignoring malformed path condition definition", ex); // NON-NLS
499  }
500  } else if (!path.isEmpty() && pathRegex.isEmpty()) {
501  condition = new FilesSet.Rule.ParentPathCondition(path);
502  }
503  return condition;
504  }
505 
515  // Note: This method takes a file path to support the possibility of
516  // multiple intersting files set definition files, e.g., one for
517  // definitions that ship with Autopsy and one for user definitions.
518  static boolean writeDefinitionsFile(String filePath, Map<String, FilesSet> interestingFilesSets) throws InterestingItemDefsManagerException {
519  try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(filePath))) {
520  out.writeObject(new InterestingItemsFilesSetSettings(interestingFilesSets));
521  } catch (IOException ex) {
522  throw new InterestingItemDefsManagerException(String.format("Failed to write settings to %s", filePath), ex);
523  }
524  return true;
525  }
526  }
527 
528  static class InterestingItemDefsManagerException extends Exception {
529 
530  InterestingItemDefsManagerException() {
531 
532  }
533 
534  InterestingItemDefsManagerException(String message) {
535  super(message);
536  }
537 
538  InterestingItemDefsManagerException(String message, Throwable cause) {
539  super(message, cause);
540  }
541 
542  InterestingItemDefsManagerException(Throwable cause) {
543  super(cause);
544  }
545  }
546 
547 }
static< T > Document loadDoc(Class< T > clazz, String xmlPath)
Definition: XMLUtil.java:230
static void readFilesSet(Element setElem, Map< String, FilesSet > filesSets, String filePath)
synchronized static Logger getLogger(String name)
Definition: Logger.java:166

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