diff --git a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java index 18c09c20d8db47778709aa559bc2ca08904a472e..8497dc77569db7cbd4dfe3f96fae5d371f20a819 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java +++ b/bindings/java/src/org/sleuthkit/datamodel/CaseDatabaseFactory.java @@ -150,6 +150,7 @@ private void addTables(Connection conn) throws TskCoreException { createAnalysisResultsTables(stmt); createTagTables(stmt); createIngestTables(stmt); + createHostTables(stmt); createAccountTables(stmt); createEventTables(stmt); createAttributeTables(stmt); @@ -390,6 +391,15 @@ private void createIngestTables(Statement stmt) throws SQLException { + "FOREIGN KEY(ingest_module_id) REFERENCES ingest_modules(ingest_module_id) ON DELETE CASCADE);"); } + private void createHostTables(Statement stmt) throws SQLException { + + stmt.execute("CREATE TABLE tsk_hosts (id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + + "name TEXT NOT NULL, " // host name + + "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted + + "UNIQUE(name)) "); + + } + private void createAccountTables(Statement stmt) throws SQLException { stmt.execute("CREATE TABLE account_types (account_type_id " + dbQueryHelper.getPrimaryKey() + " PRIMARY KEY, " + "type_name TEXT UNIQUE NOT NULL, display_name TEXT NOT NULL)"); diff --git a/bindings/java/src/org/sleuthkit/datamodel/Host.java b/bindings/java/src/org/sleuthkit/datamodel/Host.java new file mode 100644 index 0000000000000000000000000000000000000000..5ec09c99debbc6dbcbebaa622d92e94056bba7cf --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/Host.java @@ -0,0 +1,146 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2021-2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import java.util.Objects; + +/** + * + * Encapsulates a host. + */ +public final class Host { + + private final long id; + private final String name; + private HostStatus status; + + Host(long id, String name) { + this(id, name, HostStatus.ACTIVE); + } + + Host(long id, String name, HostStatus status) { + this.id = id; + this.name = name; + this.status = status; + } + + /** + * Gets the row id for the host. + * + * @return Row id. + */ + public long getId() { + return id; + } + + /** + * Gets the name for the host. + * + * @return Host name. + */ + public String getName() { + return name; + } + + /** + * Gets the status for the host. + * + * @return Host status. + */ + public HostStatus getStatus() { + return status; + } + + /** + * Sets the status for the host. + * + * @param status Host status. + */ + public void setStatus(HostStatus status) { + this.status = status; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 67 * hash + (int) (this.id ^ (this.id >>> 32)); + hash = 67 * hash + Objects.hashCode(this.name); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + final Host other = (Host) obj; + if (this.id != other.id) { + return false; + } + + if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { + return false; + } + + return true; + } + + /** + * Encapsulates status of host row. + */ + public enum HostStatus { + ACTIVE(0, "Active"), + MERGED(1, "Merged"), + DELETED(2, "Deleted"); + + + private final int id; + private final String name; + + HostStatus(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + String getName() { + return name; + } + + public static HostStatus fromID(int typeId) { + for (HostStatus type : HostStatus.values()) { + if (type.ordinal() == typeId) { + return type; + } + } + return null; + } + } + +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/HostManager.java b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java new file mode 100644 index 0000000000000000000000000000000000000000..f1fe3f49b91e7eb542bdd8b305025f13fb1d2862 --- /dev/null +++ b/bindings/java/src/org/sleuthkit/datamodel/HostManager.java @@ -0,0 +1,171 @@ +/* + * Sleuth Kit Data Model + * + * Copyright 2020 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.datamodel; + +import com.google.common.base.Strings; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection; +import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction; + +/** + * Responsible for creating/updating/retrieving Hosts. + * + */ +public final class HostManager { + + private static final Logger LOGGER = Logger.getLogger(HostManager.class.getName()); + + private final SleuthkitCase db; + + /** + * Construct a HostManager for the given SleuthkitCase. + * + * @param skCase The SleuthkitCase + * + */ + HostManager(SleuthkitCase skCase) { + this.db = skCase; + } + + + /** + * Get or create host with specified name. + * + * @param name Host name. + * @param transaction Database transaction to use. + * + * @return Host with the specified name. + * + * @throws TskCoreException + */ + Host getOrCreateHost(String name, CaseDbTransaction transaction) throws TskCoreException { + + // must have a name + if (Strings.isNullOrEmpty(name) ) { + throw new IllegalArgumentException("Host name is required."); + } + + CaseDbConnection connection = transaction.getConnection(); + + // First search for host by name + Optional<Host> host = getHost(name, connection); + if (host.isPresent()) { + return host.get(); + } + + // couldn't find it, create a new host + return createHost(name, connection); + } + + /** + * Get host with given name. + * + * @param name Host name to look for. + * @param connection Database connection to use. + * + * @return Optional with host. Optional.empty if no matching host is found. + * + * @throws TskCoreException + */ + Optional<Host> getHost(String name, CaseDbConnection connection ) throws TskCoreException { + + String queryString = "SELECT * FROM tsk_hosts" + + " WHERE LOWER(name) = LOWER('" + name + "')"; + + try (Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + if (!rs.next()) { + return Optional.empty(); // no match found + } else { + return Optional.of(new Host(rs.getLong("id"), rs.getString("name"), Host.HostStatus.fromID(rs.getInt("status")))); + } + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting host with name = %s", name), ex); + } + } + + + /** + * Create a host with given name. + * + * @param name Host name. + * @param connection Database connection to use. + * @return Newly created host. + * @throws TskCoreException + */ + private Host createHost(String name, CaseDbConnection connection) throws TskCoreException { + db.acquireSingleUserCaseWriteLock(); + try { + String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS + PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS); + + preparedStatement.clearParameters(); + preparedStatement.setString(1, name); + + connection.executeUpdate(preparedStatement); + + // Read back the row id + try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) { + if (resultSet.next()) { + return new Host(resultSet.getLong(1), name); //last_insert_rowid() + } else { + throw new SQLException("Error executing " + hostInsertSQL); + } + } + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, null, ex); + throw new TskCoreException(String.format("Error adding host with name = %s", name), ex); + } finally { + db.releaseSingleUserCaseWriteLock(); + } + } + + /** + * Get all hosts. + * + * @return Collection of hosts. + * @throws TskCoreException + */ + Set<Host> getHosts() throws TskCoreException { + String queryString = "SELECT * FROM tsk_hosts"; + + Set<Host> hosts = new HashSet<>(); + try (CaseDbConnection connection = this.db.getConnection(); + Statement s = connection.createStatement(); + ResultSet rs = connection.executeQuery(s, queryString)) { + + while (rs.next()) { + hosts.add(new Host(rs.getLong("id"), rs.getString("name"), Host.HostStatus.fromID(rs.getInt("status")))); + } + + return hosts; + } catch (SQLException ex) { + throw new TskCoreException(String.format("Error getting hosts"), ex); + } + } +} diff --git a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java index 0ed5a405299f69681ecd1920a0dd0b27f4fd2c06..e534c08b2ab321558f9550d026552c4799b8a910 100644 --- a/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java +++ b/bindings/java/src/org/sleuthkit/datamodel/SleuthkitCase.java @@ -212,6 +212,7 @@ public class SleuthkitCase { private CaseDbAccessManager dbAccessManager; private TaggingManager taggingMgr; private ScoringManager scoringManager; + private HostManager hostManager; private final Map<String, Set<Long>> deviceIdToDatasourceObjIdMap = new HashMap<>(); @@ -391,6 +392,7 @@ private void init() throws Exception { dbAccessManager = new CaseDbAccessManager(this); taggingMgr = new TaggingManager(this); scoringManager = new ScoringManager(this); + hostManager = new HostManager(this); } /** @@ -514,6 +516,17 @@ public ScoringManager getScoringManager() throws TskCoreException { return scoringManager; } + /** + * Gets the host manager for this case. + * + * @return The per case HostManager object. + * + * @throws org.sleuthkit.datamodel.TskCoreException + */ + public HostManager getHostManager() throws TskCoreException { + return hostManager; + } + /** * Make sure the predefined artifact types are in the artifact types table. * @@ -2242,14 +2255,22 @@ private CaseDbSchemaVersionNumber updateFromSchema8dot6toSchema8dot7(CaseDbSchem acquireSingleUserCaseWriteLock(); try { String dateDataType = "BIGINT"; + String primaryKeyType = "BIGSERIAL"; if (this.dbType.equals(DbType.SQLITE)) { dateDataType = "INTEGER"; + primaryKeyType = "INTEGER"; } statement.execute("ALTER TABLE data_source_info ADD COLUMN added_date_time "+ dateDataType); statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_settings TEXT"); statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_name TEXT"); statement.execute("ALTER TABLE data_source_info ADD COLUMN acquisition_tool_version TEXT"); + // create host table. + statement.execute("CREATE TABLE tsk_hosts (id " + primaryKeyType + " PRIMARY KEY, " + + "name TEXT NOT NULL, " // host name + + "status INTEGER DEFAULT 0, " // to indicate if the host was merged/deleted + + "UNIQUE(name)) "); + return new CaseDbSchemaVersionNumber(8, 7); } finally {