Sleuth Kit Java Bindings (JNI)  4.11.0
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-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 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) && StringUtils.isBlank(realmName)) {
86  throw new TskCoreException("Either an address or a name is required to create a realm.");
87  }
88 
89  Host scopeHost;
90  OsAccountRealm.ScopeConfidence scopeConfidence;
91 
92  switch (realmScope) {
93  case DOMAIN:
94  scopeHost = null;
95  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
96  break;
97  case LOCAL:
98  scopeHost = referringHost;
99  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
100  break;
101 
102  case UNKNOWN:
103  default:
104  // check if the referring host already has a realm
105  boolean isHostRealmKnown = isHostRealmKnown(referringHost);
106  if (isHostRealmKnown) {
107  scopeHost = null; // the realm does not scope to the referring host since it already has one.
108  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
109  } else {
110  scopeHost = referringHost;
111  scopeConfidence = OsAccountRealm.ScopeConfidence.INFERRED;
112  }
113  break;
114 
115  }
116 
117  // get windows realm address from sid
118  String realmAddr = null;
119  if (!Strings.isNullOrEmpty(accountSid)) {
120 
121  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
122  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
123  }
124 
125  realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
126 
127  // if the account is special windows account, create a local realm for it.
128  if (realmAddr.equals(WindowsAccountUtils.SPECIAL_WINDOWS_REALM_ADDR)) {
129  scopeHost = referringHost;
130  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
131  }
132  }
133 
134  String signature = makeRealmSignature(realmAddr, realmName, scopeHost);
135 
136  // create a realm
137  return newRealm(realmName, realmAddr, signature, scopeHost, scopeConfidence);
138  }
139 
157  public Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost) throws TskCoreException, OsAccountManager.NotUserSIDException {
158 
159  if (referringHost == null) {
160  throw new TskCoreException("A referring host is required get a realm.");
161  }
162 
163  // need at least one of the two, the addr or name to look up
164  if (Strings.isNullOrEmpty(accountSid) && Strings.isNullOrEmpty(realmName)) {
165  throw new TskCoreException("Realm address or name is required get a realm.");
166  }
167 
168  try (CaseDbConnection connection = this.db.getConnection()) {
169  return getWindowsRealm(accountSid, realmName, referringHost, connection);
170  }
171  }
172 
173 
188  Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
189 
190  if (referringHost == null) {
191  throw new TskCoreException("A referring host is required get a realm.");
192  }
193 
194  // need at least one of the two, the addr or name to look up
195  if (StringUtils.isBlank(accountSid) && StringUtils.isBlank(realmName)) {
196  throw new TskCoreException("Realm address or name is required get a realm.");
197  }
198 
199  // If an accountSID is provided search for realm by addr.
200  if (!Strings.isNullOrEmpty(accountSid)) {
201 
202  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
203  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
204  }
205  // get realm addr from the account SID.
206  String realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
207  Optional<OsAccountRealm> realm = getRealmByAddr(realmAddr, referringHost, connection);
208  if (realm.isPresent()) {
209  return realm;
210  }
211  }
212 
213  // No realm addr so search by name.
214  Optional<OsAccountRealm> realm = getRealmByName(realmName, referringHost, connection);
215  if (realm.isPresent() && !Strings.isNullOrEmpty(accountSid)) {
216  // If we were given an accountSID, make sure there isn't one set on the matching realm.
217  // We know it won't match because the previous search by SID failed.
218  if (realm.get().getRealmAddr().isPresent()) {
219  return Optional.empty();
220  }
221  }
222  return realm;
223  }
224 
225 
245  Optional<OsAccountRealm> getAndUpdateWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
246 
247  // get realm
248  Optional<OsAccountRealm> realmOptional = getWindowsRealm(accountSid, realmName, referringHost, connection );
249 
250  // if found, update it if needed
251  if (realmOptional.isPresent()) {
252  String realmAddr = StringUtils.isNotBlank(accountSid) ? WindowsAccountUtils.getWindowsRealmAddress(accountSid) : null;
253  OsRealmUpdateResult realmUpdateResult = updateRealm(realmOptional.get(), realmAddr, realmName, connection);
254 
255  // if realm was updated, return the updated realm
256  if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
257  return realmUpdateResult.getUpdatedRealm();
258  }
259  }
260 
261  return realmOptional; // return the found realm as is, if any
262  }
263 
264 
285  public OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName) throws TskCoreException {
286 
287  try (CaseDbConnection connection = db.getConnection()) {
288  return updateRealm(realm, realmAddr, realmName, connection);
289  }
290  }
291 
308  private OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName, CaseDbConnection connection) throws TskCoreException {
309 
310  // need at least one of the two
311  if (StringUtils.isBlank(realmAddr) && StringUtils.isBlank(realmName)) {
312  throw new TskCoreException("Realm address or name is required to update realm.");
313  }
314 
315  OsRealmUpdateStatus updateStatusCode = OsRealmUpdateStatus.NO_CHANGE;
316  OsAccountRealm updatedRealm = null;
317 
319  try {
320  List<String> realmNames = realm.getRealmNames();
321  String currRealmName = realmNames.isEmpty() ? null : realmNames.get(0); // currently there is only one name.
322  String currRealmAddr = realm.getRealmAddr().orElse(null);
323 
324  // set name and address to new values only if the current value is blank and the new value isn't.
325  if ((StringUtils.isBlank(currRealmAddr) && StringUtils.isNotBlank(realmAddr))) {
326  updateRealmColumn(realm.getRealmId(), "realm_addr", realmAddr, connection);
327  updateStatusCode = OsRealmUpdateStatus.UPDATED;
328  }
329 
330  if (StringUtils.isBlank(currRealmName) && StringUtils.isNotBlank(realmName)) {
331  updateRealmColumn(realm.getRealmId(), "realm_name", realmName, connection);
332  updateStatusCode = OsRealmUpdateStatus.UPDATED;
333  }
334 
335  // if nothing is to be changed, return
336  if (updateStatusCode == OsRealmUpdateStatus.NO_CHANGE) {
337  return new OsRealmUpdateResult(updateStatusCode, realm);
338  }
339 
340  // update realm signature - based on the most current address and name
341  OsAccountRealm currRealm = getRealmByRealmId(realm.getRealmId(), connection);
342  String newRealmAddr = currRealm.getRealmAddr().orElse(null);
343  String newRealmName = (currRealm.getRealmNames().isEmpty() == false) ? currRealm.getRealmNames().get(0) : null;
344 
345  // make new signature
346  String newSignature = makeRealmSignature(newRealmAddr, newRealmName, realm.getScopeHost().orElse(null));
347 
348  // Use a random string as the signature if the realm is not active.
349  String updateSQL = "UPDATE tsk_os_account_realms SET "
350  + " realm_signature = "
351  + " CASE WHEN db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId() + " THEN ? ELSE realm_signature END "
352  + " WHERE id = ?";
353  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
354  preparedStatement.clearParameters();
355 
356  preparedStatement.setString(1, newSignature); // Is only set for active accounts
357  preparedStatement.setLong(2, realm.getRealmId());
358  connection.executeUpdate(preparedStatement);
359 
360  // read the updated realm
361  updatedRealm = this.getRealmByRealmId(realm.getRealmId(), connection);
362 
363  return new OsRealmUpdateResult(updateStatusCode, updatedRealm);
364  } catch (SQLException ex) {
365  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);
366  } finally {
368  }
369 
370  }
371 
384  private <T> void updateRealmColumn(long realmId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
385 
386  String updateSQL = "UPDATE tsk_os_account_realms "
387  + " SET " + colName + " = ? "
388  + " WHERE id = ?";
389 
391  try {
392  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
393  preparedStatement.clearParameters();
394 
395  if (Objects.isNull(colValue)) {
396  preparedStatement.setNull(1, Types.NULL); // handle null value
397  } else {
398  if (colValue instanceof String) {
399  preparedStatement.setString(1, (String) colValue);
400  } else if (colValue instanceof Long) {
401  preparedStatement.setLong(1, (Long) colValue);
402  } else if (colValue instanceof Integer) {
403  preparedStatement.setInt(1, (Integer) colValue);
404  } else {
405  throw new TskCoreException(String.format("Unhandled column data type received while updating the realm (id = %d) ", realmId));
406  }
407  }
408 
409  preparedStatement.setLong(2, realmId);
410 
411  connection.executeUpdate(preparedStatement);
412  } finally {
414  }
415  }
416 
417  private final static String REALM_QUERY_STRING = "SELECT realms.id as realm_id, realms.realm_name as realm_name,"
418  + " realms.realm_addr as realm_addr, realms.realm_signature as realm_signature, realms.scope_host_id, realms.scope_confidence, realms.db_status,"
419  + " hosts.id, hosts.name as host_name "
420  + " FROM tsk_os_account_realms as realms"
421  + " LEFT JOIN tsk_hosts as hosts"
422  + " ON realms.scope_host_id = hosts.id";
423 
433  public OsAccountRealm getRealmByRealmId(long id) throws TskCoreException {
434  try (CaseDbConnection connection = this.db.getConnection()) {
435  return getRealmByRealmId(id, connection);
436  }
437  }
438 
448  OsAccountRealm getRealmByRealmId(long id, CaseDbConnection connection) throws TskCoreException {
449 
450  String queryString = REALM_QUERY_STRING
451  + " WHERE realms.id = " + id;
452 
454  try ( Statement s = connection.createStatement();
455  ResultSet rs = connection.executeQuery(s, queryString)) {
456  OsAccountRealm accountRealm = null;
457  if (rs.next()) {
458  accountRealm = resultSetToAccountRealm(rs);
459  } else {
460  throw new TskCoreException(String.format("No realm found with id = %d", id));
461  }
462 
463  return accountRealm;
464  } catch (SQLException ex) {
465  throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
466  }
467  finally {
469  }
470  }
471 
483  Optional<OsAccountRealm> getRealmByAddr(String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
484 
485  // 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.
486  // If no host is specified, then we return the first realm with matching addr.
487  String whereHostClause = (host == null)
488  ? " 1 = 1 "
489  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
490  String queryString = REALM_QUERY_STRING
491  + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') "
492  + " AND " + whereHostClause
493  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
494  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
495 
497  try ( Statement s = connection.createStatement();
498  ResultSet rs = connection.executeQuery(s, queryString)) {
499 
500  OsAccountRealm accountRealm = null;
501  if (rs.next()) {
502  Host realmHost = null;
503  long hostId = rs.getLong("scope_host_id");
504  if (!rs.wasNull()) {
505  if (host != null ) {
506  realmHost = host; // exact match on given host
507  } else {
508  realmHost = new Host(hostId, rs.getString("host_name"));
509  }
510  }
511 
512  accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
513  rs.getString("realm_addr"), rs.getString("realm_signature"),
514  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
515  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
516  }
517  return Optional.ofNullable(accountRealm);
518  } catch (SQLException ex) {
519  throw new TskCoreException(String.format("Error running the realms query = %s with realmaddr = %s and host name = %s",
520  queryString, realmAddr, (host != null ? host.getName() : "Null")), ex);
521  } finally {
523  }
524  }
525 
536  Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
537 
538  // 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.
539  // If no host is specified, then we return the first realm with matching name.
540  String whereHostClause = (host == null)
541  ? " 1 = 1 "
542  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL ) ";
543  String queryString = REALM_QUERY_STRING
544  + " WHERE LOWER(realms.realm_name) = LOWER('" + realmName + "')"
545  + " AND " + whereHostClause
546  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
547  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
548 
550  try (Statement s = connection.createStatement();
551  ResultSet rs = connection.executeQuery(s, queryString)) {
552 
553  OsAccountRealm accountRealm = null;
554  if (rs.next()) {
555  Host realmHost = null;
556  long hostId = rs.getLong("scope_host_id");
557  if (!rs.wasNull()) {
558  if (host != null ) {
559  realmHost = host;
560  } else {
561  realmHost = new Host(hostId, rs.getString("host_name"));
562  }
563  }
564 
565  accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
566  rs.getString("realm_addr"), rs.getString("realm_signature"),
567  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
568  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
569 
570  }
571  return Optional.ofNullable(accountRealm);
572  } catch (SQLException ex) {
573  throw new TskCoreException(String.format("Error getting account realm for with name = %s", realmName), ex);
574  } finally {
576  }
577  }
578 
589  private boolean isHostRealmKnown(Host host) throws TskCoreException {
590 
591  // check if this host has a local known realm aleady, other than the special windows realm.
592  String queryString = REALM_QUERY_STRING
593  + " WHERE realms.scope_host_id = " + host.getHostId()
594  + " AND realms.scope_confidence = " + OsAccountRealm.ScopeConfidence.KNOWN.getId()
595  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
596  + " AND LOWER(realms.realm_addr) <> LOWER('"+ WindowsAccountUtils.SPECIAL_WINDOWS_REALM_ADDR + "') ";
597 
599  try (CaseDbConnection connection = this.db.getConnection();
600  Statement s = connection.createStatement();
601  ResultSet rs = connection.executeQuery(s, queryString)) {
602 
603  // return true if there is any match.
604  return rs.next();
605  } catch (SQLException ex) {
606  throw new TskCoreException(String.format("Error getting account realm for with host = %s", host.getName()), ex);
607  }
608  finally {
610  }
611 
612  }
613 
621  private OsAccountRealm resultSetToAccountRealm(ResultSet rs) throws SQLException {
622 
623  long hostId = rs.getLong("scope_host_id");
624  Host realmHost = null;
625  if (!rs.wasNull()) {
626  realmHost = new Host(hostId, rs.getString("host_name"));
627  }
628 
629  return new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
630  rs.getString("realm_addr"), rs.getString("realm_signature"),
631  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
632  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
633  }
634 
635 // /**
636 // * Get all realms.
637 // *
638 // * @return Collection of OsAccountRealm
639 // */
640 // Collection<OsAccountRealm> getRealms() throws TskCoreException {
641 // 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, "
642 // + " hosts.id, hosts.name as host_name "
643 // + " FROM tsk_os_account_realms as realms"
644 // + " LEFT JOIN tsk_hosts as hosts"
645 // + " ON realms.scope_host_id = hosts.id";
646 //
647 // db.acquireSingleUserCaseReadLock();
648 // try (CaseDbConnection connection = this.db.getConnection();
649 // Statement s = connection.createStatement();
650 // ResultSet rs = connection.executeQuery(s, queryString)) {
651 //
652 // ArrayList<OsAccountRealm> accountRealms = new ArrayList<>();
653 // while (rs.next()) {
654 // long hostId = rs.getLong("scope_host_id");
655 // Host host = null;
656 // if (!rs.wasNull()) {
657 // host = new Host(hostId, rs.getString("host_name"));
658 // }
659 //
660 // accountRealms.add(new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
661 // ScopeConfidence.fromID(rs.getInt("scope_confidence")),
662 // rs.getString("realm_addr"), host));
663 // }
664 //
665 // return accountRealms;
666 // } catch (SQLException ex) {
667 // throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
668 // }
669 // finally {
670 // db.releaseSingleUserCaseReadLock();
671 // }
672 // }
673 
674 
692  private OsAccountRealm newRealm(String realmName, String realmAddr, String signature, Host host, OsAccountRealm.ScopeConfidence scopeConfidence) throws TskCoreException {
693 
695  try (CaseDbConnection connection = this.db.getConnection()) {
696  String realmInsertSQL = "INSERT INTO tsk_os_account_realms(realm_name, realm_addr, realm_signature, scope_host_id, scope_confidence)"
697  + " VALUES (?, ?, ?, ?, ?)"; // NON-NLS
698 
699  PreparedStatement preparedStatement = connection.getPreparedStatement(realmInsertSQL, Statement.RETURN_GENERATED_KEYS);
700  preparedStatement.clearParameters();
701 
702  preparedStatement.setString(1, realmName);
703  preparedStatement.setString(2, realmAddr);
704  preparedStatement.setString(3, signature);
705  if (host != null) {
706  preparedStatement.setLong(4, host.getHostId());
707  } else {
708  preparedStatement.setNull(4, java.sql.Types.BIGINT);
709  }
710  preparedStatement.setInt(5, scopeConfidence.getId());
711 
712  connection.executeUpdate(preparedStatement);
713 
714  // Read back the row id
715  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
716  long rowId = resultSet.getLong(1); // last_insert_rowid()
717  return new OsAccountRealm(rowId, realmName, realmAddr, signature, host, scopeConfidence, OsAccountRealm.RealmDbStatus.ACTIVE);
718  }
719 
720  } catch (SQLException ex) {
721  // Create may have failed if the realm already exists. Try and get the matching realm
722  try (CaseDbConnection connection = this.db.getConnection()) {
723  if (!Strings.isNullOrEmpty(realmAddr)) {
724  Optional<OsAccountRealm> accountRealm = this.getRealmByAddr(realmAddr, host, connection);
725  if (accountRealm.isPresent()) {
726  return accountRealm.get();
727  }
728  } else if (!Strings.isNullOrEmpty(realmName)) {
729  Optional<OsAccountRealm> accountRealm = this.getRealmByName(realmName, host, connection);
730  if (accountRealm.isPresent()) {
731  return accountRealm.get();
732  }
733  }
734 
735  // some other failure - throw an exception
736  throw new TskCoreException(String.format("Error creating realm with address = %s and name = %s, with host = %s",
737  realmAddr != null ? realmAddr : "", realmName != null ? realmName : "", host != null ? host.getName() : ""), ex);
738  }
739  } finally {
741  }
742  }
743 
744 
761  static String makeRealmSignature(String realmAddr, String realmName, Host scopeHost) throws TskCoreException {
762 
763  // need at least one of the two, the addr or name to look up
764  if (Strings.isNullOrEmpty(realmAddr) && Strings.isNullOrEmpty(realmName)) {
765  throw new TskCoreException("Realm address and name can't both be null.");
766  }
767 
768  String signature = String.format("%s_%s", !Strings.isNullOrEmpty(realmAddr) ? realmAddr : realmName,
769  scopeHost != null ? scopeHost.getHostId() : "DOMAIN");
770  return signature;
771  }
772 
778  private String makeMergedRealmSignature() {
779  return "MERGED " + UUID.randomUUID().toString();
780  }
781 
782 
791  void moveOrMergeRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
792  // Look for a matching realm by address
793  Optional<OsAccountRealm> optDestRealmAddr = Optional.empty();
794  if (sourceRealm.getRealmAddr().isPresent()) {
795  optDestRealmAddr = db.getOsAccountRealmManager().getRealmByAddr(sourceRealm.getRealmAddr().get(), destHost, trans.getConnection());
796  }
797 
798  // Look for a matching realm by name
799  Optional<OsAccountRealm> optDestRealmName = Optional.empty();
800  if (!sourceRealm.getRealmNames().isEmpty()) {
801  optDestRealmName = db.getOsAccountRealmManager().getRealmByName(sourceRealm.getRealmNames().get(0), destHost, trans.getConnection());
802  }
803 
804  // Decide how to proceed:
805  // - If we only got one match:
806  // -- If the address matched, set destRealm to the matching address realm
807  // -- If the name matched but the original and the matching realm have different addresses, leave destRealm null (it'll be a move)
808  // -- If the name matched and at least one of the address fields was null, set destRealm to the matching name realm
809  // - If we got no matches, leave destRealm null (we'll do a move not a merge)
810  // - If we got two of the same matches, set destRealm to that realm
811  // - If we got two different matches:
812  // -- If the name match has no address set, merge the matching name realm into the matching address realm, then
813  // set destRealm to the matching address realm
814  // -- Otherwise we're in the case where the addresses are different. We will consider the address the
815  // stronger match and set destRealm to the matching address realm and leave the matching name realm as-is.
816  OsAccountRealm destRealm = null;
817  if (optDestRealmAddr.isPresent() && optDestRealmName.isPresent()) {
818  if (optDestRealmAddr.get().getRealmId() == optDestRealmName.get().getRealmId()) {
819  // The two matches are the same
820  destRealm = optDestRealmAddr.get();
821  } else {
822  if (optDestRealmName.get().getRealmAddr().isPresent()) {
823  // The addresses are different, so use the one with the matching address
824  destRealm = optDestRealmAddr.get();
825  } else {
826  // Merge the realm with the matching name into the realm with the matching address.
827  // Reload from database afterward to make sure everything is up-to-date.
828  mergeRealms(optDestRealmName.get(), optDestRealmAddr.get(), trans);
829  destRealm = getRealmByRealmId(optDestRealmAddr.get().getRealmId(), trans.getConnection());
830  }
831  }
832  } else if (optDestRealmAddr.isPresent()) {
833  // Only address matched - use it
834  destRealm = optDestRealmAddr.get();
835  } else if (optDestRealmName.isPresent()) {
836  // Only name matched - check whether both have addresses set.
837  // Due to earlier checks we know the address fields can't be the same, so
838  // don't do anything if both have addresses - we consider the address to be a stronger identifier than the name
839  if (! (optDestRealmName.get().getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent())) {
840  destRealm = optDestRealmName.get();
841  }
842  }
843 
844  // Move or merge the source realm
845  if (destRealm == null) {
846  moveRealm(sourceRealm, destHost, trans);
847  } else {
848  mergeRealms(sourceRealm, destRealm, trans);
849  }
850  }
851 
863  private void moveRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
864  try(Statement s = trans.getConnection().createStatement()) {
865  String query = "UPDATE tsk_os_account_realms SET scope_host_id = " + destHost.getHostId() + " WHERE id = " + sourceRealm.getRealmId();
866  s.executeUpdate(query);
867  } catch (SQLException ex) {
868  throw new TskCoreException("Error moving realm with id: " + sourceRealm.getRealmId() + " to host with id: " + destHost.getHostId(), ex);
869  }
870  }
871 
872 
882  void mergeRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
883 
884  // Update accounts
885  db.getOsAccountManager().mergeOsAccountsForRealms(sourceRealm, destRealm, trans);
886 
887  // Update the sourceRealm realm
888  CaseDbConnection connection = trans.getConnection();
889  try (Statement statement = connection.createStatement()) {
890  String updateStr = "UPDATE tsk_os_account_realms SET db_status = " + OsAccountRealm.RealmDbStatus.MERGED.getId()
891  + ", merged_into = " + destRealm.getRealmId()
892  + ", realm_signature = '" + makeMergedRealmSignature() + "' "
893  + " WHERE id = " + sourceRealm.getRealmId();
894  connection.executeUpdate(statement, updateStr);
895  } catch (SQLException ex) {
896  throw new TskCoreException ("Error updating status of realm with id: " + sourceRealm.getRealmId(), ex);
897  }
898 
899  // Update the destination realm if it doesn't have the name or addr set and the source realm does
900  if (!destRealm.getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent()) {
901  updateRealm(destRealm, sourceRealm.getRealmAddr().get(), null, trans.getConnection());
902  } else if (destRealm.getRealmNames().isEmpty() && !sourceRealm.getRealmNames().isEmpty()) {
903  updateRealm(destRealm, null, sourceRealm.getRealmNames().get(0), trans.getConnection());
904  }
905  }
906 
917  List<OsAccountRealm> getRealmsByHost(Host host, CaseDbConnection connection) throws TskCoreException {
918  List<OsAccountRealm> results = new ArrayList<>();
919  String queryString = REALM_QUERY_STRING
920  + " WHERE realms.scope_host_id = " + host.getHostId();
921 
923  try ( Statement s = connection.createStatement();
924  ResultSet rs = connection.executeQuery(s, queryString)) {
925  while (rs.next()) {
926  results.add(resultSetToAccountRealm(rs));
927  }
928  return results;
929  } catch (SQLException ex) {
930  throw new TskCoreException(String.format("Error gettings realms for host with id = " + host.getHostId()), ex);
931  }
932  finally {
934  }
935  }
936 
940  public enum OsRealmUpdateStatus {
941 
944  MERGED
945  }
946 
951  public final static class OsRealmUpdateResult {
952 
953  private final OsRealmUpdateStatus updateStatus;
954  private final OsAccountRealm updatedRealm;
955 
956  OsRealmUpdateResult(OsRealmUpdateStatus updateStatus, OsAccountRealm updatedRealm) {
957  this.updateStatus = updateStatus;
958  this.updatedRealm = updatedRealm;
959  }
960 
962  return updateStatus;
963  }
964 
965  public Optional<OsAccountRealm> getUpdatedRealm() {
966  return Optional.ofNullable(updatedRealm);
967  }
968  }
969 }
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.