19 package org.sleuthkit.autopsy.centralrepository.datamodel;
 
   21 import java.sql.Connection;
 
   22 import java.sql.DriverManager;
 
   23 import java.sql.PreparedStatement;
 
   24 import java.sql.ResultSet;
 
   25 import java.sql.SQLException;
 
   26 import java.sql.Statement;
 
   27 import java.util.List;
 
   28 import java.util.Properties;
 
   29 import java.util.logging.Level;
 
   30 import java.util.regex.Pattern;
 
   53     private final static String 
JDBC_DRIVER = 
"org.postgresql.Driver"; 
 
   69         if (host == null || host.isEmpty()) {
 
   75             if (portString == null || portString.isEmpty()) {
 
   78                 port = Integer.parseInt(portString);
 
   79                 if (port < 0 || port > 65535) {
 
   83         } 
catch (NumberFormatException ex) {
 
   88         if (dbName == null || dbName.isEmpty()) {
 
   94             if (bulkThresholdString == null || bulkThresholdString.isEmpty()) {
 
   95                 this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
 
   97                 this.bulkThreshold = Integer.parseInt(bulkThresholdString);
 
   98                 if (getBulkThreshold() <= 0) {
 
   99                     this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
 
  102         } 
catch (NumberFormatException ex) {
 
  103             this.bulkThreshold = AbstractSqlEamDb.DEFAULT_BULK_THRESHHOLD;
 
  107         if (userName == null || userName.isEmpty()) {
 
  112         if (password == null || password.isEmpty()) {
 
  118                 LOGGER.log(Level.WARNING, 
"Failed to convert password from hex text to text.", ex);
 
  133             LOGGER.log(Level.SEVERE, 
"Failed to convert password from text to hex text.", ex);
 
  145     String getConnectionURL(
boolean usePostgresDb) {
 
  146         StringBuilder url = 
new StringBuilder();
 
  147         url.append(getJDBCBaseURI());
 
  151             url.append(
"postgres"); 
 
  156         return url.toString();
 
  168             String url = getConnectionURL(usePostgresDb);
 
  169             Properties props = 
new Properties();
 
  173             Class.forName(getDriver());
 
  174             conn = DriverManager.getConnection(url, props);
 
  175         } 
catch (ClassNotFoundException | SQLException ex) {
 
  178             LOGGER.log(Level.SEVERE, 
"Failed to acquire ephemeral connection to postgresql."); 
 
  212         String sql = 
"SELECT datname FROM pg_catalog.pg_database WHERE lower(datname) = lower(?) LIMIT 1"; 
 
  213         PreparedStatement ps = null;
 
  216             ps = conn.prepareStatement(sql);
 
  218             rs = ps.executeQuery();
 
  222         } 
catch (SQLException ex) {
 
  223             LOGGER.log(Level.SEVERE, 
"Failed to execute database existance query.", ex); 
 
  257         String sql = 
"CREATE DATABASE %s OWNER %s"; 
 
  260             stmt = conn.createStatement();
 
  262         } 
catch (SQLException ex) {
 
  263             LOGGER.log(Level.SEVERE, 
"Failed to execute create database statement.", ex); 
 
  278         String sql = 
"DROP DATABASE %s"; 
 
  281             stmt = conn.createStatement();
 
  282             stmt.execute(String.format(sql, 
getDbName()));
 
  283         } 
catch (SQLException ex) {
 
  284             LOGGER.log(Level.SEVERE, 
"Failed to execute drop database statement.", ex); 
 
  310         StringBuilder createOrganizationsTable = 
new StringBuilder();
 
  311         createOrganizationsTable.append(
"CREATE TABLE IF NOT EXISTS organizations (");
 
  312         createOrganizationsTable.append(
"id SERIAL PRIMARY KEY,");
 
  313         createOrganizationsTable.append(
"org_name text NOT NULL,");
 
  314         createOrganizationsTable.append(
"poc_name text NOT NULL,");
 
  315         createOrganizationsTable.append(
"poc_email text NOT NULL,");
 
  316         createOrganizationsTable.append(
"poc_phone text NOT NULL,");
 
  317         createOrganizationsTable.append(
"CONSTRAINT org_name_unique UNIQUE (org_name)");
 
  318         createOrganizationsTable.append(
")");
 
  322         StringBuilder createCasesTable = 
new StringBuilder();
 
  323         createCasesTable.append(
"CREATE TABLE IF NOT EXISTS cases (");
 
  324         createCasesTable.append(
"id SERIAL PRIMARY KEY,");
 
  325         createCasesTable.append(
"case_uid text NOT NULL,");
 
  326         createCasesTable.append(
"org_id integer,");
 
  327         createCasesTable.append(
"case_name text NOT NULL,");
 
  328         createCasesTable.append(
"creation_date text NOT NULL,");
 
  329         createCasesTable.append(
"case_number text,");
 
  330         createCasesTable.append(
"examiner_name text,");
 
  331         createCasesTable.append(
"examiner_email text,");
 
  332         createCasesTable.append(
"examiner_phone text,");
 
  333         createCasesTable.append(
"notes text,");
 
  334         createCasesTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
 
  335         createCasesTable.append(
"CONSTRAINT case_uid_unique UNIQUE (case_uid)");
 
  336         createCasesTable.append(
")");
 
  339         String casesIdx1 = 
"CREATE INDEX IF NOT EXISTS cases_org_id ON cases (org_id)";
 
  340         String casesIdx2 = 
"CREATE INDEX IF NOT EXISTS cases_case_uid ON cases (case_uid)";
 
  342         StringBuilder createReferenceSetsTable = 
new StringBuilder();
 
  343         createReferenceSetsTable.append(
"CREATE TABLE IF NOT EXISTS reference_sets (");
 
  344         createReferenceSetsTable.append(
"id SERIAL PRIMARY KEY,");
 
  345         createReferenceSetsTable.append(
"org_id integer NOT NULL,");
 
  346         createReferenceSetsTable.append(
"set_name text NOT NULL,");
 
  347         createReferenceSetsTable.append(
"version text NOT NULL,");
 
  348         createReferenceSetsTable.append(
"known_status integer NOT NULL,");
 
  349         createReferenceSetsTable.append(
"read_only boolean NOT NULL,");
 
  350         createReferenceSetsTable.append(
"type integer NOT NULL,");
 
  351         createReferenceSetsTable.append(
"import_date text NOT NULL,");
 
  352         createReferenceSetsTable.append(
"foreign key (org_id) references organizations(id) ON UPDATE SET NULL ON DELETE SET NULL,");
 
  353         createReferenceSetsTable.append(
"CONSTRAINT hash_set_unique UNIQUE (set_name, version)");
 
  354         createReferenceSetsTable.append(
")");
 
  356         String referenceSetsIdx1 = 
"CREATE INDEX IF NOT EXISTS reference_sets_org_id ON reference_sets (org_id)";
 
  359         StringBuilder createReferenceTypesTableTemplate = 
new StringBuilder();
 
  360         createReferenceTypesTableTemplate.append(
"CREATE TABLE IF NOT EXISTS %s (");
 
  361         createReferenceTypesTableTemplate.append(
"id SERIAL PRIMARY KEY,");
 
  362         createReferenceTypesTableTemplate.append(
"reference_set_id integer,");
 
  363         createReferenceTypesTableTemplate.append(
"value text NOT NULL,");
 
  364         createReferenceTypesTableTemplate.append(
"known_status integer NOT NULL,");
 
  365         createReferenceTypesTableTemplate.append(
"comment text,");
 
  366         createReferenceTypesTableTemplate.append(
"CONSTRAINT %s_multi_unique UNIQUE (reference_set_id, value),");
 
  367         createReferenceTypesTableTemplate.append(
"foreign key (reference_set_id) references reference_sets(id) ON UPDATE SET NULL ON DELETE SET NULL");
 
  368         createReferenceTypesTableTemplate.append(
")");
 
  371         String referenceTypesIdx1 = 
"CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
 
  372         String referenceTypesIdx2 = 
"CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
 
  374         StringBuilder createCorrelationTypesTable = 
new StringBuilder();
 
  375         createCorrelationTypesTable.append(
"CREATE TABLE IF NOT EXISTS correlation_types (");
 
  376         createCorrelationTypesTable.append(
"id SERIAL PRIMARY KEY,");
 
  377         createCorrelationTypesTable.append(
"display_name text NOT NULL,");
 
  378         createCorrelationTypesTable.append(
"db_table_name text NOT NULL,");
 
  379         createCorrelationTypesTable.append(
"supported integer NOT NULL,");
 
  380         createCorrelationTypesTable.append(
"enabled integer NOT NULL,");
 
  381         createCorrelationTypesTable.append(
"CONSTRAINT correlation_types_names UNIQUE (display_name, db_table_name)");
 
  382         createCorrelationTypesTable.append(
")");
 
  384         String createArtifactInstancesTableTemplate = getCreateArtifactInstancesTableTemplate();
 
  386         String instancesCaseIdIdx = getAddCaseIdIndexTemplate();
 
  387         String instancesDatasourceIdIdx = getAddDataSourceIdIndexTemplate();
 
  388         String instancesValueIdx = getAddValueIndexTemplate();
 
  389         String instancesKnownStatusIdx = getAddKnownStatusIndexTemplate();
 
  390         String instancesObjectIdIdx = getAddObjectIdIndexTemplate();
 
  394         Connection conn = null;
 
  400             Statement stmt = conn.createStatement();
 
  402             stmt.execute(createOrganizationsTable.toString());
 
  404             stmt.execute(createCasesTable.toString());
 
  405             stmt.execute(casesIdx1);
 
  406             stmt.execute(casesIdx2);
 
  408             stmt.execute(getCreateDataSourcesTableStatement());
 
  409             stmt.execute(getAddDataSourcesNameIndexStatement());
 
  410             stmt.execute(getAddDataSourcesObjectIdIndexStatement());
 
  412             stmt.execute(createReferenceSetsTable.toString());
 
  413             stmt.execute(referenceSetsIdx1);
 
  415             stmt.execute(createCorrelationTypesTable.toString());
 
  422             stmt.execute(
"CREATE TABLE db_info (id SERIAL, name TEXT UNIQUE NOT NULL, value TEXT NOT NULL)");
 
  423             stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MAJOR_VERSION_KEY + 
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + 
"')");
 
  424             stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.SCHEMA_MINOR_VERSION_KEY + 
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + 
"')");
 
  425             stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MAJOR_VERSION_KEY + 
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMajor() + 
"')");
 
  426             stmt.execute(
"INSERT INTO db_info (name, value) VALUES ('" + AbstractSqlEamDb.CREATION_SCHEMA_MINOR_VERSION_KEY + 
"', '" + SOFTWARE_CR_DB_SCHEMA_VERSION.getMinor() + 
"')");
 
  431             String reference_type_dbname;
 
  432             String instance_type_dbname;
 
  437                 stmt.execute(String.format(createArtifactInstancesTableTemplate, instance_type_dbname, instance_type_dbname));
 
  438                 stmt.execute(String.format(instancesCaseIdIdx, instance_type_dbname, instance_type_dbname));
 
  439                 stmt.execute(String.format(instancesDatasourceIdIdx, instance_type_dbname, instance_type_dbname));
 
  440                 stmt.execute(String.format(instancesValueIdx, instance_type_dbname, instance_type_dbname));
 
  441                 stmt.execute(String.format(instancesKnownStatusIdx, instance_type_dbname, instance_type_dbname));
 
  442                 stmt.execute(String.format(instancesObjectIdIdx, instance_type_dbname, instance_type_dbname));
 
  446                     stmt.execute(String.format(createReferenceTypesTableTemplate.toString(), reference_type_dbname, reference_type_dbname));
 
  447                     stmt.execute(String.format(referenceTypesIdx1, reference_type_dbname, reference_type_dbname));
 
  448                     stmt.execute(String.format(referenceTypesIdx2, reference_type_dbname, reference_type_dbname));
 
  452         } 
catch (SQLException ex) {
 
  453             LOGGER.log(Level.SEVERE, 
"Error initializing db schema.", ex); 
 
  456             LOGGER.log(Level.SEVERE, 
"Error getting default correlation types. Likely due to one or more Type's with an invalid db table name."); 
 
  471     static String getCreateArtifactInstancesTableTemplate() {
 
  473         return (
"CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY,case_id integer NOT NULL," 
  474                 + 
"data_source_id integer NOT NULL,value text NOT NULL,file_path text NOT NULL," 
  475                 + 
"known_status integer NOT NULL,comment text,file_obj_id BIGINT," 
  476                 + 
"CONSTRAINT %s_multi_unique_ UNIQUE (data_source_id, value, file_path)," 
  477                 + 
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL," 
  478                 + 
"foreign key (data_source_id) references data_sources(id) ON UPDATE SET NULL ON DELETE SET NULL)");
 
  488     static String getCreateDataSourcesTableStatement() {
 
  489         return "CREATE TABLE IF NOT EXISTS data_sources " 
  490                 + 
"(id SERIAL PRIMARY KEY,case_id integer NOT NULL,device_id text NOT NULL," 
  491                 + 
"name text NOT NULL,datasource_obj_id BIGINT,md5 text DEFAULT NULL," 
  492                 + 
"sha1 text DEFAULT NULL,sha256 text DEFAULT NULL," 
  493                 + 
"foreign key (case_id) references cases(id) ON UPDATE SET NULL ON DELETE SET NULL," 
  494                 + 
"CONSTRAINT datasource_unique UNIQUE (case_id, datasource_obj_id))";
 
  504     static String getAddDataSourcesNameIndexStatement() {
 
  505         return "CREATE INDEX IF NOT EXISTS data_sources_name ON data_sources (name)";
 
  515     static String getAddDataSourcesObjectIdIndexStatement() {
 
  516         return "CREATE INDEX IF NOT EXISTS data_sources_object_id ON data_sources (datasource_obj_id)";
 
  527     static String getAddCaseIdIndexTemplate() {
 
  529         return "CREATE INDEX IF NOT EXISTS %s_case_id ON %s (case_id)";
 
  540     static String getAddDataSourceIdIndexTemplate() {
 
  542         return "CREATE INDEX IF NOT EXISTS %s_data_source_id ON %s (data_source_id)";
 
  553     static String getAddValueIndexTemplate() {
 
  555         return "CREATE INDEX IF NOT EXISTS %s_value ON %s (value)";
 
  566     static String getAddKnownStatusIndexTemplate() {
 
  568         return "CREATE INDEX IF NOT EXISTS %s_value_known_status ON %s (value, known_status)";
 
  579     static String getAddObjectIdIndexTemplate() {
 
  581         return "CREATE INDEX IF NOT EXISTS %s_file_obj_id ON %s (file_obj_id)";
 
  596     boolean isChanged() {
 
  604         return !host.equals(hostString) || !Integer.toString(port).equals(portString)
 
  605                 || !dbName.equals(dbNameString) || !Integer.toString(bulkThreshold).equals(bulkThresholdString)
 
  606                 || !userName.equals(userNameString) || !password.equals(userPasswordString);
 
  620         if (null != host && !host.isEmpty()) {
 
  638         if (port > 0 && port < 65535) {
 
  641             throw new EamDbException(
"Invalid port. Must be a number greater than 0."); 
 
  652         return dbName.toLowerCase();
 
  659         if (dbName == null || dbName.isEmpty()) {
 
  660             throw new EamDbException(
"Invalid database name. Cannot be empty."); 
 
  661         } 
else if (!Pattern.matches(DB_NAMES_REGEX, dbName)) {
 
  662             throw new EamDbException(
"Invalid database name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'."); 
 
  665         this.dbName = dbName.toLowerCase();
 
  671     int getBulkThreshold() {
 
  679         if (bulkThreshold > 0) {
 
  697         if (userName == null || userName.isEmpty()) {
 
  699         } 
else if (!Pattern.matches(DB_USER_NAMES_REGEX, userName)) {
 
  700             throw new EamDbException(
"Invalid user name. Name must start with a letter and can only contain letters, numbers, and '_'."); 
 
  716         if (password == null || password.isEmpty()) {
 
  717             throw new EamDbException(
"Invalid user password. Cannot be empty."); 
 
  725     String getValidationQuery() {
 
  739     String getJDBCBaseURI() {
 
static final Logger LOGGER
void setHost(String host)
static String correlationTypeToInstanceTableName(CorrelationAttributeInstance.Type type)
Connection getEphemeralConnection(boolean usePostgresDb)
static final String JDBC_DRIVER
static boolean schemaVersionIsSet(Connection conn)
static final String VALIDATION_QUERY
static boolean executeValidationQuery(Connection conn, String validationQuery)
static final String DEFAULT_DBNAME
boolean insertDefaultDatabaseContent()
boolean verifyDatabaseSchema()
static final String JDBC_BASE_URI
static final String DEFAULT_USERNAME
static void closeResultSet(ResultSet resultSet)
static void closeStatement(Statement statement)
static List< CorrelationAttributeInstance.Type > getDefaultCorrelationTypes()
static final String DEFAULT_HOST
static final String DB_NAMES_REGEX
void setPassword(String password)
static final String DEFAULT_PASSWORD
static final int DEFAULT_PORT
static void closeConnection(Connection conn)
static synchronized void setConfigSetting(String moduleName, String settingName, String settingVal)
boolean initializeDatabaseSchema()
boolean verifyDatabaseExists()
static boolean insertDefaultCorrelationTypes(Connection conn)
static String getConfigSetting(String moduleName, String settingName)
boolean verifyConnection()
void setUserName(String userName)
void setDbName(String dbName)
synchronized static Logger getLogger(String name)
void setBulkThreshold(int bulkThreshold)
static String correlationTypeToReferenceTableName(CorrelationAttributeInstance.Type type)
static String convertTextToHexText(String property)
static final String DB_USER_NAMES_REGEX
static final int FILES_TYPE_ID
static String convertHexTextToText(String property)