Sleuth Kit Java Bindings (JNI)  4.12.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.annotations.Beta;
22 import com.google.common.base.Strings;
23 import org.apache.commons.lang3.StringUtils;
24 import java.sql.PreparedStatement;
25 import java.sql.ResultSet;
26 import java.sql.SQLException;
27 import java.sql.Statement;
28 import java.sql.Types;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.Locale;
32 import java.util.Objects;
33 import java.util.Optional;
34 import java.util.UUID;
35 import java.util.logging.Logger;
39 
40 
45 public final class OsAccountRealmManager {
46 
47  private static final Logger LOGGER = Logger.getLogger(OsAccountRealmManager.class.getName());
48  private static final String LOCAL_REALM_NAME = "local";
49 
50  private final SleuthkitCase db;
51 
59  this.db = skCase;
60  }
61 
80  public OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope) throws TskCoreException, OsAccountManager.NotUserSIDException {
81 
82  if (realmScope == null) {
83  throw new TskCoreException("RealmScope cannot be null. Use UNKNOWN if scope is not known.");
84  }
85  if (referringHost == null) {
86  throw new TskCoreException("A referring host is required to create a realm.");
87  }
88  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
89  && StringUtils.isBlank(realmName)) {
90  throw new TskCoreException("Either an address or a name is required to create a realm.");
91  }
92 
93  if (StringUtils.isNotBlank(accountSid)) {
94  // SID Normalized to uppercase
95  accountSid = accountSid.toUpperCase(Locale.ENGLISH);
96  }
97  if (StringUtils.isNotBlank(realmName)) {
98  // Windows realm names are case insensitive. saving them in lower case.
99  realmName = realmName.toLowerCase(Locale.ENGLISH);
100  }
101 
102  Host scopeHost;
103  OsAccountRealm.ScopeConfidence scopeConfidence;
104 
105  switch (realmScope) {
106  case DOMAIN:
107  scopeHost = null;
108  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
109  break;
110  case LOCAL:
111  scopeHost = referringHost;
112  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
113  break;
114 
115  case UNKNOWN:
116  default:
117  // NOTE: if there's a well known SID, the scope will be changed to LOCAL later.
118  // check if the referring host already has a realm
119  boolean isHostRealmKnown = isHostRealmKnown(referringHost);
120  if (isHostRealmKnown) {
121  scopeHost = null; // the realm does not scope to the referring host since it already has one.
122  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
123  } else {
124  scopeHost = referringHost;
125  scopeConfidence = OsAccountRealm.ScopeConfidence.INFERRED;
126  }
127  break;
128 
129  }
130 
131  // get windows realm address from sid
132  String realmAddr = null;
133  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
134  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
135 
136  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
137  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
138  }
139 
140  realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
141 
142 
143  if (WindowsAccountUtils.isWindowsWellKnownSid(accountSid)) {
144 
145  // if the sid is a Windows well known SID, create a local realm for it.
146  scopeHost = referringHost;
147  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
148 
149  // if the SID is a Windows well known SID, then prefer to use the default well known name to create the realm
150  String wellKnownRealmName = WindowsAccountUtils.getWindowsWellKnownSidRealmName(accountSid);
151  if (!StringUtils.isEmpty(wellKnownRealmName)) {
152  resolvedRealmName = wellKnownRealmName;
153  }
154  }
155  }
156 
157  String signature = makeRealmSignature(realmAddr, resolvedRealmName, scopeHost);
158 
159  // create a realm
160  return newRealm(resolvedRealmName, realmAddr, signature, scopeHost, scopeConfidence);
161  }
162 
173  @Beta
175 
176  if (referringHost == null) {
177  throw new TskCoreException("A referring host is required to create a realm.");
178  }
179 
180  String realmName = LOCAL_REALM_NAME;
182  String signature = makeRealmSignature("", realmName, referringHost);
183 
184  // create a realm
185  return newRealm(realmName, "", signature, referringHost, scopeConfidence);
186  }
187 
198  @Beta
199  public Optional<OsAccountRealm> getLocalLinuxRealm(Host referringHost) throws TskCoreException {
200  if (referringHost == null) {
201  throw new TskCoreException("A referring host is required get a realm.");
202  }
203 
204  try (CaseDbConnection connection = this.db.getConnection()) {
205  return getRealmByName(LOCAL_REALM_NAME, referringHost, connection);
206  }
207  }
208 
226  public Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost) throws TskCoreException, OsAccountManager.NotUserSIDException {
227 
228  if (referringHost == null) {
229  throw new TskCoreException("A referring host is required get a realm.");
230  }
231 
232  // need at least one of the two, the addr or name to look up
233  if ((Strings.isNullOrEmpty(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) )
234  && Strings.isNullOrEmpty(realmName)) {
235  throw new TskCoreException("Realm address or name is required get a realm.");
236  }
237  if (StringUtils.isNotBlank(accountSid)) {
238  // SID Normalized to uppercase
239  accountSid = accountSid.toUpperCase(Locale.ENGLISH);
240  }
241  if (StringUtils.isNotBlank(realmName)) {
242  // Windows realm names are case insensitive. saving them in lower case.
243  realmName = realmName.toLowerCase(Locale.ENGLISH);
244  }
245 
246  try (CaseDbConnection connection = this.db.getConnection()) {
247  return getWindowsRealm(accountSid, realmName, referringHost, connection);
248  }
249  }
250 
251 
266  Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
267 
268  if (referringHost == null) {
269  throw new TskCoreException("A referring host is required get a realm.");
270  }
271 
272  // need at least one of the two, the addr or name to look up
273  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
274  && StringUtils.isBlank(realmName)) {
275  throw new TskCoreException("Realm address or name is required get a realm.");
276  }
277 
278  // If a non null accountSID is provided search for realm by addr.
279  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
280 
281  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
282  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
283  }
284  // get realm addr from the account SID.
285  String realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
286  Optional<OsAccountRealm> realm = getRealmByAddr(realmAddr, referringHost, connection);
287  if (realm.isPresent()) {
288  return realm;
289  }
290  }
291 
292  // ensure we are using English names for any well known SIDs.
293  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
294 
295  // No realm addr so search by name.
296  Optional<OsAccountRealm> realm = getRealmByName(resolvedRealmName, referringHost, connection);
297  if (realm.isPresent() && !Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
298  // If we were given a non-null accountSID, make sure there isn't one set on the matching realm.
299  // We know it won't match because the previous search by SID failed.
300  if (realm.get().getRealmAddr().isPresent()) {
301  return Optional.empty();
302  }
303  }
304  return realm;
305  }
306 
307 
326  OsRealmUpdateResult getAndUpdateWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
327 
328  // get realm
329  Optional<OsAccountRealm> realmOptional = getWindowsRealm(accountSid, realmName, referringHost, connection);
330 
331  // if found, update it if needed
332  if (realmOptional.isPresent()) {
333  String realmAddr = (StringUtils.isNotBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ? WindowsAccountUtils.getWindowsRealmAddress(accountSid) : null;
334  OsRealmUpdateResult realmUpdateResult = updateRealm(realmOptional.get(), realmAddr, realmName, connection);
335 
336  return realmUpdateResult;
337 
338  } else {
339  return new OsRealmUpdateResult(OsRealmUpdateStatus.NO_CHANGE, null);
340  }
341 
342  }
343 
344 
365  public OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName) throws TskCoreException {
366 
367  try (CaseDbConnection connection = db.getConnection()) {
368  return updateRealm(realm, realmAddr, realmName, connection);
369  }
370  }
371 
391  private OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName, CaseDbConnection connection) throws TskCoreException {
392 
393  // need at least one of the two
394  if ( (StringUtils.isBlank(realmAddr) || realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
395  && StringUtils.isBlank(realmName)) {
396  throw new TskCoreException("Realm address or name is required to update realm.");
397  }
398 
399  OsRealmUpdateStatus updateStatusCode = OsRealmUpdateStatus.NO_CHANGE;
400  OsAccountRealm updatedRealm = null;
401 
403  try {
404  String currRealmAddr = realm.getRealmAddr().orElse(null);
405 
406  // set name and address to new values only if the current value is blank and the new value isn't.
407  if ((StringUtils.isBlank(currRealmAddr) && StringUtils.isNotBlank(realmAddr) && !realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))) {
408  updateRealmColumn(realm.getRealmId(), "realm_addr", realmAddr, connection);
409  currRealmAddr = realmAddr;
410  updateStatusCode = OsRealmUpdateStatus.UPDATED;
411  }
412 
413  List<String> realmNames = realm.getRealmNames();
414  String currRealmName = realmNames.isEmpty() ? null : realmNames.get(0); // currently there is only one name.
415 
416  // Update realm name if:
417  // Current realm name is empty
418  // The passed in realm name is not empty
419  if (StringUtils.isBlank(currRealmName) && StringUtils.isNotBlank(realmName)) {
420  updateRealmColumn(realm.getRealmId(), "realm_name", realmName, connection);
421  updateStatusCode = OsRealmUpdateStatus.UPDATED;
422  }
423 
424  // if nothing is to be changed, return
425  if (updateStatusCode == OsRealmUpdateStatus.NO_CHANGE) {
426  return new OsRealmUpdateResult(updateStatusCode, realm);
427  }
428 
429  // update realm signature - based on the most current address and name
430  OsAccountRealm currRealm = getRealmByRealmId(realm.getRealmId(), connection);
431  String newRealmAddr = currRealm.getRealmAddr().orElse(null);
432  String newRealmName = (currRealm.getRealmNames().isEmpty() == false) ? currRealm.getRealmNames().get(0) : null;
433 
434  // make new signature
435  String newSignature = makeRealmSignature(newRealmAddr, newRealmName, realm.getScopeHost().orElse(null));
436 
437  // Use a random string as the signature if the realm is not active.
438  String updateSQL = "UPDATE tsk_os_account_realms SET "
439  + " realm_signature = "
440  + " CASE WHEN db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId() + " THEN ? ELSE realm_signature END "
441  + " WHERE id = ?";
442  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
443  preparedStatement.clearParameters();
444 
445  preparedStatement.setString(1, newSignature); // Is only set for active accounts
446  preparedStatement.setLong(2, realm.getRealmId());
447  connection.executeUpdate(preparedStatement);
448 
449  // read the updated realm
450  updatedRealm = this.getRealmByRealmId(realm.getRealmId(), connection);
451 
452  return new OsRealmUpdateResult(updateStatusCode, updatedRealm);
453  } catch (SQLException ex) {
454  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);
455  } finally {
457  }
458 
459  }
460 
473  private <T> void updateRealmColumn(long realmId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
474 
475  String updateSQL = "UPDATE tsk_os_account_realms "
476  + " SET " + colName + " = ? "
477  + " WHERE id = ?";
478 
480  try {
481  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
482  preparedStatement.clearParameters();
483 
484  if (Objects.isNull(colValue)) {
485  preparedStatement.setNull(1, Types.NULL); // handle null value
486  } else {
487  if (colValue instanceof String) {
488  preparedStatement.setString(1, (String) colValue);
489  } else if (colValue instanceof Long) {
490  preparedStatement.setLong(1, (Long) colValue);
491  } else if (colValue instanceof Integer) {
492  preparedStatement.setInt(1, (Integer) colValue);
493  } else {
494  throw new TskCoreException(String.format("Unhandled column data type received while updating the realm (id = %d) ", realmId));
495  }
496  }
497 
498  preparedStatement.setLong(2, realmId);
499 
500  connection.executeUpdate(preparedStatement);
501  } finally {
503  }
504  }
505 
506  private final static String REALM_QUERY_STRING = "SELECT realms.id as realm_id, realms.realm_name as realm_name,"
507  + " realms.realm_addr as realm_addr, realms.realm_signature as realm_signature, realms.scope_host_id, realms.scope_confidence, realms.db_status,"
508  + " hosts.id, hosts.name as host_name "
509  + " FROM tsk_os_account_realms as realms"
510  + " LEFT JOIN tsk_hosts as hosts"
511  + " ON realms.scope_host_id = hosts.id";
512 
522  public OsAccountRealm getRealmByRealmId(long id) throws TskCoreException {
523  try (CaseDbConnection connection = this.db.getConnection()) {
524  return getRealmByRealmId(id, connection);
525  }
526  }
527 
537  OsAccountRealm getRealmByRealmId(long id, CaseDbConnection connection) throws TskCoreException {
538 
539  String queryString = REALM_QUERY_STRING
540  + " WHERE realms.id = " + id;
541 
543  try ( Statement s = connection.createStatement();
544  ResultSet rs = connection.executeQuery(s, queryString)) {
545  OsAccountRealm accountRealm = null;
546  if (rs.next()) {
547  accountRealm = resultSetToAccountRealm(rs);
548  } else {
549  throw new TskCoreException(String.format("No realm found with id = %d", id));
550  }
551 
552  return accountRealm;
553  } catch (SQLException ex) {
554  throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
555  }
556  finally {
558  }
559  }
560 
572  Optional<OsAccountRealm> getRealmByAddr(String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
573 
574  // 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.
575  // If no host is specified, then we return the first realm with matching addr.
576  String whereHostClause = (host == null)
577  ? " 1 = 1 "
578  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
579  String queryString = REALM_QUERY_STRING
580  + " WHERE realms.realm_addr = '"+ realmAddr + "' "
581  + " AND " + whereHostClause
582  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
583  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
584 
585  return getRealmUsingQuery(queryString, host, connection);
586  }
587 
600  Optional<OsAccountRealm> getAnotherRealmByAddr(OsAccountRealm realm, String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
601 
602  // If the given realm has a host id, then the other realm should have the same host id
603  // If the given realm has no host id, then the other realm should have no host id
604  String whereHostClause = realm.getScopeHost().isPresent()
605  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
606  : " realms.scope_host_id IS NULL ";
607  String queryString = REALM_QUERY_STRING
608  + " WHERE realms.realm_addr = '"+ realmAddr + "' "
609  + " AND " + whereHostClause
610  + " AND realms.id <> " + realm.getRealmId()
611  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
612  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
613 
614  return getRealmUsingQuery(queryString, host, connection);
615  }
616 
627  Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
628 
629  // 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.
630  // If no host is specified, then we return the first realm with matching name.
631  String whereHostClause = (host == null)
632  ? " 1 = 1 "
633  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL ) ";
634  String queryString = REALM_QUERY_STRING
635  + " WHERE realms.realm_name = '" + realmName + "'"
636  + " AND " + whereHostClause
637  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
638  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
639 
640  return getRealmUsingQuery(queryString, host, connection);
641  }
642 
655  Optional<OsAccountRealm> getAnotherRealmByName(OsAccountRealm realm, String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
656 
657  // If the given realm has a host id, then the other realm should have the same host id
658  // If the given realm has no host id, then the other realm should have no host id
659  String whereHostClause = realm.getScopeHost().isPresent()
660  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
661  : " realms.scope_host_id IS NULL ";
662  String queryString = REALM_QUERY_STRING
663  + " WHERE realms.realm_name = '" + realmName + "'"
664  + " AND " + whereHostClause
665  + " AND realms.id <> " + realm.getRealmId()
666  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
667  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
668 
669  return getRealmUsingQuery(queryString, host, connection);
670 
671  }
672 
684  private Optional<OsAccountRealm> getRealmUsingQuery(String queryString, Host host, CaseDbConnection connection) throws TskCoreException {
685 
687  try (Statement s = connection.createStatement();
688  ResultSet rs = connection.executeQuery(s, queryString)) {
689 
690  OsAccountRealm accountRealm = null;
691  if (rs.next()) {
692  Host realmHost = null;
693  long hostId = rs.getLong("scope_host_id");
694  if (!rs.wasNull()) {
695  if (host != null ) {
696  realmHost = host;
697  } else {
698  realmHost = new Host(hostId, rs.getString("host_name"));
699  }
700  }
701 
702  accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
703  rs.getString("realm_addr"), rs.getString("realm_signature"),
704  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
705  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
706 
707  }
708  return Optional.ofNullable(accountRealm);
709  } catch (SQLException ex) {
710  throw new TskCoreException(String.format("Error getting realm using query = %s", queryString), ex);
711  } finally {
713  }
714  }
715 
731  private boolean isHostRealmKnown(Host host) throws TskCoreException {
732 
733  // check if this host has a local known realm aleady, other than the special windows realm.
734  String queryString = REALM_QUERY_STRING
735  + " WHERE realms.scope_host_id = " + host.getHostId()
736  + " AND realms.scope_confidence = " + OsAccountRealm.ScopeConfidence.KNOWN.getId()
737  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId();
738 
740  try (CaseDbConnection connection = this.db.getConnection();
741  Statement s = connection.createStatement();
742  ResultSet rs = connection.executeQuery(s, queryString)) {
743 
744  // return true if there is any match.
745  return rs.next();
746  } catch (SQLException ex) {
747  throw new TskCoreException(String.format("Error getting account realm for with host = %s", host.getName()), ex);
748  }
749  finally {
751  }
752 
753  }
754 
762  private OsAccountRealm resultSetToAccountRealm(ResultSet rs) throws SQLException {
763 
764  long hostId = rs.getLong("scope_host_id");
765  Host realmHost = null;
766  if (!rs.wasNull()) {
767  realmHost = new Host(hostId, rs.getString("host_name"));
768  }
769 
770  return new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
771  rs.getString("realm_addr"), rs.getString("realm_signature"),
772  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
773  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
774  }
775 
776 // /**
777 // * Get all realms.
778 // *
779 // * @return Collection of OsAccountRealm
780 // */
781 // Collection<OsAccountRealm> getRealms() throws TskCoreException {
782 // 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, "
783 // + " hosts.id, hosts.name as host_name "
784 // + " FROM tsk_os_account_realms as realms"
785 // + " LEFT JOIN tsk_hosts as hosts"
786 // + " ON realms.scope_host_id = hosts.id";
787 //
788 // db.acquireSingleUserCaseReadLock();
789 // try (CaseDbConnection connection = this.db.getConnection();
790 // Statement s = connection.createStatement();
791 // ResultSet rs = connection.executeQuery(s, queryString)) {
792 //
793 // ArrayList<OsAccountRealm> accountRealms = new ArrayList<>();
794 // while (rs.next()) {
795 // long hostId = rs.getLong("scope_host_id");
796 // Host host = null;
797 // if (!rs.wasNull()) {
798 // host = new Host(hostId, rs.getString("host_name"));
799 // }
800 //
801 // accountRealms.add(new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
802 // ScopeConfidence.fromID(rs.getInt("scope_confidence")),
803 // rs.getString("realm_addr"), host));
804 // }
805 //
806 // return accountRealms;
807 // } catch (SQLException ex) {
808 // throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
809 // }
810 // finally {
811 // db.releaseSingleUserCaseReadLock();
812 // }
813 // }
814 
815 
833  private OsAccountRealm newRealm(String realmName, String realmAddr, String signature, Host host, OsAccountRealm.ScopeConfidence scopeConfidence) throws TskCoreException {
834 
836  try (CaseDbConnection connection = this.db.getConnection()) {
837  String realmInsertSQL = "INSERT INTO tsk_os_account_realms(realm_name, realm_addr, realm_signature, scope_host_id, scope_confidence)"
838  + " VALUES (?, ?, ?, ?, ?)"; // NON-NLS
839 
840  PreparedStatement preparedStatement = connection.getPreparedStatement(realmInsertSQL, Statement.RETURN_GENERATED_KEYS);
841  preparedStatement.clearParameters();
842 
843  preparedStatement.setString(1, realmName);
844  preparedStatement.setString(2, realmAddr);
845  preparedStatement.setString(3, signature);
846  if (host != null) {
847  preparedStatement.setLong(4, host.getHostId());
848  } else {
849  preparedStatement.setNull(4, java.sql.Types.BIGINT);
850  }
851  preparedStatement.setInt(5, scopeConfidence.getId());
852 
853  connection.executeUpdate(preparedStatement);
854 
855  // Read back the row id
856  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
857  long rowId = resultSet.getLong(1); // last_insert_rowid()
858  return new OsAccountRealm(rowId, realmName, realmAddr, signature, host, scopeConfidence, OsAccountRealm.RealmDbStatus.ACTIVE);
859  }
860 
861  } catch (SQLException ex) {
862  // Create may have failed if the realm already exists. Try and get the matching realm
863  try (CaseDbConnection connection = this.db.getConnection()) {
864  if (!Strings.isNullOrEmpty(realmAddr)) {
865  Optional<OsAccountRealm> accountRealm = this.getRealmByAddr(realmAddr, host, connection);
866  if (accountRealm.isPresent()) {
867  return accountRealm.get();
868  }
869  } else if (!Strings.isNullOrEmpty(realmName)) {
870  Optional<OsAccountRealm> accountRealm = this.getRealmByName(realmName, host, connection);
871  if (accountRealm.isPresent()) {
872  return accountRealm.get();
873  }
874  }
875 
876  // some other failure - throw an exception
877  throw new TskCoreException(String.format("Error creating realm with address = %s and name = %s, with host = %s",
878  realmAddr != null ? realmAddr : "", realmName != null ? realmName : "", host != null ? host.getName() : ""), ex);
879  }
880  } finally {
882  }
883  }
884 
885 
902  static String makeRealmSignature(String realmAddr, String realmName, Host scopeHost) throws TskCoreException {
903 
904  // need at least one of the two, the addr or name to look up
905  if (Strings.isNullOrEmpty(realmAddr) && Strings.isNullOrEmpty(realmName)) {
906  throw new TskCoreException("Realm address and name can't both be null.");
907  }
908 
909  String signature = String.format("%s_%s", !Strings.isNullOrEmpty(realmAddr) ? realmAddr : realmName,
910  scopeHost != null ? scopeHost.getHostId() : "DOMAIN");
911  return signature;
912  }
913 
919  private String makeMergedRealmSignature() {
920  return "MERGED " + UUID.randomUUID().toString();
921  }
922 
923 
932  void moveOrMergeRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
933  // Look for a matching realm by address
934  Optional<OsAccountRealm> optDestRealmAddr = Optional.empty();
935  if (sourceRealm.getRealmAddr().isPresent()) {
936  optDestRealmAddr = db.getOsAccountRealmManager().getRealmByAddr(sourceRealm.getRealmAddr().get(), destHost, trans.getConnection());
937  }
938 
939  // Look for a matching realm by name
940  Optional<OsAccountRealm> optDestRealmName = Optional.empty();
941  if (!sourceRealm.getRealmNames().isEmpty()) {
942  optDestRealmName = db.getOsAccountRealmManager().getRealmByName(sourceRealm.getRealmNames().get(0), destHost, trans.getConnection());
943  }
944 
945  // Decide how to proceed:
946  // - If we only got one match:
947  // -- If the address matched, set destRealm to the matching address realm
948  // -- If the name matched but the original and the matching realm have different addresses, leave destRealm null (it'll be a move)
949  // -- If the name matched and at least one of the address fields was null, set destRealm to the matching name realm
950  // - If we got no matches, leave destRealm null (we'll do a move not a merge)
951  // - If we got two of the same matches, set destRealm to that realm
952  // - If we got two different matches:
953  // -- If the name match has no address set, merge the matching name realm into the matching address realm, then
954  // set destRealm to the matching address realm
955  // -- Otherwise we're in the case where the addresses are different. We will consider the address the
956  // stronger match and set destRealm to the matching address realm and leave the matching name realm as-is.
957  OsAccountRealm destRealm = null;
958  if (optDestRealmAddr.isPresent() && optDestRealmName.isPresent()) {
959  if (optDestRealmAddr.get().getRealmId() == optDestRealmName.get().getRealmId()) {
960  // The two matches are the same
961  destRealm = optDestRealmAddr.get();
962  } else {
963  if (optDestRealmName.get().getRealmAddr().isPresent()) {
964  // The addresses are different, so use the one with the matching address
965  destRealm = optDestRealmAddr.get();
966  } else {
967  // Merge the realm with the matching name into the realm with the matching address.
968  // Reload from database afterward to make sure everything is up-to-date.
969  mergeRealms(optDestRealmName.get(), optDestRealmAddr.get(), trans);
970  destRealm = getRealmByRealmId(optDestRealmAddr.get().getRealmId(), trans.getConnection());
971  }
972  }
973  } else if (optDestRealmAddr.isPresent()) {
974  // Only address matched - use it
975  destRealm = optDestRealmAddr.get();
976  } else if (optDestRealmName.isPresent()) {
977  // Only name matched - check whether both have addresses set.
978  // Due to earlier checks we know the address fields can't be the same, so
979  // don't do anything if both have addresses - we consider the address to be a stronger identifier than the name
980  if (! (optDestRealmName.get().getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent())) {
981  destRealm = optDestRealmName.get();
982  }
983  }
984 
985  // Move or merge the source realm
986  if (destRealm == null) {
987  moveRealm(sourceRealm, destHost, trans);
988  } else {
989  mergeRealms(sourceRealm, destRealm, trans);
990  }
991  }
992 
1004  private void moveRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
1005  try(Statement s = trans.getConnection().createStatement()) {
1006  String query = "UPDATE tsk_os_account_realms SET scope_host_id = " + destHost.getHostId() + " WHERE id = " + sourceRealm.getRealmId();
1007  s.executeUpdate(query);
1008  } catch (SQLException ex) {
1009  throw new TskCoreException("Error moving realm with id: " + sourceRealm.getRealmId() + " to host with id: " + destHost.getHostId(), ex);
1010  }
1011  }
1012 
1013 
1023  void mergeRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
1024 
1025  // Update accounts
1026  db.getOsAccountManager().mergeOsAccountsForRealms(sourceRealm, destRealm, trans);
1027 
1028  // Update the sourceRealm realm
1029  CaseDbConnection connection = trans.getConnection();
1030  try (Statement statement = connection.createStatement()) {
1031  String updateStr = "UPDATE tsk_os_account_realms SET db_status = " + OsAccountRealm.RealmDbStatus.MERGED.getId()
1032  + ", merged_into = " + destRealm.getRealmId()
1033  + ", realm_signature = '" + makeMergedRealmSignature() + "' "
1034  + " WHERE id = " + sourceRealm.getRealmId();
1035  connection.executeUpdate(statement, updateStr);
1036  } catch (SQLException ex) {
1037  throw new TskCoreException ("Error updating status of realm with id: " + sourceRealm.getRealmId(), ex);
1038  }
1039 
1040  // Update the destination realm if it doesn't have the name or addr set and the source realm does
1041  if (!destRealm.getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent()) {
1042  updateRealm(destRealm, sourceRealm.getRealmAddr().get(), null, trans.getConnection());
1043  } else if (destRealm.getRealmNames().isEmpty() && !sourceRealm.getRealmNames().isEmpty()) {
1044  updateRealm(destRealm, null, sourceRealm.getRealmNames().get(0), trans.getConnection());
1045  }
1046  }
1047 
1058  List<OsAccountRealm> getRealmsByHost(Host host, CaseDbConnection connection) throws TskCoreException {
1059  List<OsAccountRealm> results = new ArrayList<>();
1060  String queryString = REALM_QUERY_STRING
1061  + " WHERE realms.scope_host_id = " + host.getHostId();
1062 
1064  try ( Statement s = connection.createStatement();
1065  ResultSet rs = connection.executeQuery(s, queryString)) {
1066  while (rs.next()) {
1067  results.add(resultSetToAccountRealm(rs));
1068  }
1069  return results;
1070  } catch (SQLException ex) {
1071  throw new TskCoreException(String.format("Error gettings realms for host with id = " + host.getHostId()), ex);
1072  }
1073  finally {
1075  }
1076  }
1077 
1081  public enum OsRealmUpdateStatus {
1082 
1085  MERGED
1086  }
1087 
1092  public final static class OsRealmUpdateResult {
1093 
1094  private final OsRealmUpdateStatus updateStatus;
1095  private final OsAccountRealm updatedRealm;
1096 
1097  OsRealmUpdateResult(OsRealmUpdateStatus updateStatus, OsAccountRealm updatedRealm) {
1098  this.updateStatus = updateStatus;
1099  this.updatedRealm = updatedRealm;
1100  }
1101 
1103  return updateStatus;
1104  }
1105 
1106  public Optional<OsAccountRealm> getUpdatedRealm() {
1107  return Optional.ofNullable(updatedRealm);
1108  }
1109  }
1110 }
OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope)
OsAccountRealm newLocalLinuxRealm(Host referringHost)
Optional< OsAccountRealm > getLocalLinuxRealm(Host referringHost)
OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName)
OsAccountRealmManager getOsAccountRealmManager()
Optional< OsAccountRealm > getWindowsRealm(String accountSid, String realmName, Host referringHost)

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