19 package org.sleuthkit.autopsy.centralrepository.datamodel;
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;
69 if (dbName == null || dbName.isEmpty()) {
74 if (dbDirectory == null || dbDirectory.isEmpty()) {
80 if (bulkThresholdString == null || bulkThresholdString.isEmpty()) {
81 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
83 this.bulkThreshold = Integer.parseInt(bulkThresholdString);
84 if (getBulkThreshold() <= 0) {
85 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
88 }
catch (NumberFormatException ex) {
89 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
108 if (!dbFile.exists()) {
112 return (!dbFile.isDirectory());
124 if (!dbDir.exists()) {
126 }
else if (!dbDir.isDirectory()) {
143 Files.createDirectories(dbDir.toPath());
144 LOGGER.log(Level.INFO,
"sqlite directory did not exist, created it at {0}.",
getDbDirectory());
145 }
catch (IOException | InvalidPathException | SecurityException ex) {
146 LOGGER.log(Level.SEVERE,
"Failed to create sqlite database directory.", ex);
161 return dbFile.delete();
169 String getConnectionURL() {
170 StringBuilder url =
new StringBuilder();
171 url.append(getJDBCBaseURI());
174 return url.toString();
192 String url = getConnectionURL();
193 Class.forName(getDriver());
194 conn = DriverManager.getConnection(url);
195 }
catch (ClassNotFoundException | SQLException ex) {
196 LOGGER.log(Level.SEVERE,
"Failed to acquire ephemeral connection to sqlite.", ex);
253 StringBuilder createOrganizationsTable =
new StringBuilder();
254 createOrganizationsTable.append(
"CREATE TABLE IF NOT EXISTS organizations (");
255 createOrganizationsTable.append(
"id integer primary key autoincrement NOT NULL,");
256 createOrganizationsTable.append(
"org_name text NOT NULL,");
257 createOrganizationsTable.append(
"poc_name text NOT NULL,");
258 createOrganizationsTable.append(
"poc_email text NOT NULL,");
259 createOrganizationsTable.append(
"poc_phone text NOT NULL,");
260 createOrganizationsTable.append(
"CONSTRAINT org_name_unique UNIQUE (org_name)");
261 createOrganizationsTable.append(
")");
265 StringBuilder createCasesTable =
new StringBuilder();
266 createCasesTable.append(
"CREATE TABLE IF NOT EXISTS cases (");
267 createCasesTable.append(
"id integer primary key autoincrement NOT NULL,");
268 createCasesTable.append(
"case_uid text NOT NULL,");
269 createCasesTable.append(
"org_id integer,");
270 createCasesTable.append(
"case_name text NOT NULL,");
271 createCasesTable.append(
"creation_date text NOT NULL,");
272 createCasesTable.append(
"case_number text,");
273 createCasesTable.append(
"examiner_name text,");
274 createCasesTable.append(
"examiner_email text,");
275 createCasesTable.append(
"examiner_phone text,");
276 createCasesTable.append(
"notes text,");
277 createCasesTable.append(
"CONSTRAINT case_uid_unique UNIQUE(case_uid) ON CONFLICT IGNORE,");
278 createCasesTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL");
279 createCasesTable.append(
")");
282 String casesIdx1 =
"CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
283 String casesIdx2 =
"CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
285 StringBuilder createDataSourcesTable =
new StringBuilder();
286 createDataSourcesTable.append(
"CREATE TABLE IF NOT EXISTS data_sources (");
287 createDataSourcesTable.append(
"id integer primary key autoincrement NOT NULL,");
288 createDataSourcesTable.append(
"case_id integer NOT NULL,");
289 createDataSourcesTable.append(
"device_id text NOT NULL,");
290 createDataSourcesTable.append(
"name text NOT NULL,");
291 createDataSourcesTable.append(
"datasource_obj_id integer,");
292 createDataSourcesTable.append(
"md5 text DEFAULT NULL,");
293 createDataSourcesTable.append(
"sha1 text DEFAULT NULL,");
294 createDataSourcesTable.append(
"sha256 text DEFAULT NULL,");
295 createDataSourcesTable.append(
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
296 createDataSourcesTable.append(
"CONSTRAINT datasource_unique UNIQUE (case_id, device_id, name)");
297 createDataSourcesTable.append(
")");
299 String dataSourceIdx1 =
"CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
300 String dataSourceIdx2 =
"CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
302 StringBuilder createReferenceSetsTable =
new StringBuilder();
303 createReferenceSetsTable.append(
"CREATE TABLE IF NOT EXISTS reference_sets (");
304 createReferenceSetsTable.append(
"id integer primary key autoincrement NOT NULL,");
305 createReferenceSetsTable.append(
"org_id integer NOT NULL,");
306 createReferenceSetsTable.append(
"set_name text NOT NULL,");
307 createReferenceSetsTable.append(
"version text NOT NULL,");
308 createReferenceSetsTable.append(
"known_status integer NOT NULL,");
309 createReferenceSetsTable.append(
"read_only boolean NOT NULL,");
310 createReferenceSetsTable.append(
"type integer NOT NULL,");
311 createReferenceSetsTable.append(
"import_date text NOT NULL,");
312 createReferenceSetsTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
313 createReferenceSetsTable.append(
"CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
314 createReferenceSetsTable.append(
")");
316 String referenceSetsIdx1 =
"CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
319 StringBuilder createReferenceTypesTableTemplate =
new StringBuilder();
320 createReferenceTypesTableTemplate.append(
"CREATE TABLE IF NOT EXISTS %s (");
321 createReferenceTypesTableTemplate.append(
"id integer primary key autoincrement NOT NULL,");
322 createReferenceTypesTableTemplate.append(
"reference_set_id integer,");
323 createReferenceTypesTableTemplate.append(
"value text NOT NULL,");
324 createReferenceTypesTableTemplate.append(
"known_status integer NOT NULL,");
325 createReferenceTypesTableTemplate.append(
"comment text,");
326 createReferenceTypesTableTemplate.append(
"CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value) ON CONFLICT IGNORE,");
327 createReferenceTypesTableTemplate.append(
"foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
328 createReferenceTypesTableTemplate.append(
")");
331 String referenceTypesIdx1 =
"CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
332 String referenceTypesIdx2 =
"CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
334 StringBuilder createCorrelationTypesTable =
new StringBuilder();
335 createCorrelationTypesTable.append(
"CREATE TABLE IF NOT EXISTS correlation_types (");
336 createCorrelationTypesTable.append(
"id integer primary key autoincrement NOT NULL,");
337 createCorrelationTypesTable.append(
"display_name text NOT NULL,");
338 createCorrelationTypesTable.append(
"db_table_name text NOT NULL,");
339 createCorrelationTypesTable.append(
"supported integer NOT NULL,");
340 createCorrelationTypesTable.append(
"enabled integer NOT NULL,");
341 createCorrelationTypesTable.append(
"CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
342 createCorrelationTypesTable.append(
")");
344 String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
346 String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
347 String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
348 String instancesValueIdx = getAddValueIndexTemplate();
349 String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
350 String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
354 Connection conn = null;
360 Statement stmt = conn.createStatement();
361 stmt.execute(PRAGMA_JOURNAL_WAL);
362 stmt.execute(PRAGMA_SYNC_OFF);
363 stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
364 stmt.execute(PRAGMA_ENCODING_UTF8);
365 stmt.execute(PRAGMA_PAGE_SIZE_4096);
366 stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
368 stmt.execute(createOrganizationsTable.toString());
370 stmt.execute(createCasesTable.toString());
371 stmt.execute(casesIdx1);
372 stmt.execute(casesIdx2);
374 stmt.execute(createDataSourcesTable.toString());
375 stmt.execute(dataSourceIdx1);
376 stmt.execute(dataSourceIdx2);
378 stmt.execute(createReferenceSetsTable.toString());
379 stmt.execute(referenceSetsIdx1);
381 stmt.execute(createCorrelationTypesTable.toString());
388 stmt.execute(
"CREATE TABLE db_info (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
389 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MAJOR_VERSION_KEY +
"', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() +
"')");
390 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MINOR_VERSION_KEY +
"', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() +
"')");
391 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MAJOR_VERSION_KEY +
"', '" + CURRENT_DB_SCHEMA_VERSION.getMajor() +
"')");
392 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MINOR_VERSION_KEY +
"', '" + CURRENT_DB_SCHEMA_VERSION.getMinor() +
"')");
397 String reference_type_dbname;
398 String instance_type_dbname;
403 stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
404 stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
405 stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
406 stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
407 stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
408 stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
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));
417 }
catch (SQLException ex) {
418 LOGGER.log(Level.SEVERE,
"Error initializing db schema.", 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.");
436 static String getCreateArtifactInstancesTableTemplate() {
438 StringBuilder createArtifactInstancesTableTemplate =
new StringBuilder();
439 createArtifactInstancesTableTemplate.append(
"CREATE TABLE IF NOT EXISTS %s (");
440 createArtifactInstancesTableTemplate.append(
"id integer primary key autoincrement NOT NULL,");
441 createArtifactInstancesTableTemplate.append(
"case_id integer NOT NULL,");
442 createArtifactInstancesTableTemplate.append(
"data_source_id integer NOT NULL,");
443 createArtifactInstancesTableTemplate.append(
"value text NOT NULL,");
444 createArtifactInstancesTableTemplate.append(
"file_path text NOT NULL,");
445 createArtifactInstancesTableTemplate.append(
"known_status integer NOT NULL,");
446 createArtifactInstancesTableTemplate.append(
"comment text,");
447 createArtifactInstancesTableTemplate.append(
"file_obj_id integer,");
448 createArtifactInstancesTableTemplate.append(
"CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path) ON CONFLICT IGNORE,");
449 createArtifactInstancesTableTemplate.append(
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,");
450 createArtifactInstancesTableTemplate.append(
"foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL");
451 createArtifactInstancesTableTemplate.append(
")");
452 return createArtifactInstancesTableTemplate.toString();
463 static String getAddCaseIdIndexTemplate() {
465 return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
476 static String getAddDataSourceIdIndexTemplate() {
478 return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
489 static String getAddValueIndexTemplate() {
491 return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
502 static String getAddKnownStatusIndexTemplate() {
504 return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
515 static String getAddObjectIdIndexTemplate() {
517 return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
531 boolean isChanged() {
536 return !dbName.equals(dbNameString)
537 || !dbDirectory.equals(dbDirectoryString)
538 || !Integer.toString(bulkThreshold).equals(bulkThresholdString);
554 if (dbName == null || dbName.isEmpty()) {
555 throw new EamDbException(
"Invalid database file name. Cannot be null or empty.");
556 }
else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
557 throw new EamDbException(
"Invalid database file name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.");
566 int getBulkThreshold() {
574 if (bulkThreshold > 0) {
577 throw new EamDbException(
"Invalid bulk threshold.");
596 if (dbDirectory != null && !dbDirectory.isEmpty()) {
599 throw new EamDbException(
"Invalid directory for sqlite database. Cannot empty");
622 String getValidationQuery() {
629 String getJDBCBaseURI() {
boolean createDbDirectory()
boolean insertDefaultDatabaseContent()
static String correlationTypeToInstanceTableName(CorrelationAttributeInstance.Type type)
static boolean schemaVersionIsSet(Connection conn)
static final String PRAGMA_JOURNAL_WAL
static final String PRAGMA_SYNC_OFF
static boolean executeValidationQuery(Connection conn, String validationQuery)
String getFileNameWithPath()
static List< CorrelationAttributeInstance.Type > getDefaultCorrelationTypes()
boolean verifyConnection()
boolean initializeDatabaseSchema()
Connection getEphemeralConnection()
final String VALIDATION_QUERY
final String DEFAULT_DBDIRECTORY
static final String PRAGMA_READ_UNCOMMITTED_TRUE
final String JDBC_BASE_URI
static void closeConnection(Connection conn)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
boolean verifyDatabaseSchema()
static final String PRAGMA_PAGE_SIZE_4096
static boolean insertDefaultCorrelationTypes(Connection conn)
static final Logger LOGGER
static String getConfigSetting(String moduleName, String settingName)
static final String PRAGMA_SYNC_NORMAL
final String DB_NAMES_REGEX
final String DEFAULT_DBNAME
void setDbName(String dbName)
synchronized static Logger getLogger(String name)
static String correlationTypeToReferenceTableName(CorrelationAttributeInstance.Type type)
static final String PRAGMA_FOREIGN_KEYS_ON
static final String PRAGMA_ENCODING_UTF8
void setDbDirectory(String dbDirectory)
boolean dbDirectoryExists()
static final int FILES_TYPE_ID