Autopsy  4.7.0
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.List;
30 import java.util.logging.Level;
31 import java.util.regex.Pattern;
35 
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 String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS
47  private final String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS
48  private final String VALIDATION_QUERY = "SELECT count(*) from sqlite_master"; // NON-NLS
49  private static final String PRAGMA_SYNC_OFF = "PRAGMA synchronous = OFF";
50  private static final String PRAGMA_SYNC_NORMAL = "PRAGMA synchronous = NORMAL";
51  private static final String PRAGMA_JOURNAL_WAL = "PRAGMA journal_mode = WAL";
52  private static final String PRAGMA_READ_UNCOMMITTED_TRUE = "PRAGMA read_uncommitted = True";
53  private static final String PRAGMA_ENCODING_UTF8 = "PRAGMA encoding = 'UTF-8'";
54  private static final String PRAGMA_PAGE_SIZE_4096 = "PRAGMA page_size = 4096";
55  private static final String PRAGMA_FOREIGN_KEYS_ON = "PRAGMA foreign_keys = ON";
56  private final String DB_NAMES_REGEX = "[a-z][a-z0-9_]*(\\.db)?";
57  private String dbName;
58  private String dbDirectory;
59  private int bulkThreshold;
60 
62  loadSettings();
63  }
64 
65  public void loadSettings() {
66  dbName = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS
67  if (dbName == null || dbName.isEmpty()) {
68  dbName = DEFAULT_DBNAME;
69  }
70 
71  dbDirectory = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS
72  if (dbDirectory == null || dbDirectory.isEmpty()) {
73  dbDirectory = DEFAULT_DBDIRECTORY;
74  }
75 
76  try {
77  String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS
78  if (bulkThresholdString == null || bulkThresholdString.isEmpty()) {
79  this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
80  } else {
81  this.bulkThreshold = Integer.parseInt(bulkThresholdString);
82  if (getBulkThreshold() <= 0) {
83  this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
84  }
85  }
86  } catch (NumberFormatException ex) {
87  this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
88  }
89  }
90 
91  public void saveSettings() {
93 
94  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbName", getDbName()); // NON-NLS
95  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.dbDirectory", getDbDirectory()); // NON-NLS
96  ModuleSettings.setConfigSetting("CentralRepository", "db.sqlite.bulkThreshold", Integer.toString(getBulkThreshold())); // NON-NLS
97  }
98 
104  public boolean dbFileExists() {
105  File dbFile = new File(getFileNameWithPath());
106  if(! dbFile.exists()){
107  return false;
108  }
109  // It's unlikely, but make sure the file isn't actually a directory
110  return ( ! dbFile.isDirectory());
111  }
112 
118  public boolean dbDirectoryExists() {
119  // Ensure dbDirectory is a valid directory
120  File dbDir = new File(getDbDirectory());
121 
122  if (!dbDir.exists()) {
123  return false;
124  } else if (!dbDir.isDirectory()) {
125  return false;
126  }
127 
128  return true;
129 
130  }
131 
137  public boolean createDbDirectory() {
138  if (!dbDirectoryExists()) {
139  try {
140  File dbDir = new File(getDbDirectory());
141  Files.createDirectories(dbDir.toPath());
142  LOGGER.log(Level.INFO, "sqlite directory did not exist, created it at {0}.", getDbDirectory()); // NON-NLS
143  } catch (IOException | InvalidPathException | SecurityException ex) {
144  LOGGER.log(Level.SEVERE, "Failed to create sqlite database directory.", ex); // NON-NLS
145  return false;
146  }
147  }
148 
149  return true;
150  }
151 
156  public boolean deleteDatabase() {
157  File dbFile = new File(this.getFileNameWithPath());
158  return dbFile.delete();
159  }
160 
166  String getConnectionURL() {
167  StringBuilder url = new StringBuilder();
168  url.append(getJDBCBaseURI());
169  url.append(getFileNameWithPath());
170 
171  return url.toString();
172  }
173 
182  private Connection getEphemeralConnection() {
183  if (!dbDirectoryExists()) {
184  return null;
185  }
186 
187  Connection conn;
188  try {
189  String url = getConnectionURL();
190  Class.forName(getDriver());
191  conn = DriverManager.getConnection(url);
192  } catch (ClassNotFoundException | SQLException ex) {
193  LOGGER.log(Level.SEVERE, "Failed to acquire ephemeral connection to sqlite.", ex); // NON-NLS
194  conn = null;
195  }
196  return conn;
197  }
198 
205  public boolean verifyConnection() {
206  Connection conn = getEphemeralConnection();
207  if (null == conn) {
208  return false;
209  }
210 
211  boolean result = EamDbUtil.executeValidationQuery(conn, VALIDATION_QUERY);
213  return result;
214  }
215 
222  public boolean verifyDatabaseSchema() {
223  Connection conn = getEphemeralConnection();
224  if (null == conn) {
225  return false;
226  }
227 
228  boolean result = EamDbUtil.schemaVersionIsSet(conn);
230  return result;
231  }
232 
244  public boolean initializeDatabaseSchema() {
245  // The "id" column is an alias for the built-in 64-bit int "rowid" column.
246  // It is autoincrementing by default and must be of type "integer primary key".
247  // We've omitted the autoincrement argument because we are not currently
248  // using the id value to search for specific rows, so we do not care
249  // if a rowid is re-used after an existing rows was previously deleted.
250  StringBuilder createOrganizationsTable = new StringBuilder();
251  createOrganizationsTable.append("CREATE TABLE IF NOT EXISTS organizations (");
252  createOrganizationsTable.append("id integer primary key autoincrement NOT NULL,");
253  createOrganizationsTable.append("org_name text NOT NULL,");
254  createOrganizationsTable.append("poc_name text NOT NULL,");
255  createOrganizationsTable.append("poc_email text NOT NULL,");
256  createOrganizationsTable.append("poc_phone text NOT NULL,");
257  createOrganizationsTable.append("CONSTRAINT org_name_unique UNIQUE (org_name)");
258  createOrganizationsTable.append(")");
259 
260  // NOTE: The organizations will only have a small number of rows, so
261  // an index is probably not worthwhile.
262  StringBuilder createCasesTable = new StringBuilder();
263  createCasesTable.append("CREATE TABLE IF NOT EXISTS cases (");
264  createCasesTable.append("id integer primary key autoincrement NOT NULL,");
265  createCasesTable.append("case_uid text NOT NULL,");
266  createCasesTable.append("org_id integer,");
267  createCasesTable.append("case_name text NOT NULL,");
268  createCasesTable.append("creation_date text NOT NULL,");
269  createCasesTable.append("case_number text,");
270  createCasesTable.append("examiner_name text,");
271  createCasesTable.append("examiner_email text,");
272  createCasesTable.append("examiner_phone text,");
273  createCasesTable.append("notes text,");
274  createCasesTable.append("CONSTRAINT case_uid_unique UNIQUE(case_uid) ON CONFLICT IGNORE,");
275  createCasesTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL");
276  createCasesTable.append(")");
277 
278  // NOTE: when there are few cases in the cases table, these indices may not be worthwhile
279  String casesIdx1 = "CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
280  String casesIdx2 = "CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
281 
282  StringBuilder createDataSourcesTable = new StringBuilder();
283  createDataSourcesTable.append("CREATE TABLE IF NOT EXISTS data_sources (");
284  createDataSourcesTable.append("id integer primary key autoincrement NOT NULL,");
285  createDataSourcesTable.append("case_id integer NOT NULL,");
286  createDataSourcesTable.append("device_id text NOT NULL,");
287  createDataSourcesTable.append("name text NOT NULL,");
288  createDataSourcesTable.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
289  createDataSourcesTable.append("CONSTRAINT datasource_unique UNIQUE (case_id, device_id, name)");
290  createDataSourcesTable.append(")");
291 
292  String dataSourceIdx1 = "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
293 
294  StringBuilder createReferenceSetsTable = new StringBuilder();
295  createReferenceSetsTable.append("CREATE TABLE IF NOT EXISTS reference_sets (");
296  createReferenceSetsTable.append("id integer primary key autoincrement NOT NULL,");
297  createReferenceSetsTable.append("org_id integer NOT NULL,");
298  createReferenceSetsTable.append("set_name text NOT NULL,");
299  createReferenceSetsTable.append("version text NOT NULL,");
300  createReferenceSetsTable.append("known_status integer NOT NULL,");
301  createReferenceSetsTable.append("read_only boolean NOT NULL,");
302  createReferenceSetsTable.append("type integer NOT NULL,");
303  createReferenceSetsTable.append("import_date text NOT NULL,");
304  createReferenceSetsTable.append("foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
305  createReferenceSetsTable.append("CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
306  createReferenceSetsTable.append(")");
307 
308  String referenceSetsIdx1 = "CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
309 
310  // Each "%s" will be replaced with the relevant reference_TYPE table name.
311  StringBuilder createReferenceTypesTableTemplate = new StringBuilder();
312  createReferenceTypesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
313  createReferenceTypesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
314  createReferenceTypesTableTemplate.append("reference_set_id integer,");
315  createReferenceTypesTableTemplate.append("value text NOT NULL,");
316  createReferenceTypesTableTemplate.append("known_status integer NOT NULL,");
317  createReferenceTypesTableTemplate.append("comment text,");
318  createReferenceTypesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value) ON CONFLICT IGNORE,");
319  createReferenceTypesTableTemplate.append("foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
320  createReferenceTypesTableTemplate.append(")");
321 
322  // Each "%s" will be replaced with the relevant reference_TYPE table name.
323  String referenceTypesIdx1 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
324  String referenceTypesIdx2 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
325 
326  StringBuilder createCorrelationTypesTable = new StringBuilder();
327  createCorrelationTypesTable.append("CREATE TABLE IF NOT EXISTS correlation_types (");
328  createCorrelationTypesTable.append("id integer primary key autoincrement NOT NULL,");
329  createCorrelationTypesTable.append("display_name text NOT NULL,");
330  createCorrelationTypesTable.append("db_table_name text NOT NULL,");
331  createCorrelationTypesTable.append("supported integer NOT NULL,");
332  createCorrelationTypesTable.append("enabled integer NOT NULL,");
333  createCorrelationTypesTable.append("CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
334  createCorrelationTypesTable.append(")");
335 
336  // Each "%s" will be replaced with the relevant TYPE_instances table name.
337  StringBuilder createArtifactInstancesTableTemplate = new StringBuilder();
338  createArtifactInstancesTableTemplate.append("CREATE TABLE IF NOT EXISTS %s (");
339  createArtifactInstancesTableTemplate.append("id integer primary key autoincrement NOT NULL,");
340  createArtifactInstancesTableTemplate.append("case_id integer NOT NULL,");
341  createArtifactInstancesTableTemplate.append("data_source_id integer NOT NULL,");
342  createArtifactInstancesTableTemplate.append("value text NOT NULL,");
343  createArtifactInstancesTableTemplate.append("file_path text NOT NULL,");
344  createArtifactInstancesTableTemplate.append("known_status integer NOT NULL,");
345  createArtifactInstancesTableTemplate.append("comment text,");
346  createArtifactInstancesTableTemplate.append("CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path) ON CONFLICT IGNORE,");
347  createArtifactInstancesTableTemplate.append("foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
348  createArtifactInstancesTableTemplate.append("foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL");
349  createArtifactInstancesTableTemplate.append(")");
350 
351  // Each "%s" will be replaced with the relevant TYPE_instances table name.
352  String instancesIdx1 = "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
353  String instancesIdx2 = "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
354  String instancesIdx3 = "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
355  String instancesIdx4 = "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
356 
357  StringBuilder createDbInfoTable = new StringBuilder();
358  createDbInfoTable.append("CREATE TABLE IF NOT EXISTS db_info (");
359  createDbInfoTable.append("id integer primary key NOT NULL,");
360  createDbInfoTable.append("name text NOT NULL,");
361  createDbInfoTable.append("value text NOT NULL");
362  createDbInfoTable.append(")");
363 
364  // NOTE: the db_info table currenly only has 1 row, so having an index
365  // provides no benefit.
366  Connection conn = null;
367  try {
368  conn = getEphemeralConnection();
369  if (null == conn) {
370  return false;
371  }
372  Statement stmt = conn.createStatement();
373  stmt.execute(PRAGMA_JOURNAL_WAL);
374  stmt.execute(PRAGMA_SYNC_OFF);
375  stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
376  stmt.execute(PRAGMA_ENCODING_UTF8);
377  stmt.execute(PRAGMA_PAGE_SIZE_4096);
378  stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
379 
380  stmt.execute(createOrganizationsTable.toString());
381 
382  stmt.execute(createCasesTable.toString());
383  stmt.execute(casesIdx1);
384  stmt.execute(casesIdx2);
385 
386  stmt.execute(createDataSourcesTable.toString());
387  stmt.execute(dataSourceIdx1);
388 
389  stmt.execute(createReferenceSetsTable.toString());
390  stmt.execute(referenceSetsIdx1);
391 
392  stmt.execute(createCorrelationTypesTable.toString());
393 
394  stmt.execute(createDbInfoTable.toString());
395 
396  // Create a separate instance and reference table for each artifact type
397  List<CorrelationAttribute.Type> DEFAULT_CORRELATION_TYPES = CorrelationAttribute.getDefaultCorrelationTypes();
398 
399  String reference_type_dbname;
400  String instance_type_dbname;
401  for (CorrelationAttribute.Type type : DEFAULT_CORRELATION_TYPES) {
402  reference_type_dbname = EamDbUtil.correlationTypeToReferenceTableName(type);
403  instance_type_dbname = EamDbUtil.correlationTypeToInstanceTableName(type);
404 
405  stmt.execute(String.format(createArtifactInstancesTableTemplate.toString(), instance_type_dbname, instance_type_dbname));
406  stmt.execute(String.format(instancesIdx1, instance_type_dbname, instance_type_dbname));
407  stmt.execute(String.format(instancesIdx2, instance_type_dbname, instance_type_dbname));
408  stmt.execute(String.format(instancesIdx3, instance_type_dbname, instance_type_dbname));
409  stmt.execute(String.format(instancesIdx4, instance_type_dbname, instance_type_dbname));
410 
411  // FUTURE: allow more than the FILES type
412  if (type.getId() == CorrelationAttribute.FILES_TYPE_ID) {
413  stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
414  stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
415  stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
416  }
417  }
418  } catch (SQLException ex) {
419  LOGGER.log(Level.SEVERE, "Error initializing db schema.", ex); // NON-NLS
420  return false;
421  } catch (EamDbException ex) {
422  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
423  return false;
424  } finally {
426  }
427  return true;
428  }
429 
430  public boolean insertDefaultDatabaseContent() {
431  Connection conn = getEphemeralConnection();
432  if (null == conn) {
433  return false;
434  }
435 
436  boolean result = EamDbUtil.insertDefaultCorrelationTypes(conn)
437  && EamDbUtil.updateSchemaVersion(conn)
438  && EamDbUtil.insertDefaultOrganization(conn);
440  return result;
441  }
442 
443  boolean isChanged() {
444  String dbNameString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbName"); // NON-NLS
445  String dbDirectoryString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.dbDirectory"); // NON-NLS
446  String bulkThresholdString = ModuleSettings.getConfigSetting("CentralRepository", "db.sqlite.bulkThreshold"); // NON-NLS
447 
448  return !dbName.equals(dbNameString)
449  || !dbDirectory.equals(dbDirectoryString)
450  || !Integer.toString(bulkThreshold).equals(bulkThresholdString);
451  }
452 
456  public String getDbName() {
457  return dbName;
458  }
459 
465  public void setDbName(String dbName) throws EamDbException {
466  if (dbName == null || dbName.isEmpty()) {
467  throw new EamDbException("Invalid database file name. Cannot be null or empty."); // NON-NLS
468  } else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
469  throw new EamDbException("Invalid database file name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."); // NON-NLS
470  }
471 
472  this.dbName = dbName;
473  }
474 
478  int getBulkThreshold() {
479  return bulkThreshold;
480  }
481 
485  void setBulkThreshold(int bulkThreshold) throws EamDbException {
486  if (bulkThreshold > 0) {
487  this.bulkThreshold = bulkThreshold;
488  } else {
489  throw new EamDbException("Invalid bulk threshold."); // NON-NLS
490  }
491  }
492 
493 
494 
498  public String getDbDirectory() {
499  return dbDirectory;
500  }
501 
509  public void setDbDirectory(String dbDirectory) throws EamDbException {
510  if (dbDirectory != null && !dbDirectory.isEmpty()) {
511  this.dbDirectory = dbDirectory;
512  } else {
513  throw new EamDbException("Invalid directory for sqlite database. Cannot empty"); // NON-NLS
514  }
515  }
516 
522  public String getFileNameWithPath() {
523  return getDbDirectory() + File.separator + getDbName();
524  }
525 
529  String getDriver() {
530  return JDBC_DRIVER;
531  }
532 
536  String getValidationQuery() {
537  return VALIDATION_QUERY;
538  }
539 
543  String getJDBCBaseURI() {
544  return JDBC_BASE_URI;
545  }
546 
547 }
static boolean executeValidationQuery(Connection conn, String validationQuery)
Definition: EamDbUtil.java:321
static String correlationTypeToReferenceTableName(CorrelationAttribute.Type type)
Definition: EamDbUtil.java:360
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
static boolean insertDefaultCorrelationTypes(Connection conn)
Definition: EamDbUtil.java:102
static String correlationTypeToInstanceTableName(CorrelationAttribute.Type type)
Definition: EamDbUtil.java:349
static String getConfigSetting(String moduleName, String settingName)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124

Copyright © 2012-2016 Basis Technology. Generated on: Mon Jun 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.