Autopsy  4.4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
SqliteEamDbSettings.java
Go to the documentation of this file.
1 /*
2  * Central Repository
3  *
4  * Copyright 2015-2017 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.centralrepository.datamodel;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.nio.file.Files;
24 import java.nio.file.InvalidPathException;
25 import java.sql.Connection;
26 import java.sql.DriverManager;
27 import java.sql.SQLException;
28 import java.sql.Statement;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.List;
32 import java.util.logging.Level;
33 import java.util.regex.Pattern;
37 
41 public final class SqliteEamDbSettings {
42 
43  private final static Logger LOGGER = Logger.getLogger(SqliteEamDbSettings.class.getName());
44  private final String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS
45  private final String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS
46  private final int DEFAULT_BULK_THRESHHOLD = 1000;
47  private final String DEFAULT_BAD_TAGS = "Evidence"; // NON-NLS
48  private final String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS
49  private final String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS
50  private final String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS
51  private static final String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF";
52  private static final String PRAGMA_SYNC_NORMAL = "PRAGMA synchronous = NORMAL";
53  private static final String PRAGMA_JOURNAL_WAL = "PRAGMA journal_mode = WAL";
54  private static final String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True";
55  private static final String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'";
56  private static final String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096";
57  private static final String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON";
58  private final String DB_NAMES_REGEX = "[a-z][a-z0-9_]*(\\.db)?";
59  private String dbName;
60  private String dbDirectory;
61  private int bulkThreshold;
62  private List<String> badTags;
63 
65  loadSettings();
66  }
67 
68  public void loadSettings() {
69  dbName = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS
70  if (dbName == null || dbName.isEmpty()) {
71  dbName = DEFAULT_DBNAME;
72  }
73 
74  dbDirectory = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS
75  if (dbDirectory == null || dbDirectory.isEmpty()) {
76  dbDirectory = DEFAULT_DBDIRECTORY;
77  }
78 
79  try {
80  String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS
81  if (bulkThresholdString == null || bulkThresholdString.isEmpty()) {
82  this.bulkThreshold = DEFAULT_BULK_THRESHHOLD;
83  } else {
84  this.bulkThreshold = Integer.parseInt(bulkThresholdString);
85  if (getBulkThreshold() <= 0) {
86  this.bulkThreshold = DEFAULT_BULK_THRESHHOLD;
87  }
88  }
89  } catch (NumberFormatException ex) {
90  this.bulkThreshold = DEFAULT_BULK_THRESHHOLD;
91  }
92 
93  String badTagsStr = ModuleSettings.getConfigSetting("CentralRepository", "db.badTags"); // NON-NLS
94  if (badTagsStr == null) {
95  badTagsStr = DEFAULT_BAD_TAGS;
96  }
97  if (badTagsStr.isEmpty()) {
98  badTags = new ArrayList<>();
99  } else {
100  badTags = new ArrayList<>(Arrays.asList(badTagsStr.split(",")));
101  }
102  }
103 
104  public void saveSettings() {
106 
107  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbName", getDbName()); // NON-NLS
108  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbDirectory", getDbDirectory()); // NON-NLS
109  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.bulkThreshold", Integer.toString(getBulkThreshold())); // NON-NLS
110  ModuleSettings.setConfigSetting("CentralRepository", "db.badTags", String.join(",", badTags)); // NON-NLS
111  }
112 
118  public boolean dbFileExists() {
119  File dbFile = new File(getFileNameWithPath());
120  if(! dbFile.exists()){
121  return false;
122  }
123  // It's unlikely, but make sure the file isn't actually a directory
124  return ( ! dbFile.isDirectory());
125  }
126 
132  public boolean dbDirectoryExists() {
133  // Ensure dbDirectory is a valid directory
134  File dbDir = new File(getDbDirectory());
135 
136  if (!dbDir.exists()) {
137  return false;
138  } else if (!dbDir.isDirectory()) {
139  return false;
140  }
141 
142  return true;
143 
144  }
145 
151  public boolean createDbDirectory() {
152  if (!dbDirectoryExists()) {
153  try {
154  File dbDir = new File(getDbDirectory());
155  Files.createDirectories(dbDir.toPath());
156  LOGGER.log(Level.INFO, "sqlite directory did not exist, created it at {0}.", getDbDirectory()); // NON-NLS
157  } catch (IOException | InvalidPathException | SecurityException ex) {
158  LOGGER.log(Level.SEVERE, "Failed to create sqlite database directory.", ex); // NON-NLS
159  return false;
160  }
161  }
162 
163  return true;
164  }
165 
170  public boolean deleteDatabase() {
171  File dbFile = new File(this.getFileNameWithPath());
172  return dbFile.delete();
173  }
174 
180  public String getConnectionURL() {
181  StringBuilder url = new StringBuilder();
182  url.append(getJDBCBaseURI());
183  url.append(getFileNameWithPath());
184 
185  return url.toString();
186  }
187 
196  private Connection getEphemeralConnection() {
197  if (!dbDirectoryExists()) {
198  return null;
199  }
200 
201  Connection conn;
202  try {
203  String url = getConnectionURL();
204  Class.forName(getDriver());
205  conn = DriverManager.getConnection(url);
206  } catch (ClassNotFoundException | SQLException ex) {
207  LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to sqlite.", ex); // NON-NLS
208  conn = null;
209  }
210  return conn;
211  }
212 
219  public boolean verifyConnection() {
220  Connection conn = getEphemeralConnection();
221  if (null == conn) {
222  return false;
223  }
224 
225  boolean result = EamDbUtil.executeValidationQuery(conn, VALIDATION_QUERY);
227  return result;
228  }
229 
236  public boolean verifyDatabaseSchema() {
237  Connection conn = getEphemeralConnection();
238  if (null == conn) {
239  return false;
240  }
241 
242  boolean result = EamDbUtil.schemaVersionIsSet(conn);
244  return result;
245  }
246 
258  public boolean initializeDatabaseSchema() {
259  // The "id" column is an alias for the built-in 64-bit int "rowid" column.
260  // It is autoincrementing by default and must be of type "integer primary key".
261  // We've omitted the autoincrement argument because we are not currently
262  // using the id value to search for specific rows, so we do not care
263  // if a rowid is re-used after an existing rows was previously deleted.
264  StringBuilder createOrganizationsTable = new StringBuilder();
265  createOrganizationsTable.append("CREATE TABLE IF NOT EXISTS organizations (");
266  createOrganizationsTable.append("id integer primary key autoincrement NOT NULL,");
267  createOrganizationsTable.append("org_name text NOT NULL,");
268  createOrganizationsTable.append("poc_name text NOT NULL,");
269  createOrganizationsTable.append("poc_email text NOT NULL,");
270  createOrganizationsTable.append("poc_phone text NOT NULL,");
271  createOrganizationsTable.append("CONSTRAINT org_name_unique UNIQUE (org_name)");
272  createOrganizationsTable.append(")");
273 
274  // NOTE: The organizations will only have a small number of rows, so
275  // an index is probably not worthwhile.
276  StringBuilder createCasesTable = new StringBuilder();
277  createCasesTable.append("CREATE TABLE IF NOT EXISTS cases (");
278  createCasesTable.append("id integer primary key autoincrement NOT NULL,");
279  createCasesTable.append("case_uid text NOT NULL,");
280  createCasesTable.append("org_id integer,");
281  createCasesTable.append("case_name text NOT NULL,");
282  createCasesTable.append("creation_date text NOT NULL,");
283  createCasesTable.append("case_number text,");
284  createCasesTable.append("examiner_name text,");
285  createCasesTable.append("examiner_email text,");
286  createCasesTable.append("examiner_phone text,");
287  createCasesTable.append("notes text,");
288  createCasesTable.append("CONSTRAINT case_uid_unique UNIQUE(case_uid) ON CONFLICT IGNORE,");
289  createCasesTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL");
290  createCasesTable.append(")");
291 
292  // NOTE: when there are few cases in the cases table, these indices may not be worthwhile
293  String casesIdx1 = "CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
294  String casesIdx2 = "CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
295 
296  StringBuilder createDataSourcesTable = new StringBuilder();
297  createDataSourcesTable.append("CREATE TABLE IF NOT EXISTS data_sources (");
298  createDataSourcesTable.append("id integer primary key autoincrement NOT NULL,");
299  createDataSourcesTable.append("device_id text NOT NULL,");
300  createDataSourcesTable.append("name text NOT NULL,");
301  createDataSourcesTable.append("CONSTRAINT device_id_unique UNIQUE(device_id)");
302  createDataSourcesTable.append(")");
303 
304  String dataSourceIdx1 = "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
305 
306  StringBuilder createReferenceSetsTable = new StringBuilder();
307  createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
308  createReferenceSetsTable.append("id integer primary key autoincrement NOT NULL,");
309  createReferenceSetsTable.append("org_id integer NOT NULL,");
310  createReferenceSetsTable.append("set_name text NOT NULL,");
311  createReferenceSetsTable.append("version text NOT NULL,");
312  createReferenceSetsTable.append("import_date text NOT NULL,");
313  createReferenceSetsTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
314  createReferenceSetsTable.append("CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
315  createReferenceSetsTable.append(")");
316 
317  String referenceSetsIdx1 = "CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
318 
319  // Each "%s" will be replaced with the relevant reference_TYPE table name.
320  StringBuilder createReferenceTypesTableTemplate = new StringBuilder();
321  createReferenceTypesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
322  createReferenceTypesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
323  createReferenceTypesTableTemplate.append("reference_set_id integer,");
324  createReferenceTypesTableTemplate.append("value text NOT NULL,");
325  createReferenceTypesTableTemplate.append("known_status integer NOT NULL,");
326  createReferenceTypesTableTemplate.append("comment text,");
327  createReferenceTypesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value) ON CONFLICT IGNORE,");
328  createReferenceTypesTableTemplate.append("foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
329  createReferenceTypesTableTemplate.append(")");
330 
331  // Each "%s" will be replaced with the relevant reference_TYPE table name.
332  String referenceTypesIdx1 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
333  String referenceTypesIdx2 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
334 
335  StringBuilder createCorrelationTypesTable = new StringBuilder();
336  createCorrelationTypesTable.append("CREATE TABLE IF NOT EXISTS correlation_types (");
337  createCorrelationTypesTable.append("id integer primary key autoincrement NOT NULL,");
338  createCorrelationTypesTable.append("display_name text NOT NULL,");
339  createCorrelationTypesTable.append("db_table_name text NOT NULL,");
340  createCorrelationTypesTable.append("supported integer NOT NULL,");
341  createCorrelationTypesTable.append("enabled integer NOT NULL,");
342  createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
343  createCorrelationTypesTable.append(")");
344 
345  // Each "%s" will be replaced with the relevant TYPE_instances table name.
346  StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
347  createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
348  createArtifactInstancesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
349  createArtifactInstancesTableTemplate.append("case_id integer,");
350  createArtifactInstancesTableTemplate.append("data_source_id integer,");
351  createArtifactInstancesTableTemplate.append("value text NOT NULL,");
352  createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
353  createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
354  createArtifactInstancesTableTemplate.append("comment text,");
355  createArtifactInstancesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(case_id, data_source_id, value, file_path) ON CONFLICT IGNORE,");
356  createArtifactInstancesTableTemplate.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
357  createArtifactInstancesTableTemplate.append("foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL");
358  createArtifactInstancesTableTemplate.append(")");
359 
360  // Each "%s" will be replaced with the relevant TYPE_instances table name.
361  String instancesIdx1 = "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
362  String instancesIdx2 = "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
363  String instancesIdx3 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
364  String instancesIdx4 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
365 
366  StringBuilder createDbInfoTable = new StringBuilder();
367  createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info (");
368  createDbInfoTable.append("id integer primary key NOT NULL,");
369  createDbInfoTable.append("name text NOT NULL,");
370  createDbInfoTable.append("value text NOT NULL");
371  createDbInfoTable.append(")");
372 
373  // NOTE: the db_info table currenly only has 1 row, so having an index
374  // provides no benefit.
375  Connection conn = null;
376  try {
377  conn = getEphemeralConnection();
378  if (null == conn) {
379  return false;
380  }
381  Statement stmt = conn.createStatement();
382  stmt.execute(PRAGMA_JOURNAL_WAL);
383  stmt.execute(PRAGMA_SYNC_OFF);
384  stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
385  stmt.execute(PRAGMA_ENCODING_UTF8);
386  stmt.execute(PRAGMA_PAGE_SIZE_4096);
387  stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
388 
389  stmt.execute(createOrganizationsTable.toString());
390 
391  stmt.execute(createCasesTable.toString());
392  stmt.execute(casesIdx1);
393  stmt.execute(casesIdx2);
394 
395  stmt.execute(createDataSourcesTable.toString());
396  stmt.execute(dataSourceIdx1);
397 
398  stmt.execute(createReferenceSetsTable.toString());
399  stmt.execute(referenceSetsIdx1);
400 
401  stmt.execute(createCorrelationTypesTable.toString());
402 
403  stmt.execute(createDbInfoTable.toString());
404 
405  // Create a separate instance and reference table for each artifact type
406  List<CorrelationAttribute.Type> DEFAULT_CORRELATION_TYPES = CorrelationAttribute.getDefaultCorrelationTypes();
407 
408  String reference_type_dbname;
409  String instance_type_dbname;
410  for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) {
411  reference_type_dbname = EamDbUtil.correlationTypeToReferenceTableName(type);
412  instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
413 
414  stmt.execute(String.format(createArtifactInstancesTableTemplate.toString(), instance_type_dbname, instance_type_dbname));
415  stmt.execute(String.format(instancesIdx1, instance_type_dbname, instance_type_dbname));
416  stmt.execute(String.format(instancesIdx2, instance_type_dbname, instance_type_dbname));
417  stmt.execute(String.format(instancesIdx3, instance_type_dbname, instance_type_dbname));
418  stmt.execute(String.format(instancesIdx4, instance_type_dbname, instance_type_dbname));
419 
420  // FUTURE: allow more than the FILES type
421  if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) {
422  stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
423  stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
424  stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
425  }
426  }
427  } catch (SQLException ex) {
428  LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS
429  return false;
430  } catch (EamDbException ex) {
431  LOGGER.log(Level.SEVERE, "Error getting default correlation types. Likely due to one or more Type's with an invalid db table name."); // NON-NLS
432  return false;
433  } finally {
435  }
436  return true;
437  }
438 
439  public boolean insertDefaultDatabaseContent() {
440  Connection conn = getEphemeralConnection();
441  if (null == conn) {
442  return false;
443  }
444 
445  boolean result = EamDbUtil.insertDefaultCorrelationTypes(conn)
448  return result;
449  }
450 
451  public boolean isChanged() {
452  String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS
453  String dbDirectoryString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS
454  String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS
455 
456  return !dbName.equals(dbNameString)
457  || !dbDirectory.equals(dbDirectoryString)
458  || !Integer.toString(bulkThreshold).equals(bulkThresholdString);
459  }
460 
464  public String getDbName() {
465  return dbName;
466  }
467 
473  public void setDbName(String dbName) throws EamDbException {
474  if (dbName == null || dbName.isEmpty()) {
475  throw new EamDbException("Invalid database file name. Cannot be null or empty."); // NON-NLS
476  } else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
477  throw new EamDbException("Invalid database file name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."); // NON-NLS
478  }
479 
480  this.dbName = dbName;
481  }
482 
486  public int getBulkThreshold() {
487  return bulkThreshold;
488  }
489 
493  public void setBulkThreshold(int bulkThreshold) throws EamDbException {
494  if (bulkThreshold > 0) {
495  this.bulkThreshold = bulkThreshold;
496  } else {
497  throw new EamDbException("Invalid bulk threshold."); // NON-NLS
498  }
499  }
500 
504  public List<String> getBadTags() {
505  return badTags;
506  }
507 
511  public void setBadTags(List<String> badTags) {
512  this.badTags = badTags;
513  }
514 
518  public String getDbDirectory() {
519  return dbDirectory;
520  }
521 
529  public void setDbDirectory(String dbDirectory) throws EamDbException {
530  if (dbDirectory != null && !dbDirectory.isEmpty()) {
531  this.dbDirectory = dbDirectory;
532  } else {
533  throw new EamDbException("Invalid directory for sqlite database. Cannot empty"); // NON-NLS
534  }
535  }
536 
542  public String getFileNameWithPath() {
543  return getDbDirectory() + File.separator + getDbName();
544  }
545 
549  public String getDriver() {
550  return JDBC_DRIVER;
551  }
552 
556  public String getValidationQuery() {
557  return VALIDATION_QUERY;
558  }
559 
563  public String getJDBCBaseURI() {
564  return JDBC_BASE_URI;
565  }
566 
567 }
static boolean executeValidationQuery(Connection conn, String validationQuery)
Definition: EamDbUtil.java:205
static String correlationTypeToReferenceTableName(CorrelationAttribute.Type type)
Definition: EamDbUtil.java:244
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
static boolean insertDefaultCorrelationTypes(Connection conn)
Definition: EamDbUtil.java:99
static String correlationTypeToInstanceTableName(CorrelationAttribute.Type type)
Definition: EamDbUtil.java:233
static String getConfigSetting(String moduleName, String settingName)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

Copyright © 2012-2016 Basis Technology. Generated on: Fri Sep 29 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.