Sleuth Kit Java Bindings (JNI)  4.11.0
Java bindings for using The Sleuth Kit
HostManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2020-2021 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.datamodel;
20 
21 import com.google.common.base.Strings;
22 import java.sql.PreparedStatement;
23 import java.sql.ResultSet;
24 import java.sql.Savepoint;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Optional;
31 import java.util.UUID;
37 
41 public final class HostManager {
42 
43  private final SleuthkitCase db;
44 
51  HostManager(SleuthkitCase skCase) {
52  this.db = skCase;
53  }
54 
65  public Host newHost(String name) throws TskCoreException {
66  CaseDbTransaction transaction = db.beginTransaction();
67  try {
68  Host host = newHost(name, transaction);
69  transaction.commit();
70  transaction = null;
71  return host;
72  } finally {
73  if (transaction != null) {
74  transaction.rollback();
75  }
76  }
77  }
78 
103  Host newHost(String name, CaseDbTransaction trans) throws TskCoreException {
104  // must have a name
105  if (Strings.isNullOrEmpty(name)) {
106  throw new TskCoreException("Illegal argument passed to createHost: Host name is required.");
107  }
108 
109  CaseDbConnection connection = trans.getConnection();
110  Savepoint savepoint = null;
111 
112  try {
113  savepoint = connection.getConnection().setSavepoint();
114  String hostInsertSQL = "INSERT INTO tsk_hosts(name) VALUES (?)"; // NON-NLS
115  PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS);
116 
117  preparedStatement.clearParameters();
118  preparedStatement.setString(1, name);
119 
120  connection.executeUpdate(preparedStatement);
121 
122  // Read back the row id
123  Host host = null;
124  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
125  if (resultSet.next()) {
126  host = new Host(resultSet.getLong(1), name); //last_insert_rowid()
127  } else {
128  throw new SQLException("Error executing " + hostInsertSQL);
129  }
130  }
131 
132  if (host != null) {
133  trans.registerAddedHost(host);
134  }
135  return host;
136  } catch (SQLException ex) {
137  if (savepoint != null) {
138  try {
139  connection.getConnection().rollback(savepoint);
140  } catch (SQLException ex2) {
141  throw new TskCoreException(String.format("Error adding host with name = %s and unable to rollback", name), ex);
142  }
143  }
144 
145  // It may be the case that the host already exists, so try to get it.
146  Optional<Host> optHost = getHostByName(name, connection);
147  if (optHost.isPresent()) {
148  return optHost.get();
149  }
150  throw new TskCoreException(String.format("Error adding host with name = %s", name), ex);
151  }
152  }
153 
164  public Host updateHostName(Host host, String newName) throws TskCoreException {
165  if (host == null) {
166  throw new TskCoreException("Illegal argument passed to updateHost: No host argument provided.");
167  } else if (newName == null) {
168  throw new TskCoreException(String.format("Illegal argument passed to updateHost: Host with id %d has no name", host.getHostId()));
169  }
170 
171  long hostId = host.getHostId();
172  Host updatedHost = null;
174  try (CaseDbConnection connection = db.getConnection()) {
175  // Don't update the name for non-active hosts
176  String hostInsertSQL = "UPDATE tsk_hosts "
177  + "SET name = "
178  + " CASE WHEN db_status = " + Host.HostDbStatus.ACTIVE.getId() + " THEN ? ELSE name END "
179  + "WHERE id = ?";
180 
181  PreparedStatement preparedStatement = connection.getPreparedStatement(hostInsertSQL, Statement.RETURN_GENERATED_KEYS);
182 
183  preparedStatement.clearParameters();
184  preparedStatement.setString(1, newName);
185  preparedStatement.setLong(2, hostId);
186 
187  connection.executeUpdate(preparedStatement);
188 
189  updatedHost = getHostById(hostId, connection).orElseThrow(()
190  -> new TskCoreException((String.format("Error while fetching newly updated host with id: %d, "))));
191 
192  } catch (SQLException ex) {
193  throw new TskCoreException(String.format("Error updating host with name = %s", newName), ex);
194  } finally {
196  }
197 
198  if (updatedHost != null) {
199  fireChangeEvent(updatedHost);
200  }
201  return updatedHost;
202  }
203 
213  public Long deleteHost(String name) throws TskCoreException {
214  if (name == null) {
215  throw new TskCoreException("Illegal argument passed to deleteHost: Name provided must be non-null");
216  }
217 
218  // query to check if there are any dependencies on this host. If so, don't delete.
219  String queryString = "SELECT COUNT(*) AS count FROM\n"
220  + "(SELECT obj_id AS id, host_id FROM data_source_info\n"
221  + "UNION\n"
222  + "SELECT id, scope_host_id AS host_id FROM tsk_os_account_realms\n"
223  + "UNION\n"
224  + "SELECT id, host_id FROM tsk_os_account_attributes\n"
225  + "UNION\n"
226  + "SELECT id, host_id FROM tsk_host_address_map) children\n"
227  + "INNER JOIN tsk_hosts h ON children.host_id = h.id WHERE LOWER(h.name)=LOWER(?)";
228 
229  String deleteString = "DELETE FROM tsk_hosts WHERE LOWER(name) = LOWER(?)";
230 
231  CaseDbTransaction trans = this.db.beginTransaction();
232  try {
233  // check if host has any child data sources. if so, don't delete and throw exception.
234  PreparedStatement query = trans.getConnection().getPreparedStatement(queryString, Statement.NO_GENERATED_KEYS);
235  query.clearParameters();
236  query.setString(1, name);
237  try (ResultSet queryResults = query.executeQuery()) {
238  if (queryResults.next() && queryResults.getLong("count") > 0) {
239  throw new TskCoreException(String.format("Host with name '%s' has child data and cannot be deleted.", name));
240  }
241  }
242 
243  // otherwise, delete the host
244  PreparedStatement update = trans.getConnection().getPreparedStatement(deleteString, Statement.RETURN_GENERATED_KEYS);
245  update.clearParameters();
246  update.setString(1, name);
247  int numUpdated = update.executeUpdate();
248 
249  // get ids for deleted.
250  Long hostId = null;
251 
252  if (numUpdated > 0) {
253  try (ResultSet updateResult = update.getGeneratedKeys()) {
254  if (updateResult.next()) {
255  hostId = updateResult.getLong(1);
256  }
257  }
258  }
259 
260  trans.commit();
261  trans = null;
262 
263  fireDeletedEvent(new Host(hostId, name));
264  return hostId;
265  } catch (SQLException ex) {
266  throw new TskCoreException(String.format("Error deleting host with name %s", name), ex);
267  } finally {
268  if (trans != null) {
269  trans.rollback();
270  }
271  }
272  }
273 
283  public List<DataSource> getDataSourcesForHost(Host host) throws TskCoreException {
284  String queryString = "SELECT * FROM data_source_info WHERE host_id = " + host.getHostId();
285 
286  List<DataSource> dataSources = new ArrayList<>();
288  try (CaseDbConnection connection = this.db.getConnection();
289  Statement s = connection.createStatement();
290  ResultSet rs = connection.executeQuery(s, queryString)) {
291 
292  while (rs.next()) {
293  dataSources.add(db.getDataSource(rs.getLong("obj_id")));
294  }
295 
296  return dataSources;
297  } catch (SQLException | TskDataException ex) {
298  throw new TskCoreException(String.format("Error getting data sources for host " + host.getName()), ex);
299  } finally {
301  }
302  }
303 
313  public Optional<Host> getHostByName(String name) throws TskCoreException {
314  try (CaseDbConnection connection = db.getConnection()) {
315  return getHostByName(name, connection);
316  }
317  }
318 
329  private Optional<Host> getHostByName(String name, CaseDbConnection connection) throws TskCoreException {
330 
331  String queryString = "SELECT * FROM tsk_hosts"
332  + " WHERE LOWER(name) = LOWER(?)"
333  + " AND db_status = " + Host.HostDbStatus.ACTIVE.getId();
334 
336  try {
337  PreparedStatement s = connection.getPreparedStatement(queryString, Statement.RETURN_GENERATED_KEYS);
338  s.clearParameters();
339  s.setString(1, name);
340 
341  try (ResultSet rs = s.executeQuery()) {
342  if (!rs.next()) {
343  return Optional.empty(); // no match found
344  } else {
345  return Optional.of(new Host(rs.getLong("id"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
346  }
347  }
348  } catch (SQLException ex) {
349  throw new TskCoreException(String.format("Error getting host with name = %s", name), ex);
350  } finally {
352  }
353  }
354 
364  public Optional<Host> getHostById(long id) throws TskCoreException {
365  try (CaseDbConnection connection = db.getConnection()) {
366  return getHostById(id, connection);
367  }
368  }
369 
380  private Optional<Host> getHostById(long id, CaseDbConnection connection) throws TskCoreException {
381 
382  String queryString = "SELECT * FROM tsk_hosts WHERE id = " + id;
383 
385  try (Statement s = connection.createStatement();
386  ResultSet rs = connection.executeQuery(s, queryString)) {
387 
388  if (rs.next()) {
389  return Optional.of(new Host(rs.getLong("id"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
390  } else {
391  return Optional.empty();
392  }
393  } catch (SQLException ex) {
394  throw new TskCoreException(String.format("Error getting host with id: " + id), ex);
395  } finally {
397  }
398  }
399 
407  public List<Host> getAllHosts() throws TskCoreException {
408  String queryString = "SELECT * FROM tsk_hosts WHERE db_status = " + HostDbStatus.ACTIVE.getId();
409 
410  List<Host> hosts = new ArrayList<>();
412  try (CaseDbConnection connection = this.db.getConnection();
413  Statement s = connection.createStatement();
414  ResultSet rs = connection.executeQuery(s, queryString)) {
415 
416  while (rs.next()) {
417  hosts.add(new Host(rs.getLong("id"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
418  }
419 
420  return hosts;
421  } catch (SQLException ex) {
422  throw new TskCoreException(String.format("Error getting hosts"), ex);
423  } finally {
425  }
426  }
427 
438  return getHostByDataSource(dataSource.getId());
439  }
440 
450  Host getHostByDataSource(long dataSourceId) throws TskCoreException {
451  String queryString = "SELECT tsk_hosts.id AS hostId, tsk_hosts.name AS name, tsk_hosts.db_status AS db_status FROM \n"
452  + "tsk_hosts INNER JOIN data_source_info \n"
453  + "ON tsk_hosts.id = data_source_info.host_id \n"
454  + "WHERE data_source_info.obj_id = " + dataSourceId;
455 
457  try (CaseDbConnection connection = this.db.getConnection();
458  Statement s = connection.createStatement();
459  ResultSet rs = connection.executeQuery(s, queryString)) {
460 
461  if (!rs.next()) {
462  throw new TskCoreException(String.format("Host not found for data source with ID = %d", dataSourceId));
463  } else {
464  return new Host(rs.getLong("hostId"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status")));
465  }
466  } catch (SQLException ex) {
467  throw new TskCoreException(String.format("Error getting host for data source with ID = %d", dataSourceId), ex);
468  } finally {
470  }
471  }
472 
486  public void mergeHosts(Host sourceHost, Host destHost) throws TskCoreException {
487  String query = "";
488  CaseDbTransaction trans = null;
489  try {
490  trans = db.beginTransaction();
491 
492  // Merge or move any realms associated with the source host
493  List<OsAccountRealm> realms = db.getOsAccountRealmManager().getRealmsByHost(sourceHost, trans.getConnection());
494  for (OsAccountRealm realm : realms) {
495  db.getOsAccountRealmManager().moveOrMergeRealm(realm, destHost, trans);
496  }
497 
498  try (Statement s = trans.getConnection().createStatement()) {
499  // Update references to the source host
500 
501  // tsk_host_address_map has a unique constraint on host_id, addr_obj_id, time,
502  // so delete any rows that would be duplicates.
503  query = "DELETE FROM tsk_host_address_map "
504  + "WHERE id IN ( "
505  + "SELECT "
506  + " sourceMapRow.id "
507  + "FROM "
508  + " tsk_host_address_map destMapRow "
509  + "INNER JOIN tsk_host_address_map sourceMapRow ON destMapRow.addr_obj_id = sourceMapRow.addr_obj_id AND destMapRow.time = sourceMapRow.time "
510  + "WHERE destMapRow.host_id = " + destHost.getHostId()
511  + " AND sourceMapRow.host_id = " + sourceHost.getHostId() + " )";
512  s.executeUpdate(query);
513  query = makeOsAccountUpdateQuery("tsk_host_address_map", "host_id", sourceHost, destHost);
514  s.executeUpdate(query);
515 
516  query = makeOsAccountUpdateQuery("tsk_os_account_attributes", "host_id", sourceHost, destHost);
517  s.executeUpdate(query);
518 
519  query = makeOsAccountUpdateQuery("data_source_info", "host_id", sourceHost, destHost);
520  s.executeUpdate(query);
521 
522  // Mark the source host as merged and change the name to a random string.
523  String mergedName = makeMergedHostName();
524  query = "UPDATE tsk_hosts SET merged_into = " + destHost.getHostId()
525  + ", db_status = " + Host.HostDbStatus.MERGED.getId()
526  + ", name = '" + mergedName + "' "
527  + " WHERE id = " + sourceHost.getHostId();
528  s.executeUpdate(query);
529  }
530 
531  trans.commit();
532  trans = null;
533 
534  // Fire events for updated and deleted hosts
535  fireChangeEvent(sourceHost);
536  fireDeletedEvent(destHost);
537  } catch (SQLException ex) {
538  throw new TskCoreException("Error executing query: " + query, ex);
539  } finally {
540  if (trans != null) {
541  trans.rollback();
542  }
543  }
544  }
545 
556  private String makeOsAccountUpdateQuery(String tableName, String columnName, Host sourceHost, Host destHost) {
557  return "UPDATE " + tableName + " SET " + columnName + " = " + destHost.getHostId() + " WHERE " + columnName + " = " + sourceHost.getHostId();
558  }
559 
565  private String makeMergedHostName() {
566  return "MERGED " + UUID.randomUUID().toString();
567  }
568 
575  private void fireChangeEvent(Host newValue) {
576  db.fireTSKEvent(new HostsUpdatedTskEvent(Collections.singletonList(newValue)));
577  }
578 
585  private void fireDeletedEvent(Host deleted) {
586  db.fireTSKEvent(new HostsDeletedTskEvent(Collections.singletonList(deleted.getHostId())));
587  }
588 }
Host getHostByDataSource(DataSource dataSource)
List< DataSource > getDataSourcesForHost(Host host)
Optional< Host > getHostByName(String name)
OsAccountRealmManager getOsAccountRealmManager()
Optional< Host > getHostById(long id)
void mergeHosts(Host sourceHost, Host destHost)
DataSource getDataSource(long objectId)
Host updateHostName(Host host, String newName)

Copyright © 2011-2021 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.