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 createReferenceSetsTable =
new StringBuilder();
286 createReferenceSetsTable.append(
"CREATE TABLE IF NOT EXISTS reference_sets (");
287 createReferenceSetsTable.append(
"id integer primary key autoincrement NOT NULL,");
288 createReferenceSetsTable.append(
"org_id integer NOT NULL,");
289 createReferenceSetsTable.append(
"set_name text NOT NULL,");
290 createReferenceSetsTable.append(
"version text NOT NULL,");
291 createReferenceSetsTable.append(
"known_status integer NOT NULL,");
292 createReferenceSetsTable.append(
"read_only boolean NOT NULL,");
293 createReferenceSetsTable.append(
"type integer NOT NULL,");
294 createReferenceSetsTable.append(
"import_date text NOT NULL,");
295 createReferenceSetsTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
296 createReferenceSetsTable.append(
"CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
297 createReferenceSetsTable.append(
")");
299 String referenceSetsIdx1 =
"CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
302 StringBuilder createReferenceTypesTableTemplate =
new StringBuilder();
303 createReferenceTypesTableTemplate.append(
"CREATE TABLE IF NOT EXISTS %s (");
304 createReferenceTypesTableTemplate.append(
"id integer primary key autoincrement NOT NULL,");
305 createReferenceTypesTableTemplate.append(
"reference_set_id integer,");
306 createReferenceTypesTableTemplate.append(
"value text NOT NULL,");
307 createReferenceTypesTableTemplate.append(
"known_status integer NOT NULL,");
308 createReferenceTypesTableTemplate.append(
"comment text,");
309 createReferenceTypesTableTemplate.append(
"CONSTRAINT %s_multi_unique UNIQUE(reference_set_id, value) ON CONFLICT IGNORE,");
310 createReferenceTypesTableTemplate.append(
"foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
311 createReferenceTypesTableTemplate.append(
")");
314 String referenceTypesIdx1 =
"CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
315 String referenceTypesIdx2 =
"CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
317 StringBuilder createCorrelationTypesTable =
new StringBuilder();
318 createCorrelationTypesTable.append(
"CREATE TABLE IF NOT EXISTS correlation_types (");
319 createCorrelationTypesTable.append(
"id integer primary key autoincrement NOT NULL,");
320 createCorrelationTypesTable.append(
"display_name text NOT NULL,");
321 createCorrelationTypesTable.append(
"db_table_name text NOT NULL,");
322 createCorrelationTypesTable.append(
"supported integer NOT NULL,");
323 createCorrelationTypesTable.append(
"enabled integer NOT NULL,");
324 createCorrelationTypesTable.append(
"CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
325 createCorrelationTypesTable.append(
")");
327 String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
329 String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
330 String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
331 String instancesValueIdx = getAddValueIndexTemplate();
332 String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
333 String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
337 Connection conn = null;
343 Statement stmt = conn.createStatement();
344 stmt.execute(PRAGMA_JOURNAL_WAL);
345 stmt.execute(PRAGMA_SYNC_OFF);
346 stmt.execute(PRAGMA_READ_UNCOMMITTED_TRUE);
347 stmt.execute(PRAGMA_ENCODING_UTF8);
348 stmt.execute(PRAGMA_PAGE_SIZE_4096);
349 stmt.execute(PRAGMA_FOREIGN_KEYS_ON);
351 stmt.execute(createOrganizationsTable.toString());
353 stmt.execute(createCasesTable.toString());
354 stmt.execute(casesIdx1);
355 stmt.execute(casesIdx2);
357 stmt.execute(getCreateDataSourcesTableStatement());
358 stmt.execute(getAddDataSourcesNameIndexStatement());
359 stmt.execute(getAddDataSourcesObjectIdIndexStatement());
361 stmt.execute(createReferenceSetsTable.toString());
362 stmt.execute(referenceSetsIdx1);
364 stmt.execute(createCorrelationTypesTable.toString());
371 stmt.execute(
"CREATE TABLE db_info (id INTEGER PRIMARY KEY, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
372 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MAJOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() +
"')");
373 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MINOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() +
"')");
374 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MAJOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() +
"')");
375 stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MINOR_VERSION_KEY +
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() +
"')");
380 String reference_type_dbname;
381 String instance_type_dbname;
386 stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
387 stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
388 stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
389 stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
390 stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
391 stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
395 stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
396 stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
397 stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
400 }
catch (SQLException ex) {
401 LOGGER.log(Level.SEVERE,
"Error initializing db schema.", ex);
404 LOGGER.log(Level.SEVERE,
"Error getting default correlation types. Likely due to one or more Type's with an invalid db table name.");
419 static String getCreateArtifactInstancesTableTemplate() {
421 return "CREATE TABLE IF NOT EXISTS %s (id integer primary key autoincrement NOT NULL,"
422 +
"case_id integer NOT NULL,data_source_id integer NOT NULL,value text NOT NULL,"
423 +
"file_path text NOT NULL,known_status integer NOT NULL,comment text,file_obj_id integer,"
424 +
"CONSTRAINT %s_multi_unique UNIQUE(data_source_id, value, file_path) ON CONFLICT IGNORE,"
425 +
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
426 +
"foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)";
436 static String getCreateDataSourcesTableStatement() {
437 return "CREATE TABLE IF NOT EXISTS data_sources (id integer primary key autoincrement NOT NULL,"
438 +
"case_id integer NOT NULL,device_id text NOT NULL,name text NOT NULL,datasource_obj_id integer,"
439 +
"md5 text DEFAULT NULL,sha1 text DEFAULT NULL,sha256 text DEFAULT NULL,"
440 +
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL,"
441 +
"CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
451 static String getAddDataSourcesNameIndexStatement() {
452 return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
462 static String getAddDataSourcesObjectIdIndexStatement() {
463 return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
474 static String getAddCaseIdIndexTemplate() {
476 return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
487 static String getAddDataSourceIdIndexTemplate() {
489 return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
500 static String getAddValueIndexTemplate() {
502 return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
513 static String getAddKnownStatusIndexTemplate() {
515 return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
526 static String getAddObjectIdIndexTemplate() {
528 return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
542 boolean isChanged() {
547 return !dbName.equals(dbNameString)
548 || !dbDirectory.equals(dbDirectoryString)
549 || !Integer.toString(bulkThreshold).equals(bulkThresholdString);
565 if (dbName == null || dbName.isEmpty()) {
566 throw new EamDbException(
"Invalid database file name. Cannot be null or empty.");
567 }
else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
568 throw new EamDbException(
"Invalid database file name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.");
577 int getBulkThreshold() {
585 if (bulkThreshold > 0) {
588 throw new EamDbException(
"Invalid bulk threshold.");
607 if (dbDirectory != null && !dbDirectory.isEmpty()) {
610 throw new EamDbException(
"Invalid directory for sqlite database. Cannot empty");
633 String getValidationQuery() {
640 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()
static final String VALIDATION_QUERY
static final String DEFAULT_DBDIRECTORY
static final String PRAGMA_READ_UNCOMMITTED_TRUE
static 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
static final String DB_NAMES_REGEX
static 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 JDBC_DRIVER
static final String PRAGMA_ENCODING_UTF8
void setDbDirectory(String dbDirectory)
boolean dbDirectoryExists()
static final int FILES_TYPE_ID