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