diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 4ebbe9223d2eb1cdb5b73995ff4de8801af8d8cf..3ffae2bd0f528a14e01a076fe30f66327812fe6f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -87,10 +87,10 @@ import org.sleuthkit.autopsy.casemodule.events.HostsRemovedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountAddedEvent; import org.sleuthkit.autopsy.casemodule.events.OsAccountChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.OsAccountRemovedEvent; +import org.sleuthkit.autopsy.casemodule.events.OsAccountDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsAddedEvent; import org.sleuthkit.autopsy.casemodule.events.PersonsChangedEvent; -import org.sleuthkit.autopsy.casemodule.events.PersonsRemovedEvent; +import org.sleuthkit.autopsy.casemodule.events.PersonsDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent; import org.sleuthkit.autopsy.casemodule.multiusercases.CaseNodeData.CaseNodeDataException; import org.sleuthkit.autopsy.casemodule.multiusercases.CoordinationServiceUtils; @@ -140,24 +140,16 @@ import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.FileSystem; import org.sleuthkit.datamodel.Host; -import org.sleuthkit.datamodel.HostManager.HostsCreationEvent; -import org.sleuthkit.datamodel.HostManager.HostsUpdateEvent; -import org.sleuthkit.datamodel.HostManager.HostsDeletionEvent; import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.OsAccount; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsCreationEvent; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsDeleteEvent; -import org.sleuthkit.datamodel.OsAccountManager.OsAccountsUpdateEvent; import org.sleuthkit.datamodel.Person; -import org.sleuthkit.datamodel.PersonManager.PersonsCreationEvent; -import org.sleuthkit.datamodel.PersonManager.PersonsUpdateEvent; -import org.sleuthkit.datamodel.PersonManager.PersonsDeletionEvent; import org.sleuthkit.datamodel.Report; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TimelineManager; import org.sleuthkit.datamodel.SleuthkitCaseAdminUtil; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.datamodel.TskEvent; import org.sleuthkit.datamodel.TskUnsupportedSchemaVersionException; /** @@ -504,36 +496,36 @@ public void publishArtifactsPostedEvent(Blackboard.ArtifactsPostedEvent event) { event.getArtifacts(artifactType))); } } - - @Subscribe - public void publishOsAccountAddedEvent(OsAccountsCreationEvent event) { - for (OsAccount account : event.getOsAcounts()) { + + @Subscribe + public void publishOsAccountAddedEvent(TskEvent.OsAccountsAddedTskEvent event) { + for(OsAccount account: event.getOsAcounts()) { eventPublisher.publish(new OsAccountAddedEvent(account)); } } - - @Subscribe - public void publishOsAccountChangedEvent(OsAccountsUpdateEvent event) { - for (OsAccount account : event.getOsAcounts()) { + + @Subscribe + public void publishOsAccountChangedEvent(TskEvent.OsAccountsChangedTskEvent event) { + for(OsAccount account: event.getOsAcounts()) { eventPublisher.publish(new OsAccountChangedEvent(account)); } } - - @Subscribe - public void publishOsAccountDeletedEvent(OsAccountsDeleteEvent event) { - for (Long accountId : event.getOsAcountObjectIds()) { - eventPublisher.publish(new OsAccountRemovedEvent(accountId)); + + @Subscribe + public void publishOsAccountDeletedEvent(TskEvent.OsAccountsDeletedTskEvent event) { + for(Long accountId: event.getOsAcountObjectIds()) { + eventPublisher.publish(new OsAccountDeletedEvent(accountId)); } } /** - * Publishes an autopsy event from the sleuthkit HostCreationEvent + * Publishes an autopsy event from the sleuthkit HostAddedEvent * indicating that hosts have been created. * * @param event The sleuthkit event for the creation of hosts. */ - @Subscribe - public void publishHostsAddedEvent(HostsCreationEvent event) { + @Subscribe + public void publishHostsAddedEvent(TskEvent.HostsAddedTskEvent event) { eventPublisher.publish(new HostsAddedEvent( event == null ? Collections.emptyList() : event.getHosts())); } @@ -543,9 +535,9 @@ public void publishHostsAddedEvent(HostsCreationEvent event) { * indicating that hosts have been updated. * * @param event The sleuthkit event for the updating of hosts. - */ - @Subscribe - public void publishHostsChangedEvent(HostsUpdateEvent event) { + */ + @Subscribe + public void publishHostsChangedEvent(TskEvent.HostsChangedTskEvent event) { eventPublisher.publish(new HostsChangedEvent( event == null ? Collections.emptyList() : event.getHosts())); } @@ -555,33 +547,33 @@ public void publishHostsChangedEvent(HostsUpdateEvent event) { * indicating that hosts have been deleted. * * @param event The sleuthkit event for the deleting of hosts. - */ - @Subscribe - public void publishHostsDeletedEvent(HostsDeletionEvent event) { + */ + @Subscribe + public void publishHostsDeletedEvent(TskEvent.HostsDeletedTskEvent event) { eventPublisher.publish(new HostsRemovedEvent( event == null ? Collections.emptyList() : event.getHosts())); } /** - * Publishes an autopsy event from the sleuthkit PersonCreationEvent + * Publishes an autopsy event from the sleuthkit PersonAddedEvent * indicating that persons have been created. * * @param event The sleuthkit event for the creation of persons. */ - @Subscribe - public void publishPersonsAddedEvent(PersonsCreationEvent event) { + @Subscribe + public void publishPersonsAddedEvent(TskEvent.PersonsAddedTskEvent event) { eventPublisher.publish(new PersonsAddedEvent( event == null ? Collections.emptyList() : event.getPersons())); } /** - * Publishes an autopsy event from the sleuthkit PersonUpdateEvent + * Publishes an autopsy event from the sleuthkit PersonChangedEvent * indicating that persons have been updated. * * @param event The sleuthkit event for the updating of persons. - */ - @Subscribe - public void publishPersonsChangedEvent(PersonsUpdateEvent event) { + */ + @Subscribe + public void publishPersonsChangedEvent(TskEvent.PersonsChangedTskEvent event) { eventPublisher.publish(new PersonsChangedEvent( event == null ? Collections.emptyList() : event.getPersons())); } @@ -591,10 +583,10 @@ public void publishPersonsChangedEvent(PersonsUpdateEvent event) { * indicating that persons have been deleted. * * @param event The sleuthkit event for the deleting of persons. - */ - @Subscribe - public void publishPersonsDeletedEvent(PersonsDeletionEvent event) { - eventPublisher.publish(new PersonsRemovedEvent( + */ + @Subscribe + public void publishPersonsDeletedEvent(TskEvent.PersonsDeletedTskEvent event) { + eventPublisher.publish(new PersonsDeletedEvent( event == null ? Collections.emptyList() : event.getPersons())); } } @@ -1796,7 +1788,7 @@ public void notifyOsAccountChanged(OsAccount account) { } public void notifyOsAccountRemoved(Long osAccountObjectId) { - eventPublisher.publish(new OsAccountRemovedEvent(osAccountObjectId)); + eventPublisher.publish(new OsAccountDeletedEvent(osAccountObjectId)); } /** @@ -1850,7 +1842,7 @@ public void notifyPersonChanged(Person newValue) { * @param person The person that has been deleted. */ public void notifyPersonDeleted(Person person) { - eventPublisher.publish(new PersonsRemovedEvent(Collections.singletonList(person))); + eventPublisher.publish(new PersonsDeletedEvent(Collections.singletonList(person))); } /** diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java index efa60c8b4796c62652a31dfc7d9e20871d42a176..7c9a31f01e1b3aba2f41794a7704871e22a6521e 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/HostsEvent.java @@ -33,6 +33,8 @@ */ public class HostsEvent extends TskDataModelChangeEvent<Host> { + private static final long serialVersionUID = 1L; + /** * Retrieves a list of ids from a list of hosts. * diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java rename to Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java index 4bf03c90d98db1369cc5e368976fa622989c93fa..adc726fca85018a24f5f30e49a7deefb4bf4fe15 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountRemovedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/OsAccountDeletedEvent.java @@ -27,11 +27,11 @@ * oldValue will contain the objectId of the account that was removed. newValue * will be null. */ -public final class OsAccountRemovedEvent extends AutopsyEvent { +public final class OsAccountDeletedEvent extends AutopsyEvent { private static final long serialVersionUID = 1L; - public OsAccountRemovedEvent(Long osAccountObjectId) { + public OsAccountDeletedEvent(Long osAccountObjectId) { super(Case.Events.OS_ACCOUNT_REMOVED.toString(), osAccountObjectId, null); } } diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java similarity index 90% rename from Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java rename to Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java index 522c841461d03e3b82fa311dffd87c26fcc9e42e..a63fef32cbfe074f5489caafee76d3c128f6dfba 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsRemovedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/PersonsDeletedEvent.java @@ -25,7 +25,7 @@ /** * Event fired when persons are removed. */ -public class PersonsRemovedEvent extends PersonsEvent { +public class PersonsDeletedEvent extends PersonsEvent { private static final long serialVersionUID = 1L; @@ -33,7 +33,7 @@ public class PersonsRemovedEvent extends PersonsEvent { * Main constructor. * @param dataModelObjects The list of persons that have been deleted. */ - public PersonsRemovedEvent(List<Person> dataModelObjects) { + public PersonsDeletedEvent(List<Person> dataModelObjects) { super(Case.Events.PERSONS_DELETED.name(), dataModelObjects); } } diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java index 842c8e3f0488f30d31e6d0a681985114b02f4562..289dfa476f85ea0a1363174632936ba9dfdd443c 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepository.java @@ -21,6 +21,7 @@ import java.sql.SQLException; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.Set; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.autopsy.casemodule.Case; @@ -860,10 +861,10 @@ CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttribut * Get account type by type name. * * @param accountTypeName account type name to look for - * @return CR account type + * @return CR account type (if found) * @throws CentralRepoException */ - CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException; + Optional<CentralRepoAccountType> getAccountTypeByName(String accountTypeName) throws CentralRepoException; /** * Gets all account types. diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java index 4867b4eb0a82f116f8b935eab9f6325922f6ea9a..2d0315ef7b21320258c309d27226c090d9b70b5d 100755 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/CorrelationAttributeUtil.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.openide.util.NbBundle.Messages; @@ -308,8 +309,12 @@ private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstan if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false && predefinedAccountType != null) { // Get the corresponding CentralRepoAccountType from the database. - CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); - + Optional<CentralRepoAccountType> optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + if (!optCrAccountType.isPresent()) { + return; + } + CentralRepoAccountType crAccountType = optCrAccountType.get(); + int corrTypeId = crAccountType.getCorrelationTypeId(); CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java index afbb2fe4b5dd73ff37bae3b4e18fb7389ac1eb67..59b0ebe62728b484d711e8f0b023d77ff70bb6d6 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/PersonaAccount.java @@ -26,8 +26,10 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.datamodel.Account; +import org.sleuthkit.datamodel.TskCoreException; /** * This class represents an association between a Persona and an Account. @@ -206,10 +208,15 @@ public void process(ResultSet rs) throws CentralRepoException, SQLException { ); // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional<CentralRepoAccount.CentralRepoAccountType> optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); // create persona account @@ -389,10 +396,15 @@ public void process(ResultSet rs) throws CentralRepoException, SQLException { while (rs.next()) { // create account - CentralRepoAccount.CentralRepoAccountType crAccountType = getCRInstance().getAccountTypeByName(rs.getString("type_name")); + String accountTypeName = rs.getString("type_name"); + Optional<CentralRepoAccount.CentralRepoAccountType> optCrAccountType = getCRInstance().getAccountTypeByName(accountTypeName); + if (! optCrAccountType.isPresent()) { + // The CR account can not be null, so throw an exception + throw new CentralRepoException("Account type with name '" + accountTypeName + "' not found in Central Repository"); + } CentralRepoAccount account = new CentralRepoAccount( rs.getInt("account_id"), - crAccountType, + optCrAccountType.get(), rs.getString("account_unique_identifier")); accountsList.add(account); diff --git a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java index b48797e3fc458b79a64e35088f2284fb77d492be..1b4ce08c18f08f38b7df2a54ecd285097686178d 100644 --- a/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java +++ b/Core/src/org/sleuthkit/autopsy/centralrepository/datamodel/RdbmsCentralRepo.java @@ -37,6 +37,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -78,7 +79,7 @@ abstract class RdbmsCentralRepo implements CentralRepository { private static final int CASE_CACHE_TIMEOUT = 5; private static final int DATA_SOURCE_CACHE_TIMEOUT = 5; private static final int ACCOUNTS_CACHE_TIMEOUT = 5; - private static final Cache<String, CentralRepoAccountType> accountTypesCache = CacheBuilder.newBuilder().build(); + private static final Cache<String, Optional<CentralRepoAccountType>> accountTypesCache = CacheBuilder.newBuilder().build(); private static final Cache<Pair<CentralRepoAccountType, String>, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder() .expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES). build(); @@ -1115,7 +1116,7 @@ public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountTyp } @Override - public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException { + public Optional<CentralRepoAccountType> getAccountTypeByName(String accountTypeName) throws CentralRepoException { try { return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName)); } catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) { @@ -1155,7 +1156,7 @@ public Collection<CentralRepoAccountType> getAllAccountTypes() throws CentralRep * * @throws CentralRepoException */ - private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { + private Optional<CentralRepoAccountType> getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException { String sql = "SELECT * FROM account_types WHERE type_name = ?"; try (Connection conn = connect(); @@ -1166,10 +1167,11 @@ private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) th if (resultSet.next()) { Account.Type acctType = new Account.Type(accountTypeName, resultSet.getString("display_name")); CentralRepoAccountType crAccountType = new CentralRepoAccountType(resultSet.getInt("id"), acctType, resultSet.getInt("correlation_type_id")); - accountTypesCache.put(accountTypeName, crAccountType); - return crAccountType; + accountTypesCache.put(accountTypeName, Optional.of(crAccountType)); + return Optional.of(crAccountType); } else { - throw new CentralRepoException("Failed to find entry for account type = " + accountTypeName); + accountTypesCache.put(accountTypeName, Optional.empty()); + return Optional.empty(); } } } catch (SQLException ex) { diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java index 588e474303b8738612d3b9206636c3c228f5902e..1e2b3506694225937bee2c5ca0e5d2e2fe42d5bb 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/CorrelationCaseChildNodeFactory.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.logging.Level; import org.openide.nodes.AbstractNode; @@ -75,9 +76,9 @@ protected boolean createKeys(List<CorrelationCase> list) { accounts.forEach((account) -> { try { - CorrelationAttributeInstance.Type correlationType = getCorrelationType(account.getAccountType()); - if (correlationType != null) { - List<CorrelationAttributeInstance> correlationInstances = dbInstance.getArtifactInstancesByTypeValue(correlationType, account.getTypeSpecificID()); + Optional<CorrelationAttributeInstance.Type> optCorrelationType = getCorrelationType(account.getAccountType()); + if (optCorrelationType.isPresent()) { + List<CorrelationAttributeInstance> correlationInstances = dbInstance.getArtifactInstancesByTypeValue(optCorrelationType.get(), account.getTypeSpecificID()); correlationInstances.forEach((correlationInstance) -> { CorrelationCase correlationCase = correlationInstance.getCorrelationCase(); uniqueCaseMap.put(correlationCase.getCaseUUID(), correlationCase); @@ -103,20 +104,22 @@ protected Node createNodeForKey(CorrelationCase correlationCase) { * * @param accountType Account type * - * @return CorrelationAttributeInstance.Type for given account or null if + * @return CorrelationAttributeInstance.Type for given account or empty if * there is no match * * @throws CentralRepoException */ - private CorrelationAttributeInstance.Type getCorrelationType(Account.Type accountType) throws CentralRepoException { + private Optional<CorrelationAttributeInstance.Type> getCorrelationType(Account.Type accountType) throws CentralRepoException { + String accountTypeStr = accountType.getTypeName(); if (Account.Type.DEVICE.getTypeName().equalsIgnoreCase(accountTypeStr) == false) { - CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); - int corrTypeId = crAccountType.getCorrelationTypeId(); - return CentralRepository.getInstance().getCorrelationTypeById(corrTypeId); + Optional<CentralRepoAccount.CentralRepoAccountType> optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr); + if (optCrAccountType.isPresent()) { + int corrTypeId = optCrAccountType.get().getCorrelationTypeId(); + return Optional.of(CentralRepository.getInstance().getCorrelationTypeById(corrTypeId)); + } } - - return null; + return Optional.empty(); } /** diff --git a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java index ddc3282fe0ad214b769e4f7ab9e89dd1883fc22a..e18f3883d9c59e10d7cbe7feebbee2349e0aabbf 100755 --- a/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java +++ b/Core/src/org/sleuthkit/autopsy/communications/relationships/SummaryPanelWorker.java @@ -21,10 +21,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.logging.Level; import javax.swing.SwingWorker; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount; +import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; @@ -76,12 +78,15 @@ protected SummaryWorkerResults doInBackground() throws Exception { personaList.add(pAccount.getPersona()); } - try { - crAccount = CentralRepository.getInstance().getAccount(CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()), account.getTypeSpecificID()); - } catch (InvalidAccountIDException unused) { - // This was probably caused to a phone number not making - // threw the normalization. - logger.log(Level.WARNING, String.format("Exception thrown from CR getAccount for account %s (%d)", account.getTypeSpecificID(), account.getAccountID())); + Optional<CentralRepoAccount.CentralRepoAccountType> optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); + if (optCrAccountType.isPresent()) { + try { + crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID()); + } catch (InvalidAccountIDException unused) { + // This was probably caused to a phone number not making + // threw the normalization. + logger.log(Level.WARNING, String.format("Exception thrown from CR getAccount for account %s (%d)", account.getTypeSpecificID(), account.getAccountID())); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java index 2bd3d9087518072bff83fd1845f43cb5d0a9e660..b17263a26de02d0336700d020a00502abb15f134 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/artifactviewers/ContactArtifactViewer.java @@ -32,6 +32,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.logging.Level; @@ -630,11 +631,13 @@ protected Map<Persona, ArrayList<CentralRepoAccount>> doInBackground() throws Ex // make a list of all unique accounts for this contact if (!account.getAccountType().equals(Account.Type.DEVICE)) { - CentralRepoAccount.CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); - CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(crAccountType, account.getTypeSpecificID()); + Optional<CentralRepoAccount.CentralRepoAccountType> optCrAccountType = CentralRepository.getInstance().getAccountTypeByName(account.getAccountType().getTypeName()); + if (optCrAccountType.isPresent()) { + CentralRepoAccount crAccount = CentralRepository.getInstance().getAccount(optCrAccountType.get(), account.getTypeSpecificID()); - if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) { - uniqueAccountsList.add(crAccount); + if (crAccount != null && uniqueAccountsList.contains(crAccount) == false) { + uniqueAccountsList.add(crAccount); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java old mode 100755 new mode 100644 index 9c5ae67fa1fcdfd7cbf3acc22e10c662950362d7..2c88660cb03a80992a3b3f822f47225e6264ca71 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/osaccount/OsAccountDataPanel.java @@ -485,7 +485,7 @@ protected void done() { // // data.add(instanceSection); // } - + addDataComponents(data); revalidate(); diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form index 4c42c44668901c0b3acd9ddfe53d295097f264dc..d47982f2a774c0a94f5b1f9b18891ce56f1089eb 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.form @@ -244,7 +244,7 @@ <Group type="102" alignment="0" attributes="0"> <EmptySpace min="-2" pref="23" max="-2" attributes="0"/> <Component id="memFieldValidationLabel" min="-2" pref="478" max="-2" attributes="0"/> - <EmptySpace max="32767" attributes="0"/> + <EmptySpace pref="12" max="32767" attributes="0"/> </Group> <Group type="102" alignment="0" attributes="0"> <EmptySpace min="-2" pref="18" max="-2" attributes="0"/> @@ -255,7 +255,24 @@ </Group> </Group> </Group> - <Component id="restartNecessaryWarning" alignment="0" min="-2" pref="615" max="-2" attributes="0"/> + <Group type="102" attributes="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="restartNecessaryWarning" alignment="0" min="-2" pref="615" max="-2" attributes="0"/> + <Group type="102" alignment="0" attributes="0"> + <Component id="heapFileLabel" min="-2" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Component id="heapFieldValidationLabel" min="-2" pref="478" max="-2" attributes="0"/> + <Group type="102" attributes="0"> + <Component id="heapDumpFileField" min="-2" pref="415" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Component id="heapDumpBrowseButton" min="-2" max="-2" attributes="0"/> + </Group> + </Group> + </Group> + </Group> + <EmptySpace min="0" pref="0" max="32767" attributes="0"/> + </Group> </Group> </Group> </Group> @@ -293,7 +310,15 @@ <Component id="maxLogFileCount" alignment="3" min="-2" max="-2" attributes="0"/> <Component id="logFileCount" alignment="3" min="-2" max="-2" attributes="0"/> </Group> - <EmptySpace type="unrelated" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="heapFileLabel" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="heapDumpFileField" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="heapDumpBrowseButton" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace max="-2" attributes="0"/> + <Component id="heapFieldValidationLabel" min="-2" pref="16" max="-2" attributes="0"/> + <EmptySpace max="-2" attributes="0"/> <Component id="restartNecessaryWarning" min="-2" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/> </Group> @@ -414,6 +439,40 @@ </Property> </Properties> </Component> + <Component class="javax.swing.JLabel" name="heapFileLabel"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="AutopsyOptionsPanel.heapFileLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JTextField" name="heapDumpFileField"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="AutopsyOptionsPanel.heapDumpFileField.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> + <Component class="javax.swing.JButton" name="heapDumpBrowseButton"> + <Properties> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="AutopsyOptionsPanel.heapDumpBrowseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="heapDumpBrowseButtonActionPerformed"/> + </Events> + </Component> + <Component class="javax.swing.JLabel" name="heapFieldValidationLabel"> + <Properties> + <Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor"> + <Color blue="0" green="0" red="ff" type="rgb"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="org/sleuthkit/autopsy/corecomponents/Bundle.properties" key="AutopsyOptionsPanel.heapFieldValidationLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + </Component> </SubComponents> </Container> <Container class="javax.swing.JPanel" name="tempDirectoryPanel"> @@ -462,7 +521,7 @@ </Group> </Group> </Group> - <EmptySpace pref="158" max="32767" attributes="0"/> + <EmptySpace pref="164" max="32767" attributes="0"/> </Group> </Group> </DimensionLayout> diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java index 193b1836e9bef8fb130ed6dbfc4ea71eb0bf8c23..edbef370b2ed5a828299b24c4d402268c743c6b3 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/AutopsyOptionsPanel.java @@ -29,8 +29,11 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.StringJoiner; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JFileChooser; @@ -38,7 +41,9 @@ import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.tools.ant.types.Commandline; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; @@ -76,6 +81,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private static final long serialVersionUID = 1L; + private static final String DEFAULT_HEAP_DUMP_FILE_FIELD = ""; private final JFileChooser logoFileChooser; private final JFileChooser tempDirChooser; private static final String ETC_FOLDER_NAME = "etc"; @@ -88,6 +94,7 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { private String initialMemValue = Long.toString(Runtime.getRuntime().maxMemory() / ONE_BILLION); private final ReportBranding reportBranding; + private final JFileChooser heapFileChooser; /** * Instantiate the Autopsy options panel. @@ -103,13 +110,19 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { tempDirChooser = new JFileChooser(); tempDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); tempDirChooser.setMultiSelectionEnabled(false); + + heapFileChooser = new JFileChooser(); + heapFileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + heapFileChooser.setMultiSelectionEnabled(false); - if (!PlatformUtil.is64BitJVM() || Version.getBuildType() == Version.Type.DEVELOPMENT) { + if (!isJVMHeapSettingsCapable()) { //32 bit JVM has a max heap size of 1.4 gb to 4 gb depending on OS //So disabling the setting of heap size when the JVM is not 64 bit //Is the safest course of action //And the file won't exist in the install folder when running through netbeans memField.setEnabled(false); + heapDumpFileField.setEnabled(false); + heapDumpBrowseButton.setEnabled(false); solrMaxHeapSpinner.setEnabled(false); } systemMemoryTotal.setText(Long.toString(getSystemMemoryInGB())); @@ -118,13 +131,21 @@ final class AutopsyOptionsPanel extends javax.swing.JPanel { solrMaxHeapSpinner.setModel(new javax.swing.SpinnerNumberModel(UserPreferences.getMaxSolrVMSize(), JVM_MEMORY_STEP_SIZE_MB, ((int) getSystemMemoryInGB()) * MEGA_IN_GIGA, JVM_MEMORY_STEP_SIZE_MB)); - TextFieldListener textFieldListener = new TextFieldListener(); - agencyLogoPathField.getDocument().addDocumentListener(textFieldListener); - tempCustomField.getDocument().addDocumentListener(new TempCustomTextListener()); + agencyLogoPathField.getDocument().addDocumentListener(new TextFieldListener(null)); + heapDumpFileField.getDocument().addDocumentListener(new TextFieldListener(this::isHeapPathValid)); + tempCustomField.getDocument().addDocumentListener(new TextFieldListener(this::evaluateTempDirState)); logFileCount.setText(String.valueOf(UserPreferences.getLogFileCount())); reportBranding = new ReportBranding(); } + + /** + * Returns whether or not the jvm runtime heap settings can effectively be changed. + * @return Whether or not the jvm runtime heap settings can effectively be changed. + */ + private static boolean isJVMHeapSettingsCapable() { + return PlatformUtil.is64BitJVM() && Version.getBuildType() != Version.Type.DEVELOPMENT; + } /** * Get the total system memory in gigabytes which exists on the machine @@ -140,18 +161,23 @@ private long getSystemMemoryInGB() { /** * Gets the currently saved max java heap space in gigabytes. + * @param The conf file memory value (i.e. 4G). * - * @return @throws IOException when unable to get a valid setting + * @return The value in gigabytes. + * @throws IOException when unable to get a valid setting */ - private long getCurrentJvmMaxMemoryInGB() throws IOException { - String currentXmx = getCurrentXmxValue(); + private long getCurrentJvmMaxMemoryInGB(String confFileMemValue) throws IOException { char units = '-'; Long value = 0L; - if (currentXmx.length() > 1) { - units = currentXmx.charAt(currentXmx.length() - 1); - value = Long.parseLong(currentXmx.substring(0, currentXmx.length() - 1)); + if (confFileMemValue.length() > 1) { + units = confFileMemValue.charAt(confFileMemValue.length() - 1); + try { + value = Long.parseLong(confFileMemValue.substring(0, confFileMemValue.length() - 1)); + } catch (NumberFormatException ex) { + throw new IOException("Unable to properly parse memory number.", ex); + } } else { - throw new IOException("No memory setting found in String: " + currentXmx); + throw new IOException("No memory setting found in String: " + confFileMemValue); } //some older .conf files might have the units as megabytes instead of gigabytes switch (units) { @@ -166,33 +192,6 @@ private long getCurrentJvmMaxMemoryInGB() throws IOException { } } - /* - * The value currently saved in the conf file as the max java heap space - * available to this application. Form will be an integer followed by a - * character indicating units. Helper method for - * getCurrentJvmMaxMemoryInGB() - * - * @return the saved value for the max java heap space - * - * @throws IOException if the conf file does not exist in either the user - * directory or the install directory - */ - private String getCurrentXmxValue() throws IOException { - String[] settings; - String currentSetting = ""; - File userConfFile = getUserFolderConfFile(); - if (!userConfFile.exists()) { - settings = getDefaultsFromFileContents(readConfFile(getInstallFolderConfFile())); - } else { - settings = getDefaultsFromFileContents(readConfFile(userConfFile)); - } - for (String option : settings) { - if (option.startsWith("-J-Xmx")) { - currentSetting = option.replace("-J-Xmx", "").trim(); - } - } - return currentSetting; - } /** * Get the conf file from the install directory which stores the default @@ -229,7 +228,61 @@ private static File getUserFolderConfFile() { } return new File(userEtcFolder, confFileName); } - + + private static final String JVM_SETTINGS_REGEX_PARAM = "options"; + private static final String JVM_SETTINGS_REGEX_STR = "^\\s*default_options\\s*=\\s*\"?(?<" + JVM_SETTINGS_REGEX_PARAM + ">.+?)\"?\\s*$"; + private static final Pattern JVM_SETTINGS_REGEX = Pattern.compile(JVM_SETTINGS_REGEX_STR); + private static final String XMX_REGEX_PARAM = "mem"; + private static final String XMX_REGEX_STR = "^\\s*\\-J\\-Xmx(?<" + XMX_REGEX_PARAM + ">.+?)\\s*$"; + private static final Pattern XMX_REGEX = Pattern.compile(XMX_REGEX_STR); + private static final String HEAP_DUMP_REGEX_PARAM = "path"; + private static final String HEAP_DUMP_REGEX_STR = "^\\s*\\-J\\-XX:HeapDumpPath=(\\\")?\\s*(?<" + HEAP_DUMP_REGEX_PARAM + ">.+?)\\s*(\\\")?$"; + private static final Pattern HEAP_DUMP_REGEX = Pattern.compile(HEAP_DUMP_REGEX_STR); + + /** + * Parse the autopsy conf file line. If the line is the default_options line, + * then replaces with current memory and heap path value. Otherwise, returns + * the line provided as parameter. + * + * @param line The line. + * @param memText The text to add as an argument to be used as memory with -J-Xmx. + * @param heapText The text to add as an argument to be used as the heap dump path with + * -J-XX:HeapDumpPath. + * @return The line modified to contain memory and heap arguments. + */ + private static String updateConfLine(String line, String memText, String heapText) { + Matcher match = JVM_SETTINGS_REGEX.matcher(line); + if (match.find()) { + // split on command line arguments + String[] parsedArgs = Commandline.translateCommandline(match.group(JVM_SETTINGS_REGEX_PARAM)); + + String memString = "-J-Xmx" + memText.replaceAll("[^\\d]", "") + "g"; + + // only add in heap path argument if a heap path is specified + String heapString = null; + if (StringUtils.isNotBlank(heapText)) { + while (heapText.endsWith("\\") && heapText.length() > 0) { + heapText = heapText.substring(0, heapText.length() - 1); + } + + heapString = String.format("-J-XX:HeapDumpPath=\"%s\"", heapText); + } + + Stream<String> argsNoMemHeap = Stream.of(parsedArgs) + // remove saved version of memory and heap dump path + .filter(s -> !s.matches(XMX_REGEX_STR) && !s.matches(HEAP_DUMP_REGEX_STR)); + + String newArgs = Stream.concat(argsNoMemHeap, Stream.of(memString, heapString)) + .filter(s -> s != null) + .collect(Collectors.joining(" ")); + + return String.format("default_options=\"%s\"", newArgs); + }; + + return line; + } + + /** * Take the conf file in the install directory and save a copy of it to the * user directory. The copy will be modified to include the current memory @@ -239,25 +292,124 @@ private static File getUserFolderConfFile() { * install folders conf file */ private void writeEtcConfFile() throws IOException { - StringBuilder content = new StringBuilder(); - List<String> confFile = readConfFile(getInstallFolderConfFile()); - for (String line : confFile) { - if (line.contains("-J-Xmx")) { - String[] splitLine = line.split(" "); - StringJoiner modifiedLine = new StringJoiner(" "); - for (String piece : splitLine) { - if (piece.contains("-J-Xmx")) { - piece = "-J-Xmx" + memField.getText() + "g"; - } - modifiedLine.add(piece); - } - content.append(modifiedLine.toString()); - } else { - content.append(line); + String fileText = readConfFile(getInstallFolderConfFile()).stream() + .map((line) -> updateConfLine(line, memField.getText(), heapDumpFileField.getText())) + .collect(Collectors.joining("\n")); + + FileUtils.writeStringToFile(getUserFolderConfFile(), fileText, "UTF-8"); + } + + + /** + * Values for configuration located in the /etc/*.conf file. + */ + private static class ConfValues { + private final String XmxVal; + private final String heapDumpPath; + + /** + * Main constructor. + * @param XmxVal The heap memory size. + * @param heapDumpPath The heap dump path. + */ + ConfValues(String XmxVal, String heapDumpPath) { + this.XmxVal = XmxVal; + this.heapDumpPath = heapDumpPath; + } + + /** + * Returns the heap memory value specified in the conf file. + * @return The heap memory value specified in the conf file. + */ + String getXmxVal() { + return XmxVal; + } + + /** + * Returns path to the heap dump specified in the conf file. + * @return Path to the heap dump specified in the conf file. + */ + String getHeapDumpPath() { + return heapDumpPath; + } + } + + /** + * Retrieve the /etc/*.conf file values pertinent to settings. + * @return The conf file values. + * @throws IOException + */ + private ConfValues getEtcConfValues() throws IOException { + File userConfFile = getUserFolderConfFile(); + String[] args = userConfFile.exists() ? + getDefaultsFromFileContents(readConfFile(userConfFile)) : + getDefaultsFromFileContents(readConfFile(getInstallFolderConfFile())); + + String heapFile = ""; + String memSize = ""; + + for (String arg : args) { + Matcher memMatch = XMX_REGEX.matcher(arg); + if (memMatch.find()) { + memSize = memMatch.group(XMX_REGEX_PARAM); + continue; + } + + Matcher heapFileMatch = HEAP_DUMP_REGEX.matcher(arg); + if (heapFileMatch.find()) { + heapFile = heapFileMatch.group(HEAP_DUMP_REGEX_PARAM); + continue; + } + } + + return new ConfValues(memSize, heapFile); + } + + + + /** + * Checks current heap path value to see if it is valid, and displays an error message if invalid. + * @return True if the heap path is valid. + */ + @Messages({ + "AutopsyOptionsPanel_isHeapPathValid_selectValidDirectory=Please select an existing directory.", + "AutopsyOptionsPanel_isHeapPathValid_developerMode=Cannot change heap dump path while in developer mode.", + "AutopsyOptionsPanel_isHeapPathValid_not64BitMachine=Changing heap dump path settings only enabled for 64 bit version.", + "AutopsyOPtionsPanel_isHeapPathValid_illegalCharacters=Please select a path with no quotes." + }) + private boolean isHeapPathValid() { + if (Version.getBuildType() == Version.Type.DEVELOPMENT) { + heapFieldValidationLabel.setVisible(true); + heapFieldValidationLabel.setText(Bundle.AutopsyOptionsPanel_isHeapPathValid_developerMode()); + return true; + } + + if (!PlatformUtil.is64BitJVM()) { + heapFieldValidationLabel.setVisible(true); + heapFieldValidationLabel.setText(Bundle.AutopsyOptionsPanel_isHeapPathValid_not64BitMachine()); + return true; + } + + //allow blank field as the default will be used + if (StringUtils.isNotBlank(heapDumpFileField.getText())) { + String heapText = heapDumpFileField.getText().trim(); + if (heapText.contains("\"") || heapText.contains("'")) { + heapFieldValidationLabel.setVisible(true); + heapFieldValidationLabel.setText(Bundle.AutopsyOPtionsPanel_isHeapPathValid_illegalCharacters()); + return false; + } + + File curHeapFile = new File(heapText); + if (!curHeapFile.exists() || !curHeapFile.isDirectory()) { + heapFieldValidationLabel.setVisible(true); + heapFieldValidationLabel.setText(Bundle.AutopsyOptionsPanel_isHeapPathValid_selectValidDirectory()); + return false; } - content.append("\n"); } - Files.write(getUserFolderConfFile().toPath(), content.toString().getBytes()); + + heapFieldValidationLabel.setVisible(false); + heapFieldValidationLabel.setText(""); + return true; } /** @@ -295,11 +447,17 @@ private static List<String> readConfFile(File configFile) { * options is not present. */ private static String[] getDefaultsFromFileContents(List<String> list) { - Optional<String> defaultSettings = list.stream().filter(line -> line.startsWith("default_options=")).findFirst(); + Optional<String> defaultSettings = list.stream() + .filter(line -> line.matches(JVM_SETTINGS_REGEX_STR)) + .findFirst(); if (defaultSettings.isPresent()) { - return defaultSettings.get().replace("default_options=", "").replaceAll("\"", "").split(" "); + Matcher match = JVM_SETTINGS_REGEX.matcher(defaultSettings.get()); + if (match.find()) { + return Commandline.translateCommandline(match.group(JVM_SETTINGS_REGEX_PARAM)); + } } + return new String[]{}; } @@ -347,15 +505,25 @@ void load() { } catch (IOException ex) { logger.log(Level.WARNING, "Error loading image from previously saved agency logo path", ex); } - if (memField.isEnabled()) { + + boolean confLoaded = false; + if (isJVMHeapSettingsCapable()) { try { - initialMemValue = Long.toString(getCurrentJvmMaxMemoryInGB()); + ConfValues confValues = getEtcConfValues(); + heapDumpFileField.setText(confValues.getHeapDumpPath()); + initialMemValue = Long.toString(getCurrentJvmMaxMemoryInGB(confValues.getXmxVal())); + confLoaded = true; } catch (IOException ex) { logger.log(Level.SEVERE, "Can't read current Jvm max memory setting from file", ex); memField.setEnabled(false); + heapDumpFileField.setText(DEFAULT_HEAP_DUMP_FILE_FIELD); } memField.setText(initialMemValue); } + + heapDumpBrowseButton.setEnabled(confLoaded); + heapDumpFileField.setEnabled(confLoaded); + setTempDirEnabled(); valid(); //ensure the error messages are up to date } @@ -459,7 +627,7 @@ void store() { reportBranding.setAgencyLogoPath(""); } UserPreferences.setMaxSolrVMSize((int) solrMaxHeapSpinner.getValue()); - if (memField.isEnabled()) { //if the field could of been changed we need to try and save it + if (isJVMHeapSettingsCapable()) { //if the field could of been changed we need to try and save it try { writeEtcConfFile(); } catch (IOException ex) { @@ -474,18 +642,12 @@ void store() { * @return True if valid; false otherwise. */ boolean valid() { - boolean valid = true; - if (!isAgencyLogoPathValid()) { - valid = false; - } - if (!isMemFieldValid()) { - valid = false; - } - if (!isLogNumFieldValid()) { - valid = false; - } - - return valid; + boolean agencyValid = isAgencyLogoPathValid(); + boolean memFieldValid = isMemFieldValid(); + boolean logNumValid = isLogNumFieldValid(); + boolean heapPathValid = isHeapPathValid(); + + return agencyValid && memFieldValid && logNumValid && heapPathValid; } /** @@ -589,47 +751,41 @@ private boolean isLogNumFieldValid() { /** * Listens for registered text fields that have changed and fires a - * PropertyChangeEvent accordingly. + * PropertyChangeEvent accordingly as well as firing an optional additional listener. */ private class TextFieldListener implements DocumentListener { + private final Runnable onChange; - @Override - public void insertUpdate(DocumentEvent e) { - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); - } - - @Override - public void removeUpdate(DocumentEvent e) { - firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + + /** + * Main constructor. + * @param onChange Additional listener for change events. + */ + TextFieldListener(Runnable onChange) { + this.onChange = onChange; } - - @Override - public void changedUpdate(DocumentEvent e) { + + private void baseOnChange() { + if (onChange != null) { + onChange.run(); + } + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); } - } - - /** - * Listens for changes in the temp directory custom directory text field. - */ - private class TempCustomTextListener extends TextFieldListener { - + @Override public void changedUpdate(DocumentEvent e) { - evaluateTempDirState(); - super.changedUpdate(e); + baseOnChange(); } @Override public void removeUpdate(DocumentEvent e) { - evaluateTempDirState(); - super.changedUpdate(e); + baseOnChange(); } @Override public void insertUpdate(DocumentEvent e) { - evaluateTempDirState(); - super.changedUpdate(e); + baseOnChange(); } @@ -673,6 +829,10 @@ private void initComponents() { maxMemoryUnitsLabel2 = new javax.swing.JLabel(); solrMaxHeapSpinner = new javax.swing.JSpinner(); solrJVMHeapWarning = new javax.swing.JLabel(); + heapFileLabel = new javax.swing.JLabel(); + heapDumpFileField = new javax.swing.JTextField(); + heapDumpBrowseButton = new javax.swing.JButton(); + heapFieldValidationLabel = new javax.swing.JLabel(); tempDirectoryPanel = new javax.swing.JPanel(); tempCustomField = new javax.swing.JTextField(); tempDirectoryBrowseButton = new javax.swing.JButton(); @@ -823,6 +983,20 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { org.openide.awt.Mnemonics.setLocalizedText(solrJVMHeapWarning, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.solrJVMHeapWarning.text")); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(heapFileLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.heapFileLabel.text")); // NOI18N + + heapDumpFileField.setText(org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.heapDumpFileField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(heapDumpBrowseButton, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.heapDumpBrowseButton.text")); // NOI18N + heapDumpBrowseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + heapDumpBrowseButtonActionPerformed(evt); + } + }); + + heapFieldValidationLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(heapFieldValidationLabel, org.openide.util.NbBundle.getMessage(AutopsyOptionsPanel.class, "AutopsyOptionsPanel.heapFieldValidationLabel.text")); // NOI18N + javax.swing.GroupLayout runtimePanelLayout = new javax.swing.GroupLayout(runtimePanel); runtimePanel.setLayout(runtimePanelLayout); runtimePanelLayout.setHorizontalGroup( @@ -851,14 +1025,26 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { .addGroup(runtimePanelLayout.createSequentialGroup() .addGap(23, 23, 23) .addComponent(memFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 478, javax.swing.GroupLayout.PREFERRED_SIZE) - .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap(12, Short.MAX_VALUE)) .addGroup(runtimePanelLayout.createSequentialGroup() .addGap(18, 18, 18) .addComponent(solrJVMHeapWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 331, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(44, 44, 44) .addComponent(logNumAlert) .addContainerGap()))) - .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 615, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(restartNecessaryWarning, javax.swing.GroupLayout.PREFERRED_SIZE, 615, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(heapFileLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(heapFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 478, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(runtimePanelLayout.createSequentialGroup() + .addComponent(heapDumpFileField, javax.swing.GroupLayout.PREFERRED_SIZE, 415, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(heapDumpBrowseButton))))) + .addGap(0, 0, Short.MAX_VALUE)))) ); runtimePanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {maxLogFileCount, maxMemoryLabel, totalMemoryLabel}); @@ -892,7 +1078,14 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(maxLogFileCount) .addComponent(logFileCount, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(runtimePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(heapFileLabel) + .addComponent(heapDumpFileField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(heapDumpBrowseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(heapFieldValidationLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 16, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(restartNecessaryWarning) .addContainerGap()) ); @@ -965,7 +1158,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { .addComponent(tempCustomField, javax.swing.GroupLayout.PREFERRED_SIZE, 459, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(tempDirectoryBrowseButton))))) - .addContainerGap(158, Short.MAX_VALUE)) + .addContainerGap(164, Short.MAX_VALUE)) ); tempDirectoryPanelLayout.setVerticalGroup( tempDirectoryPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -1162,6 +1355,24 @@ private void tempCustomRadioActionPerformed(java.awt.event.ActionEvent evt) {//G evaluateTempDirState(); }//GEN-LAST:event_tempCustomRadioActionPerformed + @Messages({ + "AutopsyOptionsPanel_heapDumpBrowseButtonActionPerformed_fileAlreadyExistsTitle=File Already Exists", + "AutopsyOptionsPanel_heapDumpBrowseButtonActionPerformed_fileAlreadyExistsMessage=File already exists. Please select a new location." + }) + private void heapDumpBrowseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_heapDumpBrowseButtonActionPerformed + String oldHeapPath = heapDumpFileField.getText(); + if (!StringUtils.isBlank(oldHeapPath)) { + heapFileChooser.setCurrentDirectory(new File(oldHeapPath)); + } + + int returnState = heapFileChooser.showOpenDialog(this); + if (returnState == JFileChooser.APPROVE_OPTION) { + File selectedDirectory = heapFileChooser.getSelectedFile(); + heapDumpFileField.setText(selectedDirectory.getAbsolutePath()); + firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); + } + }//GEN-LAST:event_heapDumpBrowseButtonActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JTextField agencyLogoPathField; private javax.swing.JLabel agencyLogoPathFieldValidationLabel; @@ -1170,6 +1381,10 @@ private void tempCustomRadioActionPerformed(java.awt.event.ActionEvent evt) {//G private javax.swing.JRadioButton defaultLogoRB; private javax.swing.ButtonGroup displayTimesButtonGroup; private javax.swing.ButtonGroup fileSelectionButtonGroup; + private javax.swing.JButton heapDumpBrowseButton; + private javax.swing.JTextField heapDumpFileField; + private javax.swing.JLabel heapFieldValidationLabel; + private javax.swing.JLabel heapFileLabel; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextField logFileCount; private javax.swing.JTextField logNumAlert; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties index bad8f8ff838c96e508cf242752c70542469a8704..2deaab0d3b835dc7732e03ed492c5d7835a9b571 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties @@ -251,3 +251,7 @@ AutopsyOptionsPanel.tempCaseRadio.text=Temp folder in case directory AutopsyOptionsPanel.tempCustomRadio.text=Custom AutopsyOptionsPanel.tempCustomField.text= AutopsyOptionsPanel.tempOnCustomNoPath.text=Please select a path for the custom root temp directory. +AutopsyOptionsPanel.heapDumpFileField.text= +AutopsyOptionsPanel.heapDumpBrowseButton.text=Browse +AutopsyOptionsPanel.heapFileLabel.text=Custom Heap Dump Location: +AutopsyOptionsPanel.heapFieldValidationLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED index bcf95a19a9a53f3ea4fd8f2875ebca2314375e7d..16fa8d0e5fada523bc62d1a7af91626adb6dedac 100755 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/Bundle.properties-MERGED @@ -12,6 +12,12 @@ AutopsyOptionsPanel.memFieldValidationLabel.noValueEntered.text=No value entered AutopsyOptionsPanel.memFieldValidationLabel.overMaxMemory.text=Value must be less than the total system memory of {0}GB # {0} - minimumMemory AutopsyOptionsPanel.memFieldValidationLabel.underMinMemory.text=Value must be at least {0}GB +AutopsyOptionsPanel_heapDumpBrowseButtonActionPerformed_fileAlreadyExistsMessage=File already exists. Please select a new location. +AutopsyOptionsPanel_heapDumpBrowseButtonActionPerformed_fileAlreadyExistsTitle=File Already Exists +AutopsyOptionsPanel_isHeapPathValid_developerMode=Cannot change heap dump path while in developer mode. +AutopsyOPtionsPanel_isHeapPathValid_illegalCharacters=Please select a path with no quotes. +AutopsyOptionsPanel_isHeapPathValid_not64BitMachine=Changing heap dump path settings only enabled for 64 bit version. +AutopsyOptionsPanel_isHeapPathValid_selectValidDirectory=Please select an existing directory. AutopsyOptionsPanel_storeTempDir_onChoiceError_description=There was an error updating temporary directory choice selection. AutopsyOptionsPanel_storeTempDir_onChoiceError_title=Error Saving Temporary Directory Choice # {0} - path @@ -311,3 +317,7 @@ AutopsyOptionsPanel.tempCaseRadio.text=Temp folder in case directory AutopsyOptionsPanel.tempCustomRadio.text=Custom AutopsyOptionsPanel.tempCustomField.text= AutopsyOptionsPanel.tempOnCustomNoPath.text=Please select a path for the custom root temp directory. +AutopsyOptionsPanel.heapDumpFileField.text= +AutopsyOptionsPanel.heapDumpBrowseButton.text=Browse +AutopsyOptionsPanel.heapFileLabel.text=Custom Heap Dump Location: +AutopsyOptionsPanel.heapFieldValidationLabel.text= diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java index 433a98972c6e2631d7c03c87d36ada0ccedc663e..a98bdb61ddd0733e682c6025857b26f691a1d232 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/HostNode.java @@ -78,13 +78,16 @@ private static class HostGroupingChildren extends ChildFactory.Detachable<DataSo } /** - * Listener for handling DATA_SOURCE_ADDED events. + * Listener for handling DATA_SOURCE_ADDED / HOST_DELETED events. + * A host may have been deleted as part of a merge, which means its data sources could + * have moved to a different host requiring a refresh. */ private final PropertyChangeListener dataSourceAddedPcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); - if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + if (eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString()) + || eventType.equals(Case.Events.HOSTS_DELETED.toString())) { refresh(true); } } @@ -92,12 +95,12 @@ public void propertyChange(PropertyChangeEvent evt) { @Override protected void addNotify() { - Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), dataSourceAddedPcl); + Case.addEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), dataSourceAddedPcl); } @Override protected void removeNotify() { - Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED), dataSourceAddedPcl); + Case.removeEventTypeSubscriber(EnumSet.of(Case.Events.DATA_SOURCE_ADDED, Case.Events.HOSTS_DELETED), dataSourceAddedPcl); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java index 4b2588c6bb7524705b5a6e1a9939e471139f8948..9b7802bdbc60508fc7151d40b10759395e1fb889 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/OsAccounts.java @@ -255,8 +255,7 @@ protected Sheet createSheet() { Bundle.OsAccounts_loginNameProperty_displayName(), Bundle.OsAccounts_loginNameProperty_desc(), optional.isPresent() ? optional.get() : "")); - - // TODO - load realm on background thread + // TODO - load realm on background thread String realmName = ""; //String realmName = account.getRealm().getRealmNames().isEmpty() ? "" : account.getRealm().getRealmNames().get(0); propertiesSet.put(new NodeProperty<>( diff --git a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java index b4a9a0182a001e9855f124d94358a5c7a1ed9caa..cd50214b84a81ebef69bea1230d473a7d96c5cd8 100644 --- a/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java +++ b/Core/src/org/sleuthkit/autopsy/discovery/ui/ObjectDetectedFilterPanel.java @@ -60,7 +60,7 @@ private void setUpObjectFilter() { objectsList.clearList(); List<String> setNames = DiscoveryUiUtils.getSetNames(BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION); for (String name : setNames) { - objectsList.addElement(name, null, null); + objectsList.addElement(name, null, name); } } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Error loading object detected set names", ex); diff --git a/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java b/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java index ef7b138ef057b1d2b730a0c20425b651d85cb32d..f0aa9b06d57101ae2076ce2046bbe32073bc7cd0 100644 --- a/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java +++ b/Core/src/org/sleuthkit/autopsy/machinesettings/UserMachinePreferences.java @@ -21,6 +21,7 @@ import java.io.File; import java.nio.file.Paths; import java.util.Optional; +import java.util.logging.Level; import java.util.prefs.Preferences; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; @@ -30,12 +31,15 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.FileUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.NetworkUtils; /** * Provides case-specific settings like the user-specified temp folder. */ public final class UserMachinePreferences { + private static final Logger logger = Logger.getLogger(UserMachinePreferences.class.getName()); private static final Preferences preferences = NbPreferences.forModule(UserMachinePreferences.class); /** @@ -81,6 +85,17 @@ static Optional<TempDirChoice> getValue(String val) { private static final TempDirChoice DEFAULT_CHOICE = TempDirChoice.SYSTEM; + /** + * Returns the name of this computer's host name to be used as a directory + * in some instances. + * + * @return The name of this computer's host name to be used as a directory + * in some instances. + */ + private static String getHostName() { + return NetworkUtils.getLocalHostName(); + } + /** * @return A subdirectory of java.io.tmpdir. */ @@ -94,8 +109,16 @@ private static File getSystemTempDirFile() { */ private static File getCaseTempDirFile() { try { - String caseDirStr = Case.getCurrentCaseThrows().getCaseDirectory(); - return Paths.get(caseDirStr, CASE_SUBDIR).toFile(); + Case autCase = Case.getCurrentCaseThrows(); + String caseDirStr = autCase.getCaseDirectory(); + switch (autCase.getCaseType()) { + case MULTI_USER_CASE: return Paths.get(caseDirStr, getHostName(), CASE_SUBDIR).toFile(); + case SINGLE_USER_CASE: return Paths.get(caseDirStr, CASE_SUBDIR).toFile(); + default: + logger.log(Level.SEVERE, "Unknown case type: " + autCase.getCaseType()); + return getSystemTempDirFile(); + } + } catch (NoCurrentCaseException ex) { return getSystemTempDirFile(); } @@ -111,7 +134,7 @@ private static File getCaseTempDirFile() { private static File getCustomTempDirFile() { String customDirectory = getCustomTempDirectory(); return (StringUtils.isBlank(customDirectory)) - ? getSystemTempDirFile() : Paths.get(customDirectory, AUTOPSY_SUBDIR).toFile(); + ? getSystemTempDirFile() : Paths.get(customDirectory, AUTOPSY_SUBDIR, getHostName()).toFile(); } /** @@ -214,8 +237,9 @@ public static TempDirChoice getTempDirChoice() { /** * Sets the temp directory choice (i.e. system, case, custom). + * * @param tempDirChoice The choice (must be non-null). - * @throws UserMachinePreferencesException + * @throws UserMachinePreferencesException */ public static void setTempDirChoice(TempDirChoice tempDirChoice) throws UserMachinePreferencesException { if (tempDirChoice == null) { diff --git a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java index 5b2932b7186ec1233cc99d0a2915110b768cf881..bb5c2abbae641ef84290d2b045a5329e5f692219 100644 --- a/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java +++ b/Core/src/org/sleuthkit/autopsy/url/analytics/domaincategorization/WebCategoriesDataModel.java @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; @@ -44,7 +45,7 @@ import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; -import org.openide.modules.InstalledFileLocator; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.url.analytics.DomainCategory; /** @@ -136,13 +137,19 @@ static int getMaxCategoryLength() { * @return The path or null if the path cannot be reconciled. */ private static File getDefaultPath() { - File dir = InstalledFileLocator.getDefault().locate(ROOT_FOLDER, WebCategoriesDataModel.class.getPackage().getName(), false); - if (dir == null || !dir.exists()) { - logger.log(Level.WARNING, String.format("Unable to find file %s with InstalledFileLocator", ROOT_FOLDER)); + String configDir = PlatformUtil.getUserConfigDirectory(); + if (configDir == null || !new File(configDir).exists()) { + logger.log(Level.WARNING, "Unable to find UserConfigDirectory"); return null; } - return Paths.get(dir.getAbsolutePath(), FILE_REL_PATH).toFile(); + Path subDirPath = Paths.get(configDir, ROOT_FOLDER); + File subDir = subDirPath.toFile(); + if (!subDir.exists() && !subDir.mkdirs()) { + logger.log(Level.WARNING, "There was an issue creating custom domain config at: {0}", subDirPath.toString()); + } + + return Paths.get(configDir, ROOT_FOLDER, FILE_REL_PATH).toFile(); } /** @@ -530,7 +537,7 @@ private List<String> getSuffixes(String host) { public synchronized void close() throws SQLException { if (dbConn != null) { dbConn.close(); - dbConn = null; + dbConn = null; } } } diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java index a87c2959badf359285f1e25271cc84660c47f092..537835bc01c25dd2cdf0fa9e5f1bcd10f9480502 100755 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoAccountsTest.java @@ -22,6 +22,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import junit.framework.Assert; import junit.framework.TestCase; import junit.framework.Test; @@ -104,10 +105,11 @@ public void testPredefinedAccountTypes() { if(expectedAccountType == Account.Type.DEVICE) continue; try { - CentralRepoAccountType crAccountType = CentralRepository.getInstance() + Optional<CentralRepoAccountType> optCrAccountType = CentralRepository.getInstance() .getAccountTypeByName(expectedAccountType.getTypeName()); + Assert.assertTrue(optCrAccountType.isPresent()); - Account.Type actualAccountType = crAccountType.getAcctType(); + Account.Type actualAccountType = optCrAccountType.get().getAcctType(); Assert.assertEquals(expectedAccountType, actualAccountType); } catch (CentralRepoException ex) { Assert.fail("Didn't expect an exception here. Exception: " + ex); @@ -118,35 +120,34 @@ public void testPredefinedAccountTypes() { public void testRejectionOfDeviceAccountType() { try { Account.Type deviceAccount = Account.Type.DEVICE; - CentralRepository.getInstance() + Optional<CentralRepoAccountType> optType = CentralRepository.getInstance() .getAccountTypeByName(deviceAccount.getTypeName()); - Assert.fail("Expected an exception from getAccountTypeByName() when" - + " querying the device account type"); + Assert.assertFalse(optType.isPresent()); } catch (CentralRepoException ex) { - // Pass + Assert.fail("Didn't expect an exception here. Exception: " + ex); } } public void testNonExistentAccountType() { try { - CentralRepository.getInstance() + Optional<CentralRepoAccountType> optType = CentralRepository.getInstance() .getAccountTypeByName("NotARealAccountType"); - Assert.fail("Expected an exception from getAccountTypeByName()" - + " when querying a non-existent account type"); + Assert.assertFalse(optType.isPresent()); } catch (CentralRepoException ex) { - // Pass + Assert.fail("Didn't expect an exception here. Exception: " + ex); } } public void testCreatingAccount() { try { Account.Type facebookAccountType = Account.Type.FACEBOOK; - CentralRepoAccountType expectedAccountType = CentralRepository.getInstance() + Optional<CentralRepoAccountType> optExpectedAccountType = CentralRepository.getInstance() .getAccountTypeByName(facebookAccountType.getTypeName()); + assertTrue(optExpectedAccountType.isPresent()); // Create the account CentralRepository.getInstance() - .getOrCreateAccount(expectedAccountType, "+1 401-231-2552"); + .getOrCreateAccount(optExpectedAccountType.get(), "+1 401-231-2552"); } catch (InvalidAccountIDException | CentralRepoException ex) { Assert.fail("Didn't expect an exception here. Exception: " + ex); } @@ -155,19 +156,20 @@ public void testCreatingAccount() { public void testRetreivingAnAccount() { try { Account.Type facebookAccountType = Account.Type.FACEBOOK; - CentralRepoAccountType expectedAccountType = CentralRepository + Optional<CentralRepoAccountType> optExpectedAccountType = CentralRepository .getInstance() .getAccountTypeByName(facebookAccountType.getTypeName()); + assertTrue(optExpectedAccountType.isPresent()); // Create the account CentralRepository.getInstance() - .getOrCreateAccount(expectedAccountType, "+1 441-231-2552"); + .getOrCreateAccount(optExpectedAccountType.get(), "+1 441-231-2552"); // Retrieve the account CentralRepoAccount actualAccount = CentralRepository.getInstance() - .getOrCreateAccount(expectedAccountType, "+1 441-231-2552"); + .getOrCreateAccount(optExpectedAccountType.get(), "+1 441-231-2552"); - Assert.assertEquals(expectedAccountType, actualAccount.getAccountType()); + Assert.assertEquals(optExpectedAccountType.get(), actualAccount.getAccountType()); Assert.assertEquals("+1 441-231-2552", actualAccount.getIdentifier()); } catch (InvalidAccountIDException | CentralRepoException ex) { Assert.fail("Didn't expect an exception here. Exception: " + ex); diff --git a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java index 199a48b8451089e82a4711ea1d3c8f72750f3300..47372fcc07826e3e654963a894c63827a3a975f4 100644 --- a/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java +++ b/Core/test/qa-functional/src/org/sleuthkit/autopsy/centralrepository/datamodel/CentralRepoPersonasTest.java @@ -24,6 +24,7 @@ import java.nio.file.Paths; import java.time.Instant; import java.util.Collection; +import java.util.Optional; import junit.framework.Assert; import static junit.framework.Assert.assertTrue; import junit.framework.TestCase; @@ -222,27 +223,39 @@ public void setUp() throws CentralRepoException, IOException { org2 = CentralRepository.getInstance().newOrganization(org2); // get some correltion types for different account types, for later use - phoneAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.PHONE.getTypeName()); + Optional<CentralRepoAccount.CentralRepoAccountType> optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.PHONE.getTypeName()); + assertTrue(optType.isPresent()); + phoneAccountType = optType.get(); phoneInstanceType = CentralRepository.getInstance().getCorrelationTypeById(phoneAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(PHONE) returned null", phoneInstanceType != null); - emailAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.EMAIL.getTypeName()); + optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.EMAIL.getTypeName()); + assertTrue(optType.isPresent()); + emailAccountType = optType.get(); emailInstanceType = CentralRepository.getInstance().getCorrelationTypeById(emailAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(EMAIL) returned null", emailInstanceType != null); - facebookAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.FACEBOOK.getTypeName()); + optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.FACEBOOK.getTypeName()); + assertTrue(optType.isPresent()); + facebookAccountType = optType.get(); facebookInstanceType = CentralRepository.getInstance().getCorrelationTypeById(facebookAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(FACEBOOK) returned null", facebookInstanceType != null); - textnowAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.TEXTNOW.getTypeName()); + optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.TEXTNOW.getTypeName()); + assertTrue(optType.isPresent()); + textnowAccountType = optType.get(); textnowInstanceType = CentralRepository.getInstance().getCorrelationTypeById(textnowAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(TEXTNOW) returned null", textnowInstanceType != null); - whatsAppAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.WHATSAPP.getTypeName()); + optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.WHATSAPP.getTypeName()); + assertTrue(optType.isPresent()); + whatsAppAccountType = optType.get(); whatsAppInstanceType = CentralRepository.getInstance().getCorrelationTypeById(whatsAppAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(WHATSAPP) returned null", whatsAppInstanceType != null); - skypeAccountType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.SKYPE.getTypeName()); + optType = CentralRepository.getInstance().getAccountTypeByName( Account.Type.SKYPE.getTypeName()); + assertTrue(optType.isPresent()); + skypeAccountType = optType.get(); skypeInstanceType = CentralRepository.getInstance().getCorrelationTypeById(skypeAccountType.getCorrelationTypeId()); assertTrue("getCorrelationTypeById(SKYPE) returned null", skypeInstanceType != null); diff --git a/RecentActivity/nbproject/project.properties b/RecentActivity/nbproject/project.properties index 9736070e535c751845709a9d241ec4b2a13a5faa..aab9fa2a60e077c0fb1c1e3ba57f7241c5d30e64 100644 --- a/RecentActivity/nbproject/project.properties +++ b/RecentActivity/nbproject/project.properties @@ -1,3 +1,4 @@ +file.reference.Rejistry-1.1-SNAPSHOT.jar=release/modules/ext/Rejistry-1.1-SNAPSHOT.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt diff --git a/RecentActivity/nbproject/project.xml b/RecentActivity/nbproject/project.xml index af8c2edace68d6a0b8b52f08a71e4978cafa5baf..8fc5e13b534f82e9b645a481875399df3d77f1e3 100644 --- a/RecentActivity/nbproject/project.xml +++ b/RecentActivity/nbproject/project.xml @@ -74,6 +74,10 @@ </dependency> </module-dependencies> <public-packages/> + <class-path-extension> + <runtime-relative-path>ext/Rejistry-1.1-SNAPSHOT.jar</runtime-relative-path> + <binary-origin>release/modules/ext/Rejistry-1.1-SNAPSHOT.jar</binary-origin> + </class-path-extension> </data> </configuration> </project> diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED index 26181755d016e4d90d483658503293e81ef0ffb7..4538c2836cdbded63b1f4245fc5c69996d6501ce 100755 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Bundle.properties-MERGED @@ -15,6 +15,7 @@ DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card DataSourceUsage_FlashDrive=Flash Drive DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.parentModuleName=Recent Activity +DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine DomainCategoryRunner_moduleName_text=DomainCategoryRunner DomainCategoryRunner_parentModuleName=Recent Activity DomainCategoryRunner_Progress_Message_Domain_Types=Finding Domain Types diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java index 0d64661f6c0cd544e1a7ee5431bb8bdc44edaff2..9519a37bb14d5aeb8d1570e64a713ae67047be8c 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultDomainCategorizer.java @@ -128,18 +128,33 @@ private static void addItem(Map<String, String> mapping, String line, int lineNu private Map<String, String> mapping = null; @Override - public void initialize() throws DomainCategorizerException { - if (this.mapping == null) { - try { - this.mapping = loadMapping(); - } catch (IOException ex) { - throw new DomainCategorizerException("Unable to load domain type csv for domain category analysis", ex); - } + public synchronized void initialize() throws DomainCategorizerException { + if (isInitialized()) { + return; + } + + try { + this.mapping = loadMapping(); + } catch (IOException ex) { + throw new DomainCategorizerException("Unable to load domain type csv for domain category analysis", ex); } } + /** + * Returns true if this categorizer is properly initialized. + * + * @return True if this categorizer is properly initialized. + */ + private synchronized boolean isInitialized() { + return this.mapping != null; + } + @Override - public DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + public synchronized DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + if (!isInitialized()) { + initialize(); + } + // use host; use domain as fallback if no host provided String hostToUse = StringUtils.isBlank(host) ? domain : host; @@ -162,7 +177,7 @@ public DomainCategory getCategory(String domain, String host) throws DomainCateg } @Override - public void close() throws Exception { + public synchronized void close() throws Exception { // clear out the mapping to release resources mapping = null; } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java new file mode 100644 index 0000000000000000000000000000000000000000..da84660cc282a24db6c10c9925a2761384588019 --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DefaultPriorityDomainCategorizer.java @@ -0,0 +1,104 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * Contact: carrier <at> sleuthkit <dot> org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.recentactivity; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang.StringUtils; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.url.analytics.DomainCategorizer; +import org.sleuthkit.autopsy.url.analytics.DomainCategorizerException; +import org.sleuthkit.autopsy.url.analytics.DomainCategory; + +/** + * The autopsy provided domain category provider that overrides all domain + * category providers except the custom web domain categorizations. + */ +@Messages({ + "DefaultPriorityDomainCategorizer_searchEngineCategory=Search Engine" +}) +public class DefaultPriorityDomainCategorizer implements DomainCategorizer { + + // taken from https://www.google.com/supported_domains + private static final List<String> GOOGLE_DOMAINS = Arrays.asList("google.com", "google.ad", "google.ae", "google.com.af", "google.com.ag", "google.com.ai", "google.al", "google.am", "google.co.ao", "google.com.ar", "google.as", "google.at", "google.com.au", "google.az", "google.ba", "google.com.bd", "google.be", "google.bf", "google.bg", "google.com.bh", "google.bi", "google.bj", "google.com.bn", "google.com.bo", "google.com.br", "google.bs", "google.bt", "google.co.bw", "google.by", "google.com.bz", "google.ca", "google.cd", "google.cf", "google.cg", "google.ch", "google.ci", "google.co.ck", "google.cl", "google.cm", "google.cn", "google.com.co", "google.co.cr", "google.com.cu", "google.cv", "google.com.cy", "google.cz", "google.de", "google.dj", "google.dk", "google.dm", "google.com.do", "google.dz", "google.com.ec", "google.ee", "google.com.eg", "google.es", "google.com.et", "google.fi", "google.com.fj", "google.fm", "google.fr", "google.ga", "google.ge", "google.gg", "google.com.gh", "google.com.gi", "google.gl", "google.gm", "google.gr", "google.com.gt", "google.gy", "google.com.hk", "google.hn", "google.hr", "google.ht", "google.hu", "google.co.id", "google.ie", "google.co.il", "google.im", "google.co.in", "google.iq", "google.is", "google.it", "google.je", "google.com.jm", "google.jo", "google.co.jp", "google.co.ke", "google.com.kh", "google.ki", "google.kg", "google.co.kr", "google.com.kw", "google.kz", "google.la", "google.com.lb", "google.li", "google.lk", "google.co.ls", "google.lt", "google.lu", "google.lv", "google.com.ly", "google.co.ma", "google.md", "google.me", "google.mg", "google.mk", "google.ml", "google.com.mm", "google.mn", "google.ms", "google.com.mt", "google.mu", "google.mv", "google.mw", "google.com.mx", "google.com.my", "google.co.mz", "google.com.na", "google.com.ng", "google.com.ni", "google.ne", "google.nl", "google.no", "google.com.np", "google.nr", "google.nu", "google.co.nz", "google.com.om", "google.com.pa", "google.com.pe", "google.com.pg", "google.com.ph", "google.com.pk", "google.pl", "google.pn", "google.com.pr", "google.ps", "google.pt", "google.com.py", "google.com.qa", "google.ro", "google.ru", "google.rw", "google.com.sa", "google.com.sb", "google.sc", "google.se", "google.com.sg", "google.sh", "google.si", "google.sk", "google.com.sl", "google.sn", "google.so", "google.sm", "google.sr", "google.st", "google.com.sv", "google.td", "google.tg", "google.co.th", "google.com.tj", "google.tl", "google.tm", "google.tn", "google.to", "google.com.tr", "google.tt", "google.com.tw", "google.co.tz", "google.com.ua", "google.co.ug", "google.co.uk", "google.com.uy", "google.co.uz", "google.com.vc", "google.co.ve", "google.vg", "google.co.vi", "google.com.vn", "google.vu", "google.ws", "google.rs", "google.co.za", "google.co.zm", "google.co.zw", "google.cat"); + + // taken from https://www.yahoo.com/everything/world + private static final List<String> YAHOO_DOMAINS = Arrays.asList("espanol.yahoo.com", "au.yahoo.com", "be.yahoo.com", "fr-be.yahoo.com", "br.yahoo.com", "ca.yahoo.com", "espanol.yahoo.com", "espanol.yahoo.com", "de.yahoo.com", "es.yahoo.com", "espanol.yahoo.com", "fr.yahoo.com", "in.yahoo.com", "id.yahoo.com", "ie.yahoo.com", "it.yahoo.com", "en-maktoob.yahoo.com", "malaysia.yahoo.com", "espanol.yahoo.com", "nz.yahoo.com", "espanol.yahoo.com", "ph.yahoo.com", "qc.yahoo.com", "ro.yahoo.com", "sg.yahoo.com", "za.yahoo.com", "se.yahoo.com", "uk.yahoo.com", "yahoo.com", "espanol.yahoo.com", "vn.yahoo.com", "gr.yahoo.com", "maktoob.yahoo.com", "yahoo.com", "hk.yahoo.com", "tw.yahoo.com", "yahoo.co.jp"); + + private static final List<String> OTHER_SEARCH_ENGINES = Arrays.asList( + "bing.com", + "baidu.com", + "sogou.com", + "soso.com", + "duckduckgo.com", + "swisscows.com", + "gibiru.com", + "cutestat.com", + "youdao.com", + "biglobe.ne.jp", + "givewater.com", + "ekoru.org", + "ecosia.org", + // according to https://en.wikipedia.org/wiki/Yandex + "yandex.ru", + "yandex.com" + ); + + private static final String WWW_PREFIX = "www"; + + private static final Map<String, String> DOMAIN_LOOKUP + = Stream.of(GOOGLE_DOMAINS, YAHOO_DOMAINS, OTHER_SEARCH_ENGINES) + .flatMap((lst) -> lst.stream()) + .collect(Collectors.toMap((k) -> k, (k) -> Bundle.DefaultPriorityDomainCategorizer_searchEngineCategory(), (v1, v2) -> v1)); + + @Override + public void initialize() throws DomainCategorizerException { + } + + @Override + public DomainCategory getCategory(String domain, String host) throws DomainCategorizerException { + + String hostToUse = StringUtils.isBlank(host) ? domain : host; + + if (StringUtils.isBlank(hostToUse)) { + return null; + } + + List<String> domainWords = Stream.of(hostToUse.toLowerCase().split("\\.")) + .filter(StringUtils::isNotBlank) + .map(String::trim) + .collect(Collectors.toList()); + + String sanitizedDomain = domainWords.stream() + // skip first word segment if 'www' + .skip(domainWords.size() > 0 && WWW_PREFIX.equals(domainWords.get(0)) ? 1 : 0) + .collect(Collectors.joining(".")); + + String category = DOMAIN_LOOKUP.get(sanitizedDomain); + return category == null ? null : new DomainCategory(sanitizedDomain, category); + } + + @Override + public void close() throws IOException { + } +} diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java index 0102f6e868640417ccc5cca149efb6f639737122..d24a031a48414d8ce32239a231bd7890a8e50518 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/DomainCategoryRunner.java @@ -20,6 +20,7 @@ import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -453,33 +454,45 @@ public void process(Content dataSource, IngestJobContext context, DataSourceInge @Override void configExtractor() throws IngestModule.IngestModuleException { // lookup all providers, filter null providers, and sort providers - Collection<? extends DomainCategorizer> lookupList = Lookup.getDefault().lookupAll(DomainCategorizer.class); - if (lookupList == null) { - lookupList = Collections.emptyList(); - } - - List<DomainCategorizer> foundProviders = lookupList.stream() - .filter(provider -> provider != null) - .sorted((a, b) -> { - boolean aIsCustom = a.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH); - boolean bIsCustom = b.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH); - if (aIsCustom != bIsCustom) { - // push custom categorizer to top - return -Boolean.compare(aIsCustom, bIsCustom); - } - - return a.getClass().getName().compareToIgnoreCase(b.getClass().getName()); + Collection<? extends DomainCategorizer> lookupCollection = Lookup.getDefault().lookupAll(DomainCategorizer.class); + Collection<? extends DomainCategorizer> lookupList = (lookupCollection == null) ? + Collections.emptyList() : + lookupCollection; + + // this will be the class instance of the foundProviders + List<DomainCategorizer> foundProviders = new ArrayList<>(); + + // find the custom domain categories provider if present and add it first to the list + lookupList.stream() + .filter(categorizer -> categorizer.getClass().getName().contains(CUSTOM_CATEGORIZER_PATH)) + .findFirst() + .ifPresent((provider) -> foundProviders.add(provider)); + + // add the default priority categorizer + foundProviders.add(new DefaultPriorityDomainCategorizer()); + + // add all others except for the custom web domain categorizer, the default priority + // categorizer and the default categorizer + lookupList.stream() + .filter(categorizer -> categorizer != null) + .filter(categorizer -> { + String className = categorizer.getClass().getName(); + return !className.contains(CUSTOM_CATEGORIZER_PATH) && + !className.equals(DefaultPriorityDomainCategorizer.class.getName()) && + !className.equals(DefaultDomainCategorizer.class.getName()); }) - .collect(Collectors.toList()); - - // add the default categorizer last as a last resort + .sorted((a, b) -> a.getClass().getName().compareToIgnoreCase(b.getClass().getName())) + .forEach(foundProviders::add); + + // add the default categorizer last foundProviders.add(new DefaultDomainCategorizer()); - + for (DomainCategorizer provider : foundProviders) { try { provider.initialize(); } catch (DomainCategorizerException ex) { - throw new IngestModule.IngestModuleException("There was an error instantiating the provider: " + provider.getClass().getSimpleName(), ex); + throw new IngestModule.IngestModuleException("There was an error instantiating the provider: " + + provider.getClass().getSimpleName(), ex); } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java index c7f17d6f7696ac91a06da77d59c90a22af1dd52a..32312c2ee1ea28550c188c82fb4dc83dc7522e6b 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractPrefetch.java @@ -33,9 +33,13 @@ import java.util.List; import java.util.Set; import java.util.logging.Level; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.io.FilenameUtils; import org.openide.modules.InstalledFileLocator; import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.ExecUtil; import org.sleuthkit.autopsy.coreutils.Logger; @@ -62,8 +66,6 @@ final class ExtractPrefetch extends Extract { private IngestJobContext context; - private static final String MODULE_NAME = "extractPREFETCH"; //NON-NLS - private static final String PREFETCH_TSK_COMMENT = "Prefetch File"; private static final String PREFETCH_FILE_LOCATION = "/windows/prefetch"; private static final String PREFETCH_TOOL_FOLDER = "markmckinnon"; //NON-NLS @@ -147,8 +149,13 @@ void extractPrefetchFiles(Content dataSource) { return; } - String prefetchFile = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME) + File.separator + pFile.getName(); - if (pFile.getParentPath().toLowerCase().contains(PREFETCH_FILE_LOCATION.toLowerCase())) { + if (pFile.getParentPath().toLowerCase().contains(PREFETCH_FILE_LOCATION.toLowerCase()) && pFile.getSize() > 0) { + String origFileName = pFile.getName(); + String ext = FilenameUtils.getExtension(origFileName); + String baseName = FilenameUtils.getBaseName(origFileName); + String fileName = String.format("%s_%d.%s", baseName, pFile.getId(), ext); + String baseRaTempPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), dataSource.getName() + "-" + PREFETCH_DIR_NAME); + String prefetchFile = Paths.get(baseRaTempPath, fileName).toString(); try { ContentUtils.writeToFile(pFile, new File(prefetchFile)); } catch (IOException ex) { @@ -259,10 +266,32 @@ private void createAppExecArtifacts(String prefetchDb, Content dataSource) { String timesProgramRun = resultSet.getString("Number_time_file_run"); String filePath = resultSet.getString("file_path"); - AbstractFile pfAbstractFile = getAbstractFile(prefetchFileName, PREFETCH_FILE_LOCATION, dataSource); - Set<Long> prefetchExecutionTimes = findNonZeroExecutionTimes(executionTimes); + String baseName = FilenameUtils.getBaseName(prefetchFileName); + Matcher match = Pattern.compile("_(?<objId>\\d*)\\s*$").matcher(baseName); + if (!match.find()) { + logger.log(Level.WARNING, "Invalid format for PF file: " + prefetchFileName);//NON-NLS + continue; + } + + + /** + * A prefetch file is created when a program is run and the superfetch service collected data about the first 10 + * seconds of the run, the trace data is then written to a new prefetch file or merged with an existing prefetch file. + * If the prefetch file gets deleted for some reason then a new one will be created. See 7500 in JIRA for more + * information. + */ + AbstractFile pfAbstractFile = null; + try { + Content c = Case.getCurrentCaseThrows().getSleuthkitCase().getContentById(Long.parseLong(match.group("objId"))); + if (c instanceof AbstractFile) { + pfAbstractFile = (AbstractFile) c; + } + } catch (NoCurrentCaseException | TskCoreException | NumberFormatException ex ) { + logger.log(Level.SEVERE, "Unable to find content for: " + prefetchFileName, ex); + } + if (pfAbstractFile != null) { for (Long executionTime : prefetchExecutionTimes) { @@ -305,26 +334,7 @@ BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COUNT, getName(), Integer.valueOf(timesPr postArtifacts(blkBrdArtList); } } - - /** - * Cycle thru the execution times list and only return a new list of times - * that are greater than zero. - * - * @param executionTimes - list of prefetch execution times 8 possible - * timestamps - * - * @return List of timestamps that are greater than zero - */ - private Set<Long> findNonZeroExecutionTimes(List<Long> executionTimes) { - Set<Long> prefetchExecutionTimes = new HashSet<>(); - for (Long executionTime : executionTimes) { // only add prefetch file entries that have an actual date associated with them - if (executionTime > 0) { - prefetchExecutionTimes.add(executionTime); - } - } - return prefetchExecutionTimes; - } - + /** * Create associated artifacts using file path name and the artifact it * associates with @@ -339,7 +349,7 @@ private Set<Long> findNonZeroExecutionTimes(List<Long> executionTimes) { private BlackboardArtifact createAssociatedArtifact(String fileName, String filePathName, BlackboardArtifact bba, Content dataSource) throws TskCoreException { AbstractFile sourceFile = getAbstractFile(fileName, filePathName, dataSource); if (sourceFile != null) { - return createAssociatedArtifact(sourceFile, bba); + return createAssociatedArtifact(sourceFile, bba); } return null; } @@ -368,7 +378,6 @@ AbstractFile getAbstractFile(String fileName, String filePath, Content dataSourc } for (AbstractFile pFile : files) { - if (pFile.getParentPath().toLowerCase().endsWith(filePath.toLowerCase() + '/')) { return pFile; } @@ -376,6 +385,24 @@ AbstractFile getAbstractFile(String fileName, String filePath, Content dataSourc return null; - } + } + /** + * Cycle thru the execution times list and only return a new list of times + * that are greater than zero. + * + * @param executionTimes - list of prefetch execution times 8 possible + * timestamps + * + * @return List of timestamps that are greater than zero + */ + private Set<Long> findNonZeroExecutionTimes(List<Long> executionTimes) { + Set<Long> prefetchExecutionTimes = new HashSet<>(); + for (Long executionTime : executionTimes) { // only add prefetch file entries that have an actual date associated with them + if (executionTime > 0) { + prefetchExecutionTimes.add(executionTime); + } + } + return prefetchExecutionTimes; + } } diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java index 3330e6181a623a6f98017ef56378c428fc5e20a4..85c622cc621dd1650cf51cc1aa492a9a9e3d6bbf 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractRegistry.java @@ -175,6 +175,9 @@ class ExtractRegistry extends Extract { private IngestJobContext context; private Map<String, String> userNameMap; + private String hostName = null; + private String domainName = null; + private static final String SHELLBAG_ARTIFACT_NAME = "RA_SHELL_BAG"; //NON-NLS private static final String SHELLBAG_ATTRIBUTE_LAST_WRITE = "RA_SHELL_BAG_LAST_WRITE"; //NON-NLS private static final String SHELLBAG_ATTRIBUTE_KEY = "RA_SHELL_BAG_KEY"; //NON-NLS @@ -1050,6 +1053,8 @@ private void addBlueToothAttribute(String line, Collection<BlackboardAttribute> * @return true if successful, false if parsing failed at some point */ private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile) { + parseSystemHostDomain(); + File regfile = new File(regFilePath); List<BlackboardArtifact> newArtifacts = new ArrayList<>(); try (BufferedReader bufferedReader = new BufferedReader(new FileReader(regfile))) { @@ -1098,7 +1103,7 @@ private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstrac //add remaining userinfos as accounts; for (Map<String, String> userInfo : userInfoMap.values()) { - OsAccount osAccount = accountMgr.newWindowsOsAccount(userInfo.get(SID_KEY), null, null, host, OsAccountRealm.RealmScope.UNKNOWN); + OsAccount osAccount = accountMgr.newWindowsOsAccount(userInfo.get(SID_KEY), null, domainName, host, domainName != null || !domainName.isEmpty() ? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN); accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED); updateOsAccount(osAccount, userInfo, groupMap.get(userInfo.get(SID_KEY)), regAbstractFile); } @@ -1131,6 +1136,53 @@ private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstrac return false; } + + /** + * Finds the Host and Domain information from the registry. + */ + private void parseSystemHostDomain() { + List<AbstractFile> regFiles = findRegistryFiles(); + + for (AbstractFile systemHive: regFiles) { + if (systemHive.getName().toLowerCase().equals("system")) { + + String systemFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg") + File.separator + systemHive.getName(); + File systemFileNameLocalFile = new File(systemFileNameLocal); + + if (!systemFileNameLocalFile.exists()) { + try { + ContentUtils.writeToFile(systemHive, systemFileNameLocalFile, context::dataSourceIngestIsCancelled); + } catch (ReadContentInputStreamException ex) { + logger.log(Level.WARNING, String.format("Error reading registry file '%s' (id=%d).", + systemHive.getName(), systemHive.getId()), ex); //NON-NLS + this.addErrorMessage( + NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp", + this.getName(), systemHive.getName())); + continue; + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error writing temp registry file '%s' for registry file '%s' (id=%d).", + systemFileNameLocal, systemHive.getName(), systemHive.getId()), ex); //NON-NLS + this.addErrorMessage( + NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp", + this.getName(), systemHive.getName())); + continue; + } + } + + try { + ParseRegistryHive systemRegFile = new ParseRegistryHive(systemFileNameLocalFile); + hostName = systemRegFile.getRegistryKeyValue("ControlSet001/Services/Tcpip/Parameters", "hostname"); + domainName = systemRegFile.getRegistryKeyValue("ControlSet001/Services/Tcpip/Parameters", "domain"); + break; + } catch (IOException ex) { + logger.log(Level.SEVERE, String.format("Error reading registry file '%s' for registry file '%s' (id=%d).", + systemFileNameLocal, systemHive.getName(), systemHive.getId()), ex); //NON-NLS + this.addErrorMessage(NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp", + this.getName(), systemHive.getName())); } + } + } + } + /** * Read the User Information section of the SAM regripper plugin's output * and collect user account information from the file. @@ -1970,7 +2022,7 @@ private void createOrUpdateOsAccount(AbstractFile file, String sid, String userN Optional<OsAccount> optional = accountMgr.getWindowsOsAccount(sid, null, null, host); OsAccount osAccount; if (!optional.isPresent()) { - osAccount = accountMgr.newWindowsOsAccount(sid, userName != null && userName.isEmpty() ? null : userName, null, host, OsAccountRealm.RealmScope.UNKNOWN); + osAccount = accountMgr.newWindowsOsAccount(sid, userName != null && userName.isEmpty() ? null : userName, domainName, host, domainName != null || !domainName.isEmpty()? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN); accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED); } else { osAccount = optional.get(); diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java index 0954081f3007920fd36de7db9d28f4ece48741f5..c4a472a350d103ff703d899023432985c733c67d 100644 --- a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/Firefox.java @@ -600,7 +600,6 @@ private void getDownloadPreVersion24() { } j++; dbFile.delete(); - break; } if(!context.dataSourceIngestIsCancelled()) { @@ -733,7 +732,6 @@ private void getDownloadVersion24() { } j++; dbFile.delete(); - break; } if(!context.dataSourceIngestIsCancelled()) { diff --git a/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java new file mode 100644 index 0000000000000000000000000000000000000000..2048313c43f1b514d8b87cb49dc495c122321c4b --- /dev/null +++ b/RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ParseRegistryHive.java @@ -0,0 +1,124 @@ +/* + * + * Autopsy Forensic Browser + * + * Copyright 2021 Basis Technology Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.recentactivity; + +import com.williballenthin.rejistry.RegistryHiveFile; +import com.williballenthin.rejistry.RegistryKey; +import com.williballenthin.rejistry.RegistryParseException; +import com.williballenthin.rejistry.RegistryValue; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * + * Parsers registry keys from a registry hive + */ +public class ParseRegistryHive { + + private final File registryHiveFile; + private final RegistryHiveFile registryHive; + final private static Logger logger = Logger.getLogger(ParseRegistryHive.class.getName()); + + ParseRegistryHive(File registryHiveFile) throws IOException { + this.registryHiveFile = registryHiveFile; + registryHive = new RegistryHiveFile(this.registryHiveFile); + } + + /** + * + * @param registryKey Registry key to get the value for + * @param registryValue Value of the registry key to get the data for + * @return String data from the registry key/value pair + * + */ + public String getRegistryKeyValue(String registryKey, String registryValue) { + + RegistryKey currentKey = findRegistryKey(registryHive, registryKey); + + if (currentKey == null) { + return null; + } + + try { + List<RegistryValue> parameterList = currentKey.getValueList(); + for (RegistryValue parameter : parameterList) { + if (parameter.getName().toLowerCase().equals(registryValue)) { + return parameter.getValue().getAsString(); + } + } + } catch (RegistryParseException ex) { + logger.log(Level.WARNING, String.format("Error reading registry file '%s'", registryHiveFile.getName()), ex); //NON-NLS + + } catch (UnsupportedEncodingException ex) { + logger.log(Level.WARNING, String.format("Unsupported Encoding Error for registry key '%s' and registry value '%s'", + registryKey, registryValue), ex); //NON-NLS + } + + return null; + + } + + /** + * Gets the time that the registry key was written. + * @param registryKey Registry key to get the timestamp for + * @return date/time that the key was written + * + */ + public Calendar getRegistryKeyTime(String registryKey) { + + RegistryKey currentKey = findRegistryKey(registryHive, registryKey); + + if (currentKey == null) { + return null; + } + + return currentKey.getTimestamp(); + + } + + /** + * Gets the timestamp of the Registry key + * + * @param registryHiveFile Hive to parse + * @param registryKey registry key to find in hive + * @return registry key or null if it cannot be found + */ + private RegistryKey findRegistryKey(RegistryHiveFile registryHiveFile, String registryKey) { + + RegistryKey currentKey; + try { + RegistryKey rootKey = registryHiveFile.getRoot(); + String regKeyList[] = registryKey.split("/"); + currentKey = rootKey; + for (String key : regKeyList) { + currentKey = currentKey.getSubkey(key); + } + } catch (RegistryParseException ex) { + return null; + } + return currentKey; + + } + +} \ No newline at end of file