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.Objects;
32 import java.util.Optional;
33 import java.util.UUID;
34 import java.util.logging.Logger;
38 
39 
44 public final class OsAccountRealmManager {
45 
46  private static final Logger LOGGER = Logger.getLogger(OsAccountRealmManager.class.getName());
47  private static final String LOCAL_REALM_NAME = "local";
48 
49  private final SleuthkitCase db;
50 
58  this.db = skCase;
59  }
60 
79  public OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope) throws TskCoreException, OsAccountManager.NotUserSIDException {
80 
81  if (realmScope == null) {
82  throw new TskCoreException("RealmScope cannot be null. Use UNKNOWN if scope is not known.");
83  }
84  if (referringHost == null) {
85  throw new TskCoreException("A referring host is required to create a realm.");
86  }
87  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
88  && StringUtils.isBlank(realmName)) {
89  throw new TskCoreException("Either an address or a name is required to create a realm.");
90  }
91 
92  Host scopeHost;
93  OsAccountRealm.ScopeConfidence scopeConfidence;
94 
95  switch (realmScope) {
96  case DOMAIN:
97  scopeHost = null;
98  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
99  break;
100  case LOCAL:
101  scopeHost = referringHost;
102  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
103  break;
104 
105  case UNKNOWN:
106  default:
107  // NOTE: if there's a well known SID, the scope will be changed to LOCAL later.
108  // check if the referring host already has a realm
109  boolean isHostRealmKnown = isHostRealmKnown(referringHost);
110  if (isHostRealmKnown) {
111  scopeHost = null; // the realm does not scope to the referring host since it already has one.
112  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
113  } else {
114  scopeHost = referringHost;
115  scopeConfidence = OsAccountRealm.ScopeConfidence.INFERRED;
116  }
117  break;
118 
119  }
120 
121  // get windows realm address from sid
122  String realmAddr = null;
123  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
124  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
125 
126  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
127  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
128  }
129 
130  realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
131 
132 
133  if (WindowsAccountUtils.isWindowsWellKnownSid(accountSid)) {
134 
135  // if the sid is a Windows well known SID, create a local realm for it.
136  scopeHost = referringHost;
137  scopeConfidence = OsAccountRealm.ScopeConfidence.KNOWN;
138 
139  // if the SID is a Windows well known SID, then prefer to use the default well known name to create the realm
140  String wellKnownRealmName = WindowsAccountUtils.getWindowsWellKnownSidRealmName(accountSid);
141  if (!StringUtils.isEmpty(wellKnownRealmName)) {
142  resolvedRealmName = wellKnownRealmName;
143  }
144  }
145  }
146 
147  String signature = makeRealmSignature(realmAddr, resolvedRealmName, scopeHost);
148 
149  // create a realm
150  return newRealm(resolvedRealmName, realmAddr, signature, scopeHost, scopeConfidence);
151  }
152 
163  @Beta
165 
166  if (referringHost == null) {
167  throw new TskCoreException("A referring host is required to create a realm.");
168  }
169 
170  String realmName = LOCAL_REALM_NAME;
172  String signature = makeRealmSignature("", realmName, referringHost);
173 
174  // create a realm
175  return newRealm(realmName, "", signature, referringHost, scopeConfidence);
176  }
177 
188  @Beta
189  public Optional<OsAccountRealm> getLocalLinuxRealm(Host referringHost) throws TskCoreException {
190  if (referringHost == null) {
191  throw new TskCoreException("A referring host is required get a realm.");
192  }
193 
194  try (CaseDbConnection connection = this.db.getConnection()) {
195  return getRealmByName(LOCAL_REALM_NAME, referringHost, connection);
196  }
197  }
198 
216  public Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost) throws TskCoreException, OsAccountManager.NotUserSIDException {
217 
218  if (referringHost == null) {
219  throw new TskCoreException("A referring host is required get a realm.");
220  }
221 
222  // need at least one of the two, the addr or name to look up
223  if ((Strings.isNullOrEmpty(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) )
224  && Strings.isNullOrEmpty(realmName)) {
225  throw new TskCoreException("Realm address or name is required get a realm.");
226  }
227 
228  try (CaseDbConnection connection = this.db.getConnection()) {
229  return getWindowsRealm(accountSid, realmName, referringHost, connection);
230  }
231  }
232 
233 
248  Optional<OsAccountRealm> getWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
249 
250  if (referringHost == null) {
251  throw new TskCoreException("A referring host is required get a realm.");
252  }
253 
254  // need at least one of the two, the addr or name to look up
255  if ((StringUtils.isBlank(accountSid) || accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
256  && StringUtils.isBlank(realmName)) {
257  throw new TskCoreException("Realm address or name is required get a realm.");
258  }
259 
260  // If a non null accountSID is provided search for realm by addr.
261  if (!Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
262 
263  if (!WindowsAccountUtils.isWindowsUserSid(accountSid)) {
264  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", accountSid ));
265  }
266  // get realm addr from the account SID.
267  String realmAddr = WindowsAccountUtils.getWindowsRealmAddress(accountSid);
268  Optional<OsAccountRealm> realm = getRealmByAddr(realmAddr, referringHost, connection);
269  if (realm.isPresent()) {
270  return realm;
271  }
272  }
273 
274  // ensure we are using English names for any well known SIDs.
275  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
276 
277  // No realm addr so search by name.
278  Optional<OsAccountRealm> realm = getRealmByName(resolvedRealmName, referringHost, connection);
279  if (realm.isPresent() && !Strings.isNullOrEmpty(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
280  // If we were given a non-null accountSID, make sure there isn't one set on the matching realm.
281  // We know it won't match because the previous search by SID failed.
282  if (realm.get().getRealmAddr().isPresent()) {
283  return Optional.empty();
284  }
285  }
286  return realm;
287  }
288 
289 
308  OsRealmUpdateResult getAndUpdateWindowsRealm(String accountSid, String realmName, Host referringHost, CaseDbConnection connection) throws TskCoreException, OsAccountManager.NotUserSIDException {
309 
310  // get realm
311  Optional<OsAccountRealm> realmOptional = getWindowsRealm(accountSid, realmName, referringHost, connection);
312 
313  // if found, update it if needed
314  if (realmOptional.isPresent()) {
315  String realmAddr = (StringUtils.isNotBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ? WindowsAccountUtils.getWindowsRealmAddress(accountSid) : null;
316  OsRealmUpdateResult realmUpdateResult = updateRealm(realmOptional.get(), realmAddr, realmName, connection);
317 
318  return realmUpdateResult;
319 
320  } else {
321  return new OsRealmUpdateResult(OsRealmUpdateStatus.NO_CHANGE, null);
322  }
323 
324  }
325 
326 
347  public OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName) throws TskCoreException {
348 
349  try (CaseDbConnection connection = db.getConnection()) {
350  return updateRealm(realm, realmAddr, realmName, connection);
351  }
352  }
353 
373  private OsRealmUpdateResult updateRealm(OsAccountRealm realm, String realmAddr, String realmName, CaseDbConnection connection) throws TskCoreException {
374 
375  // need at least one of the two
376  if ( (StringUtils.isBlank(realmAddr) || realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
377  && StringUtils.isBlank(realmName)) {
378  throw new TskCoreException("Realm address or name is required to update realm.");
379  }
380 
381  OsRealmUpdateStatus updateStatusCode = OsRealmUpdateStatus.NO_CHANGE;
382  OsAccountRealm updatedRealm = null;
383 
385  try {
386  String currRealmAddr = realm.getRealmAddr().orElse(null);
387 
388  // set name and address to new values only if the current value is blank and the new value isn't.
389  if ((StringUtils.isBlank(currRealmAddr) && StringUtils.isNotBlank(realmAddr) && !realmAddr.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))) {
390  updateRealmColumn(realm.getRealmId(), "realm_addr", realmAddr, connection);
391  currRealmAddr = realmAddr;
392  updateStatusCode = OsRealmUpdateStatus.UPDATED;
393  }
394 
395  List<String> realmNames = realm.getRealmNames();
396  String currRealmName = realmNames.isEmpty() ? null : realmNames.get(0); // currently there is only one name.
397 
398  // Update realm name if:
399  // Current realm name is empty
400  // The passed in realm name is not empty
401  if (StringUtils.isBlank(currRealmName) && StringUtils.isNotBlank(realmName)) {
402  updateRealmColumn(realm.getRealmId(), "realm_name", realmName, connection);
403  updateStatusCode = OsRealmUpdateStatus.UPDATED;
404  }
405 
406  // if nothing is to be changed, return
407  if (updateStatusCode == OsRealmUpdateStatus.NO_CHANGE) {
408  return new OsRealmUpdateResult(updateStatusCode, realm);
409  }
410 
411  // update realm signature - based on the most current address and name
412  OsAccountRealm currRealm = getRealmByRealmId(realm.getRealmId(), connection);
413  String newRealmAddr = currRealm.getRealmAddr().orElse(null);
414  String newRealmName = (currRealm.getRealmNames().isEmpty() == false) ? currRealm.getRealmNames().get(0) : null;
415 
416  // make new signature
417  String newSignature = makeRealmSignature(newRealmAddr, newRealmName, realm.getScopeHost().orElse(null));
418 
419  // Use a random string as the signature if the realm is not active.
420  String updateSQL = "UPDATE tsk_os_account_realms SET "
421  + " realm_signature = "
422  + " CASE WHEN db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId() + " THEN ? ELSE realm_signature END "
423  + " WHERE id = ?";
424  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
425  preparedStatement.clearParameters();
426 
427  preparedStatement.setString(1, newSignature); // Is only set for active accounts
428  preparedStatement.setLong(2, realm.getRealmId());
429  connection.executeUpdate(preparedStatement);
430 
431  // read the updated realm
432  updatedRealm = this.getRealmByRealmId(realm.getRealmId(), connection);
433 
434  return new OsRealmUpdateResult(updateStatusCode, updatedRealm);
435  } catch (SQLException ex) {
436  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);
437  } finally {
439  }
440 
441  }
442 
455  private <T> void updateRealmColumn(long realmId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
456 
457  String updateSQL = "UPDATE tsk_os_account_realms "
458  + " SET " + colName + " = ? "
459  + " WHERE id = ?";
460 
462  try {
463  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
464  preparedStatement.clearParameters();
465 
466  if (Objects.isNull(colValue)) {
467  preparedStatement.setNull(1, Types.NULL); // handle null value
468  } else {
469  if (colValue instanceof String) {
470  preparedStatement.setString(1, (String) colValue);
471  } else if (colValue instanceof Long) {
472  preparedStatement.setLong(1, (Long) colValue);
473  } else if (colValue instanceof Integer) {
474  preparedStatement.setInt(1, (Integer) colValue);
475  } else {
476  throw new TskCoreException(String.format("Unhandled column data type received while updating the realm (id = %d) ", realmId));
477  }
478  }
479 
480  preparedStatement.setLong(2, realmId);
481 
482  connection.executeUpdate(preparedStatement);
483  } finally {
485  }
486  }
487 
488  private final static String REALM_QUERY_STRING = "SELECT realms.id as realm_id, realms.realm_name as realm_name,"
489  + " realms.realm_addr as realm_addr, realms.realm_signature as realm_signature, realms.scope_host_id, realms.scope_confidence, realms.db_status,"
490  + " hosts.id, hosts.name as host_name "
491  + " FROM tsk_os_account_realms as realms"
492  + " LEFT JOIN tsk_hosts as hosts"
493  + " ON realms.scope_host_id = hosts.id";
494 
504  public OsAccountRealm getRealmByRealmId(long id) throws TskCoreException {
505  try (CaseDbConnection connection = this.db.getConnection()) {
506  return getRealmByRealmId(id, connection);
507  }
508  }
509 
519  OsAccountRealm getRealmByRealmId(long id, CaseDbConnection connection) throws TskCoreException {
520 
521  String queryString = REALM_QUERY_STRING
522  + " WHERE realms.id = " + id;
523 
525  try ( Statement s = connection.createStatement();
526  ResultSet rs = connection.executeQuery(s, queryString)) {
527  OsAccountRealm accountRealm = null;
528  if (rs.next()) {
529  accountRealm = resultSetToAccountRealm(rs);
530  } else {
531  throw new TskCoreException(String.format("No realm found with id = %d", id));
532  }
533 
534  return accountRealm;
535  } catch (SQLException ex) {
536  throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
537  }
538  finally {
540  }
541  }
542 
554  Optional<OsAccountRealm> getRealmByAddr(String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
555 
556  // 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.
557  // If no host is specified, then we return the first realm with matching addr.
558  String whereHostClause = (host == null)
559  ? " 1 = 1 "
560  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
561  String queryString = REALM_QUERY_STRING
562  + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') "
563  + " AND " + whereHostClause
564  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
565  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
566 
567  return getRealmUsingQuery(queryString, host, connection);
568  }
569 
582  Optional<OsAccountRealm> getAnotherRealmByAddr(OsAccountRealm realm, String realmAddr, Host host, CaseDbConnection connection) throws TskCoreException {
583 
584  // If the given realm has a host id, then the other realm should have the same host id
585  // If the given realm has no host id, then the other realm should have no host id
586  String whereHostClause = realm.getScopeHost().isPresent()
587  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
588  : " realms.scope_host_id IS NULL ";
589  String queryString = REALM_QUERY_STRING
590  + " WHERE LOWER(realms.realm_addr) = LOWER('"+ realmAddr + "') "
591  + " AND " + whereHostClause
592  + " AND realms.id <> " + realm.getRealmId()
593  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
594  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id is at the front
595 
596  return getRealmUsingQuery(queryString, host, connection);
597  }
598 
609  Optional<OsAccountRealm> getRealmByName(String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
610 
611  // 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.
612  // If no host is specified, then we return the first realm with matching name.
613  String whereHostClause = (host == null)
614  ? " 1 = 1 "
615  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL ) ";
616  String queryString = REALM_QUERY_STRING
617  + " WHERE LOWER(realms.realm_name) = LOWER('" + realmName + "')"
618  + " AND " + whereHostClause
619  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
620  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
621 
622  return getRealmUsingQuery(queryString, host, connection);
623  }
624 
637  Optional<OsAccountRealm> getAnotherRealmByName(OsAccountRealm realm, String realmName, Host host, CaseDbConnection connection) throws TskCoreException {
638 
639  // If the given realm has a host id, then the other realm should have the same host id
640  // If the given realm has no host id, then the other realm should have no host id
641  String whereHostClause = realm.getScopeHost().isPresent()
642  ? " ( realms.scope_host_id = " + realm.getScopeHost().get().getHostId() + " ) "
643  : " realms.scope_host_id IS NULL ";
644  String queryString = REALM_QUERY_STRING
645  + " WHERE LOWER(realms.realm_name) = LOWER('" + realmName + "')"
646  + " AND " + whereHostClause
647  + " AND realms.id <> " + realm.getRealmId()
648  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId()
649  + " ORDER BY realms.scope_host_id IS NOT NULL, realms.scope_host_id"; // ensure that non null host_id are at the front
650 
651  return getRealmUsingQuery(queryString, host, connection);
652 
653  }
654 
666  private Optional<OsAccountRealm> getRealmUsingQuery(String queryString, Host host, CaseDbConnection connection) throws TskCoreException {
667 
669  try (Statement s = connection.createStatement();
670  ResultSet rs = connection.executeQuery(s, queryString)) {
671 
672  OsAccountRealm accountRealm = null;
673  if (rs.next()) {
674  Host realmHost = null;
675  long hostId = rs.getLong("scope_host_id");
676  if (!rs.wasNull()) {
677  if (host != null ) {
678  realmHost = host;
679  } else {
680  realmHost = new Host(hostId, rs.getString("host_name"));
681  }
682  }
683 
684  accountRealm = new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
685  rs.getString("realm_addr"), rs.getString("realm_signature"),
686  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
687  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
688 
689  }
690  return Optional.ofNullable(accountRealm);
691  } catch (SQLException ex) {
692  throw new TskCoreException(String.format("Error getting realm using query = %s", queryString), ex);
693  } finally {
695  }
696  }
697 
713  private boolean isHostRealmKnown(Host host) throws TskCoreException {
714 
715  // check if this host has a local known realm aleady, other than the special windows realm.
716  String queryString = REALM_QUERY_STRING
717  + " WHERE realms.scope_host_id = " + host.getHostId()
718  + " AND realms.scope_confidence = " + OsAccountRealm.ScopeConfidence.KNOWN.getId()
719  + " AND realms.db_status = " + OsAccountRealm.RealmDbStatus.ACTIVE.getId();
720 
722  try (CaseDbConnection connection = this.db.getConnection();
723  Statement s = connection.createStatement();
724  ResultSet rs = connection.executeQuery(s, queryString)) {
725 
726  // return true if there is any match.
727  return rs.next();
728  } catch (SQLException ex) {
729  throw new TskCoreException(String.format("Error getting account realm for with host = %s", host.getName()), ex);
730  }
731  finally {
733  }
734 
735  }
736 
744  private OsAccountRealm resultSetToAccountRealm(ResultSet rs) throws SQLException {
745 
746  long hostId = rs.getLong("scope_host_id");
747  Host realmHost = null;
748  if (!rs.wasNull()) {
749  realmHost = new Host(hostId, rs.getString("host_name"));
750  }
751 
752  return new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
753  rs.getString("realm_addr"), rs.getString("realm_signature"),
754  realmHost, ScopeConfidence.fromID(rs.getInt("scope_confidence")),
755  OsAccountRealm.RealmDbStatus.fromID(rs.getInt("db_status")));
756  }
757 
758 // /**
759 // * Get all realms.
760 // *
761 // * @return Collection of OsAccountRealm
762 // */
763 // Collection<OsAccountRealm> getRealms() throws TskCoreException {
764 // 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, "
765 // + " hosts.id, hosts.name as host_name "
766 // + " FROM tsk_os_account_realms as realms"
767 // + " LEFT JOIN tsk_hosts as hosts"
768 // + " ON realms.scope_host_id = hosts.id";
769 //
770 // db.acquireSingleUserCaseReadLock();
771 // try (CaseDbConnection connection = this.db.getConnection();
772 // Statement s = connection.createStatement();
773 // ResultSet rs = connection.executeQuery(s, queryString)) {
774 //
775 // ArrayList<OsAccountRealm> accountRealms = new ArrayList<>();
776 // while (rs.next()) {
777 // long hostId = rs.getLong("scope_host_id");
778 // Host host = null;
779 // if (!rs.wasNull()) {
780 // host = new Host(hostId, rs.getString("host_name"));
781 // }
782 //
783 // accountRealms.add(new OsAccountRealm(rs.getLong("realm_id"), rs.getString("realm_name"),
784 // ScopeConfidence.fromID(rs.getInt("scope_confidence")),
785 // rs.getString("realm_addr"), host));
786 // }
787 //
788 // return accountRealms;
789 // } catch (SQLException ex) {
790 // throw new TskCoreException(String.format("Error running the realms query = %s", queryString), ex);
791 // }
792 // finally {
793 // db.releaseSingleUserCaseReadLock();
794 // }
795 // }
796 
797 
815  private OsAccountRealm newRealm(String realmName, String realmAddr, String signature, Host host, OsAccountRealm.ScopeConfidence scopeConfidence) throws TskCoreException {
816 
818  try (CaseDbConnection connection = this.db.getConnection()) {
819  String realmInsertSQL = "INSERT INTO tsk_os_account_realms(realm_name, realm_addr, realm_signature, scope_host_id, scope_confidence)"
820  + " VALUES (?, ?, ?, ?, ?)"; // NON-NLS
821 
822  PreparedStatement preparedStatement = connection.getPreparedStatement(realmInsertSQL, Statement.RETURN_GENERATED_KEYS);
823  preparedStatement.clearParameters();
824 
825  preparedStatement.setString(1, realmName);
826  preparedStatement.setString(2, realmAddr);
827  preparedStatement.setString(3, signature);
828  if (host != null) {
829  preparedStatement.setLong(4, host.getHostId());
830  } else {
831  preparedStatement.setNull(4, java.sql.Types.BIGINT);
832  }
833  preparedStatement.setInt(5, scopeConfidence.getId());
834 
835  connection.executeUpdate(preparedStatement);
836 
837  // Read back the row id
838  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
839  long rowId = resultSet.getLong(1); // last_insert_rowid()
840  return new OsAccountRealm(rowId, realmName, realmAddr, signature, host, scopeConfidence, OsAccountRealm.RealmDbStatus.ACTIVE);
841  }
842 
843  } catch (SQLException ex) {
844  // Create may have failed if the realm already exists. Try and get the matching realm
845  try (CaseDbConnection connection = this.db.getConnection()) {
846  if (!Strings.isNullOrEmpty(realmAddr)) {
847  Optional<OsAccountRealm> accountRealm = this.getRealmByAddr(realmAddr, host, connection);
848  if (accountRealm.isPresent()) {
849  return accountRealm.get();
850  }
851  } else if (!Strings.isNullOrEmpty(realmName)) {
852  Optional<OsAccountRealm> accountRealm = this.getRealmByName(realmName, host, connection);
853  if (accountRealm.isPresent()) {
854  return accountRealm.get();
855  }
856  }
857 
858  // some other failure - throw an exception
859  throw new TskCoreException(String.format("Error creating realm with address = %s and name = %s, with host = %s",
860  realmAddr != null ? realmAddr : "", realmName != null ? realmName : "", host != null ? host.getName() : ""), ex);
861  }
862  } finally {
864  }
865  }
866 
867 
884  static String makeRealmSignature(String realmAddr, String realmName, Host scopeHost) throws TskCoreException {
885 
886  // need at least one of the two, the addr or name to look up
887  if (Strings.isNullOrEmpty(realmAddr) && Strings.isNullOrEmpty(realmName)) {
888  throw new TskCoreException("Realm address and name can't both be null.");
889  }
890 
891  String signature = String.format("%s_%s", !Strings.isNullOrEmpty(realmAddr) ? realmAddr : realmName,
892  scopeHost != null ? scopeHost.getHostId() : "DOMAIN");
893  return signature;
894  }
895 
901  private String makeMergedRealmSignature() {
902  return "MERGED " + UUID.randomUUID().toString();
903  }
904 
905 
914  void moveOrMergeRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
915  // Look for a matching realm by address
916  Optional<OsAccountRealm> optDestRealmAddr = Optional.empty();
917  if (sourceRealm.getRealmAddr().isPresent()) {
918  optDestRealmAddr = db.getOsAccountRealmManager().getRealmByAddr(sourceRealm.getRealmAddr().get(), destHost, trans.getConnection());
919  }
920 
921  // Look for a matching realm by name
922  Optional<OsAccountRealm> optDestRealmName = Optional.empty();
923  if (!sourceRealm.getRealmNames().isEmpty()) {
924  optDestRealmName = db.getOsAccountRealmManager().getRealmByName(sourceRealm.getRealmNames().get(0), destHost, trans.getConnection());
925  }
926 
927  // Decide how to proceed:
928  // - If we only got one match:
929  // -- If the address matched, set destRealm to the matching address realm
930  // -- If the name matched but the original and the matching realm have different addresses, leave destRealm null (it'll be a move)
931  // -- If the name matched and at least one of the address fields was null, set destRealm to the matching name realm
932  // - If we got no matches, leave destRealm null (we'll do a move not a merge)
933  // - If we got two of the same matches, set destRealm to that realm
934  // - If we got two different matches:
935  // -- If the name match has no address set, merge the matching name realm into the matching address realm, then
936  // set destRealm to the matching address realm
937  // -- Otherwise we're in the case where the addresses are different. We will consider the address the
938  // stronger match and set destRealm to the matching address realm and leave the matching name realm as-is.
939  OsAccountRealm destRealm = null;
940  if (optDestRealmAddr.isPresent() && optDestRealmName.isPresent()) {
941  if (optDestRealmAddr.get().getRealmId() == optDestRealmName.get().getRealmId()) {
942  // The two matches are the same
943  destRealm = optDestRealmAddr.get();
944  } else {
945  if (optDestRealmName.get().getRealmAddr().isPresent()) {
946  // The addresses are different, so use the one with the matching address
947  destRealm = optDestRealmAddr.get();
948  } else {
949  // Merge the realm with the matching name into the realm with the matching address.
950  // Reload from database afterward to make sure everything is up-to-date.
951  mergeRealms(optDestRealmName.get(), optDestRealmAddr.get(), trans);
952  destRealm = getRealmByRealmId(optDestRealmAddr.get().getRealmId(), trans.getConnection());
953  }
954  }
955  } else if (optDestRealmAddr.isPresent()) {
956  // Only address matched - use it
957  destRealm = optDestRealmAddr.get();
958  } else if (optDestRealmName.isPresent()) {
959  // Only name matched - check whether both have addresses set.
960  // Due to earlier checks we know the address fields can't be the same, so
961  // don't do anything if both have addresses - we consider the address to be a stronger identifier than the name
962  if (! (optDestRealmName.get().getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent())) {
963  destRealm = optDestRealmName.get();
964  }
965  }
966 
967  // Move or merge the source realm
968  if (destRealm == null) {
969  moveRealm(sourceRealm, destHost, trans);
970  } else {
971  mergeRealms(sourceRealm, destRealm, trans);
972  }
973  }
974 
986  private void moveRealm(OsAccountRealm sourceRealm, Host destHost, CaseDbTransaction trans) throws TskCoreException {
987  try(Statement s = trans.getConnection().createStatement()) {
988  String query = "UPDATE tsk_os_account_realms SET scope_host_id = " + destHost.getHostId() + " WHERE id = " + sourceRealm.getRealmId();
989  s.executeUpdate(query);
990  } catch (SQLException ex) {
991  throw new TskCoreException("Error moving realm with id: " + sourceRealm.getRealmId() + " to host with id: " + destHost.getHostId(), ex);
992  }
993  }
994 
995 
1005  void mergeRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
1006 
1007  // Update accounts
1008  db.getOsAccountManager().mergeOsAccountsForRealms(sourceRealm, destRealm, trans);
1009 
1010  // Update the sourceRealm realm
1011  CaseDbConnection connection = trans.getConnection();
1012  try (Statement statement = connection.createStatement()) {
1013  String updateStr = "UPDATE tsk_os_account_realms SET db_status = " + OsAccountRealm.RealmDbStatus.MERGED.getId()
1014  + ", merged_into = " + destRealm.getRealmId()
1015  + ", realm_signature = '" + makeMergedRealmSignature() + "' "
1016  + " WHERE id = " + sourceRealm.getRealmId();
1017  connection.executeUpdate(statement, updateStr);
1018  } catch (SQLException ex) {
1019  throw new TskCoreException ("Error updating status of realm with id: " + sourceRealm.getRealmId(), ex);
1020  }
1021 
1022  // Update the destination realm if it doesn't have the name or addr set and the source realm does
1023  if (!destRealm.getRealmAddr().isPresent() && sourceRealm.getRealmAddr().isPresent()) {
1024  updateRealm(destRealm, sourceRealm.getRealmAddr().get(), null, trans.getConnection());
1025  } else if (destRealm.getRealmNames().isEmpty() && !sourceRealm.getRealmNames().isEmpty()) {
1026  updateRealm(destRealm, null, sourceRealm.getRealmNames().get(0), trans.getConnection());
1027  }
1028  }
1029 
1040  List<OsAccountRealm> getRealmsByHost(Host host, CaseDbConnection connection) throws TskCoreException {
1041  List<OsAccountRealm> results = new ArrayList<>();
1042  String queryString = REALM_QUERY_STRING
1043  + " WHERE realms.scope_host_id = " + host.getHostId();
1044 
1046  try ( Statement s = connection.createStatement();
1047  ResultSet rs = connection.executeQuery(s, queryString)) {
1048  while (rs.next()) {
1049  results.add(resultSetToAccountRealm(rs));
1050  }
1051  return results;
1052  } catch (SQLException ex) {
1053  throw new TskCoreException(String.format("Error gettings realms for host with id = " + host.getHostId()), ex);
1054  }
1055  finally {
1057  }
1058  }
1059 
1063  public enum OsRealmUpdateStatus {
1064 
1067  MERGED
1068  }
1069 
1074  public final static class OsRealmUpdateResult {
1075 
1076  private final OsRealmUpdateStatus updateStatus;
1077  private final OsAccountRealm updatedRealm;
1078 
1079  OsRealmUpdateResult(OsRealmUpdateStatus updateStatus, OsAccountRealm updatedRealm) {
1080  this.updateStatus = updateStatus;
1081  this.updatedRealm = updatedRealm;
1082  }
1083 
1085  return updateStatus;
1086  }
1087 
1088  public Optional<OsAccountRealm> getUpdatedRealm() {
1089  return Optional.ofNullable(updatedRealm);
1090  }
1091  }
1092 }
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-2021 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.