Sleuth Kit Java Bindings (JNI)  4.12.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 com.google.common.annotations.Beta;
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.Collections;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.NavigableMap;
34 import java.util.Objects;
35 import java.util.Optional;
36 import java.util.UUID;
37 import java.util.concurrent.ConcurrentSkipListMap;
38 import java.util.stream.Collectors;
48 import static org.sleuthkit.datamodel.WindowsAccountUtils.isWindowsWellKnownSid;
49 import static org.sleuthkit.datamodel.WindowsAccountUtils.getWindowsWellKnownSidFullName;
50 
55 public final class OsAccountManager {
56 
57  private final SleuthkitCase db;
58  private final Object osAcctInstancesCacheLock;
59  private final NavigableMap<OsAccountInstanceKey, OsAccountInstance> osAccountInstanceCache;
60 
68  db = skCase;
69  osAcctInstancesCacheLock = new Object();
70  osAccountInstanceCache = new ConcurrentSkipListMap<>();
71  }
72 
86  OsAccount newOsAccount(String uniqueAccountId, OsAccountRealm realm) throws TskCoreException {
87 
88  // ensure unique id is provided
89  if (Strings.isNullOrEmpty(uniqueAccountId)) {
90  throw new TskCoreException("Cannot create OS account with null uniqueId.");
91  }
92 
93  if (realm == null) {
94  throw new TskCoreException("Cannot create OS account without a realm.");
95  }
96 
98  try {
99 
100  // try to create account
101  try {
102  OsAccount account = newOsAccount(uniqueAccountId, null, realm, OsAccount.OsAccountStatus.UNKNOWN, trans);
103  trans.commit();
104  trans = null;
105  return account;
106  } catch (SQLException ex) {
107  // Close the transaction before moving on
108  trans.rollback();
109  trans = null;
110 
111  // Create may fail if an OsAccount already exists.
112  Optional<OsAccount> osAccount = this.getOsAccountByAddr(uniqueAccountId, realm);
113  if (osAccount.isPresent()) {
114  return osAccount.get();
115  }
116 
117  // create failed for some other reason, throw an exception
118  throw new TskCoreException(String.format("Error creating OsAccount with uniqueAccountId = %s in realm id = %d", uniqueAccountId, realm.getRealmId()), ex);
119  }
120  } finally {
121  if (trans != null) {
122  trans.rollback();
123  }
124  }
125  }
126 
154  public OsAccount newWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope) throws TskCoreException, NotUserSIDException {
155 
156  if (realmScope == null) {
157  throw new TskCoreException("RealmScope cannot be null. Use UNKNOWN if scope is not known.");
158  }
159  if (referringHost == null) {
160  throw new TskCoreException("A referring host is required to create an account.");
161  }
162 
163  // ensure at least one of the two is supplied - a non-null unique id or a login name
164  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
165  && StringUtils.isBlank(loginName)) {
166  throw new TskCoreException("Cannot create OS account with both uniqueId and loginName as null.");
167  }
168  // Realm name is required if the sid is null.
169  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
170  && StringUtils.isBlank(realmName)) {
171  throw new TskCoreException("Realm name or SID is required to create a Windows account.");
172  }
173 
174  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !WindowsAccountUtils.isWindowsUserSid(sid)) {
175  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
176  }
177 
178  // If no SID is given and the given realm/login names is a well known account, get and use the well known SID
179  if (StringUtils.isBlank(sid)
180  && !StringUtils.isBlank(loginName) && !StringUtils.isBlank(realmName)
181  && WindowsAccountUtils.isWindowsWellKnownAccountName(loginName, realmName)) {
182  sid = WindowsAccountUtils.getWindowsWellKnownAccountSid(loginName, realmName);
183  }
184 
185 
186  if (StringUtils.isNotBlank(sid)) {
187  // SID Normalized to uppercase
188  sid = sid.toUpperCase(Locale.ENGLISH);
189  }
190  if (StringUtils.isNotBlank(loginName)) {
191  // Windows logon names are case insensitive. saving them in lower case.
192  loginName = loginName.toLowerCase(Locale.ENGLISH);
193  }
194  if (StringUtils.isNotBlank(realmName)) {
195  // Windows realm names are case insensitive. saving them in lower case.
196  realmName = realmName.toLowerCase(Locale.ENGLISH);
197  }
198 
199 
200  OsRealmUpdateResult realmUpdateResult;
201  Optional<OsAccountRealm> anotherRealmWithSameName = Optional.empty();
202  Optional<OsAccountRealm> anotherRealmWithSameAddr = Optional.empty();
203 
204  // get the realm for the account, and update it if it is missing addr or name.
205  OsAccountRealm realm = null;
206  try (CaseDbConnection connection = db.getConnection()) {
207  realmUpdateResult = db.getOsAccountRealmManager().getAndUpdateWindowsRealm(sid, realmName, referringHost, connection);
208 
209  Optional<OsAccountRealm> realmOptional = realmUpdateResult.getUpdatedRealm();
210  if (realmOptional.isPresent()) {
211  realm = realmOptional.get();
212 
213  if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
214 
215  // Check if update of the realm triggers a merge with any other realm,
216  // say another realm with same name but no SID, or same SID but no name
217 
218  //1. Check if there is any OTHER realm with the same name, same host but no addr
219  anotherRealmWithSameName = db.getOsAccountRealmManager().getAnotherRealmByName(realmOptional.get(), realmName, referringHost, connection);
220  if (anotherRealmWithSameName.isPresent() && anotherRealmWithSameName.get().getRealmAddr().isPresent()) {
221  // realm with same name has addr, don't merge
222  anotherRealmWithSameName = Optional.empty();
223  }
224 
225  // 2. Check if there is any OTHER realm with same addr and host, but NO name
226  anotherRealmWithSameAddr = db.getOsAccountRealmManager().getAnotherRealmByAddr(realmOptional.get(), realmName, referringHost, connection);
227  if (anotherRealmWithSameAddr.isPresent() && !anotherRealmWithSameAddr.get().getRealmNames().isEmpty()) {
228  // realm with same addr has name, don't merge
229  anotherRealmWithSameName = Optional.empty();
230  }
231  }
232  }
233  }
234 
235  if (null == realm) {
236  // realm was not found, create it.
237  realm = db.getOsAccountRealmManager().newWindowsRealm(sid, realmName, referringHost, realmScope);
238  } else if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
239  // 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
240  if (anotherRealmWithSameName.isPresent() || anotherRealmWithSameAddr.isPresent()) {
241 
242  CaseDbTransaction trans = this.db.beginTransaction();
243  try {
244  if (anotherRealmWithSameName.isPresent()) {
245  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameName.get(), realm, trans);
246  }
247  if (anotherRealmWithSameAddr.isPresent()) {
248  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameAddr.get(), realm, trans);
249  }
250 
251  trans.commit();
252  } catch (TskCoreException ex) {
253  trans.rollback();
254  throw ex; // rethrow
255  }
256  }
257  }
258 
259 
260  return newWindowsOsAccount(sid, loginName, realm);
261  }
262 
280  public OsAccount newWindowsOsAccount(String sid, String loginName, OsAccountRealm realm) throws TskCoreException, NotUserSIDException {
281 
282  // ensure at least one of the two is supplied - a non-null unique id or a login name
283  if ((StringUtils.isBlank(sid) || sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))
284  && StringUtils.isBlank(loginName)) {
285  throw new TskCoreException("Cannot create OS account with both uniqueId and loginName as null.");
286  }
287 
288  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !WindowsAccountUtils.isWindowsUserSid(sid)) {
289  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
290  }
291 
292  // If the login name is well known, we use the well known english name.
293  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
294 
295  CaseDbTransaction trans = db.beginTransaction();
296  try {
297  // try to create account
298  try {
299  String uniqueId = (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ? sid : null;
300  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && isWindowsWellKnownSid(sid)) {
301  // if the SID is a Windows well known SID, then prefer to use the default well known login name
302  String wellKnownLoginName = WindowsAccountUtils.getWindowsWellKnownSidLoginName(sid);
303  if (!StringUtils.isEmpty(wellKnownLoginName)) {
304  resolvedLoginName = wellKnownLoginName;
305  }
306  }
307 
308  OsAccount account = newOsAccount(uniqueId, resolvedLoginName, realm, OsAccount.OsAccountStatus.UNKNOWN, trans);
309 
310  // If the SID indicates a special windows account, then set its full name.
311  if (!StringUtils.isBlank(sid) && !sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && isWindowsWellKnownSid(sid)) {
312  String fullName = getWindowsWellKnownSidFullName(sid);
313  if (StringUtils.isNotBlank(fullName)) {
314  OsAccountUpdateResult updateResult = updateStandardOsAccountAttributes(account, fullName, null, null, null, trans);
315  if (updateResult.getUpdatedAccount().isPresent()) {
316  account = updateResult.getUpdatedAccount().get();
317  }
318  }
319  }
320  trans.commit();
321  trans = null;
322  return account;
323  } catch (SQLException ex) {
324  // Rollback the transaction before proceeding
325  trans.rollback();
326  trans = null;
327 
328  // Create may fail if an OsAccount already exists.
329  Optional<OsAccount> osAccount;
330 
331  // First search for account by uniqueId
332  if (!Strings.isNullOrEmpty(sid)) {
333  osAccount = getOsAccountByAddr(sid, realm);
334  if (osAccount.isPresent()) {
335  return osAccount.get();
336  }
337  }
338 
339  // search by loginName
340  if (!Strings.isNullOrEmpty(resolvedLoginName)) {
341  osAccount = getOsAccountByLoginName(resolvedLoginName, realm);
342  if (osAccount.isPresent()) {
343  return osAccount.get();
344  }
345  }
346 
347  // create failed for some other reason, throw an exception
348  throw new TskCoreException(String.format("Error creating OsAccount with sid = %s, loginName = %s, realm = %s, referring host = %s",
349  (sid != null) ? sid : "Null",
350  (resolvedLoginName != null) ? resolvedLoginName : "Null",
351  (!realm.getRealmNames().isEmpty()) ? realm.getRealmNames().get(0) : "Null",
352  realm.getScopeHost().isPresent() ? realm.getScopeHost().get().getName() : "Null"), ex);
353 
354  }
355  } finally {
356  if (trans != null) {
357  trans.rollback();
358  }
359  }
360  }
361 
377  @Beta
378  public OsAccount newLocalLinuxOsAccount(String uid, String loginName, Host referringHost) throws TskCoreException {
379 
380  if (referringHost == null) {
381  throw new TskCoreException("A referring host is required to create a local OS account.");
382  }
383 
384  // Ensure at least one of the two is supplied - a non-null unique id or a login name
385  if (StringUtils.isBlank(uid) && StringUtils.isBlank(loginName)) {
386  throw new TskCoreException("Cannot create OS account with both uniqueId and loginName as null.");
387  }
388 
389  OsAccountRealm localRealm = db.getOsAccountRealmManager().newLocalLinuxRealm(referringHost);
390 
391  CaseDbTransaction trans = db.beginTransaction();
392  try {
393 
394  // try to create account
395  try {
396  OsAccount account = newOsAccount(uid, loginName, localRealm, OsAccount.OsAccountStatus.UNKNOWN, trans);
397  trans.commit();
398  trans = null;
399  return account;
400  } catch (SQLException ex) {
401  // Rollback the transaction before proceeding
402  trans.rollback();
403  trans = null;
404 
405  // Create may fail if an OsAccount already exists.
406  Optional<OsAccount> osAccount;
407 
408  // First search for account by uniqueId
409  if (!Strings.isNullOrEmpty(uid)) {
410  osAccount = getOsAccountByAddr(uid, localRealm);
411  if (osAccount.isPresent()) {
412  return osAccount.get();
413  }
414  }
415 
416  // search by loginName
417  if (!Strings.isNullOrEmpty(loginName)) {
418  osAccount = getOsAccountByLoginName(loginName, localRealm);
419  if (osAccount.isPresent()) {
420  return osAccount.get();
421  }
422  }
423 
424  // create failed for some other reason, throw an exception
425  throw new TskCoreException(String.format("Error creating OsAccount with uid = %s, loginName = %s, realm = %s, referring host = %s",
426  (uid != null) ? uid : "Null",
427  (loginName != null) ? loginName : "Null",
428  (!localRealm.getRealmNames().isEmpty()) ? localRealm.getRealmNames().get(0) : "Null",
429  localRealm.getScopeHost().isPresent() ? localRealm.getScopeHost().get().getName() : "Null"), ex);
430 
431  }
432  } finally {
433  if (trans != null) {
434  trans.rollback();
435  }
436  }
437  }
438 
439 
453  private OsAccount newOsAccount(String uniqueId, String loginName, OsAccountRealm realm, OsAccount.OsAccountStatus accountStatus, CaseDbTransaction trans) throws TskCoreException, SQLException {
454 
455  if (Objects.isNull(realm)) {
456  throw new TskCoreException("Cannot create an OS Account, realm is NULL.");
457  }
458 
459  String signature = getOsAccountSignature(uniqueId, loginName);
460  OsAccount account;
461 
462  CaseDbConnection connection = trans.getConnection();
463 
464  // first create a tsk_object for the OsAccount.
465  // RAMAN TODO: need to get the correct parent obj id.
466  // Create an Object Directory parent and used its id.
467  long parentObjId = 0;
468 
469  int objTypeId = TskData.ObjectType.OS_ACCOUNT.getObjectType();
470  long osAccountObjId = db.addObject(parentObjId, objTypeId, connection);
471 
472  String accountInsertSQL = "INSERT INTO tsk_os_accounts(os_account_obj_id, login_name, realm_id, addr, signature, status)"
473  + " VALUES (?, ?, ?, ?, ?, ?)"; // NON-NLS
474 
475  PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.NO_GENERATED_KEYS);
476  preparedStatement.clearParameters();
477 
478  preparedStatement.setLong(1, osAccountObjId);
479 
480  preparedStatement.setString(2, loginName);
481  preparedStatement.setLong(3, realm.getRealmId());
482 
483  preparedStatement.setString(4, uniqueId);
484  preparedStatement.setString(5, signature);
485  preparedStatement.setInt(6, accountStatus.getId());
486 
487  connection.executeUpdate(preparedStatement);
488 
489  account = new OsAccount(db, osAccountObjId, realm.getRealmId(), loginName, uniqueId, signature,
490  null, null, null, accountStatus, OsAccount.OsAccountDbStatus.ACTIVE);
491 
492  trans.registerAddedOsAccount(account);
493  return account;
494  }
495 
507  private Optional<OsAccount> getOsAccountByAddr(String addr, Host host) throws TskCoreException {
508 
509  try (CaseDbConnection connection = db.getConnection()) {
510  return getOsAccountByAddr(addr, host, connection);
511  }
512  }
513 
526  private Optional<OsAccount> getOsAccountByAddr(String uniqueId, Host host, CaseDbConnection connection) throws TskCoreException {
527 
528  String whereHostClause = (host == null)
529  ? " 1 = 1 "
530  : " ( realms.scope_host_id = " + host.getHostId() + " OR realms.scope_host_id IS NULL) ";
531 
532  String queryString = "SELECT accounts.os_account_obj_id as os_account_obj_id, accounts.login_name, accounts.full_name, "
533  + " accounts.realm_id, accounts.addr, accounts.signature, "
534  + " accounts.type, accounts.status, accounts.created_date, accounts.db_status, "
535  + " 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 "
536  + " FROM tsk_os_accounts as accounts"
537  + " LEFT JOIN tsk_os_account_realms as realms"
538  + " ON accounts.realm_id = realms.id"
539  + " WHERE " + whereHostClause
540  + " AND accounts.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
541  + " AND accounts.addr = '" + uniqueId + "'";
542 
544  try (Statement s = connection.createStatement();
545  ResultSet rs = connection.executeQuery(s, queryString)) {
546 
547  if (!rs.next()) {
548  return Optional.empty(); // no match found
549  } else {
550  return Optional.of(osAccountFromResultSet(rs));
551  }
552  } catch (SQLException ex) {
553  throw new TskCoreException(String.format("Error getting OS account for unique id = %s and host = %s", uniqueId, (host != null ? host.getName() : "null")), ex);
554  } finally {
556  }
557  }
558 
570  Optional<OsAccount> getOsAccountByAddr(String uniqueId, OsAccountRealm realm) throws TskCoreException {
571 
572  String queryString = "SELECT * FROM tsk_os_accounts"
573  + " WHERE addr = '" + uniqueId + "'"
574  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
575  + " AND realm_id = " + realm.getRealmId();
576 
578  try (CaseDbConnection connection = this.db.getConnection();
579  Statement s = connection.createStatement();
580  ResultSet rs = connection.executeQuery(s, queryString)) {
581 
582  if (!rs.next()) {
583  return Optional.empty(); // no match found
584  } else {
585  return Optional.of(osAccountFromResultSet(rs));
586  }
587  } catch (SQLException ex) {
588  throw new TskCoreException(String.format("Error getting OS account for realm = %s and uniqueId = %s.", (realm != null) ? realm.getSignature() : "NULL", uniqueId), ex);
589  } finally {
591  }
592  }
593 
605  Optional<OsAccount> getOsAccountByLoginName(String loginName, OsAccountRealm realm) throws TskCoreException {
606 
607  String queryString = "SELECT * FROM tsk_os_accounts"
608  + " WHERE login_name = '" + loginName + "'"
609  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
610  + " AND realm_id = " + realm.getRealmId();
611 
613  try (CaseDbConnection connection = this.db.getConnection();
614  Statement s = connection.createStatement();
615  ResultSet rs = connection.executeQuery(s, queryString)) {
616 
617  if (!rs.next()) {
618  return Optional.empty(); // no match found
619  } else {
620  return Optional.of(osAccountFromResultSet(rs));
621  }
622  } catch (SQLException ex) {
623  throw new TskCoreException(String.format("Error getting OS account for realm = %s and loginName = %s.", (realm != null) ? realm.getSignature() : "NULL", loginName), ex);
624  } finally {
626  }
627  }
628 
638  public OsAccount getOsAccountByObjectId(long osAccountObjId) throws TskCoreException {
639 
640  try (CaseDbConnection connection = this.db.getConnection()) {
641  return getOsAccountByObjectId(osAccountObjId, connection);
642  }
643  }
644 
655  OsAccount getOsAccountByObjectId(long osAccountObjId, CaseDbConnection connection) throws TskCoreException {
656 
657  String queryString = "SELECT * FROM tsk_os_accounts"
658  + " WHERE os_account_obj_id = " + osAccountObjId;
659 
661  try (Statement s = connection.createStatement();
662  ResultSet rs = connection.executeQuery(s, queryString)) {
663 
664  if (!rs.next()) {
665  throw new TskCoreException(String.format("No account found with obj id = %d ", osAccountObjId));
666  } else {
667  return osAccountFromResultSet(rs);
668  }
669  } catch (SQLException ex) {
670  throw new TskCoreException(String.format("Error getting account with obj id = %d ", osAccountObjId), ex);
671  } finally {
673  }
674  }
675 
700  public OsAccountInstance newOsAccountInstance(OsAccount osAccount, DataSource dataSource, OsAccountInstance.OsAccountInstanceType instanceType) throws TskCoreException {
701  if (osAccount == null) {
702  throw new TskCoreException("Cannot create account instance with null account.");
703  }
704  if (dataSource == null) {
705  throw new TskCoreException("Cannot create account instance with null data source.");
706  }
707 
708  // check the cache first
709  Optional<OsAccountInstance> existingInstance = cachedAccountInstance(osAccount.getId(), dataSource.getId(), instanceType);
710  if (existingInstance.isPresent()) {
711  return existingInstance.get();
712  }
713 
714  try (CaseDbConnection connection = this.db.getConnection()) {
715  return newOsAccountInstance(osAccount.getId(), dataSource.getId(), instanceType, connection);
716  }
717  }
718 
734  OsAccountInstance newOsAccountInstance(long osAccountId, long dataSourceObjId, OsAccountInstance.OsAccountInstanceType instanceType, CaseDbConnection connection) throws TskCoreException {
735 
736  Optional<OsAccountInstance> existingInstance = cachedAccountInstance(osAccountId, dataSourceObjId, instanceType);
737  if (existingInstance.isPresent()) {
738  return existingInstance.get();
739  }
740 
741  /*
742  * Create the OS account instance.
743  */
745  try {
746  String accountInsertSQL = db.getInsertOrIgnoreSQL("INTO tsk_os_account_instances(os_account_obj_id, data_source_obj_id, instance_type)"
747  + " VALUES (?, ?, ?)"); // NON-NLS
748  PreparedStatement preparedStatement = connection.getPreparedStatement(accountInsertSQL, Statement.RETURN_GENERATED_KEYS);
749  preparedStatement.clearParameters();
750  preparedStatement.setLong(1, osAccountId);
751  preparedStatement.setLong(2, dataSourceObjId);
752  preparedStatement.setInt(3, instanceType.getId());
753  connection.executeUpdate(preparedStatement);
754  try (ResultSet resultSet = preparedStatement.getGeneratedKeys();) {
755  if (resultSet.next()) {
756  OsAccountInstance accountInstance = new OsAccountInstance(db, resultSet.getLong(1), osAccountId, dataSourceObjId, instanceType);
757  synchronized (osAcctInstancesCacheLock) {
758  OsAccountInstanceKey key = new OsAccountInstanceKey(osAccountId, dataSourceObjId);
759  // remove from cache any instances less significant (higher ordinal) than this instance
760  for (OsAccountInstance.OsAccountInstanceType type : OsAccountInstance.OsAccountInstanceType.values()) {
761  if (accountInstance.getInstanceType().compareTo(type) < 0) {
762  osAccountInstanceCache.remove(key);
763  }
764  }
765  // add the new most significant instance to the cache
766  osAccountInstanceCache.put(key, accountInstance);
767  }
768  /*
769  * There is a potential issue here. The cache of OS account
770  * instances is an optimization and was not intended to be
771  * used as an authoritative indicator of whether or not a
772  * particular OS account instance was already added to the
773  * case. In fact, the entire cache is flushed during merge
774  * operations. But regardless, there is a check-then-act
775  * race condition for multi-user cases, with or without the
776  * cache. And although the case database schema and the SQL
777  * returned by getInsertOrIgnoreSQL() seamlessly prevents
778  * duplicates in the case database, a valid row ID is
779  * returned here even if the INSERT is not done. So the
780  * bottom line is that a redundant event may be published
781  * from time to time.
782  */
783  db.fireTSKEvent(new TskEvent.OsAcctInstancesAddedTskEvent(Collections.singletonList(accountInstance)));
784 
785  return accountInstance;
786  } else {
787  // there is the possibility that another thread may be adding the same os account instance at the same time
788  // the database may be updated prior to the cache being updated so this provides an extra opportunity to check
789  // the cache before throwing the exception
790  Optional<OsAccountInstance> existingInstanceRetry = cachedAccountInstance(osAccountId, dataSourceObjId, instanceType);
791  if (existingInstanceRetry.isPresent()) {
792  return existingInstanceRetry.get();
793  }
794  }
795  }
796  } catch (SQLException ex) {
797  throw new TskCoreException(String.format("Error adding OS account instance for OS account object id = %d, data source object id = %d", osAccountId, dataSourceObjId), ex);
798  } finally {
800  }
801 
802  // It's possible that we weren't able to load the account instance because it
803  // is already in the database but the instance cache was cleared during an account merge.
804  // Try loading it here and re-adding to the cache.
805  String whereClause = " tsk_os_account_instances.os_account_obj_id = " + osAccountId
806  + " AND tsk_os_account_instances.data_source_obj_id = " + dataSourceObjId;
807  List<OsAccountInstance> instances = getOsAccountInstances(whereClause);
808  if (instances.isEmpty()) {
809  throw new TskCoreException(String.format("Could not get autogen key after row insert or reload instance for OS account instance. OS account object id = %d, data source object id = %d", osAccountId, dataSourceObjId));
810  }
811 
812  OsAccountInstance accountInstance = instances.get(0);
813  synchronized (osAcctInstancesCacheLock) {
814  OsAccountInstanceKey key = new OsAccountInstanceKey(osAccountId, dataSourceObjId);
815  // remove from cache any instances less significant (higher ordinal) than this instance
816  for (OsAccountInstance.OsAccountInstanceType type : OsAccountInstance.OsAccountInstanceType.values()) {
817  if (accountInstance.getInstanceType().compareTo(type) < 0) {
818  osAccountInstanceCache.remove(key);
819  }
820  }
821  // add the most significant instance to the cache
822  osAccountInstanceCache.put(key, accountInstance);
823  }
824  return accountInstance;
825  }
826 
843  private Optional<OsAccountInstance> cachedAccountInstance(long osAccountId, long dataSourceObjId, OsAccountInstance.OsAccountInstanceType instanceType) {
844 
845  /*
846  * Check the cache of OS account instances for an existing instance for
847  * this OS account and data source. Note that the account instance
848  * created here has a bogus instance ID. This is possible since the
849  * instance ID is not considered in the equals() and hashCode() methods
850  * of this class.
851  */
852  synchronized (osAcctInstancesCacheLock) {
853  OsAccountInstanceKey key = new OsAccountInstanceKey(osAccountId, dataSourceObjId);
854  OsAccountInstance instance = osAccountInstanceCache.get(key);
855  if (instance != null) {
856  // if the new instance type same or less significant than the existing instance (i.e. same or higher ordinal value) it's a match.
857  if (instanceType.compareTo(instance.getInstanceType()) >= 0) {
858  return Optional.of(instance);
859  }
860  }
861  return Optional.empty();
862  }
863  }
864 
874  public List<OsAccount> getOsAccounts(Host host) throws TskCoreException {
875  String queryString = "SELECT * FROM tsk_os_accounts accounts "
876  + "WHERE accounts.os_account_obj_id IN "
877  + "(SELECT instances.os_account_obj_id "
878  + "FROM tsk_os_account_instances instances "
879  + "INNER JOIN data_source_info datasources ON datasources.obj_id = instances.data_source_obj_id "
880  + "WHERE datasources.host_id = " + host.getHostId() + ") "
881  + "AND accounts.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
882 
884  try (CaseDbConnection connection = this.db.getConnection();
885  Statement s = connection.createStatement();
886  ResultSet rs = connection.executeQuery(s, queryString)) {
887 
888  List<OsAccount> accounts = new ArrayList<>();
889  while (rs.next()) {
890  accounts.add(osAccountFromResultSet(rs));
891  }
892  return accounts;
893  } catch (SQLException ex) {
894  throw new TskCoreException(String.format("Error getting OS accounts for host id = %d", host.getHostId()), ex);
895  } finally {
897  }
898  }
899 
909  public List<OsAccount> getOsAccountsByDataSourceObjId(long dataSourceId) throws TskCoreException {
910  String queryString = "SELECT * FROM tsk_os_accounts acc "
911  + "WHERE acc.os_account_obj_id IN "
912  + "(SELECT instance.os_account_obj_id "
913  + "FROM tsk_os_account_instances instance "
914  + "WHERE instance.data_source_obj_id = " + dataSourceId + ") "
915  + "AND acc.db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
916 
918  try (CaseDbConnection connection = this.db.getConnection();
919  Statement s = connection.createStatement();
920  ResultSet rs = connection.executeQuery(s, queryString)) {
921 
922  List<OsAccount> accounts = new ArrayList<>();
923  while (rs.next()) {
924  accounts.add(osAccountFromResultSet(rs));
925  }
926  return accounts;
927  } catch (SQLException ex) {
928  throw new TskCoreException(String.format("Error getting OS accounts for data source id = %d", dataSourceId), ex);
929  } finally {
931  }
932  }
933 
946  void mergeOsAccountsForRealms(OsAccountRealm sourceRealm, OsAccountRealm destRealm, CaseDbTransaction trans) throws TskCoreException {
947  List<OsAccount> destinationAccounts = getOsAccounts(destRealm, trans.getConnection());
948  List<OsAccount> sourceAccounts = getOsAccounts(sourceRealm, trans.getConnection());
949 
950  for (OsAccount sourceAccount : sourceAccounts) {
951 
952  // First a check for the case where the source account has both the login name and unique ID set and
953  // we have separate matches in the destination account for both. If we find this case, we need to first merge
954  // the two accounts in the destination realm. This will ensure that all source accounts match at most one
955  // destination account.
956  // Note that we only merge accounts based on login name if the unique ID is empty.
957  if (sourceAccount.getAddr().isPresent() && sourceAccount.getLoginName().isPresent()) {
958  List<OsAccount> duplicateDestAccounts = destinationAccounts.stream()
959  .filter(p -> p.getAddr().equals(sourceAccount.getAddr())
960  || (p.getLoginName().equals(sourceAccount.getLoginName()) && (!p.getAddr().isPresent())))
961  .collect(Collectors.toList());
962  if (duplicateDestAccounts.size() > 1) {
963  OsAccount combinedDestAccount = duplicateDestAccounts.get(0);
964  duplicateDestAccounts.remove(combinedDestAccount);
965  for (OsAccount dupeDestAccount : duplicateDestAccounts) {
966  mergeOsAccounts(dupeDestAccount, combinedDestAccount, trans);
967  }
968  }
969  }
970 
971  // Look for matching destination account
972  // login name match is set to ignore case here. The current calls to
973  // this api are from windows realm merges. This "may" fail in case of
974  // Linux and will require significant refactoring.
975  Optional<OsAccount> matchingDestAccount = getMatchingAccountForMerge(sourceAccount, destinationAccounts, true);
976 
977  // If we found a match, merge the accounts. Otherwise simply update the realm id
978  if (matchingDestAccount.isPresent()) {
979  mergeOsAccounts(sourceAccount, matchingDestAccount.get(), trans);
980  } else {
981  String query = "UPDATE tsk_os_accounts SET realm_id = " + destRealm.getRealmId() + " WHERE os_account_obj_id = " + sourceAccount.getId();
982  try (Statement s = trans.getConnection().createStatement()) {
983  s.executeUpdate(query);
984  } catch (SQLException ex) {
985  throw new TskCoreException("Error executing SQL update: " + query, ex);
986  }
987  trans.registerChangedOsAccount(sourceAccount);
988  }
989  }
990  }
991 
999  private Optional<OsAccount> getMatchingAccountForMerge(OsAccount sourceAccount, List<OsAccount> destinationAccounts, boolean ignoreCase) {
1000  // Look for matching destination account
1001  OsAccount matchingDestAccount = null;
1002 
1003  // First look for matching unique id
1004  if (sourceAccount.getAddr().isPresent()) {
1005  List<OsAccount> matchingDestAccounts = destinationAccounts.stream()
1006  .filter(p -> p.getAddr().equals(sourceAccount.getAddr()))
1007  .collect(Collectors.toList());
1008  if (!matchingDestAccounts.isEmpty()) {
1009  matchingDestAccount = matchingDestAccounts.get(0);
1010  }
1011  }
1012 
1013  // If a match wasn't found yet, look for a matching login name.
1014  // We will merge only if:
1015  // - We didn't already find a unique ID match
1016  // - The source account has no unique ID OR the destination account has no unique ID
1017  // - destination account has a login name and matches the source account login name
1018  if (matchingDestAccount == null && sourceAccount.getLoginName().isPresent()) {
1019  List<OsAccount> matchingDestAccounts = destinationAccounts.stream()
1020  .filter(p -> p.getLoginName().isPresent())
1021  .filter(p -> ( ( ignoreCase ? p.getLoginName().get().equalsIgnoreCase(sourceAccount.getLoginName().get()) // Ignore case match
1022  : p.getLoginName().get().equals(sourceAccount.getLoginName().get()) )
1023  && ((!sourceAccount.getAddr().isPresent()) || (!p.getAddr().isPresent()))))
1024  .collect(Collectors.toList());
1025  if (!matchingDestAccounts.isEmpty()) {
1026  matchingDestAccount = matchingDestAccounts.get(0);
1027  }
1028  }
1029 
1030  return Optional.ofNullable(matchingDestAccount);
1031  }
1032 
1041  private void mergeOsAccount(OsAccount account, boolean ignoreCase, CaseDbTransaction trans) throws TskCoreException {
1042  // Get the realm for the account
1043  Long realmId = account.getRealmId();
1044  OsAccountRealm realm = db.getOsAccountRealmManager().getRealmByRealmId(realmId, trans.getConnection());
1045 
1046  // Get all users in the realm (excluding the account)
1047  List<OsAccount> osAccounts = getOsAccounts(realm, trans.getConnection());
1048  osAccounts.removeIf(acc -> Objects.equals(acc.getId(), account.getId()));
1049 
1050  // Look for matching account
1051  Optional<OsAccount> matchingAccount = getMatchingAccountForMerge(account, osAccounts, ignoreCase);
1052 
1053  // If we find a match, merge the accounts.
1054  if (matchingAccount.isPresent()) {
1055  mergeOsAccounts(matchingAccount.get(), account, trans);
1056  }
1057  }
1058 
1073  private void mergeOsAccounts(OsAccount sourceAccount, OsAccount destAccount, CaseDbTransaction trans) throws TskCoreException {
1074 
1075  String query = "";
1076  try (Statement s = trans.getConnection().createStatement()) {
1077 
1078  // Update all references
1079  query = makeOsAccountUpdateQuery("tsk_os_account_attributes", sourceAccount, destAccount);
1080  s.executeUpdate(query);
1081 
1082  // tsk_os_account_instances has a unique constraint on os_account_obj_id, data_source_obj_id, and instance_type,
1083  // so delete any rows that would be duplicates.
1084  query = "DELETE FROM tsk_os_account_instances "
1085  + "WHERE id IN ( "
1086  + "SELECT "
1087  + " sourceAccountInstance.id "
1088  + "FROM "
1089  + " tsk_os_account_instances destAccountInstance "
1090  + "INNER JOIN tsk_os_account_instances sourceAccountInstance ON destAccountInstance.data_source_obj_id = sourceAccountInstance.data_source_obj_id "
1091  + "WHERE destAccountInstance.os_account_obj_id = " + destAccount.getId()
1092  + " AND sourceAccountInstance.os_account_obj_id = " + sourceAccount.getId()
1093  + " AND sourceAccountInstance.instance_type = destAccountInstance.instance_type" + ")";
1094 
1095  s.executeUpdate(query);
1096 
1097  query = makeOsAccountUpdateQuery("tsk_os_account_instances", sourceAccount, destAccount);
1098  s.executeUpdate(query);
1099  synchronized (osAcctInstancesCacheLock) {
1100  osAccountInstanceCache.clear();
1101  }
1102 
1103  query = makeOsAccountUpdateQuery("tsk_files", sourceAccount, destAccount);
1104  s.executeUpdate(query);
1105 
1106  query = makeOsAccountUpdateQuery("tsk_data_artifacts", sourceAccount, destAccount);
1107  s.executeUpdate(query);
1108 
1109 
1110  // register the merged accounts with the transaction to fire off an event
1111  trans.registerMergedOsAccount(sourceAccount.getId(), destAccount.getId());
1112 
1113  // Update the source account. Make a dummy signature to prevent problems with the unique constraint.
1114  String mergedSignature = makeMergedOsAccountSignature();
1115  query = "UPDATE tsk_os_accounts SET merged_into = " + destAccount.getId()
1116  + ", db_status = " + OsAccount.OsAccountDbStatus.MERGED.getId()
1117  + ", signature = '" + mergedSignature + "' "
1118  + " WHERE os_account_obj_id = " + sourceAccount.getId();
1119 
1120  s.executeUpdate(query);
1121  trans.registerDeletedOsAccount(sourceAccount.getId());
1122 
1123  // Merge and update the destination account. Note that this must be done after updating
1124  // the source account to prevent conflicts when merging two accounts in the
1125  // same realm.
1126  mergeOsAccountObjectsAndUpdateDestAccount(sourceAccount, destAccount, trans);
1127  } catch (SQLException ex) {
1128  throw new TskCoreException("Error executing SQL update: " + query, ex);
1129  }
1130  }
1131 
1137  private String makeMergedOsAccountSignature() {
1138  return "MERGED " + UUID.randomUUID().toString();
1139  }
1140 
1150  private String makeOsAccountUpdateQuery(String tableName, OsAccount sourceAccount, OsAccount destAccount) {
1151  return "UPDATE " + tableName + " SET os_account_obj_id = " + destAccount.getId() + " WHERE os_account_obj_id = " + sourceAccount.getId();
1152  }
1153 
1165  private OsAccount mergeOsAccountObjectsAndUpdateDestAccount(OsAccount sourceAccount, OsAccount destAccount, CaseDbTransaction trans) throws TskCoreException {
1166 
1167  OsAccount mergedDestAccount = destAccount;
1168 
1169  String destLoginName = null;
1170  String destAddr = null;
1171 
1172  // Copy any fields that aren't set in the destination to the value from the source account.
1173  if (!destAccount.getLoginName().isPresent() && sourceAccount.getLoginName().isPresent()) {
1174  destLoginName = sourceAccount.getLoginName().get();
1175  }
1176 
1177  if (!destAccount.getAddr().isPresent() && sourceAccount.getAddr().isPresent()) {
1178  destAddr = sourceAccount.getAddr().get();
1179  }
1180 
1181  // update the dest account core
1182  OsAccountUpdateResult updateStatus = this.updateOsAccountCore(destAccount, destAddr, destLoginName, trans);
1183 
1184  if (updateStatus.getUpdateStatusCode() == OsAccountUpdateStatus.UPDATED && updateStatus.getUpdatedAccount().isPresent()) {
1185  mergedDestAccount = updateStatus.getUpdatedAccount().get();
1186  }
1187 
1188  String destFullName = null;
1189  Long destCreationTime = null;
1190  if (!destAccount.getFullName().isPresent() && sourceAccount.getFullName().isPresent()) {
1191  destFullName = sourceAccount.getFullName().get();
1192  }
1193 
1194  if (!destAccount.getCreationTime().isPresent() && sourceAccount.getCreationTime().isPresent()) {
1195  destCreationTime = sourceAccount.getCreationTime().get();
1196  }
1197 
1198  // update the dest account properties
1199  updateStatus = this.updateStandardOsAccountAttributes(destAccount, destFullName, null, null, destCreationTime, trans);
1200 
1201  if (updateStatus.getUpdateStatusCode() == OsAccountUpdateStatus.UPDATED && updateStatus.getUpdatedAccount().isPresent()) {
1202  mergedDestAccount = updateStatus.getUpdatedAccount().get();
1203  }
1204 
1205  return mergedDestAccount;
1206  }
1207 
1218  private List<OsAccount> getOsAccounts(OsAccountRealm realm, CaseDbConnection connection) throws TskCoreException {
1219  String queryString = "SELECT * FROM tsk_os_accounts"
1220  + " WHERE realm_id = " + realm.getRealmId()
1221  + " AND db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId()
1222  + " ORDER BY os_account_obj_id";
1223 
1224  try (Statement s = connection.createStatement();
1225  ResultSet rs = connection.executeQuery(s, queryString)) {
1226 
1227  List<OsAccount> accounts = new ArrayList<>();
1228  while (rs.next()) {
1229  accounts.add(osAccountFromResultSet(rs));
1230  }
1231  return accounts;
1232  } catch (SQLException ex) {
1233  throw new TskCoreException(String.format("Error getting OS accounts for realm id = %d", realm.getRealmId()), ex);
1234  }
1235  }
1236 
1244  public List<OsAccount> getOsAccounts() throws TskCoreException {
1245  String queryString = "SELECT * FROM tsk_os_accounts"
1246  + " WHERE db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId();
1247 
1249  try (CaseDbConnection connection = this.db.getConnection();
1250  Statement s = connection.createStatement();
1251  ResultSet rs = connection.executeQuery(s, queryString)) {
1252 
1253  List<OsAccount> accounts = new ArrayList<>();
1254  while (rs.next()) {
1255  accounts.add(osAccountFromResultSet(rs));
1256  }
1257  return accounts;
1258  } catch (SQLException ex) {
1259  throw new TskCoreException(String.format("Error getting OS accounts"), ex);
1260  } finally {
1262  }
1263  }
1264 
1283  public Optional<OsAccount> getWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost) throws TskCoreException, NotUserSIDException {
1284 
1285  if (referringHost == null) {
1286  throw new TskCoreException("A referring host is required to get an account.");
1287  }
1288 
1289  // ensure at least one of the two is supplied - a non-null sid or a login name
1290  if ((StringUtils.isBlank(sid) || (sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) ) && StringUtils.isBlank(loginName)) {
1291  throw new TskCoreException("Cannot get an OS account with both SID and loginName as null.");
1292  }
1293 
1294  // If no SID is given and the given realm/login names is a well known account, get and use the well known SID
1295  if (StringUtils.isBlank(sid)
1296  && !StringUtils.isBlank(loginName) && !StringUtils.isBlank(realmName)
1297  && WindowsAccountUtils.isWindowsWellKnownAccountName(loginName, realmName)) {
1298  sid = WindowsAccountUtils.getWindowsWellKnownAccountSid(loginName, realmName);
1299 
1300  }
1301 
1302 
1303  if (StringUtils.isNotBlank(sid)) {
1304  // SID Normalized to uppercase
1305  sid = sid.toUpperCase(Locale.ENGLISH);
1306  }
1307  if (StringUtils.isNotBlank(loginName)) {
1308  // Windows logon names are case insensitive. saving them in lower case.
1309  loginName = loginName.toLowerCase(Locale.ENGLISH);
1310  }
1311  if (StringUtils.isNotBlank(realmName)) {
1312  // Windows realm names are case insensitive. saving them in lower case.
1313  realmName = realmName.toLowerCase(Locale.ENGLISH);
1314  }
1315 
1316  // first get the realm for the given sid
1317  Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getWindowsRealm(sid, realmName, referringHost);
1318  if (!realm.isPresent()) {
1319  return Optional.empty();
1320  }
1321 
1322  // search by SID
1323  if (!Strings.isNullOrEmpty(sid) && !(sid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID))) {
1324  if (!WindowsAccountUtils.isWindowsUserSid(sid)) {
1325  throw new OsAccountManager.NotUserSIDException(String.format("SID = %s is not a user SID.", sid));
1326  }
1327 
1328  Optional<OsAccount> account = this.getOsAccountByAddr(sid, realm.get());
1329  if (account.isPresent()) {
1330  return account;
1331  }
1332  }
1333 
1334  // search by login name
1335  if (!Strings.isNullOrEmpty(loginName)) {
1336  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
1337  return this.getOsAccountByLoginName(resolvedLoginName, realm.get());
1338  } else {
1339  return Optional.empty();
1340  }
1341  }
1342 
1355  @Beta
1356  public Optional<OsAccount> getLocalLinuxOsAccount(String uid, String loginName, Host referringHost) throws TskCoreException {
1357 
1358  if (referringHost == null) {
1359  throw new TskCoreException("A referring host is required to get an account.");
1360  }
1361 
1362  // ensure at least one of the two is supplied - a non-null uid or a login name
1363  if (StringUtils.isBlank(uid) && StringUtils.isBlank(loginName)) {
1364  throw new TskCoreException("Cannot get an OS account with both UID and loginName as null.");
1365  }
1366 
1367  // First get the local realm
1368  Optional<OsAccountRealm> realm = db.getOsAccountRealmManager().getLocalLinuxRealm(referringHost);
1369  if (!realm.isPresent()) {
1370  return Optional.empty();
1371  }
1372 
1373  // Search by UID
1374  if (!Strings.isNullOrEmpty(uid)) {
1375  Optional<OsAccount> account = this.getOsAccountByAddr(uid, realm.get());
1376  if (account.isPresent()) {
1377  return account;
1378  }
1379  }
1380 
1381  // Search by login name
1382  if (!Strings.isNullOrEmpty(loginName)) {
1383  return this.getOsAccountByLoginName(loginName, realm.get());
1384  } else {
1385  return Optional.empty();
1386  }
1387  }
1388 
1398  public void addExtendedOsAccountAttributes(OsAccount account, List<OsAccountAttribute> accountAttributes) throws TskCoreException {
1399 
1400  synchronized (account) { // synchronized to prevent multiple threads trying to add osAccount attributes concurrently to the same osAccount.
1402 
1403  try (CaseDbConnection connection = db.getConnection()) {
1404  for (OsAccountAttribute accountAttribute : accountAttributes) {
1405 
1406  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)"
1407  + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS
1408 
1409  PreparedStatement preparedStatement = connection.getPreparedStatement(attributeInsertSQL, Statement.RETURN_GENERATED_KEYS);
1410  preparedStatement.clearParameters();
1411 
1412  preparedStatement.setLong(1, account.getId());
1413  if (accountAttribute.getHostId().isPresent()) {
1414  preparedStatement.setLong(2, accountAttribute.getHostId().get());
1415  } else {
1416  preparedStatement.setNull(2, java.sql.Types.NULL);
1417  }
1418  if (accountAttribute.getSourceObjectId().isPresent()) {
1419  preparedStatement.setLong(3, accountAttribute.getSourceObjectId().get());
1420  } else {
1421  preparedStatement.setNull(3, java.sql.Types.NULL);
1422  }
1423 
1424  preparedStatement.setLong(4, accountAttribute.getAttributeType().getTypeID());
1425  preparedStatement.setLong(5, accountAttribute.getAttributeType().getValueType().getType());
1426 
1427  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE) {
1428  preparedStatement.setBytes(6, accountAttribute.getValueBytes());
1429  } else {
1430  preparedStatement.setBytes(6, null);
1431  }
1432 
1433  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING
1434  || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
1435  preparedStatement.setString(7, accountAttribute.getValueString());
1436  } else {
1437  preparedStatement.setString(7, null);
1438  }
1439  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) {
1440  preparedStatement.setInt(8, accountAttribute.getValueInt());
1441  } else {
1442  preparedStatement.setNull(8, java.sql.Types.NULL);
1443  }
1444 
1445  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME
1446  || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG) {
1447  preparedStatement.setLong(9, accountAttribute.getValueLong());
1448  } else {
1449  preparedStatement.setNull(9, java.sql.Types.NULL);
1450  }
1451 
1452  if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) {
1453  preparedStatement.setDouble(10, accountAttribute.getValueDouble());
1454  } else {
1455  preparedStatement.setNull(10, java.sql.Types.NULL);
1456  }
1457 
1458  connection.executeUpdate(preparedStatement);
1459  }
1460  } catch (SQLException ex) {
1461  throw new TskCoreException(String.format("Error adding OS Account attribute for account id = %d", account.getId()), ex);
1462  } finally {
1464  }
1465  // set the atrribute list in account to the most current list from the database
1466  List<OsAccountAttribute> currentAttribsList = getOsAccountAttributes(account);
1467  account.setAttributesInternal(currentAttribsList);
1468  }
1469  fireChangeEvent(account);
1470  }
1471 
1481  List<OsAccountAttribute> getOsAccountAttributes(OsAccount account) throws TskCoreException {
1482 
1483  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, "
1484  + " attributes.attribute_type_id as attribute_type_id, attributes.value_type as value_type, attributes.value_byte as value_byte, "
1485  + " attributes.value_text as value_text, attributes.value_int32 as value_int32, attributes.value_int64 as value_int64, attributes.value_double as value_double, "
1486  + " hosts.id, hosts.name as host_name, hosts.db_status as host_status "
1487  + " FROM tsk_os_account_attributes as attributes"
1488  + " LEFT JOIN tsk_hosts as hosts "
1489  + " ON attributes.host_id = hosts.id "
1490  + " WHERE os_account_obj_id = " + account.getId();
1491 
1493  try (CaseDbConnection connection = this.db.getConnection();
1494  Statement s = connection.createStatement();
1495  ResultSet rs = connection.executeQuery(s, queryString)) {
1496 
1497  List<OsAccountAttribute> attributes = new ArrayList<>();
1498  while (rs.next()) {
1499 
1500  Host host = null;
1501  long hostId = rs.getLong("host_id");
1502  if (!rs.wasNull()) {
1503  host = new Host(hostId, rs.getString("host_name"), Host.HostDbStatus.fromID(rs.getInt("host_status")));
1504  }
1505 
1506  Content sourceContent = null;
1507  long sourceObjId = rs.getLong("source_obj_id");
1508  if (!rs.wasNull()) {
1509  sourceContent = this.db.getContentById(sourceObjId);
1510  }
1511  BlackboardAttribute.Type attributeType = db.getBlackboard().getAttributeType(rs.getInt("attribute_type_id"));
1512  OsAccountAttribute attribute = account.new OsAccountAttribute(attributeType, rs.getInt("value_int32"), rs.getLong("value_int64"),
1513  rs.getDouble("value_double"), rs.getString("value_text"), rs.getBytes("value_byte"),
1514  db, account, host, sourceContent);
1515 
1516  attributes.add(attribute);
1517  }
1518  return attributes;
1519  } catch (SQLException ex) {
1520  throw new TskCoreException(String.format("Error getting OS account attributes for account obj id = %d", account.getId()), ex);
1521  } finally {
1523  }
1524  }
1525 
1535  public List<OsAccountInstance> getOsAccountInstances(OsAccount account) throws TskCoreException {
1536  String whereClause = " tsk_os_account_instances.os_account_obj_id = " + account.getId();
1537  return getOsAccountInstances(whereClause);
1538  }
1539 
1550  public List<OsAccountInstance> getOsAccountInstances(List<Long> instanceIDs) throws TskCoreException {
1551  String instanceIds = instanceIDs.stream().map(id -> id.toString()).collect(Collectors.joining(","));
1552 
1553  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1554 
1555  String querySQL = "SELECT * FROM tsk_os_account_instances "
1556  + " WHERE tsk_os_account_instances.id IN (" + instanceIds + ")";
1557 
1559  try (CaseDbConnection connection = db.getConnection();
1560  PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1561  ResultSet results = connection.executeQuery(preparedStatement)) {
1562 
1563  osAcctInstances = getOsAccountInstancesFromResultSet(results);
1564 
1565  } catch (SQLException ex) {
1566  throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1567  } finally {
1569  }
1570  return osAcctInstances;
1571  }
1572 
1586  private List<OsAccountInstance> getOsAccountInstances(String whereClause) throws TskCoreException {
1587  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1588 
1589  String querySQL
1590  = "SELECT tsk_os_account_instances.* "
1591  + " FROM tsk_os_account_instances "
1592  + " INNER JOIN ( SELECT os_account_obj_id, data_source_obj_id, MIN(instance_type) AS min_instance_type "
1593  + " FROM tsk_os_account_instances"
1594  + " GROUP BY os_account_obj_id, data_source_obj_id ) grouped_instances "
1595  + " ON tsk_os_account_instances.os_account_obj_id = grouped_instances.os_account_obj_id "
1596  + " AND tsk_os_account_instances.instance_type = grouped_instances.min_instance_type "
1597  + " WHERE " + whereClause;
1598 
1600  try (CaseDbConnection connection = db.getConnection();
1601  PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1602  ResultSet results = connection.executeQuery(preparedStatement)) {
1603 
1604  osAcctInstances = getOsAccountInstancesFromResultSet(results);
1605 
1606  } catch (SQLException ex) {
1607  throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1608  } finally {
1610  }
1611  return osAcctInstances;
1612  }
1613 
1623  private List<OsAccountInstance> getOsAccountInstancesFromResultSet(ResultSet results) throws SQLException {
1624 
1625  List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1626  while (results.next()) {
1627  long instanceId = results.getLong("id");
1628  long osAccountObjID = results.getLong("os_account_obj_id");
1629  long dataSourceObjId = results.getLong("data_source_obj_id");
1630  int instanceType = results.getInt("instance_type");
1631  osAcctInstances.add(new OsAccountInstance(db, instanceId, osAccountObjID, dataSourceObjId, OsAccountInstance.OsAccountInstanceType.fromID(instanceType)));
1632  }
1633 
1634  return osAcctInstances;
1635  }
1636 
1653  public OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime) throws TskCoreException {
1654 
1655  CaseDbTransaction trans = db.beginTransaction();
1656  try {
1657  OsAccountUpdateResult updateStatus = updateStandardOsAccountAttributes(osAccount, fullName, accountType, accountStatus, creationTime, trans);
1658 
1659  trans.commit();
1660  trans = null;
1661 
1662  return updateStatus;
1663  } finally {
1664  if (trans != null) {
1665  trans.rollback();
1666  }
1667  }
1668  }
1669 
1687  OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime, CaseDbTransaction trans) throws TskCoreException {
1688 
1689  OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
1690 
1691  try {
1692  CaseDbConnection connection = trans.getConnection();
1693 
1694  if (!StringUtils.isBlank(fullName)) {
1695  updateAccountColumn(osAccount.getId(), "full_name", fullName, connection);
1696  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1697  }
1698 
1699  if (Objects.nonNull(accountType)) {
1700  updateAccountColumn(osAccount.getId(), "type", accountType.getId(), connection);
1701  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1702  }
1703 
1704  if (Objects.nonNull(accountStatus)) {
1705  updateAccountColumn(osAccount.getId(), "status", accountStatus.getId(), connection);
1706  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1707  }
1708 
1709  if (Objects.nonNull(creationTime)) {
1710  updateAccountColumn(osAccount.getId(), "created_date", creationTime, connection);
1711  updateStatusCode = OsAccountUpdateStatus.UPDATED;
1712  }
1713 
1714  // if nothing has been changed, return
1715  if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
1716  return new OsAccountUpdateResult(updateStatusCode, null);
1717  }
1718 
1719  // get the updated account from database
1720  OsAccount updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
1721 
1722  // register the updated account with the transaction to fire off an event
1723  trans.registerChangedOsAccount(updatedAccount);
1724 
1725  return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
1726 
1727  } catch (SQLException ex) {
1728  throw new TskCoreException(String.format("Error updating account with addr = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
1729  }
1730  }
1731 
1745  private <T> void updateAccountColumn(long accountObjId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
1746 
1747  String updateSQL = "UPDATE tsk_os_accounts "
1748  + " SET " + colName + " = ? "
1749  + " WHERE os_account_obj_id = ?";
1750 
1752  try {
1753  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1754  preparedStatement.clearParameters();
1755 
1756  if (Objects.isNull(colValue)) {
1757  preparedStatement.setNull(1, Types.NULL); // handle null value
1758  } else {
1759  if (colValue instanceof String) {
1760  preparedStatement.setString(1, (String) colValue);
1761  } else if (colValue instanceof Long) {
1762  preparedStatement.setLong(1, (Long) colValue);
1763  } else if (colValue instanceof Integer) {
1764  preparedStatement.setInt(1, (Integer) colValue);
1765  } else {
1766  throw new TskCoreException(String.format("Unhandled column data type received while updating the account (%d) ", accountObjId));
1767  }
1768  }
1769 
1770  preparedStatement.setLong(2, accountObjId);
1771 
1772  connection.executeUpdate(preparedStatement);
1773  } finally {
1775  }
1776  }
1777 
1788  private void updateAccountSignature(long accountObjId, String signature, CaseDbConnection connection) throws SQLException {
1789 
1790  String updateSQL = "UPDATE tsk_os_accounts SET "
1791  + " signature = "
1792  + " CASE WHEN db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId() + " THEN ? ELSE signature END "
1793  + " WHERE os_account_obj_id = ?"; // 8
1794 
1795  PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1796  preparedStatement.clearParameters();
1797 
1798  preparedStatement.setString(1, signature);
1799  preparedStatement.setLong(2, accountObjId);
1800 
1801  connection.executeUpdate(preparedStatement);
1802  }
1803 
1827  public OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost) throws TskCoreException, NotUserSIDException {
1828  CaseDbTransaction trans = db.beginTransaction();
1829  try {
1830 
1831  if (StringUtils.isNotBlank(accountSid)) {
1832  // SID Normalized to uppercase
1833  accountSid = accountSid.toUpperCase(Locale.ENGLISH);
1834  }
1835  if (StringUtils.isNotBlank(loginName)) {
1836  // Windows logon names are case insensitive. saving them in lower case.
1837  loginName = loginName.toLowerCase(Locale.ENGLISH);
1838  }
1839  if (StringUtils.isNotBlank(realmName)) {
1840  // Windows realm names are case insensitive. saving them in lower case.
1841  realmName = realmName.toLowerCase(Locale.ENGLISH);
1842  }
1843 
1844  OsAccountUpdateResult updateStatus = this.updateCoreWindowsOsAccountAttributes(osAccount, accountSid, loginName, realmName, referringHost, trans);
1845 
1846  trans.commit();
1847  trans = null;
1848  return updateStatus;
1849  } finally {
1850  if (trans != null) {
1851  trans.rollback();
1852  }
1853  }
1854  }
1855 
1875  private OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost, CaseDbTransaction trans) throws TskCoreException, NotUserSIDException {
1876 
1877  // first get and update the realm - if we have the info to find the realm
1878 
1879  if ((!StringUtils.isBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) || !StringUtils.isBlank(realmName)) {
1880  // If the SID is a well known SID, ensure we use the well known english name
1881  String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
1882 
1883 
1884  OsRealmUpdateResult realmUpdateResult = db.getOsAccountRealmManager().getAndUpdateWindowsRealm(accountSid, resolvedRealmName, referringHost, trans.getConnection());
1885 
1886 
1887  Optional<OsAccountRealm> realmOptional = realmUpdateResult.getUpdatedRealm();
1888 
1889  if (realmOptional.isPresent()) {
1890 
1891  if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
1892 
1893  // Check if update of the realm triggers a merge with any other realm,
1894  // say another realm with same name but no SID, or same SID but no name
1895  //1. Check if there is any OTHER realm with the same name, same host but no addr
1896  Optional<OsAccountRealm> anotherRealmWithSameName = db.getOsAccountRealmManager().getAnotherRealmByName(realmOptional.get(), realmName, referringHost, trans.getConnection());
1897  if (anotherRealmWithSameName.isPresent() && anotherRealmWithSameName.get().getRealmAddr().isPresent()) {
1898  // realm with same name has addr, don't merge
1899  anotherRealmWithSameName = Optional.empty();
1900  }
1901 
1902  // 2. Check if there is any OTHER realm with same addr and host, but NO name
1903  Optional<OsAccountRealm> anotherRealmWithSameAddr = db.getOsAccountRealmManager().getAnotherRealmByAddr(realmOptional.get(), realmName, referringHost, trans.getConnection());
1904  if (anotherRealmWithSameAddr.isPresent() && !anotherRealmWithSameAddr.get().getRealmNames().isEmpty()) {
1905  // realm with same addr has name, don't merge
1906  anotherRealmWithSameName = Optional.empty();
1907  }
1908 
1909  if (anotherRealmWithSameName.isPresent()) {
1910  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameName.get(), realmOptional.get(), trans);
1911  }
1912  if (anotherRealmWithSameAddr.isPresent()) {
1913  db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameAddr.get(), realmOptional.get(), trans);
1914  }
1915  }
1916  }
1917  }
1918 
1919  // now update the account core data
1920  String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
1921  OsAccountUpdateResult updateStatus = this.updateOsAccountCore(osAccount, accountSid, resolvedLoginName, trans);
1922 
1923  Optional<OsAccount> updatedAccount = updateStatus.getUpdatedAccount();
1924  if (updatedAccount.isPresent() && updateStatus.updateStatus != OsAccountUpdateStatus.NO_CHANGE) {
1925  // After updating account data, check if there is matching account to merge
1926  mergeOsAccount(updatedAccount.get(), true, trans);
1927  }
1928 
1929  return updateStatus;
1930  }
1931 
1949  public OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName) throws TskCoreException {
1950  CaseDbTransaction trans = db.beginTransaction();
1951  try {
1952  OsAccountUpdateResult updateStatus = this.updateCoreLocalLinuxOsAccountAttributes(osAccount, uid, loginName, trans);
1953 
1954  trans.commit();
1955  trans = null;
1956  return updateStatus;
1957  } finally {
1958  if (trans != null) {
1959  trans.rollback();
1960  }
1961  }
1962  }
1963 
1981  @Beta
1982  private OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName, CaseDbTransaction trans) throws TskCoreException {
1983 
1984  // Update the account core data
1985  OsAccountUpdateResult updateStatus = this.updateOsAccountCore(osAccount, uid, loginName, trans);
1986 
1987  Optional<OsAccount> updatedAccount = updateStatus.getUpdatedAccount();
1988  if (updatedAccount.isPresent()) {
1989  // After updating account data, check if there is matching account to merge
1990  mergeOsAccount(updatedAccount.get(), false, trans);
1991  }
1992 
1993  return updateStatus;
1994  }
1995 
2018  private OsAccountUpdateResult updateOsAccountCore(OsAccount osAccount, String address, String loginName, CaseDbTransaction trans) throws TskCoreException {
2019 
2020  OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
2021  OsAccount updatedAccount;
2022 
2023  try {
2024  CaseDbConnection connection = trans.getConnection();
2025 
2026  // if a new non-null addr is provided and the account already has an address, and they are not the same, throw an exception
2027  if (!StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !address.equalsIgnoreCase(osAccount.getAddr().orElse(""))) {
2028  throw new TskCoreException(String.format("Account (%d) already has an address (%s), address cannot be updated.", osAccount.getId(), osAccount.getAddr().orElse("NULL")));
2029  }
2030 
2031  if (StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
2032  updateAccountColumn(osAccount.getId(), "addr", address, connection);
2033  updateStatusCode = OsAccountUpdateStatus.UPDATED;
2034  }
2035 
2036  if (StringUtils.isBlank(osAccount.getLoginName().orElse(null)) && !StringUtils.isBlank(loginName)) {
2037  updateAccountColumn(osAccount.getId(), "login_name", loginName, connection);
2038  updateStatusCode = OsAccountUpdateStatus.UPDATED;
2039  }
2040 
2041  // if nothing is changed, return
2042  if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
2043  return new OsAccountUpdateResult(updateStatusCode, osAccount);
2044  }
2045 
2046  // update signature if needed, based on the most current addr/loginName
2047  OsAccount currAccount = getOsAccountByObjectId(osAccount.getId(), connection);
2048  String newAddress = currAccount.getAddr().orElse(null);
2049  String newLoginName = currAccount.getLoginName().orElse(null);
2050 
2051  String newSignature = getOsAccountSignature(newAddress, newLoginName);
2052 
2053  try {
2054  updateAccountSignature(osAccount.getId(), newSignature, connection);
2055  } catch (SQLException ex) {
2056  // There's a slight chance that we're in the case where we are trying to add an addr to an OS account
2057  // with only a name where the addr already exists on a different OS account. This will cause a unique
2058  // constraint failure in updateAccountSignature(). This is unlikely to happen in normal use
2059  // since we lookup OS accounts by addr before name when we have both (i.e., it would be strange to have an
2060  // OsAccount in hand with only the loginName set when we also know the addr).
2061  // Correctly handling every case here is non-trivial, so for the moment only look for the specific case where
2062  // we had an OsAccount with just an addr and and OsAccount with just a login name that we now
2063  // want to combine.
2064  if (osAccount.getAddr().isEmpty() && !StringUtils.isBlank(address)) {
2065  OsAccountRealm realm = db.getOsAccountRealmManager().getRealmByRealmId(osAccount.getRealmId(), connection);
2066  Optional<OsAccount> matchingAddrAcct = getOsAccountByAddr(address, realm.getScopeHost().get(), connection);
2067  if (matchingAddrAcct.isEmpty()
2068  || matchingAddrAcct.get().getId() == osAccount.getId()
2069  || matchingAddrAcct.get().getLoginName().isPresent()) {
2070  throw ex; // Rethrow the original error
2071  }
2072 
2073  // What we should have is osAccount with just a loginName and matchingAddrAcct with
2074  // just an address, so merge them.
2075  mergeOsAccounts(matchingAddrAcct.get(), osAccount, trans);
2076  }
2077  }
2078 
2079  // get the updated account from database
2080  updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
2081 
2082  // register the updated account with the transaction to fire off an event
2083  trans.registerChangedOsAccount(updatedAccount);
2084 
2085  return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
2086 
2087  } catch (SQLException ex) {
2088  throw new TskCoreException(String.format("Error updating account with unique id = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
2089  }
2090  }
2091 
2101  public List<Host> getHosts(OsAccount account) throws TskCoreException {
2102  List<Host> hostList = new ArrayList<>();
2103 
2104  String query = "SELECT tsk_hosts.id AS hostId, name, db_status FROM tsk_hosts "
2105  + " JOIN data_source_info ON tsk_hosts.id = data_source_info.host_id"
2106  + " JOIN tsk_os_account_instances ON data_source_info.obj_id = tsk_os_account_instances.data_source_obj_id"
2107  + " WHERE os_account_obj_id = " + account.getId();
2108 
2110  try (CaseDbConnection connection = db.getConnection();
2111  Statement s = connection.createStatement();
2112  ResultSet rs = connection.executeQuery(s, query)) {
2113 
2114  while (rs.next()) {
2115  hostList.add(new Host(rs.getLong("hostId"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
2116  }
2117 
2118  } catch (SQLException ex) {
2119  throw new TskCoreException(String.format("Failed to get host list for os account %d", account.getId()), ex);
2120  } finally {
2122  }
2123  return hostList;
2124  }
2125 
2137  private OsAccount osAccountFromResultSet(ResultSet rs) throws SQLException {
2138 
2139  OsAccountType accountType = null;
2140  int typeId = rs.getInt("type");
2141  if (!rs.wasNull()) {
2142  accountType = OsAccount.OsAccountType.fromID(typeId);
2143  }
2144 
2145  Long creationTime = rs.getLong("created_date"); // getLong returns 0 if value is null
2146  if (rs.wasNull()) {
2147  creationTime = null;
2148  }
2149 
2150  return new OsAccount(db, rs.getLong("os_account_obj_id"), rs.getLong("realm_id"), rs.getString("login_name"), rs.getString("addr"),
2151  rs.getString("signature"), rs.getString("full_name"), creationTime, accountType, OsAccount.OsAccountStatus.fromID(rs.getInt("status")),
2152  OsAccount.OsAccountDbStatus.fromID(rs.getInt("db_status")));
2153 
2154  }
2155 
2162  private void fireChangeEvent(OsAccount account) {
2163  db.fireTSKEvent(new OsAccountsUpdatedTskEvent(Collections.singletonList(account)));
2164  }
2165 
2180  static String getOsAccountSignature(String uniqueId, String loginName) throws TskCoreException {
2181  // Create a signature.
2182  String signature;
2183  if (Strings.isNullOrEmpty(uniqueId) == false) {
2184  signature = uniqueId;
2185  } else if (Strings.isNullOrEmpty(loginName) == false) {
2186  signature = loginName;
2187  } else {
2188  throw new TskCoreException("OS Account must have either a uniqueID or a login name.");
2189  }
2190  return signature;
2191  }
2192 
2197  public static class NotUserSIDException extends TskException {
2198 
2199  private static final long serialVersionUID = 1L;
2200 
2205  super("No error message available.");
2206  }
2207 
2213  public NotUserSIDException(String msg) {
2214  super(msg);
2215  }
2216 
2223  public NotUserSIDException(String msg, Exception ex) {
2224  super(msg, ex);
2225  }
2226  }
2227 
2232 
2235  MERGED
2236  }
2237 
2242  public final static class OsAccountUpdateResult {
2243 
2244  private final OsAccountUpdateStatus updateStatus;
2245  private final OsAccount updatedAccount;
2246 
2247  OsAccountUpdateResult(OsAccountUpdateStatus updateStatus, OsAccount updatedAccount) {
2248  this.updateStatus = updateStatus;
2249  this.updatedAccount = updatedAccount;
2250  }
2251 
2253  return updateStatus;
2254  }
2255 
2256  public Optional<OsAccount> getUpdatedAccount() {
2257  return Optional.ofNullable(updatedAccount);
2258  }
2259  }
2260 
2265  private class OsAccountInstanceKey implements Comparable<OsAccountInstanceKey>{
2266 
2267  private final long osAccountId;
2268  private final long dataSourceId;
2269 
2270  OsAccountInstanceKey(long osAccountId, long dataSourceId) {
2271  this.osAccountId = osAccountId;
2272  this.dataSourceId = dataSourceId;
2273  }
2274 
2275  @Override
2276  public boolean equals(Object other) {
2277  if (this == other) {
2278  return true;
2279  }
2280  if (other == null) {
2281  return false;
2282  }
2283  if (getClass() != other.getClass()) {
2284  return false;
2285  }
2286 
2287  final OsAccountInstanceKey otherKey = (OsAccountInstanceKey) other;
2288 
2289  if (osAccountId != otherKey.osAccountId) {
2290  return false;
2291  }
2292 
2293  return dataSourceId == otherKey.dataSourceId;
2294  }
2295 
2296  @Override
2297  public int hashCode() {
2298  int hash = 5;
2299  hash = 53 * hash + (int) (this.osAccountId ^ (this.osAccountId >>> 32));
2300  hash = 53 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
2301  return hash;
2302  }
2303 
2304  @Override
2305  public int compareTo(OsAccountInstanceKey other) {
2306  if(this.equals(other)) {
2307  return 0;
2308  }
2309 
2310  if (dataSourceId != other.dataSourceId) {
2311  return Long.compare(dataSourceId, other.dataSourceId);
2312  }
2313 
2314  return Long.compare(osAccountId, other.osAccountId);
2315  }
2316  }
2317 }
Optional< String > getAddr()
Definition: OsAccount.java:268
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:195
OsAccountRealm newLocalLinuxRealm(Host referringHost)
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)
Optional< OsAccount > getLocalLinuxOsAccount(String uid, String loginName, Host referringHost)
Optional< OsAccountRealm > getLocalLinuxRealm(Host referringHost)
OsAccountRealmManager getOsAccountRealmManager()
OsAccount newLocalLinuxOsAccount(String uid, String loginName, Host referringHost)
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:288
void addExtendedOsAccountAttributes(OsAccount account, List< OsAccountAttribute > accountAttributes)
BlackboardAttribute.Type getAttributeType(String attrTypeName)
OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName)
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-2024 Brian Carrier. (carrier -at- sleuthkit -dot- org)
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.