Sleuth Kit Java Bindings (JNI)  4.11.1
Java bindings for using The Sleuth Kit
OsAccountManager.java
Go to the documentation of this file.
1 /*
2  * Sleuth Kit Data Model
3  *
4  * Copyright 2020-2022 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.datamodel;
20 
21 import com.google.common.base.Strings;
22 import org.apache.commons.lang3.StringUtils;
23 import java.sql.PreparedStatement;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.sql.Statement;
27 import java.sql.Types;
28 import java.util.Collections;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.NavigableMap;
32 import java.util.Objects;
33 import java.util.Optional;
34 import java.util.UUID;
35 import java.util.concurrent.ConcurrentSkipListMap;
36 import java.util.stream.Collectors;
46 import static org.sleuthkit.datamodel.WindowsAccountUtils.isWindowsWellKnownSid;
47 import static org.sleuthkit.datamodel.WindowsAccountUtils.getWindowsWellKnownSidFullName;
48 
53 public final class OsAccountManager {
54 
55  private final SleuthkitCase db;
56  private final Object osAcctInstancesCacheLock;
57  private final NavigableMap<OsAccountInstanceKey, OsAccountInstance> osAccountInstanceCache;
58 
66  db = skCase;
67  osAcctInstancesCacheLock = new Object();
68  osAccountInstanceCache = new ConcurrentSkipListMap<>();
69  }
70 
84  OsAccount newOsAccount(String uniqueAccountId, OsAccountRealm realm) throws TskCoreException {
85 
86  // ensure unique id is provided
87  if (Strings.isNullOrEmpty(uniqueAccountId)) {
88  throw new TskCoreException("Cannot create OS account with null uniqueId.");
89  }
90 
91  if (realm == null) {
92  throw new TskCoreException("Cannot create OS account without a realm.");
93  }
94 
96  try {
97 
98  // try to create account
99  try {
100  OsAccount account = newOsAccount(uniqueAccountId, null, realm, OsAccount.OsAccountStatus.UNKNOWN, trans);
101  trans.commit();
102  trans = null;
103  return account;
104  } catch (SQLException ex) {
105  // Close the transaction before moving on
106  trans.rollback();
107  trans = null;
108 
109  // Create may fail if an OsAccount already exists.
110  Optional<OsAccount> osAccount = this.getOsAccountByAddr(uniqueAccountId, realm);
111  if (osAccount.isPresent()) {
112  return osAccount.get();
113  }
114 
115  // create failed for some other reason, throw an exception
116  throw new TskCoreException(String.format("Error creating OsAccount with uniqueAccountId = %s in realm id = %d", uniqueAccountId, realm.getRealmId()), ex);
117  }
118  } finally {
119  if (trans != null) {
120  trans.rollback();
121  }
122  }
123  }
124 
149  public OsAccount newWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope) throws TskCoreException, NotUserSIDException {
150 
151  if (realmScope == null) {
152  throw new TskCoreException("RealmScope cannot be null. Use UNKNOWN if scope is not known.");
153  }
154  if (referringHost == null) {
155  throw new TskCoreException("A referring host is required to create an account.");
156  }
157 
158  // ensure at least one of the two is supplied - a non-null unique id or a login name
159  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
160  && StringUtils.isBlank(loginName)) {
161  throw new TskCoreException("Cannot create OS account with both uniqueId and loginName as null.");
162  }
163  // Realm name is required if the sid is null.
164  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
165  && StringUtils.isBlank(realmName)) {
166  throw new TskCoreException("Realm name or SID is required to create a Windows account.");
167  }
168 
169  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !WindowsAccountUtils.isWindowsUserSid(sid)) {
170  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
171  }
172 
173  // If no SID is given and the given realm/login names is a well known account, get and use the well known SID
174  if (StringUtils.isBlank(sid)
175  && !StringUtils.isBlank(loginName) && !StringUtils.isBlank(realmName)
176  && WindowsAccountUtils.isWindowsWellKnownAccountName(loginName, realmName)) {
177  sid = WindowsAccountUtils.getWindowsWellKnownAccountSid(loginName, realmName);
178  }
179 
180 
181  OsRealmUpdateResult realmUpdateResult;
182  Optional<OsAccountRealm> anotherRealmWithSameName = Optional.empty();
183  Optional<OsAccountRealm> anotherRealmWithSameAddr = Optional.empty();
184 
185  // get the realm for the account, and update it if it is missing addr or name.
186  OsAccountRealm realm = null;
187  try (CaseDbConnection connection = db.getConnection()) {
188  realmUpdateResult = db.getOsAccountRealmManager().getAndUpdateWindowsRealm(sid, realmName, referringHost, connection);
189 
190  Optional<OsAccountRealm> realmOptional = realmUpdateResult.getUpdatedRealm();
191  if (realmOptional.isPresent()) {
192  realm = realmOptional.get();
193 
194  if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
195 
196  // Check if update of the realm triggers a merge with any other realm,
197  // say another realm with same name but no SID, or same SID but no name
198 
199  //1. Check if there is any OTHER realm with the same name, same host but no addr
200  anotherRealmWithSameName = db.getOsAccountRealmManager().getAnotherRealmByName(realmOptional.get(), realmName, referringHost, connection);
201 
202  // 2. Check if there is any OTHER realm with same addr and host, but NO name
203  anotherRealmWithSameAddr = db.getOsAccountRealmManager().getAnotherRealmByAddr(realmOptional.get(), realmName, referringHost, connection);
204  }
205  }
206  }
207 
208  if (null == realm) {
209  // realm was not found, create it.
210  realm = db.getOsAccountRealmManager().newWindowsRealm(sid, realmName, referringHost, realmScope);
211  } else if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
212  // if the realm already existed and was updated, and there are other realms with same name or addr that should now be merged into the updated realm
213  if (anotherRealmWithSameName.isPresent() || anotherRealmWithSameAddr.isPresent()) {
214 
215  CaseDbTransaction trans = this.db.beginTransaction();
216  try {
217  if (anotherRealmWithSameName.isPresent()) {
218  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameName.get(), realm, trans);
219  }
220  if (anotherRealmWithSameAddr.isPresent()) {
221  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameAddr.get(), realm, trans);
222  }
223 
224  trans.commit();
225  } catch (TskCoreException ex) {
226  trans.rollback();
227  throw ex; // rethrow
228  }
229  }
230  }
231 
232 
233  return newWindowsOsAccount(sid, loginName, realm);
234  }
235 
253  public OsAccount newWindowsOsAccount(String sid, String loginName, OsAccountRealm realm) throws TskCoreException, NotUserSIDException {
254 
255  // ensure at least one of the two is supplied - a non-null unique id or a login name
256  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
257  && StringUtils.isBlank(loginName)) {
258  throw new TskCoreException("Cannot create OS account with both uniqueId and loginName as null.");
259  }
260 
261  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !WindowsAccountUtils.isWindowsUserSid(sid)) {
262  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
263  }
264 
265  // If the login name is well known, we use the well known english name.
266  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
267 
268  CaseDbTransaction trans = db.beginTransaction();
269  try {
270  // try to create account
271  try {
272  String uniqueId = (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ? sid : null;
273  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && isWindowsWellKnownSid(sid)) {
274  // if the SID is a Windows well known SID, then prefer to use the default well known login name
275  String wellKnownLoginName = WindowsAccountUtils.getWindowsWellKnownSidLoginName(sid);
276  if (!StringUtils.isEmpty(wellKnownLoginName)) {
277  resolvedLoginName = wellKnownLoginName;
278  }
279  }
280 
281  OsAccount account = newOsAccount(uniqueId, resolvedLoginName, realm, OsAccount.OsAccountStatus.UNKNOWN, trans);
282 
283  // If the SID indicates a special windows account, then set its full name.
284  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && isWindowsWellKnownSid(sid)) {
285  String fullName = getWindowsWellKnownSidFullName(sid);
286  if (StringUtils.isNotBlank(fullName)) {
287  OsAccountUpdateResult updateResult = updateStandardOsAccountAttributes(account, fullName, null, null, null, trans);
288  if (updateResult.getUpdatedAccount().isPresent()) {
289  account = updateResult.getUpdatedAccount().get();
290  }
291  }
292  }
293  trans.commit();
294  trans = null;
295  return account;
296  } catch (SQLException ex) {
297  // Rollback the transaction before proceeding
298  trans.rollback();
299  trans = null;
300 
301  // Create may fail if an OsAccount already exists.
302  Optional<OsAccount> osAccount;
303 
304  // First search for account by uniqueId
305  if (!Strings.isNullOrEmpty(sid)) {
306  osAccount = getOsAccountByAddr(sid, realm);
307  if (osAccount.isPresent()) {
308  return osAccount.get();
309  }
310  }
311 
312  // search by loginName
313  if (!Strings.isNullOrEmpty(resolvedLoginName)) {
314  osAccount = getOsAccountByLoginName(resolvedLoginName, realm);
315  if (osAccount.isPresent()) {
316  return osAccount.get();
317  }
318  }
319 
320  // create failed for some other reason, throw an exception
321  throw new TskCoreException(String.format("Error creating OsAccount with sid = %s, loginName = %s, realm = %s, referring host = %s",
322  (sid != null) ? sid : "Null",
323  (resolvedLoginName != null) ? resolvedLoginName : "Null",
324  (!realm.getRealmNames().isEmpty()) ? realm.getRealmNames().get(0) : "Null",
325  realm.getScopeHost().isPresent() ? realm.getScopeHost().get().getName() : "Null"), ex);
326 
327  }
328  } finally {
329  if (trans != null) {
330  trans.rollback();
331  }
332  }
333  }
334 
348  private OsAccount newOsAccount(String uniqueId, String loginName, OsAccountRealm realm, OsAccount.OsAccountStatus accountStatus, CaseDbTransaction trans) throws TskCoreException, SQLException {
349 
350  if (Objects.isNull(realm)) {
351  throw new TskCoreException("Cannot create an OS Account, realm is NULL.");
352  }
353 
354  String signature = getOsAccountSignature(uniqueId, loginName);
355  OsAccount account;
356 
357  CaseDbConnection connection = trans.getConnection();
358 
359  // first create a tsk_object for the OsAccount.
360  // RAMAN TODO: need to get the correct parent obj id.
361  // Create an Object Directory parent and used its id.
362  long parentObjId = 0;
363 
364  int objTypeId = TskData.ObjectType.OS_ACCOUNT.getObjectType();
365  long osAccountObjId = db.addObject(parentObjId, objTypeId, connection);
366 
367  String accountInsertSQL = "INSERT INTO tsk_os_accounts(os_account_obj_id, login_name, realm_id, addr, signature, status)"
368  + " VALUES (?, ?, ?, ?, ?, ?)"; // NON-NLS
369 
370  PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.NO_GENERATED_KEYS);
371  preparedStatement.clearParameters();
372 
373  preparedStatement.setLong(1, osAccountObjId);
374 
375  preparedStatement.setString(2, loginName);
376  preparedStatement.setLong(3, realm.getRealmId());
377 
378  preparedStatement.setString(4, uniqueId);
379  preparedStatement.setString(5, signature);
380  preparedStatement.setInt(6, accountStatus.getId());
381 
382  connection.executeUpdate(preparedStatement);
383 
384  account = new OsAccount(db, osAccountObjId, realm.getRealmId(), loginName, uniqueId, signature,
385  null, null, null, accountStatus, OsAccount.OsAccountDbStatus.ACTIVE);
386 
387  trans.registerAddedOsAccount(account);
388  return account;
389  }
390 
402  private Optional<OsAccount> getOsAccountByAddr(String addr, Host host) throws TskCoreException {
403 
404  try (CaseDbConnection connection = db.getConnection()) {
405  return getOsAccountByAddr(addr, host, connection);
406  }
407  }
408 
421  private Optional<OsAccount> getOsAccountByAddr(String uniqueId, Host host, CaseDbConnection connection) throws TskCoreException {
422 
423  String whereHostClause = (host == null)
424  ? " 1 = 1 "
425  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
426 
427  String queryString = "SELECT accounts.os_account_obj_id as os_account_obj_id, accounts.login_name, accounts.full_name, "
428  + " accounts.realm_id, accounts.addr, accounts.signature, "
429  + " accounts.type, accounts.status, accounts.admin, accounts.created_date, accounts.db_status, "
430  + " realms.realm_name as realm_name, realms.realm_addr as realm_addr, realms.realm_signature, realms.scope_host_id, realms.scope_confidence, realms.db_status as realm_db_status "
431  + " FROM tsk_os_accounts as accounts"
432  + " LEFT JOIN tsk_os_account_realms as realms"
433  + " ON accounts.realm_id = realms.id"
434  + " WHERE " + whereHostClause
435  + " AND accounts.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
436  + " AND LOWER(accounts.addr) = LOWER('" + uniqueId + "')";
437 
439  try (Statement s = connection.createStatement();
440  ResultSet rs = connection.executeQuery(s, queryString)) {
441 
442  if (!rs.next()) {
443  return Optional.empty(); // no match found
444  } else {
445  return Optional.of(osAccountFromResultSet(rs));
446  }
447  } catch (SQLException ex) {
448  throw new TskCoreException(String.format("Error getting OS account for unique id = %s and host = %s", uniqueId, (host != null ? host.getName() : "null")), ex);
449  } finally {
451  }
452  }
453 
465  Optional<OsAccount> getOsAccountByAddr(String uniqueId, OsAccountRealm realm) throws TskCoreException {
466 
467  String queryString = "SELECT * FROM tsk_os_accounts"
468  + " WHERE LOWER(addr) = LOWER('" + uniqueId + "')"
469  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
470  + " AND realm_id = " + realm.getRealmId();
471 
473  try (CaseDbConnection connection = this.db.getConnection();
474  Statement s = connection.createStatement();
475  ResultSet rs = connection.executeQuery(s, queryString)) {
476 
477  if (!rs.next()) {
478  return Optional.empty(); // no match found
479  } else {
480  return Optional.of(osAccountFromResultSet(rs));
481  }
482  } catch (SQLException ex) {
483  throw new TskCoreException(String.format("Error getting OS account for realm = %s and uniqueId = %s.", (realm != null) ? realm.getSignature() : "NULL", uniqueId), ex);
484  } finally {
486  }
487  }
488 
500  Optional<OsAccount> getOsAccountByLoginName(String loginName, OsAccountRealm realm) throws TskCoreException {
501 
502  String queryString = "SELECT * FROM tsk_os_accounts"
503  + " WHERE LOWER(login_name) = LOWER('" + loginName + "')"
504  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
505  + " AND realm_id = " + realm.getRealmId();
506 
508  try (CaseDbConnection connection = this.db.getConnection();
509  Statement s = connection.createStatement();
510  ResultSet rs = connection.executeQuery(s, queryString)) {
511 
512  if (!rs.next()) {
513  return Optional.empty(); // no match found
514  } else {
515  return Optional.of(osAccountFromResultSet(rs));
516  }
517  } catch (SQLException ex) {
518  throw new TskCoreException(String.format("Error getting OS account for realm = %s and loginName = %s.", (realm != null) ? realm.getSignature() : "NULL", loginName), ex);
519  } finally {
521  }
522  }
523 
533  public OsAccount getOsAccountByObjectId(long osAccountObjId) throws TskCoreException {
534 
535  try (CaseDbConnection connection = this.db.getConnection()) {
536  return getOsAccountByObjectId(osAccountObjId, connection);
537  }
538  }
539 
550  OsAccount getOsAccountByObjectId(long osAccountObjId, CaseDbConnection connection) throws TskCoreException {
551 
552  String queryString = "SELECT * FROM tsk_os_accounts"
553  + " WHERE os_account_obj_id = " + osAccountObjId;
554 
556  try (Statement s = connection.createStatement();
557  ResultSet rs = connection.executeQuery(s, queryString)) {
558 
559  if (!rs.next()) {
560  throw new TskCoreException(String.format("No account found with obj id = %d ", osAccountObjId));
561  } else {
562  return osAccountFromResultSet(rs);
563  }
564  } catch (SQLException ex) {
565  throw new TskCoreException(String.format("Error getting account with obj id = %d ", osAccountObjId), ex);
566  } finally {
568  }
569  }
570 
595  public OsAccountInstance newOsAccountInstance(OsAccount osAccount, DataSource dataSource, OsAccountInstance.OsAccountInstanceType instanceType) throws TskCoreException {
596  if (osAccount == null) {
597  throw new TskCoreException("Cannot create account instance with null account.");
598  }
599  if (dataSource == null) {
600  throw new TskCoreException("Cannot create account instance with null data source.");
601  }
602 
603  // check the cache first
604  Optional<OsAccountInstance> existingInstance = cachedAccountInstance(osAccount.getId(), dataSource.getId(), instanceType);
605  if (existingInstance.isPresent()) {
606  return existingInstance.get();
607  }
608 
609  try (CaseDbConnection connection = this.db.getConnection()) {
610  return newOsAccountInstance(osAccount.getId(), dataSource.getId(), instanceType, connection);
611  }
612  }
613 
629  OsAccountInstance newOsAccountInstance(long osAccountId, long dataSourceObjId, OsAccountInstance.OsAccountInstanceType instanceType, CaseDbConnection connection) throws TskCoreException {
630 
631  Optional<OsAccountInstance> existingInstance = cachedAccountInstance(osAccountId, dataSourceObjId, instanceType);
632  if (existingInstance.isPresent()) {
633  return existingInstance.get();
634  }
635 
636  /*
637  * Create the OS account instance.
638  */
640  try {
641  String accountInsertSQL = db.getInsertOrIgnoreSQL("INTO tsk_os_account_instances(os_account_obj_id, data_source_obj_id, instance_type)"
642  + " VALUES (?, ?, ?)"); // NON-NLS
643  PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.RETURN_GENERATED_KEYS);
644  preparedStatement.clearParameters();
645  preparedStatement.setLong(1, osAccountId);
646  preparedStatement.setLong(2, dataSourceObjId);
647  preparedStatement.setInt(3, instanceType.getId());
648  connection.executeUpdate(preparedStatement);
649  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
650  if (resultSet.next()) {
651  OsAccountInstance accountInstance = new OsAccountInstance(db, resultSet.getLong(1), osAccountId, dataSourceObjId, instanceType);
652  synchronized (osAcctInstancesCacheLock) {
653  OsAccountInstanceKey key = new OsAccountInstanceKey(osAccountId, dataSourceObjId);
654  // remove from cache any instances less significant (higher ordinal) than this instance
655  for (OsAccountInstance.OsAccountInstanceType type : OsAccountInstance.OsAccountInstanceType.values()) {
656  if (accountInstance.getInstanceType().compareTo(type) < 0) {
657  osAccountInstanceCache.remove(key);
658  }
659  }
660  // add the new most significant instance to the cache
661  osAccountInstanceCache.put(key, accountInstance);
662  }
663  /*
664  * There is a potential issue here. The cache of OS account
665  * instances is an optimization and was not intended to be
666  * used as an authoritative indicator of whether or not a
667  * particular OS account instance was already added to the
668  * case. In fact, the entire cache is flushed during merge
669  * operations. But regardless, there is a check-then-act
670  * race condition for multi-user cases, with or without the
671  * cache. And although the case database schema and the SQL
672  * returned by getInsertOrIgnoreSQL() seamlessly prevents
673  * duplicates in the case database, a valid row ID is
674  * returned here even if the INSERT is not done. So the
675  * bottom line is that a redundant event may be published
676  * from time to time.
677  */
678  db.fireTSKEvent(new TskEvent.OsAcctInstancesAddedTskEvent(Collections.singletonList(accountInstance)));
679 
680  return accountInstance;
681  } else {
682  // there is the possibility that another thread may be adding the same os account instance at the same time
683  // the database may be updated prior to the cache being updated so this provides an extra opportunity to check
684  // the cache before throwing the exception
685  Optional<OsAccountInstance> existingInstanceRetry = cachedAccountInstance(osAccountId, dataSourceObjId, instanceType);
686  if (existingInstanceRetry.isPresent()) {
687  return existingInstanceRetry.get();
688  } else {
689  throw new TskCoreException(String.format("Could not get autogen key after row insert for OS account instance. OS account object id = %d, data source object id = %d", osAccountId, dataSourceObjId));
690  }
691  }
692  }
693  } catch (SQLException ex) {
694  throw new TskCoreException(String.format("Error adding OS account instance for OS account object id = %d, data source object id = %d", osAccountId, dataSourceObjId), ex);
695  } finally {
697  }
698  }
699 
716  private Optional<OsAccountInstance> cachedAccountInstance(long osAccountId, long dataSourceObjId, OsAccountInstance.OsAccountInstanceType instanceType) {
717 
718  /*
719  * Check the cache of OS account instances for an existing instance for
720  * this OS account and data source. Note that the account instance
721  * created here has a bogus instance ID. This is possible since the
722  * instance ID is not considered in the equals() and hashCode() methods
723  * of this class.
724  */
725  synchronized (osAcctInstancesCacheLock) {
726  OsAccountInstanceKey key = new OsAccountInstanceKey(osAccountId, dataSourceObjId);
727  OsAccountInstance instance = osAccountInstanceCache.get(key);
728  if (instance != null) {
729  // if the new instance type same or less significant than the existing instance (i.e. same or higher ordinal value) it's a match.
730  if (instanceType.compareTo(instance.getInstanceType()) >= 0) {
731  return Optional.of(instance);
732  }
733  }
734  return Optional.empty();
735  }
736  }
737 
747  public List<OsAccount> getOsAccounts(Host host) throws TskCoreException {
748  String queryString = "SELECT * FROM tsk_os_accounts accounts "
749  + "WHERE accounts.os_account_obj_id IN "
750  + "(SELECT instances.os_account_obj_id "
751  + "FROM tsk_os_account_instances instances "
752  + "INNER JOIN data_source_info datasources ON datasources.obj_id = instances.data_source_obj_id "
753  + "WHERE datasources.host_id = " + host.getHostId() + ") "
754  + "AND accounts.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
755 
757  try (CaseDbConnection connection = this.db.getConnection();
758  Statement s = connection.createStatement();
759  ResultSet rs = connection.executeQuery(s, queryString)) {
760 
761  List<OsAccount> accounts = new ArrayList<>();
762  while (rs.next()) {
763  accounts.add(osAccountFromResultSet(rs));
764  }
765  return accounts;
766  } catch (SQLException ex) {
767  throw new TskCoreException(String.format("Error getting OS accounts for host id = %d", host.getHostId()), ex);
768  } finally {
770  }
771  }
772 
782  public List<OsAccount> getOsAccountsByDataSourceObjId(long dataSourceId) throws TskCoreException {
783  String queryString = "SELECT * FROM tsk_os_accounts acc "
784  + "WHERE acc.os_account_obj_id IN "
785  + "(SELECT instance.os_account_obj_id "
786  + "FROM tsk_os_account_instances instance "
787  + "WHERE instance.data_source_obj_id = " + dataSourceId + ") "
788  + "AND acc.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
789 
791  try (CaseDbConnection connection = this.db.getConnection();
792  Statement s = connection.createStatement();
793  ResultSet rs = connection.executeQuery(s, queryString)) {
794 
795  List<OsAccount> accounts = new ArrayList<>();
796  while (rs.next()) {
797  accounts.add(osAccountFromResultSet(rs));
798  }
799  return accounts;
800  } catch (SQLException ex) {
801  throw new TskCoreException(String.format("Error getting OS accounts for data source id = %d", dataSourceId), ex);
802  } finally {
804  }
805  }
806 
819  void mergeOsAccountsForRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
820  List<OsAccount> destinationAccounts = getOsAccounts(destRealm, trans.getConnection());
821  List<OsAccount> sourceAccounts = getOsAccounts(sourceRealm, trans.getConnection());
822 
823  for (OsAccount sourceAccount : sourceAccounts) {
824 
825  // First a check for the case where the source account has both the login name and unique ID set and
826  // we have separate matches in the destination account for both. If we find this case, we need to first merge
827  // the two accounts in the destination realm. This will ensure that all source accounts match at most one
828  // destination account.
829  // Note that we only merge accounts based on login name if the unique ID is empty.
830  if (sourceAccount.getAddr().isPresent() && sourceAccount.getLoginName().isPresent()) {
831  List<OsAccount> duplicateDestAccounts = destinationAccounts.stream()
832  .filter(p -> p.getAddr().equals(sourceAccount.getAddr())
833  || (p.getLoginName().equals(sourceAccount.getLoginName()) && (!p.getAddr().isPresent())))
834  .collect(Collectors.toList());
835  if (duplicateDestAccounts.size() > 1) {
836  OsAccount combinedDestAccount = duplicateDestAccounts.get(0);
837  duplicateDestAccounts.remove(combinedDestAccount);
838  for (OsAccount dupeDestAccount : duplicateDestAccounts) {
839  mergeOsAccounts(dupeDestAccount, combinedDestAccount, trans);
840  }
841  }
842  }
843 
844  // Look for matching destination account
845  OsAccount matchingDestAccount = null;
846 
847  // First look for matching unique id
848  if (sourceAccount.getAddr().isPresent()) {
849  List<OsAccount> matchingDestAccounts = destinationAccounts.stream()
850  .filter(p -> p.getAddr().equals(sourceAccount.getAddr()))
851  .collect(Collectors.toList());
852  if (!matchingDestAccounts.isEmpty()) {
853  matchingDestAccount = matchingDestAccounts.get(0);
854  }
855  }
856 
857  // If a match wasn't found yet, look for a matching login name.
858  // We will merge only if:
859  // - We didn't already find a unique ID match
860  // - The source account has no unique ID OR the destination account has no unique ID
861  // - destination account has a login name and matches the source account login name
862  if (matchingDestAccount == null && sourceAccount.getLoginName().isPresent()) {
863  List<OsAccount> matchingDestAccounts = destinationAccounts.stream()
864  .filter(p -> p.getLoginName().isPresent())
865  .filter(p -> (p.getLoginName().get().equalsIgnoreCase(sourceAccount.getLoginName().get())
866  && ((!sourceAccount.getAddr().isPresent()) || (!p.getAddr().isPresent()))))
867  .collect(Collectors.toList());
868  if (!matchingDestAccounts.isEmpty()) {
869  matchingDestAccount = matchingDestAccounts.get(0);
870  }
871  }
872 
873  // If we found a match, merge the accounts. Otherwise simply update the realm id
874  if (matchingDestAccount != null) {
875  mergeOsAccounts(sourceAccount, matchingDestAccount, trans);
876  } else {
877  String query = "UPDATE tsk_os_accounts SET realm_id = " + destRealm.getRealmId() + " WHERE os_account_obj_id = " + sourceAccount.getId();
878  try (Statement s = trans.getConnection().createStatement()) {
879  s.executeUpdate(query);
880  } catch (SQLException ex) {
881  throw new TskCoreException("Error executing SQL update: " + query, ex);
882  }
883  trans.registerChangedOsAccount(sourceAccount);
884  }
885  }
886  }
887 
902  private void mergeOsAccounts(OsAccount sourceAccount, OsAccount destAccount, CaseDbTransaction trans) throws TskCoreException {
903 
904  String query = "";
905  try (Statement s = trans.getConnection().createStatement()) {
906 
907  // Update all references
908  query = makeOsAccountUpdateQuery("tsk_os_account_attributes", sourceAccount, destAccount);
909  s.executeUpdate(query);
910 
911  // tsk_os_account_instances has a unique constraint on os_account_obj_id, data_source_obj_id, and instance_type,
912  // so delete any rows that would be duplicates.
913  query = "DELETE FROM tsk_os_account_instances "
914  + "WHERE id IN ( "
915  + "SELECT "
916  + " sourceAccountInstance.id "
917  + "FROM "
918  + " tsk_os_account_instances destAccountInstance "
919  + "INNER JOIN tsk_os_account_instances sourceAccountInstance ON destAccountInstance.data_source_obj_id = sourceAccountInstance.data_source_obj_id "
920  + "WHERE destAccountInstance.os_account_obj_id = " + destAccount.getId()
921  + " AND sourceAccountInstance.os_account_obj_id = " + sourceAccount.getId()
922  + " AND sourceAccountInstance.instance_type = destAccountInstance.instance_type" + ")";
923 
924  s.executeUpdate(query);
925 
926  query = makeOsAccountUpdateQuery("tsk_os_account_instances", sourceAccount, destAccount);
927  s.executeUpdate(query);
928  synchronized (osAcctInstancesCacheLock) {
929  osAccountInstanceCache.clear();
930  }
931 
932  query = makeOsAccountUpdateQuery("tsk_files", sourceAccount, destAccount);
933  s.executeUpdate(query);
934 
935  query = makeOsAccountUpdateQuery("tsk_data_artifacts", sourceAccount, destAccount);
936  s.executeUpdate(query);
937 
938 
939  // TBD: We need to emit another event which tells CT that two accounts are being merged so it can updates other dedicated tables
940 
941  // Update the source account. Make a dummy signature to prevent problems with the unique constraint.
942  String mergedSignature = makeMergedOsAccountSignature();
943  query = "UPDATE tsk_os_accounts SET merged_into = " + destAccount.getId()
944  + ", db_status = " + OsAccount.OsAccountDbStatus.MERGED.getId()
945  + ", signature = '" + mergedSignature + "' "
946  + " WHERE os_account_obj_id = " + sourceAccount.getId();
947 
948  s.executeUpdate(query);
949  trans.registerDeletedOsAccount(sourceAccount.getId());
950 
951  // Merge and update the destination account. Note that this must be done after updating
952  // the source account to prevent conflicts when merging two accounts in the
953  // same realm.
954  mergeOsAccountObjectsAndUpdateDestAccount(sourceAccount, destAccount, trans);
955  } catch (SQLException ex) {
956  throw new TskCoreException("Error executing SQL update: " + query, ex);
957  }
958  }
959 
965  private String makeMergedOsAccountSignature() {
966  return "MERGED " + UUID.randomUUID().toString();
967  }
968 
978  private String makeOsAccountUpdateQuery(String tableName, OsAccount sourceAccount, OsAccount destAccount) {
979  return "UPDATE " + tableName + " SET os_account_obj_id = " + destAccount.getId() + " WHERE os_account_obj_id = " + sourceAccount.getId();
980  }
981 
993  private OsAccount mergeOsAccountObjectsAndUpdateDestAccount(OsAccount sourceAccount, OsAccount destAccount, CaseDbTransaction trans) throws TskCoreException {
994 
995  OsAccount mergedDestAccount = destAccount;
996 
997  String destLoginName = null;
998  String destAddr = null;
999 
1000  // Copy any fields that aren't set in the destination to the value from the source account.
1001  if (!destAccount.getLoginName().isPresent() && sourceAccount.getLoginName().isPresent()) {
1002  destLoginName = sourceAccount.getLoginName().get();
1003  }
1004 
1005  if (!destAccount.getAddr().isPresent() && sourceAccount.getAddr().isPresent()) {
1006  destAddr = sourceAccount.getAddr().get();
1007  }
1008 
1009  // update the dest account core
1010  OsAccountUpdateResult updateStatus = this.updateOsAccountCore(destAccount, destAddr, destLoginName, trans);
1011 
1012  if (updateStatus.getUpdateStatusCode() == OsAccountUpdateStatus.UPDATED && updateStatus.getUpdatedAccount().isPresent()) {
1013  mergedDestAccount = updateStatus.getUpdatedAccount().get();
1014  }
1015 
1016  String destFullName = null;
1017  Long destCreationTime = null;
1018  if (!destAccount.getFullName().isPresent() && sourceAccount.getFullName().isPresent()) {
1019  destFullName = sourceAccount.getFullName().get();
1020  }
1021 
1022  if (!destAccount.getCreationTime().isPresent() && sourceAccount.getCreationTime().isPresent()) {
1023  destCreationTime = sourceAccount.getCreationTime().get();
1024  }
1025 
1026  // update the dest account properties
1027  updateStatus = this.updateStandardOsAccountAttributes(destAccount, destFullName, null, null, destCreationTime, trans);
1028 
1029  if (updateStatus.getUpdateStatusCode() == OsAccountUpdateStatus.UPDATED && updateStatus.getUpdatedAccount().isPresent()) {
1030  mergedDestAccount = updateStatus.getUpdatedAccount().get();
1031  }
1032 
1033  return mergedDestAccount;
1034  }
1035 
1046  private List<OsAccount> getOsAccounts(OsAccountRealm realm, CaseDbConnection connection) throws TskCoreException {
1047  String queryString = "SELECT * FROM tsk_os_accounts"
1048  + " WHERE realm_id = " + realm.getRealmId()
1049  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
1050  + " ORDER BY os_account_obj_id";
1051 
1052  try (Statement s = connection.createStatement();
1053  ResultSet rs = connection.executeQuery(s, queryString)) {
1054 
1055  List<OsAccount> accounts = new ArrayList<>();
1056  while (rs.next()) {
1057  accounts.add(osAccountFromResultSet(rs));
1058  }
1059  return accounts;
1060  } catch (SQLException ex) {
1061  throw new TskCoreException(String.format("Error getting OS accounts for realm id = %d", realm.getRealmId()), ex);
1062  }
1063  }
1064 
1072  public List<OsAccount> getOsAccounts() throws TskCoreException {
1073  String queryString = "SELECT * FROM tsk_os_accounts"
1074  + " WHERE db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
1075 
1077  try (CaseDbConnection connection = this.db.getConnection();
1078  Statement s = connection.createStatement();
1079  ResultSet rs = connection.executeQuery(s, queryString)) {
1080 
1081  List<OsAccount> accounts = new ArrayList<>();
1082  while (rs.next()) {
1083  accounts.add(osAccountFromResultSet(rs));
1084  }
1085  return accounts;
1086  } catch (SQLException ex) {
1087  throw new TskCoreException(String.format("Error getting OS accounts"), ex);
1088  } finally {
1090  }
1091  }
1092 
1108  public Optional<OsAccount> getWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost) throws TskCoreException, NotUserSIDException {
1109 
1110  if (referringHost == null) {
1111  throw new TskCoreException("A referring host is required to get an account.");
1112  }
1113 
1114  // ensure at least one of the two is supplied - a non-null sid or a login name
1115  if ((StringUtils.isBlank(sid) || (sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ) && StringUtils.isBlank(loginName)) {
1116  throw new TskCoreException("Cannot get an OS account with both SID and loginName as null.");
1117  }
1118 
1119  // If no SID is given and the given realm/login names is a well known account, get and use the well known SID
1120  if (StringUtils.isBlank(sid)
1121  && !StringUtils.isBlank(loginName) && !StringUtils.isBlank(realmName)
1122  && WindowsAccountUtils.isWindowsWellKnownAccountName(loginName, realmName)) {
1123  sid = WindowsAccountUtils.getWindowsWellKnownAccountSid(loginName, realmName);
1124 
1125  }
1126 
1127  // first get the realm for the given sid
1128  Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getWindowsRealm(sid, realmName, referringHost);
1129  if (!realm.isPresent()) {
1130  return Optional.empty();
1131  }
1132 
1133  // search by SID
1134  if (!Strings.isNullOrEmpty(sid) && !(sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))) {
1135  if (!WindowsAccountUtils.isWindowsUserSid(sid)) {
1136  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
1137  }
1138 
1139  Optional<OsAccount> account = this.getOsAccountByAddr(sid, realm.get());
1140  if (account.isPresent()) {
1141  return account;
1142  }
1143  }
1144 
1145  // search by login name
1146  if (!Strings.isNullOrEmpty(loginName)) {
1147  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
1148  return this.getOsAccountByLoginName(resolvedLoginName, realm.get());
1149  } else {
1150  return Optional.empty();
1151  }
1152  }
1153 
1163  public void addExtendedOsAccountAttributes(OsAccount account, List<OsAccountAttribute> accountAttributes) throws TskCoreException {
1164 
1165  synchronized (account) { // synchronized to prevent multiple threads trying to add osAccount attributes concurrently to the same osAccount.
1167 
1168  try (CaseDbConnection connection = db.getConnection()) {
1169  for (OsAccountAttribute accountAttribute : accountAttributes) {
1170 
1171  String attributeInsertSQL = "INSERT INTO tsk_os_account_attributes(os_account_obj_id, host_id, source_obj_id, attribute_type_id, value_type, value_byte, value_text, value_int32, value_int64, value_double)"
1172  + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS
1173 
1174  PreparedStatement preparedStatement = connection.getPreparedStatement(attributeInsertSQL, Statement.RETURN_GENERATED_KEYS);
1175  preparedStatement.clearParameters();
1176 
1177  preparedStatement.setLong(1, account.getId());
1178  if (accountAttribute.getHostId().isPresent()) {
1179  preparedStatement.setLong(2, accountAttribute.getHostId().get());
1180  } else {
1181  preparedStatement.setNull(2, java.sql.Types.NULL);
1182  }
1183  if (accountAttribute.getSourceObjectId().isPresent()) {
1184  preparedStatement.setLong(3, accountAttribute.getSourceObjectId().get());
1185  } else {
1186  preparedStatement.setNull(3, java.sql.Types.NULL);
1187  }
1188 
1189  preparedStatement.setLong(4, accountAttribute.getAttributeType().getTypeID());
1190  preparedStatement.setLong(5, accountAttribute.getAttributeType().getValueType().getType());
1191 
1192  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE) {
1193  preparedStatement.setBytes(6, accountAttribute.getValueBytes());
1194  } else {
1195  preparedStatement.setBytes(6, null);
1196  }
1197 
1198  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING
1199  || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
1200  preparedStatement.setString(7, accountAttribute.getValueString());
1201  } else {
1202  preparedStatement.setString(7, null);
1203  }
1204  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) {
1205  preparedStatement.setInt(8, accountAttribute.getValueInt());
1206  } else {
1207  preparedStatement.setNull(8, java.sql.Types.NULL);
1208  }
1209 
1210  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME
1211  || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG) {
1212  preparedStatement.setLong(9, accountAttribute.getValueLong());
1213  } else {
1214  preparedStatement.setNull(9, java.sql.Types.NULL);
1215  }
1216 
1217  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) {
1218  preparedStatement.setDouble(10, accountAttribute.getValueDouble());
1219  } else {
1220  preparedStatement.setNull(10, java.sql.Types.NULL);
1221  }
1222 
1223  connection.executeUpdate(preparedStatement);
1224  }
1225  } catch (SQLException ex) {
1226  throw new TskCoreException(String.format("Error adding OS Account attribute for account id = %d", account.getId()), ex);
1227  } finally {
1229  }
1230  // set the atrribute list in account to the most current list from the database
1231  List<OsAccountAttribute> currentAttribsList = getOsAccountAttributes(account);
1232  account.setAttributesInternal(currentAttribsList);
1233  }
1234  fireChangeEvent(account);
1235  }
1236 
1246  List<OsAccountAttribute> getOsAccountAttributes(OsAccount account) throws TskCoreException {
1247 
1248  String queryString = "SELECT attributes.os_account_obj_id as os_account_obj_id, attributes.host_id as host_id, attributes.source_obj_id as source_obj_id, "
1249  + " attributes.attribute_type_id as attribute_type_id, attributes.value_type as value_type, attributes.value_byte as value_byte, "
1250  + " attributes.value_text as value_text, attributes.value_int32 as value_int32, attributes.value_int64 as value_int64, attributes.value_double as value_double, "
1251  + " hosts.id, hosts.name as host_name, hosts.db_status as host_status "
1252  + " FROM tsk_os_account_attributes as attributes"
1253  + " LEFT JOIN tsk_hosts as hosts "
1254  + " ON attributes.host_id = hosts.id "
1255  + " WHERE os_account_obj_id = " + account.getId();
1256 
1258  try (CaseDbConnection connection = this.db.getConnection();
1259  Statement s = connection.createStatement();
1260  ResultSet rs = connection.executeQuery(s, queryString)) {
1261 
1262  List<OsAccountAttribute> attributes = new ArrayList<>();
1263  while (rs.next()) {
1264 
1265  Host host = null;
1266  long hostId = rs.getLong("host_id");
1267  if (!rs.wasNull()) {
1268  host = new Host(hostId, rs.getString("host_name"), Host.HostDbStatus.fromID(rs.getInt("host_status")));
1269  }
1270 
1271  Content sourceContent = null;
1272  long sourceObjId = rs.getLong("source_obj_id");
1273  if (!rs.wasNull()) {
1274  sourceContent = this.db.getContentById(sourceObjId);
1275  }
1276  BlackboardAttribute.Type attributeType = db.getBlackboard().getAttributeType(rs.getInt("attribute_type_id"));
1277  OsAccountAttribute attribute = account.new OsAccountAttribute(attributeType, rs.getInt("value_int32"), rs.getLong("value_int64"),
1278  rs.getDouble("value_double"), rs.getString("value_text"), rs.getBytes("value_byte"),
1279  db, account, host, sourceContent);
1280 
1281  attributes.add(attribute);
1282  }
1283  return attributes;
1284  } catch (SQLException ex) {
1285  throw new TskCoreException(String.format("Error getting OS account attributes for account obj id = %d", account.getId()), ex);
1286  } finally {
1288  }
1289  }
1290 
1300  public List<OsAccountInstance> getOsAccountInstances(OsAccount account) throws TskCoreException {
1301  String whereClause = "tsk_os_account_instances.os_account_obj_id = " + account.getId();
1302  return getOsAccountInstances(whereClause);
1303  }
1304 
1315  public List<OsAccountInstance> getOsAccountInstances(List<Long> instanceIDs) throws TskCoreException {
1316  String instanceIds = instanceIDs.stream().map(id -> id.toString()).collect(Collectors.joining(","));
1317 
1318  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1319 
1320  String querySQL = "SELECT * FROM tsk_os_account_instances "
1321  + " WHERE tsk_os_account_instances.id IN (" + instanceIds + ")";
1322 
1324  try (CaseDbConnection connection = db.getConnection();
1325  PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1326  ResultSet results = connection.executeQuery(preparedStatement)) {
1327 
1328  osAcctInstances = getOsAccountInstancesFromResultSet(results);
1329 
1330  } catch (SQLException ex) {
1331  throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1332  } finally {
1334  }
1335  return osAcctInstances;
1336  }
1337 
1351  private List<OsAccountInstance> getOsAccountInstances(String whereClause) throws TskCoreException {
1352  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1353 
1354  String querySQL
1355  = "SELECT tsk_os_account_instances.* "
1356  + " FROM tsk_os_account_instances "
1357  + " INNER JOIN ( SELECT os_account_obj_id, data_source_obj_id, MIN(instance_type) AS min_instance_type "
1358  + " FROM tsk_os_account_instances"
1359  + " GROUP BY os_account_obj_id, data_source_obj_id ) grouped_instances "
1360  + " ON tsk_os_account_instances.os_account_obj_id = grouped_instances.os_account_obj_id "
1361  + " AND tsk_os_account_instances.instance_type = grouped_instances.min_instance_type "
1362  + " WHERE " + whereClause;
1363 
1365  try (CaseDbConnection connection = db.getConnection();
1366  PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1367  ResultSet results = connection.executeQuery(preparedStatement)) {
1368 
1369  osAcctInstances = getOsAccountInstancesFromResultSet(results);
1370 
1371  } catch (SQLException ex) {
1372  throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1373  } finally {
1375  }
1376  return osAcctInstances;
1377  }
1378 
1388  private List<OsAccountInstance> getOsAccountInstancesFromResultSet(ResultSet results) throws SQLException {
1389 
1390  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1391  while (results.next()) {
1392  long instanceId = results.getLong("id");
1393  long osAccountObjID = results.getLong("os_account_obj_id");
1394  long dataSourceObjId = results.getLong("data_source_obj_id");
1395  int instanceType = results.getInt("instance_type");
1396  osAcctInstances.add(new OsAccountInstance(db, instanceId, osAccountObjID, dataSourceObjId, OsAccountInstance.OsAccountInstanceType.fromID(instanceType)));
1397  }
1398 
1399  return osAcctInstances;
1400  }
1401 
1418  public OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime) throws TskCoreException {
1419 
1420  CaseDbTransaction trans = db.beginTransaction();
1421  try {
1422  OsAccountUpdateResult updateStatus = updateStandardOsAccountAttributes(osAccount, fullName, accountType, accountStatus, creationTime, trans);
1423 
1424  trans.commit();
1425  trans = null;
1426 
1427  return updateStatus;
1428  } finally {
1429  if (trans != null) {
1430  trans.rollback();
1431  }
1432  }
1433  }
1434 
1452  OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime, CaseDbTransaction trans) throws TskCoreException {
1453 
1454  OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
1455 
1456  try {
1457  CaseDbConnection connection = trans.getConnection();
1458 
1459  if (!StringUtils.isBlank(fullName)) {
1460  updateAccountColumn(osAccount.getId(), "full_name", fullName, connection);
1461  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1462  }
1463 
1464  if (Objects.nonNull(accountType)) {
1465  updateAccountColumn(osAccount.getId(), "type", accountType, connection);
1466  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1467  }
1468 
1469  if (Objects.nonNull(accountStatus)) {
1470  updateAccountColumn(osAccount.getId(), "status", accountStatus, connection);
1471  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1472  }
1473 
1474  if (Objects.nonNull(creationTime)) {
1475  updateAccountColumn(osAccount.getId(), "created_date", creationTime, connection);
1476  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1477  }
1478 
1479  // if nothing has been changed, return
1480  if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
1481  return new OsAccountUpdateResult(updateStatusCode, null);
1482  }
1483 
1484  // get the updated account from database
1485  OsAccount updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
1486 
1487  // register the updated account with the transaction to fire off an event
1488  trans.registerChangedOsAccount(updatedAccount);
1489 
1490  return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
1491 
1492  } catch (SQLException ex) {
1493  throw new TskCoreException(String.format("Error updating account with addr = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
1494  }
1495  }
1496 
1510  private <T> void updateAccountColumn(long accountObjId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
1511 
1512  String updateSQL = "UPDATE tsk_os_accounts "
1513  + " SET " + colName + " = ? "
1514  + " WHERE os_account_obj_id = ?";
1515 
1517  try {
1518  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1519  preparedStatement.clearParameters();
1520 
1521  if (Objects.isNull(colValue)) {
1522  preparedStatement.setNull(1, Types.NULL); // handle null value
1523  } else {
1524  if (colValue instanceof String) {
1525  preparedStatement.setString(1, (String) colValue);
1526  } else if (colValue instanceof Long) {
1527  preparedStatement.setLong(1, (Long) colValue);
1528  } else if (colValue instanceof Integer) {
1529  preparedStatement.setInt(1, (Integer) colValue);
1530  } else {
1531  throw new TskCoreException(String.format("Unhandled column data type received while updating the account (%d) ", accountObjId));
1532  }
1533  }
1534 
1535  preparedStatement.setLong(2, accountObjId);
1536 
1537  connection.executeUpdate(preparedStatement);
1538  } finally {
1540  }
1541  }
1542 
1553  private void updateAccountSignature(long accountObjId, String signature, CaseDbConnection connection) throws SQLException {
1554 
1555  String updateSQL = "UPDATE tsk_os_accounts SET "
1556  + " signature = "
1557  + " CASE WHEN db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId() + " THEN ? ELSE signature END "
1558  + " WHERE os_account_obj_id = ?"; // 8
1559 
1560  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1561  preparedStatement.clearParameters();
1562 
1563  preparedStatement.setString(1, signature);
1564  preparedStatement.setLong(2, accountObjId);
1565 
1566  connection.executeUpdate(preparedStatement);
1567  }
1568 
1589  public OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost) throws TskCoreException, NotUserSIDException {
1590  CaseDbTransaction trans = db.beginTransaction();
1591  try {
1592  OsAccountUpdateResult updateStatus = this.updateCoreWindowsOsAccountAttributes(osAccount, accountSid, loginName, realmName, referringHost, trans);
1593 
1594  trans.commit();
1595  trans = null;
1596  return updateStatus;
1597  } finally {
1598  if (trans != null) {
1599  trans.rollback();
1600  }
1601  }
1602  }
1603 
1623  private OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost, CaseDbTransaction trans) throws TskCoreException, NotUserSIDException {
1624 
1625  // first get and update the realm - if we have the info to find the realm
1626 
1627  if ((!StringUtils.isBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) || !StringUtils.isBlank(realmName)) {
1628  // If the SID is a well known SID, ensure we use the well known english name
1629  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
1630 
1631 
1632  OsRealmUpdateResult realmUpdateResult = db.getOsAccountRealmManager().getAndUpdateWindowsRealm(accountSid, resolvedRealmName, referringHost, trans.getConnection());
1633 
1634 
1635  Optional<OsAccountRealm> realmOptional = realmUpdateResult.getUpdatedRealm();
1636 
1637  if (realmOptional.isPresent()) {
1638 
1639  if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
1640 
1641  // Check if update of the realm triggers a merge with any other realm,
1642  // say another realm with same name but no SID, or same SID but no name
1643  //1. Check if there is any OTHER realm with the same name, same host but no addr
1644  Optional<OsAccountRealm> anotherRealmWithSameName = db.getOsAccountRealmManager().getAnotherRealmByName(realmOptional.get(), realmName, referringHost, trans.getConnection());
1645 
1646  // 2. Check if there is any OTHER realm with same addr and host, but NO name
1647  Optional<OsAccountRealm> anotherRealmWithSameAddr = db.getOsAccountRealmManager().getAnotherRealmByAddr(realmOptional.get(), realmName, referringHost, trans.getConnection());
1648 
1649  if (anotherRealmWithSameName.isPresent()) {
1650  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameName.get(), realmOptional.get(), trans);
1651  }
1652  if (anotherRealmWithSameAddr.isPresent()) {
1653  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameAddr.get(), realmOptional.get(), trans);
1654  }
1655  }
1656  }
1657  }
1658 
1659  // now update the account core data
1660  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
1661  OsAccountUpdateResult updateStatus = this.updateOsAccountCore(osAccount, accountSid, resolvedLoginName, trans);
1662 
1663  return updateStatus;
1664  }
1665 
1688  private OsAccountUpdateResult updateOsAccountCore(OsAccount osAccount, String address, String loginName, CaseDbTransaction trans) throws TskCoreException {
1689 
1690  OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
1691  OsAccount updatedAccount;
1692 
1693  try {
1694  CaseDbConnection connection = trans.getConnection();
1695 
1696  // if a new non-null addr is provided and the account already has an address, and they are not the same, throw an exception
1697  if (!StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !address.equalsIgnoreCase(osAccount.getAddr().orElse(""))) {
1698  throw new TskCoreException(String.format("Account (%d) already has an address (%s), address cannot be updated.", osAccount.getId(), osAccount.getAddr().orElse("NULL")));
1699  }
1700 
1701  if (StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
1702  updateAccountColumn(osAccount.getId(), "addr", address, connection);
1703  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1704  }
1705 
1706  if (StringUtils.isBlank(osAccount.getLoginName().orElse(null)) && !StringUtils.isBlank(loginName)) {
1707  updateAccountColumn(osAccount.getId(), "login_name", loginName, connection);
1708  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1709  }
1710 
1711  // if nothing is changed, return
1712  if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
1713  return new OsAccountUpdateResult(updateStatusCode, osAccount);
1714  }
1715 
1716  // update signature if needed, based on the most current addr/loginName
1717  OsAccount currAccount = getOsAccountByObjectId(osAccount.getId(), connection);
1718  String newAddress = currAccount.getAddr().orElse(null);
1719  String newLoginName = currAccount.getLoginName().orElse(null);
1720 
1721  String newSignature = getOsAccountSignature(newAddress, newLoginName);
1722  updateAccountSignature(osAccount.getId(), newSignature, connection);
1723 
1724  // get the updated account from database
1725  updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
1726 
1727  // register the updated account with the transaction to fire off an event
1728  trans.registerChangedOsAccount(updatedAccount);
1729 
1730  return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
1731 
1732  } catch (SQLException ex) {
1733  throw new TskCoreException(String.format("Error updating account with unique id = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
1734  }
1735  }
1736 
1746  public List<Host> getHosts(OsAccount account) throws TskCoreException {
1747  List<Host> hostList = new ArrayList<>();
1748 
1749  String query = "SELECT tsk_hosts.id AS hostId, name, db_status FROM tsk_hosts "
1750  + " JOIN data_source_info ON tsk_hosts.id = data_source_info.host_id"
1751  + " JOIN tsk_os_account_instances ON data_source_info.obj_id = tsk_os_account_instances.data_source_obj_id"
1752  + " WHERE os_account_obj_id = " + account.getId();
1753 
1755  try (CaseDbConnection connection = db.getConnection();
1756  Statement s = connection.createStatement();
1757  ResultSet rs = connection.executeQuery(s, query)) {
1758 
1759  while (rs.next()) {
1760  hostList.add(new Host(rs.getLong("hostId"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
1761  }
1762 
1763  } catch (SQLException ex) {
1764  throw new TskCoreException(String.format("Failed to get host list for os account %d", account.getId()), ex);
1765  } finally {
1767  }
1768  return hostList;
1769  }
1770 
1782  private OsAccount osAccountFromResultSet(ResultSet rs) throws SQLException {
1783 
1784  OsAccountType accountType = null;
1785  int typeId = rs.getInt("type");
1786  if (!rs.wasNull()) {
1787  accountType = OsAccount.OsAccountType.fromID(typeId);
1788  }
1789 
1790  Long creationTime = rs.getLong("created_date"); // getLong returns 0 if value is null
1791  if (rs.wasNull()) {
1792  creationTime = null;
1793  }
1794 
1795  return new OsAccount(db, rs.getLong("os_account_obj_id"), rs.getLong("realm_id"), rs.getString("login_name"), rs.getString("addr"),
1796  rs.getString("signature"), rs.getString("full_name"), creationTime, accountType, OsAccount.OsAccountStatus.fromID(rs.getInt("status")),
1797  OsAccount.OsAccountDbStatus.fromID(rs.getInt("db_status")));
1798 
1799  }
1800 
1807  private void fireChangeEvent(OsAccount account) {
1808  db.fireTSKEvent(new OsAccountsUpdatedTskEvent(Collections.singletonList(account)));
1809  }
1810 
1825  static String getOsAccountSignature(String uniqueId, String loginName) throws TskCoreException {
1826  // Create a signature.
1827  String signature;
1828  if (Strings.isNullOrEmpty(uniqueId) == false) {
1829  signature = uniqueId;
1830  } else if (Strings.isNullOrEmpty(loginName) == false) {
1831  signature = loginName;
1832  } else {
1833  throw new TskCoreException("OS Account must have either a uniqueID or a login name.");
1834  }
1835  return signature;
1836  }
1837 
1842  public static class NotUserSIDException extends TskException {
1843 
1844  private static final long serialVersionUID = 1L;
1845 
1850  super("No error message available.");
1851  }
1852 
1858  public NotUserSIDException(String msg) {
1859  super(msg);
1860  }
1861 
1868  public NotUserSIDException(String msg, Exception ex) {
1869  super(msg, ex);
1870  }
1871  }
1872 
1877 
1880  MERGED
1881  }
1882 
1887  public final static class OsAccountUpdateResult {
1888 
1889  private final OsAccountUpdateStatus updateStatus;
1890  private final OsAccount updatedAccount;
1891 
1892  OsAccountUpdateResult(OsAccountUpdateStatus updateStatus, OsAccount updatedAccount) {
1893  this.updateStatus = updateStatus;
1894  this.updatedAccount = updatedAccount;
1895  }
1896 
1898  return updateStatus;
1899  }
1900 
1901  public Optional<OsAccount> getUpdatedAccount() {
1902  return Optional.ofNullable(updatedAccount);
1903  }
1904  }
1905 
1910  private class OsAccountInstanceKey implements Comparable<OsAccountInstanceKey>{
1911 
1912  private final long osAccountId;
1913  private final long dataSourceId;
1914 
1915  OsAccountInstanceKey(long osAccountId, long dataSourceId) {
1916  this.osAccountId = osAccountId;
1917  this.dataSourceId = dataSourceId;
1918  }
1919 
1920  @Override
1921  public boolean equals(Object other) {
1922  if (this == other) {
1923  return true;
1924  }
1925  if (other == null) {
1926  return false;
1927  }
1928  if (getClass() != other.getClass()) {
1929  return false;
1930  }
1931 
1932  final OsAccountInstanceKey otherKey = (OsAccountInstanceKey) other;
1933 
1934  if (osAccountId != otherKey.osAccountId) {
1935  return false;
1936  }
1937 
1938  return dataSourceId == otherKey.dataSourceId;
1939  }
1940 
1941  @Override
1942  public int hashCode() {
1943  int hash = 5;
1944  hash = 53 * hash + (int) (this.osAccountId ^ (this.osAccountId >>> 32));
1945  hash = 53 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
1946  return hash;
1947  }
1948 
1949  @Override
1950  public int compareTo(OsAccountInstanceKey other) {
1951  if(this.equals(other)) {
1952  return 0;
1953  }
1954 
1955  if (dataSourceId != other.dataSourceId) {
1956  return Long.compare(dataSourceId, other.dataSourceId);
1957  }
1958 
1959  return Long.compare(osAccountId, other.osAccountId);
1960  }
1961  }
1962 }
Optional< String > getAddr()
Definition: OsAccount.java:266
OsAccountRealm newWindowsRealm(String accountSid, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope)
List< Host > getHosts(OsAccount account)
OsAccount getOsAccountByObjectId(long osAccountObjId)
static OsAccountType fromID(int typeId)
Definition: OsAccount.java:193
Optional< OsAccount > getWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost)
OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime)
OsAccount newWindowsOsAccount(String sid, String loginName, OsAccountRealm realm)
List< OsAccount > getOsAccounts(Host host)
OsAccountRealmManager getOsAccountRealmManager()
Optional< OsAccountRealm > getWindowsRealm(String accountSid, String realmName, Host referringHost)
List< OsAccount > getOsAccountsByDataSourceObjId(long dataSourceId)
List< OsAccountInstance > getOsAccountInstances(List< Long > instanceIDs)
OsAccountInstance newOsAccountInstance(OsAccount osAccount, DataSource dataSource, OsAccountInstance.OsAccountInstanceType instanceType)
List< OsAccountInstance > getOsAccountInstances(OsAccount account)
Optional< String > getLoginName()
Definition: OsAccount.java:286
void addExtendedOsAccountAttributes(OsAccount account, List< OsAccountAttribute > accountAttributes)
BlackboardAttribute.Type getAttributeType(String attrTypeName)
OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost)
OsAccount newWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope)

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.