Sleuth Kit Java Bindings (JNI) 4.14.0
Java bindings for using The Sleuth Kit
Loading...
Searching...
No Matches
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 */
19package org.sleuthkit.datamodel;
20
21import com.google.common.base.Strings;
22import com.google.common.annotations.Beta;
23import org.apache.commons.lang3.StringUtils;
24import java.sql.PreparedStatement;
25import java.sql.ResultSet;
26import java.sql.SQLException;
27import java.sql.Statement;
28import java.sql.Types;
29import java.util.Collections;
30import java.util.ArrayList;
31import java.util.List;
32import java.util.Locale;
33import java.util.NavigableMap;
34import java.util.Objects;
35import java.util.Optional;
36import java.util.UUID;
37import java.util.concurrent.ConcurrentSkipListMap;
38import java.util.stream.Collectors;
39import org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE;
40import org.sleuthkit.datamodel.OsAccount.OsAccountStatus;
41import org.sleuthkit.datamodel.OsAccount.OsAccountType;
42import org.sleuthkit.datamodel.OsAccount.OsAccountAttribute;
43import org.sleuthkit.datamodel.OsAccountRealmManager.OsRealmUpdateResult;
44import org.sleuthkit.datamodel.OsAccountRealmManager.OsRealmUpdateStatus;
45import org.sleuthkit.datamodel.SleuthkitCase.CaseDbConnection;
46import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
47import org.sleuthkit.datamodel.TskEvent.OsAccountsUpdatedTskEvent;
48import static org.sleuthkit.datamodel.WindowsAccountUtils.isWindowsWellKnownSid;
49import static org.sleuthkit.datamodel.WindowsAccountUtils.getWindowsWellKnownSidFullName;
50
55public final class OsAccountManager {
56
57 private final SleuthkitCase db;
58 private final Object osAcctInstancesCacheLock;
59 private final NavigableMap<OsAccountInstanceKey, OsAccountInstance> osAccountInstanceCache;
60
67 OsAccountManager(SleuthkitCase skCase) {
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
97 CaseDbTransaction trans = db.beginTransaction();
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
543 db.acquireSingleUserCaseReadLock();
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 {
555 db.releaseSingleUserCaseReadLock();
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
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
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
883 db.acquireSingleUserCaseReadLock();
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 {
896 db.releaseSingleUserCaseReadLock();
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
917 db.acquireSingleUserCaseReadLock();
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 {
930 db.releaseSingleUserCaseReadLock();
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
1248 db.acquireSingleUserCaseReadLock();
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 {
1261 db.releaseSingleUserCaseReadLock();
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
1389 private final Object osAccountLockObj = new Object();
1399 public void addExtendedOsAccountAttributes(OsAccount account, List<OsAccountAttribute> accountAttributes) throws TskCoreException {
1400
1401 synchronized (osAccountLockObj) { // synchronized to prevent multiple threads trying to add osAccount attributes concurrently to the same osAccount.
1402 db.acquireSingleUserCaseWriteLock();
1403
1404 try (CaseDbConnection connection = db.getConnection()) {
1405 for (OsAccountAttribute accountAttribute : accountAttributes) {
1406
1407 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)"
1408 + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; // NON-NLS
1409
1410 PreparedStatement preparedStatement = connection.getPreparedStatement(attributeInsertSQL, Statement.RETURN_GENERATED_KEYS);
1411 preparedStatement.clearParameters();
1412
1413 preparedStatement.setLong(1, account.getId());
1414 if (accountAttribute.getHostId().isPresent()) {
1415 preparedStatement.setLong(2, accountAttribute.getHostId().get());
1416 } else {
1417 preparedStatement.setNull(2, java.sql.Types.NULL);
1418 }
1419 if (accountAttribute.getSourceObjectId().isPresent()) {
1420 preparedStatement.setLong(3, accountAttribute.getSourceObjectId().get());
1421 } else {
1422 preparedStatement.setNull(3, java.sql.Types.NULL);
1423 }
1424
1425 preparedStatement.setLong(4, accountAttribute.getAttributeType().getTypeID());
1426 preparedStatement.setLong(5, accountAttribute.getAttributeType().getValueType().getType());
1427
1428 if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE) {
1429 preparedStatement.setBytes(6, accountAttribute.getValueBytes());
1430 } else {
1431 preparedStatement.setBytes(6, null);
1432 }
1433
1434 if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING
1435 || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
1436 preparedStatement.setString(7, accountAttribute.getValueString());
1437 } else {
1438 preparedStatement.setString(7, null);
1439 }
1440 if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.INTEGER) {
1441 preparedStatement.setInt(8, accountAttribute.getValueInt());
1442 } else {
1443 preparedStatement.setNull(8, java.sql.Types.NULL);
1444 }
1445
1446 if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME
1447 || accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.LONG) {
1448 preparedStatement.setLong(9, accountAttribute.getValueLong());
1449 } else {
1450 preparedStatement.setNull(9, java.sql.Types.NULL);
1451 }
1452
1453 if (accountAttribute.getAttributeType().getValueType() == TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DOUBLE) {
1454 preparedStatement.setDouble(10, accountAttribute.getValueDouble());
1455 } else {
1456 preparedStatement.setNull(10, java.sql.Types.NULL);
1457 }
1458
1459 connection.executeUpdate(preparedStatement);
1460 }
1461 } catch (SQLException ex) {
1462 throw new TskCoreException(String.format("Error adding OS Account attribute for account id = %d", account.getId()), ex);
1463 } finally {
1464 db.releaseSingleUserCaseWriteLock();
1465 }
1466 // set the atrribute list in account to the most current list from the database
1467 List<OsAccountAttribute> currentAttribsList = getOsAccountAttributes(account);
1468 account.setAttributesInternal(currentAttribsList);
1469 }
1470 fireChangeEvent(account);
1471 }
1472
1482 List<OsAccountAttribute> getOsAccountAttributes(OsAccount account) throws TskCoreException {
1483
1484 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, "
1485 + " attributes.attribute_type_id as attribute_type_id, attributes.value_type as value_type, attributes.value_byte as value_byte, "
1486 + " attributes.value_text as value_text, attributes.value_int32 as value_int32, attributes.value_int64 as value_int64, attributes.value_double as value_double, "
1487 + " hosts.id, hosts.name as host_name, hosts.db_status as host_status "
1488 + " FROM tsk_os_account_attributes as attributes"
1489 + " LEFT JOIN tsk_hosts as hosts "
1490 + " ON attributes.host_id = hosts.id "
1491 + " WHERE os_account_obj_id = " + account.getId();
1492
1494 try (CaseDbConnection connection = this.db.getConnection();
1495 Statement s = connection.createStatement();
1496 ResultSet rs = connection.executeQuery(s, queryString)) {
1497
1498 List<OsAccountAttribute> attributes = new ArrayList<>();
1499 while (rs.next()) {
1500
1501 Host host = null;
1502 long hostId = rs.getLong("host_id");
1503 if (!rs.wasNull()) {
1504 host = new Host(hostId, rs.getString("host_name"), Host.HostDbStatus.fromID(rs.getInt("host_status")));
1505 }
1506
1507 Content sourceContent = null;
1508 long sourceObjId = rs.getLong("source_obj_id");
1509 if (!rs.wasNull()) {
1510 sourceContent = this.db.getContentById(sourceObjId);
1511 }
1512 BlackboardAttribute.Type attributeType = db.getBlackboard().getAttributeType(rs.getInt("attribute_type_id"));
1513 OsAccountAttribute attribute = account.new OsAccountAttribute(attributeType, rs.getInt("value_int32"), rs.getLong("value_int64"),
1514 rs.getDouble("value_double"), rs.getString("value_text"), rs.getBytes("value_byte"),
1515 db, account, host, sourceContent);
1516
1517 attributes.add(attribute);
1518 }
1519 return attributes;
1520 } catch (SQLException ex) {
1521 throw new TskCoreException(String.format("Error getting OS account attributes for account obj id = %d", account.getId()), ex);
1522 } finally {
1524 }
1525 }
1526
1536 public List<OsAccountInstance> getOsAccountInstances(OsAccount account) throws TskCoreException {
1537 String whereClause = " tsk_os_account_instances.os_account_obj_id = " + account.getId();
1538 return getOsAccountInstances(whereClause);
1539 }
1540
1551 public List<OsAccountInstance> getOsAccountInstances(List<Long> instanceIDs) throws TskCoreException {
1552 String instanceIds = instanceIDs.stream().map(id -> id.toString()).collect(Collectors.joining(","));
1553
1554 List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1555
1556 String querySQL = "SELECT * FROM tsk_os_account_instances "
1557 + " WHERE tsk_os_account_instances.id IN (" + instanceIds + ")";
1558
1559 db.acquireSingleUserCaseReadLock();
1560 try (CaseDbConnection connection = db.getConnection();
1561 PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1562 ResultSet results = connection.executeQuery(preparedStatement)) {
1563
1564 osAcctInstances = getOsAccountInstancesFromResultSet(results);
1565
1566 } catch (SQLException ex) {
1567 throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1568 } finally {
1569 db.releaseSingleUserCaseReadLock();
1570 }
1571 return osAcctInstances;
1572 }
1573
1587 private List<OsAccountInstance> getOsAccountInstances(String whereClause) throws TskCoreException {
1588 List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1589
1590 String querySQL
1591 = "SELECT tsk_os_account_instances.* "
1592 + " FROM tsk_os_account_instances "
1593 + " INNER JOIN ( SELECT os_account_obj_id, data_source_obj_id, MIN(instance_type) AS min_instance_type "
1594 + " FROM tsk_os_account_instances"
1595 + " GROUP BY os_account_obj_id, data_source_obj_id ) grouped_instances "
1596 + " ON tsk_os_account_instances.os_account_obj_id = grouped_instances.os_account_obj_id "
1597 + " AND tsk_os_account_instances.instance_type = grouped_instances.min_instance_type "
1598 + " WHERE " + whereClause;
1599
1601 try (CaseDbConnection connection = db.getConnection();
1602 PreparedStatement preparedStatement = connection.getPreparedStatement(querySQL, Statement.NO_GENERATED_KEYS);
1603 ResultSet results = connection.executeQuery(preparedStatement)) {
1604
1605 osAcctInstances = getOsAccountInstancesFromResultSet(results);
1606
1607 } catch (SQLException ex) {
1608 throw new TskCoreException("Failed to get OsAccountInstances (SQL = " + querySQL + ")", ex);
1609 } finally {
1611 }
1612 return osAcctInstances;
1613 }
1614
1624 private List<OsAccountInstance> getOsAccountInstancesFromResultSet(ResultSet results) throws SQLException {
1625
1626 List<OsAccountInstance> osAcctInstances = new ArrayList<>();
1627 while (results.next()) {
1628 long instanceId = results.getLong("id");
1629 long osAccountObjID = results.getLong("os_account_obj_id");
1630 long dataSourceObjId = results.getLong("data_source_obj_id");
1631 int instanceType = results.getInt("instance_type");
1632 osAcctInstances.add(new OsAccountInstance(db, instanceId, osAccountObjID, dataSourceObjId, OsAccountInstance.OsAccountInstanceType.fromID(instanceType)));
1633 }
1634
1635 return osAcctInstances;
1636 }
1637
1654 public OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime) throws TskCoreException {
1655
1656 CaseDbTransaction trans = db.beginTransaction();
1657 try {
1658 OsAccountUpdateResult updateStatus = updateStandardOsAccountAttributes(osAccount, fullName, accountType, accountStatus, creationTime, trans);
1659
1660 trans.commit();
1661 trans = null;
1662
1663 return updateStatus;
1664 } finally {
1665 if (trans != null) {
1666 trans.rollback();
1667 }
1668 }
1669 }
1670
1688 OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime, CaseDbTransaction trans) throws TskCoreException {
1689
1690 OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
1691
1692 try {
1693 CaseDbConnection connection = trans.getConnection();
1694
1695 if (!StringUtils.isBlank(fullName)) {
1696 updateAccountColumn(osAccount.getId(), "full_name", fullName, connection);
1697 updateStatusCode = OsAccountUpdateStatus.UPDATED;
1698 }
1699
1700 if (Objects.nonNull(accountType)) {
1701 updateAccountColumn(osAccount.getId(), "type", accountType.getId(), connection);
1702 updateStatusCode = OsAccountUpdateStatus.UPDATED;
1703 }
1704
1705 if (Objects.nonNull(accountStatus)) {
1706 updateAccountColumn(osAccount.getId(), "status", accountStatus.getId(), connection);
1707 updateStatusCode = OsAccountUpdateStatus.UPDATED;
1708 }
1709
1710 if (Objects.nonNull(creationTime)) {
1711 updateAccountColumn(osAccount.getId(), "created_date", creationTime, connection);
1712 updateStatusCode = OsAccountUpdateStatus.UPDATED;
1713 }
1714
1715 // if nothing has been changed, return
1716 if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
1717 return new OsAccountUpdateResult(updateStatusCode, null);
1718 }
1719
1720 // get the updated account from database
1721 OsAccount updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
1722
1723 // register the updated account with the transaction to fire off an event
1724 trans.registerChangedOsAccount(updatedAccount);
1725
1726 return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
1727
1728 } catch (SQLException ex) {
1729 throw new TskCoreException(String.format("Error updating account with addr = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
1730 }
1731 }
1732
1746 private <T> void updateAccountColumn(long accountObjId, String colName, T colValue, CaseDbConnection connection) throws SQLException, TskCoreException {
1747
1748 String updateSQL = "UPDATE tsk_os_accounts "
1749 + " SET " + colName + " = ? "
1750 + " WHERE os_account_obj_id = ?";
1751
1753 try {
1754 PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1755 preparedStatement.clearParameters();
1756
1757 if (Objects.isNull(colValue)) {
1758 preparedStatement.setNull(1, Types.NULL); // handle null value
1759 } else {
1760 if (colValue instanceof String) {
1761 preparedStatement.setString(1, (String) colValue);
1762 } else if (colValue instanceof Long) {
1763 preparedStatement.setLong(1, (Long) colValue);
1764 } else if (colValue instanceof Integer) {
1765 preparedStatement.setInt(1, (Integer) colValue);
1766 } else {
1767 throw new TskCoreException(String.format("Unhandled column data type received while updating the account (%d) ", accountObjId));
1768 }
1769 }
1770
1771 preparedStatement.setLong(2, accountObjId);
1772
1773 connection.executeUpdate(preparedStatement);
1774 } finally {
1776 }
1777 }
1778
1789 private void updateAccountSignature(long accountObjId, String signature, CaseDbConnection connection) throws SQLException {
1790
1791 String updateSQL = "UPDATE tsk_os_accounts SET "
1792 + " signature = "
1793 + " CASE WHEN db_status = " + OsAccount.OsAccountDbStatus.ACTIVE.getId() + " THEN ? ELSE signature END "
1794 + " WHERE os_account_obj_id = ?"; // 8
1795
1796 PreparedStatement preparedStatement = connection.getPreparedStatement(updateSQL, Statement.NO_GENERATED_KEYS);
1797 preparedStatement.clearParameters();
1798
1799 preparedStatement.setString(1, signature);
1800 preparedStatement.setLong(2, accountObjId);
1801
1802 connection.executeUpdate(preparedStatement);
1803 }
1804
1828 public OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost) throws TskCoreException, NotUserSIDException {
1829 CaseDbTransaction trans = db.beginTransaction();
1830 try {
1831
1832 if (StringUtils.isNotBlank(accountSid)) {
1833 // SID Normalized to uppercase
1834 accountSid = accountSid.toUpperCase(Locale.ENGLISH);
1835 }
1836 if (StringUtils.isNotBlank(loginName)) {
1837 // Windows logon names are case insensitive. saving them in lower case.
1838 loginName = loginName.toLowerCase(Locale.ENGLISH);
1839 }
1840 if (StringUtils.isNotBlank(realmName)) {
1841 // Windows realm names are case insensitive. saving them in lower case.
1842 realmName = realmName.toLowerCase(Locale.ENGLISH);
1843 }
1844
1845 OsAccountUpdateResult updateStatus = this.updateCoreWindowsOsAccountAttributes(osAccount, accountSid, loginName, realmName, referringHost, trans);
1846
1847 trans.commit();
1848 trans = null;
1849 return updateStatus;
1850 } finally {
1851 if (trans != null) {
1852 trans.rollback();
1853 }
1854 }
1855 }
1856
1876 private OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost, CaseDbTransaction trans) throws TskCoreException, NotUserSIDException {
1877
1878 // first get and update the realm - if we have the info to find the realm
1879
1880 if ((!StringUtils.isBlank(accountSid) && !accountSid.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) || !StringUtils.isBlank(realmName)) {
1881 // If the SID is a well known SID, ensure we use the well known english name
1882 String resolvedRealmName = WindowsAccountUtils.toWellknownEnglishRealmName(realmName);
1883
1884
1885 OsRealmUpdateResult realmUpdateResult = db.getOsAccountRealmManager().getAndUpdateWindowsRealm(accountSid, resolvedRealmName, referringHost, trans.getConnection());
1886
1887
1888 Optional<OsAccountRealm> realmOptional = realmUpdateResult.getUpdatedRealm();
1889
1890 if (realmOptional.isPresent()) {
1891
1892 if (realmUpdateResult.getUpdateStatus() == OsRealmUpdateStatus.UPDATED) {
1893
1894 // Check if update of the realm triggers a merge with any other realm,
1895 // say another realm with same name but no SID, or same SID but no name
1896 //1. Check if there is any OTHER realm with the same name, same host but no addr
1897 Optional<OsAccountRealm> anotherRealmWithSameName = db.getOsAccountRealmManager().getAnotherRealmByName(realmOptional.get(), realmName, referringHost, trans.getConnection());
1898 if (anotherRealmWithSameName.isPresent() && anotherRealmWithSameName.get().getRealmAddr().isPresent()) {
1899 // realm with same name has addr, don't merge
1900 anotherRealmWithSameName = Optional.empty();
1901 }
1902
1903 // 2. Check if there is any OTHER realm with same addr and host, but NO name
1904 Optional<OsAccountRealm> anotherRealmWithSameAddr = db.getOsAccountRealmManager().getAnotherRealmByAddr(realmOptional.get(), realmName, referringHost, trans.getConnection());
1905 if (anotherRealmWithSameAddr.isPresent() && !anotherRealmWithSameAddr.get().getRealmNames().isEmpty()) {
1906 // realm with same addr has name, don't merge
1907 anotherRealmWithSameName = Optional.empty();
1908 }
1909
1910 if (anotherRealmWithSameName.isPresent()) {
1911 db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameName.get(), realmOptional.get(), trans);
1912 }
1913 if (anotherRealmWithSameAddr.isPresent()) {
1914 db.getOsAccountRealmManager().mergeRealms(anotherRealmWithSameAddr.get(), realmOptional.get(), trans);
1915 }
1916 }
1917 }
1918 }
1919
1920 // now update the account core data
1921 String resolvedLoginName = WindowsAccountUtils.toWellknownEnglishLoginName(loginName);
1922 OsAccountUpdateResult updateStatus = this.updateOsAccountCore(osAccount, accountSid, resolvedLoginName, trans);
1923
1924 Optional<OsAccount> updatedAccount = updateStatus.getUpdatedAccount();
1925 if (updatedAccount.isPresent() && updateStatus.updateStatus != OsAccountUpdateStatus.NO_CHANGE) {
1926 // After updating account data, check if there is matching account to merge
1927 mergeOsAccount(updatedAccount.get(), true, trans);
1928 }
1929
1930 return updateStatus;
1931 }
1932
1950 public OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName) throws TskCoreException {
1951 CaseDbTransaction trans = db.beginTransaction();
1952 try {
1953 OsAccountUpdateResult updateStatus = this.updateCoreLocalLinuxOsAccountAttributes(osAccount, uid, loginName, trans);
1954
1955 trans.commit();
1956 trans = null;
1957 return updateStatus;
1958 } finally {
1959 if (trans != null) {
1960 trans.rollback();
1961 }
1962 }
1963 }
1964
1982 @Beta
1983 private OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName, CaseDbTransaction trans) throws TskCoreException {
1984
1985 // Update the account core data
1986 OsAccountUpdateResult updateStatus = this.updateOsAccountCore(osAccount, uid, loginName, trans);
1987
1988 Optional<OsAccount> updatedAccount = updateStatus.getUpdatedAccount();
1989 if (updatedAccount.isPresent()) {
1990 // After updating account data, check if there is matching account to merge
1991 mergeOsAccount(updatedAccount.get(), false, trans);
1992 }
1993
1994 return updateStatus;
1995 }
1996
2019 private OsAccountUpdateResult updateOsAccountCore(OsAccount osAccount, String address, String loginName, CaseDbTransaction trans) throws TskCoreException {
2020
2021 OsAccountUpdateStatus updateStatusCode = OsAccountUpdateStatus.NO_CHANGE;
2022 OsAccount updatedAccount;
2023
2024 try {
2025 CaseDbConnection connection = trans.getConnection();
2026
2027 // if a new non-null addr is provided and the account already has an address, and they are not the same, throw an exception
2028 if (!StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID) && !StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !address.equalsIgnoreCase(osAccount.getAddr().orElse(""))) {
2029 throw new TskCoreException(String.format("Account (%d) already has an address (%s), address cannot be updated.", osAccount.getId(), osAccount.getAddr().orElse("NULL")));
2030 }
2031
2032 if (StringUtils.isBlank(osAccount.getAddr().orElse(null)) && !StringUtils.isBlank(address) && !address.equalsIgnoreCase(WindowsAccountUtils.WINDOWS_NULL_SID)) {
2033 updateAccountColumn(osAccount.getId(), "addr", address, connection);
2034 updateStatusCode = OsAccountUpdateStatus.UPDATED;
2035 }
2036
2037 if (StringUtils.isBlank(osAccount.getLoginName().orElse(null)) && !StringUtils.isBlank(loginName)) {
2038 updateAccountColumn(osAccount.getId(), "login_name", loginName, connection);
2039 updateStatusCode = OsAccountUpdateStatus.UPDATED;
2040 }
2041
2042 // if nothing is changed, return
2043 if (updateStatusCode == OsAccountUpdateStatus.NO_CHANGE) {
2044 return new OsAccountUpdateResult(updateStatusCode, osAccount);
2045 }
2046
2047 // update signature if needed, based on the most current addr/loginName
2048 OsAccount currAccount = getOsAccountByObjectId(osAccount.getId(), connection);
2049 String newAddress = currAccount.getAddr().orElse(null);
2050 String newLoginName = currAccount.getLoginName().orElse(null);
2051
2052 String newSignature = getOsAccountSignature(newAddress, newLoginName);
2053
2054 try {
2055 updateAccountSignature(osAccount.getId(), newSignature, connection);
2056 } catch (SQLException ex) {
2057 // There's a slight chance that we're in the case where we are trying to add an addr to an OS account
2058 // with only a name where the addr already exists on a different OS account. This will cause a unique
2059 // constraint failure in updateAccountSignature(). This is unlikely to happen in normal use
2060 // since we lookup OS accounts by addr before name when we have both (i.e., it would be strange to have an
2061 // OsAccount in hand with only the loginName set when we also know the addr).
2062 // Correctly handling every case here is non-trivial, so for the moment only look for the specific case where
2063 // we had an OsAccount with just an addr and and OsAccount with just a login name that we now
2064 // want to combine.
2065 if (osAccount.getAddr().isEmpty() && !StringUtils.isBlank(address)) {
2066 OsAccountRealm realm = db.getOsAccountRealmManager().getRealmByRealmId(osAccount.getRealmId(), connection);
2067 Optional<OsAccount> matchingAddrAcct = getOsAccountByAddr(address, realm.getScopeHost().get(), connection);
2068 if (matchingAddrAcct.isEmpty()
2069 || matchingAddrAcct.get().getId() == osAccount.getId()
2070 || matchingAddrAcct.get().getLoginName().isPresent()) {
2071 throw ex; // Rethrow the original error
2072 }
2073
2074 // What we should have is osAccount with just a loginName and matchingAddrAcct with
2075 // just an address, so merge them.
2076 mergeOsAccounts(matchingAddrAcct.get(), osAccount, trans);
2077 }
2078 }
2079
2080 // get the updated account from database
2081 updatedAccount = getOsAccountByObjectId(osAccount.getId(), connection);
2082
2083 // register the updated account with the transaction to fire off an event
2084 trans.registerChangedOsAccount(updatedAccount);
2085
2086 return new OsAccountUpdateResult(updateStatusCode, updatedAccount);
2087
2088 } catch (SQLException ex) {
2089 throw new TskCoreException(String.format("Error updating account with unique id = %s, account id = %d", osAccount.getAddr().orElse("Unknown"), osAccount.getId()), ex);
2090 }
2091 }
2092
2102 public List<Host> getHosts(OsAccount account) throws TskCoreException {
2103 List<Host> hostList = new ArrayList<>();
2104
2105 String query = "SELECT tsk_hosts.id AS hostId, name, db_status FROM tsk_hosts "
2106 + " JOIN data_source_info ON tsk_hosts.id = data_source_info.host_id"
2107 + " JOIN tsk_os_account_instances ON data_source_info.obj_id = tsk_os_account_instances.data_source_obj_id"
2108 + " WHERE os_account_obj_id = " + account.getId();
2109
2110 db.acquireSingleUserCaseReadLock();
2111 try (CaseDbConnection connection = db.getConnection();
2112 Statement s = connection.createStatement();
2113 ResultSet rs = connection.executeQuery(s, query)) {
2114
2115 while (rs.next()) {
2116 hostList.add(new Host(rs.getLong("hostId"), rs.getString("name"), Host.HostDbStatus.fromID(rs.getInt("db_status"))));
2117 }
2118
2119 } catch (SQLException ex) {
2120 throw new TskCoreException(String.format("Failed to get host list for os account %d", account.getId()), ex);
2121 } finally {
2122 db.releaseSingleUserCaseReadLock();
2123 }
2124 return hostList;
2125 }
2126
2138 private OsAccount osAccountFromResultSet(ResultSet rs) throws SQLException {
2139
2140 OsAccountType accountType = null;
2141 int typeId = rs.getInt("type");
2142 if (!rs.wasNull()) {
2143 accountType = OsAccount.OsAccountType.fromID(typeId);
2144 }
2145
2146 Long creationTime = rs.getLong("created_date"); // getLong returns 0 if value is null
2147 if (rs.wasNull()) {
2148 creationTime = null;
2149 }
2150
2151 return new OsAccount(db, rs.getLong("os_account_obj_id"), rs.getLong("realm_id"), rs.getString("login_name"), rs.getString("addr"),
2152 rs.getString("signature"), rs.getString("full_name"), creationTime, accountType, OsAccount.OsAccountStatus.fromID(rs.getInt("status")),
2153 OsAccount.OsAccountDbStatus.fromID(rs.getInt("db_status")));
2154
2155 }
2156
2163 private void fireChangeEvent(OsAccount account) {
2164 db.fireTSKEvent(new OsAccountsUpdatedTskEvent(Collections.singletonList(account)));
2165 }
2166
2181 static String getOsAccountSignature(String uniqueId, String loginName) throws TskCoreException {
2182 // Create a signature.
2183 String signature;
2184 if (Strings.isNullOrEmpty(uniqueId) == false) {
2185 signature = uniqueId;
2186 } else if (Strings.isNullOrEmpty(loginName) == false) {
2187 signature = loginName;
2188 } else {
2189 throw new TskCoreException("OS Account must have either a uniqueID or a login name.");
2190 }
2191 return signature;
2192 }
2193
2198 public static class NotUserSIDException extends TskException {
2199
2200 private static final long serialVersionUID = 1L;
2201
2206 super("No error message available.");
2207 }
2208
2214 public NotUserSIDException(String msg) {
2215 super(msg);
2216 }
2217
2224 public NotUserSIDException(String msg, Exception ex) {
2225 super(msg, ex);
2226 }
2227 }
2228
2238
2243 public final static class OsAccountUpdateResult {
2244
2245 private final OsAccountUpdateStatus updateStatus;
2246 private final OsAccount updatedAccount;
2247
2248 OsAccountUpdateResult(OsAccountUpdateStatus updateStatus, OsAccount updatedAccount) {
2249 this.updateStatus = updateStatus;
2250 this.updatedAccount = updatedAccount;
2251 }
2252
2254 return updateStatus;
2255 }
2256
2257 public Optional<OsAccount> getUpdatedAccount() {
2258 return Optional.ofNullable(updatedAccount);
2259 }
2260 }
2261
2266 private class OsAccountInstanceKey implements Comparable<OsAccountInstanceKey>{
2267
2268 private final long osAccountId;
2269 private final long dataSourceId;
2270
2271 OsAccountInstanceKey(long osAccountId, long dataSourceId) {
2272 this.osAccountId = osAccountId;
2273 this.dataSourceId = dataSourceId;
2274 }
2275
2276 @Override
2277 public boolean equals(Object other) {
2278 if (this == other) {
2279 return true;
2280 }
2281 if (other == null) {
2282 return false;
2283 }
2284 if (getClass() != other.getClass()) {
2285 return false;
2286 }
2287
2288 final OsAccountInstanceKey otherKey = (OsAccountInstanceKey) other;
2289
2290 if (osAccountId != otherKey.osAccountId) {
2291 return false;
2292 }
2293
2294 return dataSourceId == otherKey.dataSourceId;
2295 }
2296
2297 @Override
2298 public int hashCode() {
2299 int hash = 5;
2300 hash = 53 * hash + (int) (this.osAccountId ^ (this.osAccountId >>> 32));
2301 hash = 53 * hash + (int) (this.dataSourceId ^ (this.dataSourceId >>> 32));
2302 return hash;
2303 }
2304
2305 @Override
2306 public int compareTo(OsAccountInstanceKey other) {
2307 if(this.equals(other)) {
2308 return 0;
2309 }
2310
2311 if (dataSourceId != other.dataSourceId) {
2312 return Long.compare(dataSourceId, other.dataSourceId);
2313 }
2314
2315 return Long.compare(osAccountId, other.osAccountId);
2316 }
2317 }
2318}
BlackboardAttribute.Type getAttributeType(String attrTypeName)
List< OsAccountInstance > getOsAccountInstances(OsAccount account)
OsAccountUpdateResult updateStandardOsAccountAttributes(OsAccount osAccount, String fullName, OsAccountType accountType, OsAccountStatus accountStatus, Long creationTime)
OsAccount newWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost, OsAccountRealm.RealmScope realmScope)
List< Host > getHosts(OsAccount account)
OsAccountInstance newOsAccountInstance(OsAccount osAccount, DataSource dataSource, OsAccountInstance.OsAccountInstanceType instanceType)
List< OsAccountInstance > getOsAccountInstances(List< Long > instanceIDs)
OsAccount newLocalLinuxOsAccount(String uid, String loginName, Host referringHost)
OsAccountUpdateResult updateCoreLocalLinuxOsAccountAttributes(OsAccount osAccount, String uid, String loginName)
Optional< OsAccount > getLocalLinuxOsAccount(String uid, String loginName, Host referringHost)
List< OsAccount > getOsAccountsByDataSourceObjId(long dataSourceId)
Optional< OsAccount > getWindowsOsAccount(String sid, String loginName, String realmName, Host referringHost)
OsAccount getOsAccountByObjectId(long osAccountObjId)
void addExtendedOsAccountAttributes(OsAccount account, List< OsAccountAttribute > accountAttributes)
OsAccountUpdateResult updateCoreWindowsOsAccountAttributes(OsAccount osAccount, String accountSid, String loginName, String realmName, Host referringHost)
OsAccount newWindowsOsAccount(String sid, String loginName, OsAccountRealm realm)
List< OsAccount > getOsAccounts(Host host)
OsAccountRealmManager getOsAccountRealmManager()
static HostDbStatus fromID(int typeId)
Definition Host.java:125
static OsAccountType fromID(int typeId)

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.