Sleuth Kit Java Bindings (JNI)  4.11.1
Java bindings for using The Sleuth Kit
OsAccountRealmManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2020-2022 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 org.apache.commons.lang3.StringUtils;
23 import java.sql.PreparedStatement;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.sql.Types;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Objects;
31 import java.util.Optional;
32 import java.util.UUID;
33 import java.util.logging.Logger;
37 
38 
43 public final class OsAccountRealmManager {
44 
45  private static final Logger LOGGER = Logger.getLogger(OsAccountRealmManager.class.getName());
46 
47  private final SleuthkitCase db;
48 
56  this.db = skCase;
57  }
58 
77  public OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope) throws TskCoreException, OsAccountManager.NotUserSIDException {
78 
79  if (realmScope == null) {
80  throw new TskCoreException("RealmScope cannot be null. Use UNKNOWN if scope is not known.");
81  }
82  if (referringHost == null) {
83  throw new TskCoreException("A referring host is required to create a realm.");
84  }
85  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
86  && StringUtils.isBlank(realmName)) {
87  throw new TskCoreException("Either an address or a name is required to create a realm.");
88  }
89 
90  Host scopeHost;
91  OsAccountRealm.ScopeConfidence scopeConfidence;
92 
93  switch (realmScope) {
94  case DOMAIN:
95  scopeHost = null;
96  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
97  break;
98  case LOCAL:
99  scopeHost = referringHost;
100  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
101  break;
102 
103  case UNKNOWN:
104  default:
105  // NOTE: if there's a well known SID, the scope will be changed to LOCAL later.
106  // check if the referring host already has a realm
107  boolean isHostRealmKnown = isHostRealmKnown(referringHost);
108  if (isHostRealmKnown) {
109  scopeHost = null; // the realm does not scope to the referring host since it already has one.
110  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
111  } else {
112  scopeHost = referringHost;
113  scopeConfidence = OsAccountRealm.ScopeConfidence.INFERRED;
114  }
115  break;
116 
117  }
118 
119  // get windows realm address from sid
120  String realmAddr = null;
121  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
122  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
123 
124  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
125  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
126  }
127 
128  realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
129 
130 
131  if (WindowsAccountUtils.isWindowsWellKnownSid(accountSid)) {
132 
133  // if the sid is a Windows well known SID, create a local realm for it.
134  scopeHost = referringHost;
135  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
136 
137  // if the SID is a Windows well known SID, then prefer to use the default well known name to create the realm
138  String wellKnownRealmName = WindowsAccountUtils.getWindowsWellKnownSidRealmName(accountSid);
139  if (!StringUtils.isEmpty(wellKnownRealmName)) {
140  resolvedRealmName = wellKnownRealmName;
141  }
142  }
143  }
144 
145  String signature = makeRealmSignature(realmAddr, resolvedRealmName, scopeHost);
146 
147  // create a realm
148  return newRealm(resolvedRealmName, realmAddr, signature, scopeHost, scopeConfidence);
149  }
150 
168  public Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost) throws TskCoreException, OsAccountManager.NotUserSIDException {
169 
170  if (referringHost == null) {
171  throw new TskCoreException("A referring host is required get a realm.");
172  }
173 
174  // need at least one of the two, the addr or name to look up
175  if ((Strings.isNullOrEmpty(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) )
176  && Strings.isNullOrEmpty(realmName)) {
177  throw new TskCoreException("Realm address or name is required get a realm.");
178  }
179 
180  try (CaseDbConnection connection = this.db.getConnection()) {
181  return getWindowsRealm(accountSid, realmName, referringHost, connection);
182  }
183  }
184 
185 
200  Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
201 
202  if (referringHost == null) {
203  throw new TskCoreException("A referring host is required get a realm.");
204  }
205 
206  // need at least one of the two, the addr or name to look up
207  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
208  && StringUtils.isBlank(realmName)) {
209  throw new TskCoreException("Realm address or name is required get a realm.");
210  }
211 
212  // If a non null accountSID is provided search for realm by addr.
213  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
214 
215  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
216  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
217  }
218  // get realm addr from the account SID.
219  String realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
220  Optional<OsAccountRealm> realm = getRealmByAddr(realmAddr, referringHost, connection);
221  if (realm.isPresent()) {
222  return realm;
223  }
224  }
225 
226  // ensure we are using English names for any well known SIDs.
227  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
228 
229  // No realm addr so search by name.
230  Optional<OsAccountRealm> realm = getRealmByName(resolvedRealmName, referringHost, connection);
231  if (realm.isPresent() && !Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
232  // If we were given a non-null accountSID, make sure there isn't one set on the matching realm.
233  // We know it won't match because the previous search by SID failed.
234  if (realm.get().getRealmAddr().isPresent()) {
235  return Optional.empty();
236  }
237  }
238  return realm;
239  }
240 
241 
260  OsRealmUpdateResult getAndUpdateWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
261 
262  // get realm
263  Optional<OsAccountRealm> realmOptional = getWindowsRealm(accountSid, realmName, referringHost, connection);
264 
265  // if found, update it if needed
266  if (realmOptional.isPresent()) {
267  String realmAddr = (StringUtils.isNotBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ? WindowsAccountUtils.getWindowsRealmAddress(accountSid) : null;
268  OsRealmUpdateResult realmUpdateResult = updateRealm(realmOptional.get(), realmAddr, realmName, connection);
269 
270  return realmUpdateResult;
271 
272  } else {
273  return new OsRealmUpdateResult(OsRealmUpdateStatus.NO_CHANGE, null);
274  }
275 
276  }
277 
278 
299  public OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName) throws TskCoreException {
300 
301  try (CaseDbConnection connection = db.getConnection()) {
302  return updateRealm(realm, realmAddr, realmName, connection);
303  }
304  }
305 
325  private OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName, CaseDbConnection connection) throws TskCoreException {
326 
327  // need at least one of the two
328  if ( (StringUtils.isBlank(realmAddr) || realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
329  && StringUtils.isBlank(realmName)) {
330  throw new TskCoreException("Realm address or name is required to update realm.");
331  }
332 
333  OsRealmUpdateStatus updateStatusCode = OsRealmUpdateStatus.NO_CHANGE;
334  OsAccountRealm updatedRealm = null;
335 
337  try {
338  String currRealmAddr = realm.getRealmAddr().orElse(null);
339 
340  // set name and address to new values only if the current value is blank and the new value isn't.
341  if ((StringUtils.isBlank(currRealmAddr) && StringUtils.isNotBlank(realmAddr) && !realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))) {
342  updateRealmColumn(realm.getRealmId(), "realm_addr", realmAddr, connection);
343  currRealmAddr = realmAddr;
344  updateStatusCode = OsRealmUpdateStatus.UPDATED;
345  }
346 
347  List<String> realmNames = realm.getRealmNames();
348  String currRealmName = realmNames.isEmpty() ? null : realmNames.get(0); // currently there is only one name.
349 
350  // Update realm name if:
351  // Current realm name is empty
352  // The passed in realm name is not empty
353  if (StringUtils.isBlank(currRealmName) && StringUtils.isNotBlank(realmName)) {
354  updateRealmColumn(realm.getRealmId(), "realm_name", realmName, connection);
355  updateStatusCode = OsRealmUpdateStatus.UPDATED;
356  }
357 
358  // if nothing is to be changed, return
359  if (updateStatusCode == OsRealmUpdateStatus.NO_CHANGE) {
360  return new OsRealmUpdateResult(updateStatusCode, realm);
361  }
362 
363  // update realm signature - based on the most current address and name
364  OsAccountRealm currRealm = getRealmByRealmId(realm.getRealmId(), connection);
365  String newRealmAddr = currRealm.getRealmAddr().orElse(null);
366  String newRealmName = (currRealm.getRealmNames().isEmpty() == false) ? currRealm.getRealmNames().get(0) : null;
367 
368  // make new signature
369  String newSignature = makeRealmSignature(newRealmAddr, newRealmName, realm.getScopeHost().orElse(null));
370 
371  // Use a random string as the signature if the realm is not active.
372  String updateSQL = "UPDATE tsk_os_account_realms SET "
373  + " realm_signature = "
374  + " CASE WHEN db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId() + " THEN ? ELSE realm_signature END "
375  + " WHERE id = ?";
376  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
377  preparedStatement.clearParameters();
378 
379  preparedStatement.setString(1, newSignature); // Is only set for active accounts
380  preparedStatement.setLong(2, realm.getRealmId());
381  connection.executeUpdate(preparedStatement);
382 
383  // read the updated realm
384  updatedRealm = this.getRealmByRealmId(realm.getRealmId(), connection);
385 
386  return new OsRealmUpdateResult(updateStatusCode, updatedRealm);
387  } catch (SQLException ex) {
388  throw new TskCoreException(String.format("Error updating realm with id = %d, name = %s, addr = %s", realm.getRealmId(), realmName != null ? realmName : "Null", realm.getRealmAddr().orElse("Null")), ex);
389  } finally {
391  }
392 
393  }
394 
407  private <T> void updateRealmColumn(long realmId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
408 
409  String updateSQL = "UPDATE tsk_os_account_realms "
410  + " SET " + colName + " = ? "
411  + " WHERE id = ?";
412 
414  try {
415  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
416  preparedStatement.clearParameters();
417 
418  if (Objects.isNull(colValue)) {
419  preparedStatement.setNull(1, Types.NULL); // handle null value
420  } else {
421  if (colValue instanceof String) {
422  preparedStatement.setString(1, (String) colValue);
423  } else if (colValue instanceof Long) {
424  preparedStatement.setLong(1, (Long) colValue);
425  } else if (colValue instanceof Integer) {
426  preparedStatement.setInt(1, (Integer) colValue);
427  } else {
428  throw new TskCoreException(String.format("Unhandled column data type received while updating the realm (id = %d) ", realmId));
429  }
430  }
431 
432  preparedStatement.setLong(2, realmId);
433 
434  connection.executeUpdate(preparedStatement);
435  } finally {
437  }
438  }
439 
440  private final static String REALM_QUERY_STRING = "SELECT realms.id as realm_id, realms.realm_name as realm_name,"
441  + " realms.realm_addr as realm_addr, realms.realm_signature as realm_signature, realms.scope_host_id, realms.scope_confidence, realms.db_status,"
442  + " hosts.id, hosts.name as host_name "
443  + " FROM tsk_os_account_realms as realms"
444  + " LEFT JOIN tsk_hosts as hosts"
445  + " ON realms.scope_host_id = hosts.id";
446 
456  public OsAccountRealm getRealmByRealmId(long id) throws TskCoreException {
457  try (CaseDbConnection connection = this.db.getConnection()) {
458  return getRealmByRealmId(id, connection);
459  }
460  }
461 
471  OsAccountRealm getRealmByRealmId(long id, CaseDbConnection connection) throws TskCoreException {
472 
473  String queryString = REALM_QUERY_STRING
474  + " WHERE realms.id = " + id;
475 
477  try ( Statement s = connection.createStatement();
478  ResultSet rs = connection.executeQuery(s, queryString)) {
479  OsAccountRealm accountRealm = null;
480  if (rs.next()) {
481  accountRealm = resultSetToAccountRealm(rs);
482  } else {
483  throw new TskCoreException(String.format("No realm found with id = %d", id));
484  }
485 
486  return accountRealm;
487  } catch (SQLException ex) {
488  throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
489  }
490  finally {
492  }
493  }
494 
506  Optional<OsAccountRealm> getRealmByAddr(String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
507 
508  // If a host is specified, we want to match the realm with matching addr and specified host, or a realm with matching addr and no host.
509  // If no host is specified, then we return the first realm with matching addr.
510  String whereHostClause = (host == null)
511  ? " 1 = 1 "
512  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
513  String queryString = REALM_QUERY_STRING
514  + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') "
515  + " AND " + whereHostClause
516  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
517  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
518 
519  return getRealmUsingQuery(queryString, host, connection);
520  }
521 
534  Optional<OsAccountRealm> getAnotherRealmByAddr(OsAccountRealm realm, String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
535 
536  // If the given realm has a host id, then the other realm should have the same host id
537  // If the given realm has no host id, then the other realm should have no host id
538  String whereHostClause = realm.getScopeHost().isPresent()
539  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
540  : " realms.scope_host_id IS NULL ";
541  String queryString = REALM_QUERY_STRING
542  + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') "
543  + " AND " + whereHostClause
544  + " AND realms.id <> " + realm.getRealmId()
545  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
546  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
547 
548  return getRealmUsingQuery(queryString, host, connection);
549  }
550 
561  Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
562 
563  // If a host is specified, we want to match the realm with matching name and specified host, or a realm with matching name and no host.
564  // If no host is specified, then we return the first realm with matching name.
565  String whereHostClause = (host == null)
566  ? " 1 = 1 "
567  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL ) ";
568  String queryString = REALM_QUERY_STRING
569  + " WHERE LOWER(realms.realm_name) = LOWER('" + realmName + "')"
570  + " AND " + whereHostClause
571  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
572  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
573 
574  return getRealmUsingQuery(queryString, host, connection);
575  }
576 
589  Optional<OsAccountRealm> getAnotherRealmByName(OsAccountRealm realm, String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
590 
591  // If the given realm has a host id, then the other realm should have the same host id
592  // If the given realm has no host id, then the other realm should have no host id
593  String whereHostClause = realm.getScopeHost().isPresent()
594  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
595  : " realms.scope_host_id IS NULL ";
596  String queryString = REALM_QUERY_STRING
597  + " WHERE LOWER(realms.realm_name) = LOWER('" + realmName + "')"
598  + " AND " + whereHostClause
599  + " AND realms.id <> " + realm.getRealmId()
600  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
601  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
602 
603  return getRealmUsingQuery(queryString, host, connection);
604 
605  }
606 
618  private Optional<OsAccountRealm> getRealmUsingQuery(String queryString, Host host, CaseDbConnection connection) throws TskCoreException {
619 
621  try (Statement s = connection.createStatement();
622  ResultSet rs = connection.executeQuery(s, queryString)) {
623 
624  OsAccountRealm accountRealm = null;
625  if (rs.next()) {
626  Host realmHost = null;
627  long hostId = rs.getLong("scope_host_id");
628  if (!rs.wasNull()) {
629  if (host != null ) {
630  realmHost = host;
631  } else {
632  realmHost = new Host(hostId, rs.getString("host_name"));
633  }
634  }
635 
636  accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
637  rs.getString("realm_addr"), rs.getString("realm_signature"),
638  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
639  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
640 
641  }
642  return Optional.ofNullable(accountRealm);
643  } catch (SQLException ex) {
644  throw new TskCoreException(String.format("Error getting realm using query = %s", queryString), ex);
645  } finally {
647  }
648  }
649 
665  private boolean isHostRealmKnown(Host host) throws TskCoreException {
666 
667  // check if this host has a local known realm aleady, other than the special windows realm.
668  String queryString = REALM_QUERY_STRING
669  + " WHERE realms.scope_host_id = " + host.getHostId()
670  + " AND realms.scope_confidence = " + OsAccountRealm.ScopeConfidence.KNOWN.getId()
671  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId();
672 
674  try (CaseDbConnection connection = this.db.getConnection();
675  Statement s = connection.createStatement();
676  ResultSet rs = connection.executeQuery(s, queryString)) {
677 
678  // return true if there is any match.
679  return rs.next();
680  } catch (SQLException ex) {
681  throw new TskCoreException(String.format("Error getting account realm for with host = %s", host.getName()), ex);
682  }
683  finally {
685  }
686 
687  }
688 
696  private OsAccountRealm resultSetToAccountRealm(ResultSet rs) throws SQLException {
697 
698  long hostId = rs.getLong("scope_host_id");
699  Host realmHost = null;
700  if (!rs.wasNull()) {
701  realmHost = new Host(hostId, rs.getString("host_name"));
702  }
703 
704  return new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
705  rs.getString("realm_addr"), rs.getString("realm_signature"),
706  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
707  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
708  }
709 
710 // /**
711 // * Get all realms.
712 // *
713 // * @return Collection of OsAccountRealm
714 // */
715 // Collection<OsAccountRealm> getRealms() throws TskCoreException {
716 // String queryString = "SELECT realms.id as realm_id, realms.realm_name as realm_name, realms.realm_addr as realm_addr, realms.scope_host_id, realms.scope_confidence, "
717 // + " hosts.id, hosts.name as host_name "
718 // + " FROM tsk_os_account_realms as realms"
719 // + " LEFT JOIN tsk_hosts as hosts"
720 // + " ON realms.scope_host_id = hosts.id";
721 //
722 // db.acquireSingleUserCaseReadLock();
723 // try (CaseDbConnection connection = this.db.getConnection();
724 // Statement s = connection.createStatement();
725 // ResultSet rs = connection.executeQuery(s, queryString)) {
726 //
727 // ArrayList<OsAccountRealm> accountRealms = new ArrayList<>();
728 // while (rs.next()) {
729 // long hostId = rs.getLong("scope_host_id");
730 // Host host = null;
731 // if (!rs.wasNull()) {
732 // host = new Host(hostId, rs.getString("host_name"));
733 // }
734 //
735 // accountRealms.add(new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
736 // ScopeConfidence.fromID(rs.getInt("scope_confidence")),
737 // rs.getString("realm_addr"), host));
738 // }
739 //
740 // return accountRealms;
741 // } catch (SQLException ex) {
742 // throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
743 // }
744 // finally {
745 // db.releaseSingleUserCaseReadLock();
746 // }
747 // }
748 
749 
767  private OsAccountRealm newRealm(String realmName, String realmAddr, String signature, Host host, OsAccountRealm.ScopeConfidence scopeConfidence) throws TskCoreException {
768 
770  try (CaseDbConnection connection = this.db.getConnection()) {
771  String realmInsertSQL = "INSERT INTO tsk_os_account_realms(realm_name, realm_addr, realm_signature, scope_host_id, scope_confidence)"
772  + " VALUES (?, ?, ?, ?, ?)"; // NON-NLS
773 
774  PreparedStatement preparedStatement = connection.getPreparedStatement(realmInsertSQL, Statement.RETURN_GENERATED_KEYS);
775  preparedStatement.clearParameters();
776 
777  preparedStatement.setString(1, realmName);
778  preparedStatement.setString(2, realmAddr);
779  preparedStatement.setString(3, signature);
780  if (host != null) {
781  preparedStatement.setLong(4, host.getHostId());
782  } else {
783  preparedStatement.setNull(4, java.sql.Types.BIGINT);
784  }
785  preparedStatement.setInt(5, scopeConfidence.getId());
786 
787  connection.executeUpdate(preparedStatement);
788 
789  // Read back the row id
790  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
791  long rowId = resultSet.getLong(1); // last_insert_rowid()
792  return new OsAccountRealm(rowId, realmName, realmAddr, signature, host, scopeConfidence, OsAccountRealm.RealmDbStatus.ACTIVE);
793  }
794 
795  } catch (SQLException ex) {
796  // Create may have failed if the realm already exists. Try and get the matching realm
797  try (CaseDbConnection connection = this.db.getConnection()) {
798  if (!Strings.isNullOrEmpty(realmAddr)) {
799  Optional<OsAccountRealm> accountRealm = this.getRealmByAddr(realmAddr, host, connection);
800  if (accountRealm.isPresent()) {
801  return accountRealm.get();
802  }
803  } else if (!Strings.isNullOrEmpty(realmName)) {
804  Optional<OsAccountRealm> accountRealm = this.getRealmByName(realmName, host, connection);
805  if (accountRealm.isPresent()) {
806  return accountRealm.get();
807  }
808  }
809 
810  // some other failure - throw an exception
811  throw new TskCoreException(String.format("Error creating realm with address = %s and name = %s, with host = %s",
812  realmAddr != null ? realmAddr : "", realmName != null ? realmName : "", host != null ? host.getName() : ""), ex);
813  }
814  } finally {
816  }
817  }
818 
819 
836  static String makeRealmSignature(String realmAddr, String realmName, Host scopeHost) throws TskCoreException {
837 
838  // need at least one of the two, the addr or name to look up
839  if (Strings.isNullOrEmpty(realmAddr) && Strings.isNullOrEmpty(realmName)) {
840  throw new TskCoreException("Realm address and name can't both be null.");
841  }
842 
843  String signature = String.format("%s_%s", !Strings.isNullOrEmpty(realmAddr) ? realmAddr : realmName,
844  scopeHost != null ? scopeHost.getHostId() : "DOMAIN");
845  return signature;
846  }
847 
853  private String makeMergedRealmSignature() {
854  return "MERGED " + UUID.randomUUID().toString();
855  }
856 
857 
866  void moveOrMergeRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
867  // Look for a matching realm by address
868  Optional<OsAccountRealm> optDestRealmAddr = Optional.empty();
869  if (sourceRealm.getRealmAddr().isPresent()) {
870  optDestRealmAddr = db.getOsAccountRealmManager().getRealmByAddr(sourceRealm.getRealmAddr().get(), destHost, trans.getConnection());
871  }
872 
873  // Look for a matching realm by name
874  Optional<OsAccountRealm> optDestRealmName = Optional.empty();
875  if (!sourceRealm.getRealmNames().isEmpty()) {
876  optDestRealmName = db.getOsAccountRealmManager().getRealmByName(sourceRealm.getRealmNames().get(0), destHost, trans.getConnection());
877  }
878 
879  // Decide how to proceed:
880  // - If we only got one match:
881  // -- If the address matched, set destRealm to the matching address realm
882  // -- If the name matched but the original and the matching realm have different addresses, leave destRealm null (it'll be a move)
883  // -- If the name matched and at least one of the address fields was null, set destRealm to the matching name realm
884  // - If we got no matches, leave destRealm null (we'll do a move not a merge)
885  // - If we got two of the same matches, set destRealm to that realm
886  // - If we got two different matches:
887  // -- If the name match has no address set, merge the matching name realm into the matching address realm, then
888  // set destRealm to the matching address realm
889  // -- Otherwise we're in the case where the addresses are different. We will consider the address the
890  // stronger match and set destRealm to the matching address realm and leave the matching name realm as-is.
891  OsAccountRealm destRealm = null;
892  if (optDestRealmAddr.isPresent() && optDestRealmName.isPresent()) {
893  if (optDestRealmAddr.get().getRealmId() == optDestRealmName.get().getRealmId()) {
894  // The two matches are the same
895  destRealm = optDestRealmAddr.get();
896  } else {
897  if (optDestRealmName.get().getRealmAddr().isPresent()) {
898  // The addresses are different, so use the one with the matching address
899  destRealm = optDestRealmAddr.get();
900  } else {
901  // Merge the realm with the matching name into the realm with the matching address.
902  // Reload from database afterward to make sure everything is up-to-date.
903  mergeRealms(optDestRealmName.get(), optDestRealmAddr.get(), trans);
904  destRealm = getRealmByRealmId(optDestRealmAddr.get().getRealmId(), trans.getConnection());
905  }
906  }
907  } else if (optDestRealmAddr.isPresent()) {
908  // Only address matched - use it
909  destRealm = optDestRealmAddr.get();
910  } else if (optDestRealmName.isPresent()) {
911  // Only name matched - check whether both have addresses set.
912  // Due to earlier checks we know the address fields can't be the same, so
913  // don't do anything if both have addresses - we consider the address to be a stronger identifier than the name
914  if (! (optDestRealmName.get().getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent())) {
915  destRealm = optDestRealmName.get();
916  }
917  }
918 
919  // Move or merge the source realm
920  if (destRealm == null) {
921  moveRealm(sourceRealm, destHost, trans);
922  } else {
923  mergeRealms(sourceRealm, destRealm, trans);
924  }
925  }
926 
938  private void moveRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
939  try(Statement s = trans.getConnection().createStatement()) {
940  String query = "UPDATE tsk_os_account_realms SET scope_host_id = " + destHost.getHostId() + " WHERE id = " + sourceRealm.getRealmId();
941  s.executeUpdate(query);
942  } catch (SQLException ex) {
943  throw new TskCoreException("Error moving realm with id: " + sourceRealm.getRealmId() + " to host with id: " + destHost.getHostId(), ex);
944  }
945  }
946 
947 
957  void mergeRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
958 
959  // Update accounts
960  db.getOsAccountManager().mergeOsAccountsForRealms(sourceRealm, destRealm, trans);
961 
962  // Update the sourceRealm realm
963  CaseDbConnection connection = trans.getConnection();
964  try (Statement statement = connection.createStatement()) {
965  String updateStr = "UPDATE tsk_os_account_realms SET db_status = " + OsAccountRealm.RealmDbStatus.MERGED.getId()
966  + ", merged_into = " + destRealm.getRealmId()
967  + ", realm_signature = '" + makeMergedRealmSignature() + "' "
968  + " WHERE id = " + sourceRealm.getRealmId();
969  connection.executeUpdate(statement, updateStr);
970  } catch (SQLException ex) {
971  throw new TskCoreException ("Error updating status of realm with id: " + sourceRealm.getRealmId(), ex);
972  }
973 
974  // Update the destination realm if it doesn't have the name or addr set and the source realm does
975  if (!destRealm.getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent()) {
976  updateRealm(destRealm, sourceRealm.getRealmAddr().get(), null, trans.getConnection());
977  } else if (destRealm.getRealmNames().isEmpty() && !sourceRealm.getRealmNames().isEmpty()) {
978  updateRealm(destRealm, null, sourceRealm.getRealmNames().get(0), trans.getConnection());
979  }
980  }
981 
992  List<OsAccountRealm> getRealmsByHost(Host host, CaseDbConnection connection) throws TskCoreException {
993  List<OsAccountRealm> results = new ArrayList<>();
994  String queryString = REALM_QUERY_STRING
995  + " WHERE realms.scope_host_id = " + host.getHostId();
996 
998  try ( Statement s = connection.createStatement();
999  ResultSet rs = connection.executeQuery(s, queryString)) {
1000  while (rs.next()) {
1001  results.add(resultSetToAccountRealm(rs));
1002  }
1003  return results;
1004  } catch (SQLException ex) {
1005  throw new TskCoreException(String.format("Error gettings realms for host with id = " + host.getHostId()), ex);
1006  }
1007  finally {
1009  }
1010  }
1011 
1015  public enum OsRealmUpdateStatus {
1016 
1019  MERGED
1020  }
1021 
1026  public final static class OsRealmUpdateResult {
1027 
1028  private final OsRealmUpdateStatus updateStatus;
1029  private final OsAccountRealm updatedRealm;
1030 
1031  OsRealmUpdateResult(OsRealmUpdateStatus updateStatus, OsAccountRealm updatedRealm) {
1032  this.updateStatus = updateStatus;
1033  this.updatedRealm = updatedRealm;
1034  }
1035 
1037  return updateStatus;
1038  }
1039 
1040  public Optional<OsAccountRealm> getUpdatedRealm() {
1041  return Optional.ofNullable(updatedRealm);
1042  }
1043  }
1044 }
OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope)
OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName)
OsAccountRealmManager getOsAccountRealmManager()
Optional< OsAccountRealm > getWindowsRealm(String accountSid, String realmName, Host referringHost)

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.