Merge branch 'develop' of https://github.com/sleuthkit/autopsy into 6077-DisplayImageForSummary

This commit is contained in:
William Schaefer 2020-03-05 13:57:53 -05:00
commit 43cd85a33f
35 changed files with 1421 additions and 1373 deletions

View File

@ -7,8 +7,10 @@ AbstractSqlEamDb.cannotUpgrage.message=Currently selected database platform "{0}
AbstractSqlEamDb.failedToReadMajorVersion.message=Failed to read schema version for Central Repository. AbstractSqlEamDb.failedToReadMajorVersion.message=Failed to read schema version for Central Repository.
AbstractSqlEamDb.failedToReadMinorVersion.message=Failed to read schema minor version for Central Repository. AbstractSqlEamDb.failedToReadMinorVersion.message=Failed to read schema minor version for Central Repository.
AbstractSqlEamDb.upgradeSchema.incompatible=The selected Central Repository is not compatible with the current version of the application, please upgrade the application if you wish to use this Central Repository. AbstractSqlEamDb.upgradeSchema.incompatible=The selected Central Repository is not compatible with the current version of the application, please upgrade the application if you wish to use this Central Repository.
CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database.
CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'. CorrelationAttributeInstance.invalidName.message=Invalid database table name. Name must start with a lowercase letter and can only contain lowercase letters, numbers, and '_'.
CorrelationAttributeInstance.nullName.message=Database name is null. CorrelationAttributeInstance.nullName.message=Database name is null.
CorrelationAttributeUtil.emailaddresses.text=Email Addresses
CorrelationType.DOMAIN.displayName=Domains CorrelationType.DOMAIN.displayName=Domains
CorrelationType.EMAIL.displayName=Email Addresses CorrelationType.EMAIL.displayName=Email Addresses
CorrelationType.FILES.displayName=Files CorrelationType.FILES.displayName=Files
@ -23,7 +25,6 @@ DataSourceUpdateService.serviceName.text=Update Central Repository Data Sources
EamArtifactInstances.knownStatus.bad=Bad EamArtifactInstances.knownStatus.bad=Bad
EamArtifactInstances.knownStatus.known=Known EamArtifactInstances.knownStatus.known=Known
EamArtifactInstances.knownStatus.unknown=Unknown EamArtifactInstances.knownStatus.unknown=Unknown
EamArtifactUtil.emailaddresses.text=Email Addresses
EamCase.title.caseDisplayName=Case Name: EamCase.title.caseDisplayName=Case Name:
EamCase.title.caseNumber=Case Number: EamCase.title.caseNumber=Case Number:
EamCase.title.caseUUID=Case UUID: EamCase.title.caseUUID=Case UUID:

View File

@ -0,0 +1,137 @@
/*
* Central Repository
*
* Copyright 2020 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.centralrepository.datamodel;
import org.sleuthkit.datamodel.Account;
/**
* This class abstracts an Account as stored in the CR database.
*/
public final class CentralRepoAccount {
// primary key in the Accounts table in CR database
private final long accountId;
private final CentralRepoAccountType accountType;
// type specifc unique account id
private final String typeSpecificId;
/**
* Encapsulates a central repo account type and the correlation type
* that it maps to.
*/
public static final class CentralRepoAccountType {
// id is the primary key in the account_types table
private final int accountTypeId;
private final Account.Type acctType;
private final int correlationTypeId;
CentralRepoAccountType(int acctTypeID, Account.Type acctType, int correlationTypeId) {
this.acctType = acctType;
this.correlationTypeId = correlationTypeId;
this.accountTypeId = acctTypeID;
}
/**
* @return the acctType
*/
public Account.Type getAcctType() {
return acctType;
}
public int getCorrelationTypeId() {
return this.correlationTypeId;
}
public int getAccountTypeId() {
return this.accountTypeId;
}
}
public CentralRepoAccount(long accountId, CentralRepoAccountType accountType, String typeSpecificId) {
this.accountId = accountId;
this.accountType = accountType;
this.typeSpecificId = typeSpecificId;
}
/**
* Gets unique identifier (assigned by a provider) for the account. Example
* includes an email address, a phone number, or a website username.
*
* @return type specific account id.
*/
public String getTypeSpecificId() {
return this.typeSpecificId;
}
/**
* Gets the account type
*
* @return account type
*/
public CentralRepoAccountType getAccountType() {
return this.accountType;
}
/**
* Gets the unique row id for this account in the database.
*
* @return unique row id.
*/
public long getAccountId() {
return this.accountId;
}
@Override
public int hashCode() {
int hash = 5;
hash = 43 * hash + (int) (this.accountId ^ (this.accountId >>> 32));
hash = 43 * hash + (this.accountType != null ? this.accountType.hashCode() : 0);
hash = 43 * hash + (this.typeSpecificId != null ? this.typeSpecificId.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final CentralRepoAccount other = (CentralRepoAccount) obj;
if (this.accountId != other.getAccountId()) {
return false;
}
if ((this.typeSpecificId == null) ? (other.getTypeSpecificId() != null) : !this.typeSpecificId.equals(other.getTypeSpecificId())) {
return false;
}
if (this.accountType != other.getAccountType() && (this.accountType == null || !this.accountType.equals(other.getAccountType()))) {
return false;
}
return true;
}
}

View File

@ -0,0 +1,407 @@
/*
* Central Repository
*
* Copyright 2015-2020 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.centralrepository.datamodel;
import java.io.File;
import java.sql.SQLException;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Contains business logic for saving and validating settings for central repo
*/
public class CentralRepoDbManager {
private static final Logger logger = Logger.getLogger(CentralRepoDbManager.class.getName());
private static final String CENTRAL_REPO_DB_NAME = "central_repository";
/**
* obtains the database connectivity for central repository
*
* @return the CentralRepository object to connect to
* @throws CentralRepoException
*/
private static CentralRepository obtainCentralRepository() throws CentralRepoException {
//get connection
try {
return CentralRepository.getInstance();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex);
onUpgradeError("Error updating central repository, unable to make connection",
Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
}
// will never be reached
return null;
}
/**
* obtains central repository lock
*
* @param db the database connection
* @return the lock if acquired
* @throws CentralRepoException
*/
private static CoordinationService.Lock obtainCentralRepoLock(CentralRepository db) throws CentralRepoException {
try {
// This may return null if locking isn't supported, which is fine. It will
// throw an exception if locking is supported but we can't get the lock
// (meaning the database is in use by another user)
return db.getExclusiveMultiUserDbLock();
//perform upgrade
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex);
onUpgradeError("Error updating central repository, unable to acquire exclusive lock",
Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
}
// will never be reached
return null;
}
/**
* updates central repository schema if necessary
*
* @param db the database connectivity
* @param lock the acquired lock
* @throws CentralRepoException
*/
private static void updatedDbSchema(CentralRepository db, CoordinationService.Lock lock) throws CentralRepoException {
try {
db.upgradeSchema();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error updating central repository", ex);
onUpgradeError("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} catch (SQLException ex) {
logger.log(Level.SEVERE, "Error updating central repository", ex);
onUpgradeError("Error updating central repository",
Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} catch (IncompatibleCentralRepoException ex) {
logger.log(Level.SEVERE, "Error updating central repository", ex);
onUpgradeError("Error updating central repository",
ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} finally {
if (lock != null) {
try {
lock.release();
} catch (CoordinationService.CoordinationServiceException ex) {
logger.log(Level.SEVERE, "Error releasing database lock", ex);
}
}
}
}
/**
* Upgrade the current Central Reposity schema to the newest version. If the
* upgrade fails, the Central Repository will be disabled and the current
* settings will be cleared.
*/
@NbBundle.Messages(value = {"EamDbUtil.centralRepoDisabled.message= The Central Repository has been disabled.", "EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository.", "EamDbUtil.centralRepoConnectionFailed.message=Unable to connect to Central Repository.", "EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository."})
public static void upgradeDatabase() throws CentralRepoException {
if (!CentralRepository.isEnabled()) {
return;
}
CentralRepository db = obtainCentralRepository();
//get lock necessary for upgrade
if (db != null) {
CoordinationService.Lock lock = obtainCentralRepoLock(db);
updatedDbSchema(db, lock);
} else {
onUpgradeError("Unable to connect to database",
Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), null);
}
}
private static void onUpgradeError(String message, String desc, Exception innerException) throws CentralRepoException {
// Disable the central repo and clear the current settings.
try {
if (null != CentralRepository.getInstance()) {
CentralRepository.getInstance().shutdownConnections();
}
} catch (CentralRepoException ex2) {
logger.log(Level.SEVERE, "Error shutting down central repo connection pool", ex2);
}
CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.DISABLED.name());
CentralRepoPlatforms.saveSelectedPlatform();
if (innerException == null) {
throw new CentralRepoException(message, desc);
} else {
throw new CentralRepoException(message, desc, innerException);
}
}
private DatabaseTestResult testingStatus;
private CentralRepoPlatforms selectedPlatform;
private final PostgresCentralRepoSettings dbSettingsPostgres;
private final SqliteCentralRepoSettings dbSettingsSqlite;
private boolean configurationChanged = false;
public CentralRepoDbManager() {
dbSettingsPostgres = new PostgresCentralRepoSettings();
dbSettingsSqlite = new SqliteCentralRepoSettings();
selectedPlatform = CentralRepoPlatforms.getSelectedPlatform();
// set the default selected platform for displaying in the ui of EamDbSettingsDialog
// if selected option is not applicable
if (selectedPlatform == null || selectedPlatform.equals(CentralRepoPlatforms.DISABLED)) {
selectedPlatform = CentralRepoPlatforms.POSTGRESQL;
}
}
public PostgresCentralRepoSettings getDbSettingsPostgres() {
return dbSettingsPostgres;
}
public SqliteCentralRepoSettings getDbSettingsSqlite() {
return dbSettingsSqlite;
}
/**
* setup sqlite db with default settings
* @throws CentralRepoException if unable to successfully set up database
*/
public void setupDefaultSqliteDb() throws CentralRepoException {
// change in-memory settings to default sqlite
selectedPlatform = CentralRepoPlatforms.SQLITE;
dbSettingsSqlite.setupDefaultSettings();
// if db is not present, attempt to create it
DatabaseTestResult curStatus = testStatus();
if (curStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) {
createDb();
curStatus = testStatus();
}
// the only successful setup status is tested ok
if (curStatus != DatabaseTestResult.TESTEDOK) {
throw new CentralRepoException("Unable to successfully create sqlite database");
}
// if successfully got here, then save the settings
CentralRepoDbUtil.setUseCentralRepo(true);
saveNewCentralRepo();
}
/**
* Returns if changes to the central repository configuration were
* successfully applied
*
* @return true if the database configuration was successfully changed false
* if it was not
*/
public boolean wasConfigurationChanged() {
return configurationChanged;
}
private CentralRepoDbSettings getSelectedSettings() throws CentralRepoException {
switch (selectedPlatform) {
case POSTGRESQL:
return dbSettingsPostgres;
case SQLITE:
return dbSettingsSqlite;
case DISABLED:
return null;
default:
throw new CentralRepoException("Unknown database type: " + selectedPlatform);
}
}
private RdbmsCentralRepoFactory getDbFactory() throws CentralRepoException {
switch (selectedPlatform) {
case POSTGRESQL:
return new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsPostgres);
case SQLITE:
return new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsSqlite);
case DISABLED:
return null;
default:
throw new CentralRepoException("Unknown database type: " + selectedPlatform);
}
}
public boolean createDb() throws CentralRepoException {
boolean result = false;
boolean dbCreated = true;
CentralRepoDbSettings selectedDbSettings = getSelectedSettings();
if (!selectedDbSettings.verifyDatabaseExists()) {
dbCreated = selectedDbSettings.createDatabase();
}
if (dbCreated) {
try {
RdbmsCentralRepoFactory centralRepoSchemaFactory = getDbFactory();
result = centralRepoSchemaFactory.initializeDatabaseSchema()
&& centralRepoSchemaFactory.insertDefaultDatabaseContent();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Unable to create database for central repository with settings " + selectedDbSettings, ex);
throw ex;
}
}
if (!result) {
// Remove the incomplete database
if (dbCreated) {
// RAMAN TBD: migrate deleteDatabase() to RdbmsCentralRepoFactory
selectedDbSettings.deleteDatabase();
}
String schemaError = "Unable to initialize database schema or insert contents into central repository.";
logger.severe(schemaError);
throw new CentralRepoException(schemaError);
}
testingStatus = DatabaseTestResult.TESTEDOK;
return true;
}
/**
* saves a new central repository based on current settings
*/
@NbBundle.Messages({"CentralRepoDbManager.connectionErrorMsg.text=Failed to connect to central repository database."})
public void saveNewCentralRepo() throws CentralRepoException {
/**
* We have to shutdown the previous platform's connection pool first;
* assuming it wasn't DISABLED. This will close any existing idle
* connections.
*
* The next use of an EamDb API method will start a new connection pool
* using those new settings.
*/
try {
CentralRepository previousDbManager = CentralRepository.getInstance();
if (null != previousDbManager) {
// NOTE: do not set/save the seleted platform before calling this.
CentralRepository.getInstance().shutdownConnections();
}
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Failed to close database connections in previously selected platform.", ex); // NON-NLS
throw ex;
}
// Even if we fail to close the existing connections, make sure that we
// save the new connection settings, so an Autopsy restart will correctly
// start with the new settings.
CentralRepoPlatforms.setSelectedPlatform(selectedPlatform.name());
CentralRepoPlatforms.saveSelectedPlatform();
CentralRepoDbSettings selectedDbSettings = getSelectedSettings();
// save the new settings
selectedDbSettings.saveSettings();
// Load those newly saved settings into the postgres db manager instance
// in case we are still using the same instance.
if (selectedPlatform == CentralRepoPlatforms.POSTGRESQL || selectedPlatform == CentralRepoPlatforms.SQLITE) {
try {
logger.info("Creating central repo db with settings: " + selectedDbSettings);
CentralRepository.getInstance().updateSettings();
configurationChanged = true;
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, Bundle.CentralRepoDbManager_connectionErrorMsg_text(), ex); //NON-NLS
return;
}
}
}
public DatabaseTestResult getStatus() {
return testingStatus;
}
public CentralRepoPlatforms getSelectedPlatform() {
return selectedPlatform;
}
public void clearStatus() {
testingStatus = DatabaseTestResult.UNTESTED;
}
public void setSelectedPlatform(CentralRepoPlatforms newSelected) {
selectedPlatform = newSelected;
testingStatus = DatabaseTestResult.UNTESTED;
}
/**
* Tests whether or not the database settings are valid.
*
* @return True or false.
*/
public boolean testDatabaseSettingsAreValid(
String tbDbHostname, String tbDbPort, String tbDbUsername, String tfDatabasePath, String jpDbPassword) throws CentralRepoException, NumberFormatException {
switch (selectedPlatform) {
case POSTGRESQL:
dbSettingsPostgres.setHost(tbDbHostname);
dbSettingsPostgres.setPort(Integer.parseInt(tbDbPort));
dbSettingsPostgres.setDbName(CENTRAL_REPO_DB_NAME);
dbSettingsPostgres.setUserName(tbDbUsername);
dbSettingsPostgres.setPassword(jpDbPassword);
break;
case SQLITE:
File databasePath = new File(tfDatabasePath);
dbSettingsSqlite.setDbName(SqliteCentralRepoSettings.DEFAULT_DBNAME);
dbSettingsSqlite.setDbDirectory(databasePath.getPath());
break;
default:
throw new IllegalStateException("Central Repo has an unknown selected platform: " + selectedPlatform);
}
return true;
}
public DatabaseTestResult testStatus() {
if (selectedPlatform == CentralRepoPlatforms.POSTGRESQL) {
if (dbSettingsPostgres.verifyConnection()) {
if (dbSettingsPostgres.verifyDatabaseExists()) {
if (dbSettingsPostgres.verifyDatabaseSchema()) {
testingStatus = DatabaseTestResult.TESTEDOK;
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST;
}
} else {
testingStatus = DatabaseTestResult.CONNECTION_FAILED;
}
} else if (selectedPlatform == CentralRepoPlatforms.SQLITE) {
if (dbSettingsSqlite.dbFileExists()) {
if (dbSettingsSqlite.verifyConnection()) {
if (dbSettingsSqlite.verifyDatabaseSchema()) {
testingStatus = DatabaseTestResult.TESTEDOK;
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST;
}
}
return testingStatus;
}
}

View File

@ -0,0 +1,55 @@
/*
* Central Repository
*
* Copyright 2015-2020 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.centralrepository.datamodel;
/**
* common interface for settings pertaining to the database in central repository
*/
public interface CentralRepoDbSettings {
void saveSettings();
boolean createDatabase();
boolean deleteDatabase();
/**
* Use the current settings and the validation query to test the connection
* to the database.
*
* @return true if successfull connection, else false.
*/
boolean verifyConnection();
/**
* Check to see if the database exists.
*
* @return true if exists, else false
*/
boolean verifyDatabaseExists();
/**
* Use the current settings and the schema version query to test the
* database schema.
*
* @return true if successful connection, else false.
*/
boolean verifyDatabaseSchema();
}

View File

@ -25,9 +25,6 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CoordinationServiceException;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION; import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo.SOFTWARE_CR_DB_SCHEMA_VERSION;
@ -166,80 +163,6 @@ public class CentralRepoDbUtil {
return true; return true;
} }
/**
* Upgrade the current Central Reposity schema to the newest version. If the
* upgrade fails, the Central Repository will be disabled and the current
* settings will be cleared.
*/
@Messages({"EamDbUtil.centralRepoDisabled.message= The Central Repository has been disabled.",
"EamDbUtil.centralRepoUpgradeFailed.message=Failed to upgrade Central Repository.",
"EamDbUtil.centralRepoConnectionFailed.message=Unable to connect to Central Repository.",
"EamDbUtil.exclusiveLockAquisitionFailure.message=Unable to acquire exclusive lock for Central Repository."})
public static void upgradeDatabase() throws CentralRepoException {
if (!CentralRepository.isEnabled()) {
return;
}
CentralRepository db = null;
CoordinationService.Lock lock = null;
//get connection
try {
try {
db = CentralRepository.getInstance();
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to make connection", ex);
throw new CentralRepoException("Error updating central repository, unable to make connection", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
}
//get lock necessary for upgrade
if (db != null) {
try {
// This may return null if locking isn't supported, which is fine. It will
// throw an exception if locking is supported but we can't get the lock
// (meaning the database is in use by another user)
lock = db.getExclusiveMultiUserDbLock();
//perform upgrade
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error updating central repository, unable to acquire exclusive lock", ex);
throw new CentralRepoException("Error updating central repository, unable to acquire exclusive lock", Bundle.EamDbUtil_exclusiveLockAquisitionFailure_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
}
try {
db.upgradeSchema();
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
throw new CentralRepoException("Error updating central repository", ex.getUserMessage() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} catch (SQLException ex) {
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
throw new CentralRepoException("Error updating central repository", Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} catch (IncompatibleCentralRepoException ex) {
LOGGER.log(Level.SEVERE, "Error updating central repository", ex);
throw new CentralRepoException("Error updating central repository", ex.getMessage() + "\n\n" + Bundle.EamDbUtil_centralRepoUpgradeFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message(), ex);
} finally {
if (lock != null) {
try {
lock.release();
} catch (CoordinationServiceException ex) {
LOGGER.log(Level.SEVERE, "Error releasing database lock", ex);
}
}
}
} else {
throw new CentralRepoException("Unable to connect to database", Bundle.EamDbUtil_centralRepoConnectionFailed_message() + Bundle.EamDbUtil_centralRepoDisabled_message());
}
} catch (CentralRepoException ex) {
// Disable the central repo and clear the current settings.
try {
if (null != CentralRepository.getInstance()) {
CentralRepository.getInstance().shutdownConnections();
}
} catch (CentralRepoException ex2) {
LOGGER.log(Level.SEVERE, "Error shutting down central repo connection pool", ex2);
}
CentralRepoPlatforms.setSelectedPlatform(CentralRepoPlatforms.DISABLED.name());
CentralRepoPlatforms.saveSelectedPlatform();
throw ex;
}
}
/** /**
* Get the default organization name * Get the default organization name

View File

@ -23,6 +23,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService; import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
/** /**
@ -808,8 +809,32 @@ public interface CentralRepository {
/** /**
* Returns list of all correlation types. * Returns list of all correlation types.
* *
* @return list of Correlation types * @return list of Correlation types
* @throws CentralRepoException * @throws CentralRepoException
*/ */
List<CorrelationAttributeInstance.Type> getCorrelationTypes() throws CentralRepoException; List<CorrelationAttributeInstance.Type> getCorrelationTypes() throws CentralRepoException;
/**
* Get account type by type name.
*
* @param accountTypeName account type name to look for
* @return CR account type
* @throws CentralRepoException
*/
CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException;
/**
* Get an account from the accounts table matching the given type/ID.
* Inserts a row if one doesn't exists.
*
* @param crAccountType CR account type to look for or create
* @param accountUniqueID type specific unique account id
* @return CR account
*
* @throws CentralRepoException
*/
CentralRepoAccount getOrCreateAccount(CentralRepoAccount.CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException;
} }

View File

@ -50,6 +50,7 @@ public class CorrelationAttributeInstance implements Serializable {
private String comment; private String comment;
private TskData.FileKnown knownStatus; private TskData.FileKnown knownStatus;
private Long objectId; private Long objectId;
private Long accountId;
public CorrelationAttributeInstance( public CorrelationAttributeInstance(
CorrelationAttributeInstance.Type correlationType, CorrelationAttributeInstance.Type correlationType,
@ -73,6 +74,20 @@ public class CorrelationAttributeInstance implements Serializable {
String comment, String comment,
TskData.FileKnown knownStatus, TskData.FileKnown knownStatus,
Long fileObjectId Long fileObjectId
) throws CentralRepoException, CorrelationAttributeNormalizationException {
this(type, value, -1, eamCase, eamDataSource, filePath, comment, knownStatus, fileObjectId, (long)-1);
}
CorrelationAttributeInstance(
Type type,
String value,
int instanceId,
CorrelationCase eamCase,
CorrelationDataSource eamDataSource,
String filePath,
String comment,
TskData.FileKnown knownStatus,
Long fileObjectId,
Long accountId
) throws CentralRepoException, CorrelationAttributeNormalizationException { ) throws CentralRepoException, CorrelationAttributeNormalizationException {
if (filePath == null) { if (filePath == null) {
throw new CentralRepoException("file path is null"); throw new CentralRepoException("file path is null");
@ -88,6 +103,7 @@ public class CorrelationAttributeInstance implements Serializable {
this.comment = comment; this.comment = comment;
this.knownStatus = knownStatus; this.knownStatus = knownStatus;
this.objectId = fileObjectId; this.objectId = fileObjectId;
this.accountId = accountId;
} }
public Boolean equals(CorrelationAttributeInstance otherInstance) { public Boolean equals(CorrelationAttributeInstance otherInstance) {
@ -98,7 +114,8 @@ public class CorrelationAttributeInstance implements Serializable {
&& (this.getCorrelationDataSource().equals(otherInstance.getCorrelationDataSource())) && (this.getCorrelationDataSource().equals(otherInstance.getCorrelationDataSource()))
&& (this.getFilePath().equals(otherInstance.getFilePath())) && (this.getFilePath().equals(otherInstance.getFilePath()))
&& (this.getKnownStatus().equals(otherInstance.getKnownStatus())) && (this.getKnownStatus().equals(otherInstance.getKnownStatus()))
&& (this.getComment().equals(otherInstance.getComment()))); && (this.getComment().equals(otherInstance.getComment()))
&& (this.getAccountId().equals(otherInstance.getAccountId())));
} }
@Override @Override
@ -106,6 +123,7 @@ public class CorrelationAttributeInstance implements Serializable {
return this.getID() return this.getID()
+ this.getCorrelationCase().getCaseUUID() + this.getCorrelationCase().getCaseUUID()
+ this.getCorrelationDataSource().getDeviceID() + this.getCorrelationDataSource().getDeviceID()
+ this.getAccountId()
+ this.getFilePath() + this.getFilePath()
+ this.getCorrelationType().toString() + this.getCorrelationType().toString()
+ this.getCorrelationValue() + this.getCorrelationValue()
@ -210,6 +228,24 @@ public class CorrelationAttributeInstance implements Serializable {
return objectId; return objectId;
} }
/**
* Get the accountId of the account associated with the correlation
* attribute.
*
* @return the accountId of the account
*/
public Long getAccountId() {
return accountId;
}
/**
* Set the accountId of the account associated with this correlation
* attribute.
*/
void setAccountId(Long accountId) {
this.accountId = accountId;
}
// Type ID's for Default Correlation Types // Type ID's for Default Correlation Types
public static final int FILES_TYPE_ID = 0; public static final int FILES_TYPE_ID = 0;
public static final int DOMAIN_TYPE_ID = 1; public static final int DOMAIN_TYPE_ID = 1;

View File

@ -76,11 +76,23 @@ final public class CorrelationAttributeNormalizer {
return normalizeIccid(trimmedData); return normalizeIccid(trimmedData);
default: default:
final String errorMessage = String.format( try {
"Validator function not found for attribute type: %s", // If the atttribute is not one of the above
attributeType.getDisplayName()); // but is one of the other default correlation types, then let the data go as is
throw new CorrelationAttributeNormalizationException(errorMessage); List<CorrelationAttributeInstance.Type> defaultCorrelationTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes();
} for (CorrelationAttributeInstance.Type defaultCorrelationType : defaultCorrelationTypes) {
if (defaultCorrelationType.getId() == attributeType.getId()) {
return trimmedData;
}
}
final String errorMessage = String.format(
"Validator function not found for attribute type: %s",
attributeType.getDisplayName());
throw new CorrelationAttributeNormalizationException(errorMessage);
} catch (CentralRepoException ex) {
throw new CorrelationAttributeNormalizationException("Failed to get default correlation types.", ex);
}
}
} }
/** /**

View File

@ -24,6 +24,7 @@ import java.util.logging.Level;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -222,6 +223,8 @@ public class CorrelationAttributeUtil {
/** /**
* Makes a correlation attribute instance for an account artifact. * Makes a correlation attribute instance for an account artifact.
*
* Also creates an account in the CR DB if it doesn't exist.
* *
* IMPORTANT: The correlation attribute instance is NOT added to the central * IMPORTANT: The correlation attribute instance is NOT added to the central
* repository by this method. * repository by this method.
@ -239,13 +242,31 @@ public class CorrelationAttributeUtil {
* *
* @return The correlation attribute instance. * @return The correlation attribute instance.
*/ */
private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) { private static void makeCorrAttrFromAcctArtifact(List<CorrelationAttributeInstance> corrAttrInstances, BlackboardArtifact acctArtifact) throws TskCoreException, CentralRepoException {
// RAMAN TODO: Convert TSK_ACCOUNT_TYPE attribute to correlation attribute type
// RAMAN TODO: Extract TSK_ID as value // Get the account type from the artifact
// CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, corrAttrValue); BlackboardAttribute accountTypeAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE));
// if (corrAttr != null) { String accountTypeStr = accountTypeAttribute.getValueString();
// corrAttrInstances.add(corrAttr);
// } // Get the corresponding CentralRepoAccountType from the database.
CentralRepoAccountType crAccountType = CentralRepository.getInstance().getAccountTypeByName(accountTypeStr);
int corrTypeId = crAccountType.getCorrelationTypeId();
CorrelationAttributeInstance.Type corrType = CentralRepository.getInstance().getCorrelationTypeById(corrTypeId);
// Get the account identifier
BlackboardAttribute accountIdAttribute = acctArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ID));
String accountIdStr = accountIdAttribute.getValueString();
// add/get the account and get its accountId.
CentralRepoAccount crAccount = CentralRepository.getInstance().getOrCreateAccount(crAccountType, accountIdStr);
CorrelationAttributeInstance corrAttr = makeCorrAttr(acctArtifact, corrType, accountIdStr);
if (corrAttr != null) {
// set the account_id in correlation attribute
corrAttr.setAccountId(crAccount.getAccountId());
corrAttrInstances.add(corrAttr);
}
} }
/** /**

View File

@ -0,0 +1,31 @@
/*
* Central Repository
*
* Copyright 2015-2020 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.centralrepository.datamodel;
/**
* provides the status of the database after attempting to validate central repo settings
*/
public enum DatabaseTestResult {
UNTESTED,
CONNECTION_FAILED,
SCHEMA_INVALID,
DB_DOES_NOT_EXIST,
TESTEDOK;
}

View File

@ -40,7 +40,7 @@ import static org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepo
* NOTE: This is public scope because the options panel calls it directly to * NOTE: This is public scope because the options panel calls it directly to
* set/get * set/get
*/ */
public final class PostgresCentralRepoSettings { public final class PostgresCentralRepoSettings implements CentralRepoDbSettings {
private final static Logger LOGGER = Logger.getLogger(PostgresCentralRepoSettings.class.getName()); private final static Logger LOGGER = Logger.getLogger(PostgresCentralRepoSettings.class.getName());
private final static String DEFAULT_HOST = ""; // NON-NLS private final static String DEFAULT_HOST = ""; // NON-NLS
@ -64,6 +64,12 @@ public final class PostgresCentralRepoSettings {
loadSettings(); loadSettings();
} }
@Override
public String toString() {
return String.format("PostgresCentralRepoSettings: [db type: postgres, host: %s:%d, db name: %s, username: %s]",
getHost(), getPort(), getDbName(), getUserName());
}
public void loadSettings() { public void loadSettings() {
host = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS host = ModuleSettings.getConfigSetting("CentralRepository", "db.postgresql.host"); // NON-NLS
if (host == null || host.isEmpty()) { if (host == null || host.isEmpty()) {
@ -187,6 +193,7 @@ public final class PostgresCentralRepoSettings {
* *
* @return true if successfull connection, else false. * @return true if successfull connection, else false.
*/ */
@Override
public boolean verifyConnection() { public boolean verifyConnection() {
Connection conn = getEphemeralConnection(true); Connection conn = getEphemeralConnection(true);
if (null == conn) { if (null == conn) {
@ -203,6 +210,7 @@ public final class PostgresCentralRepoSettings {
* *
* @return true if exists, else false * @return true if exists, else false
*/ */
@Override
public boolean verifyDatabaseExists() { public boolean verifyDatabaseExists() {
Connection conn = getEphemeralConnection(true); Connection conn = getEphemeralConnection(true);
if (null == conn) { if (null == conn) {
@ -236,6 +244,7 @@ public final class PostgresCentralRepoSettings {
* *
* @return true if successful connection, else false. * @return true if successful connection, else false.
*/ */
@Override
public boolean verifyDatabaseSchema() { public boolean verifyDatabaseSchema() {
Connection conn = getEphemeralConnection(false); Connection conn = getEphemeralConnection(false);
if (null == conn) { if (null == conn) {
@ -248,6 +257,7 @@ public final class PostgresCentralRepoSettings {
return result; return result;
} }
@Override
public boolean createDatabase() { public boolean createDatabase() {
Connection conn = getEphemeralConnection(true); Connection conn = getEphemeralConnection(true);
if (null == conn) { if (null == conn) {
@ -269,6 +279,7 @@ public final class PostgresCentralRepoSettings {
} }
@Override
public boolean deleteDatabase() { public boolean deleteDatabase() {
Connection conn = getEphemeralConnection(true); Connection conn = getEphemeralConnection(true);
if (null == conn) { if (null == conn) {

View File

@ -41,12 +41,15 @@ import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoAccount.CentralRepoAccountType;
import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil.updateSchemaVersion; import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil.updateSchemaVersion;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.healthmonitor.HealthMonitor; import org.sleuthkit.autopsy.healthmonitor.HealthMonitor;
import org.sleuthkit.autopsy.healthmonitor.TimingMetric; import org.sleuthkit.autopsy.healthmonitor.TimingMetric;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber; import org.sleuthkit.datamodel.CaseDbSchemaVersionNumber;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
@ -71,6 +74,12 @@ abstract class RdbmsCentralRepo implements CentralRepository {
private final Map<String, Collection<CorrelationAttributeInstance>> bulkArtifacts; private final Map<String, Collection<CorrelationAttributeInstance>> bulkArtifacts;
private static final int CASE_CACHE_TIMEOUT = 5; private static final int CASE_CACHE_TIMEOUT = 5;
private static final int DATA_SOURCE_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<Pair<CentralRepoAccountType, String>, CentralRepoAccount> accountsCache = CacheBuilder.newBuilder()
.expireAfterWrite(ACCOUNTS_CACHE_TIMEOUT, TimeUnit.MINUTES).
build();
private static final Cache<Integer, CorrelationAttributeInstance.Type> typeCache = CacheBuilder.newBuilder().build(); private static final Cache<Integer, CorrelationAttributeInstance.Type> typeCache = CacheBuilder.newBuilder().build();
private static final Cache<String, CorrelationCase> caseCacheByUUID = CacheBuilder.newBuilder() private static final Cache<String, CorrelationCase> caseCacheByUUID = CacheBuilder.newBuilder()
.expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES). .expireAfterWrite(CASE_CACHE_TIMEOUT, TimeUnit.MINUTES).
@ -993,22 +1002,22 @@ abstract class RdbmsCentralRepo implements CentralRepository {
public void addArtifactInstance(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { public void addArtifactInstance(CorrelationAttributeInstance eamArtifact) throws CentralRepoException {
checkAddArtifactInstanceNulls(eamArtifact); checkAddArtifactInstanceNulls(eamArtifact);
Connection conn = connect();
PreparedStatement preparedStatement = null;
// @@@ We should cache the case and data source IDs in memory // @@@ We should cache the case and data source IDs in memory
String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType()); String tableName = CentralRepoDbUtil.correlationTypeToInstanceTableName(eamArtifact.getCorrelationType());
String sql String sql
= "INSERT INTO " = "INSERT INTO "
+ tableName + tableName
+ "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id) " + "(case_id, data_source_id, value, file_path, known_status, comment, file_obj_id, account_id) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?) "
+ getConflictClause(); + getConflictClause();
try { try (Connection conn = connect();
preparedStatement = conn.prepareStatement(sql); PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
if (!eamArtifact.getCorrelationValue().isEmpty()) { if (!eamArtifact.getCorrelationValue().isEmpty()) {
preparedStatement.setInt(1, eamArtifact.getCorrelationCase().getID()); preparedStatement.setInt(1, eamArtifact.getCorrelationCase().getID());
preparedStatement.setInt(2, eamArtifact.getCorrelationDataSource().getID()); preparedStatement.setInt(2, eamArtifact.getCorrelationDataSource().getID());
@ -1022,18 +1031,163 @@ abstract class RdbmsCentralRepo implements CentralRepository {
preparedStatement.setString(6, eamArtifact.getComment()); preparedStatement.setString(6, eamArtifact.getComment());
} }
preparedStatement.setLong(7, eamArtifact.getFileObjectId()); preparedStatement.setLong(7, eamArtifact.getFileObjectId());
if (eamArtifact.getAccountId() >= 0) {
preparedStatement.setLong(8, eamArtifact.getAccountId());
} else {
preparedStatement.setNull(8, Types.INTEGER);
}
preparedStatement.executeUpdate(); preparedStatement.executeUpdate();
} }
} catch (SQLException ex) { } catch (SQLException ex) {
throw new CentralRepoException("Error inserting new artifact into artifacts table.", ex); // NON-NLS throw new CentralRepoException("Error inserting new artifact into artifacts table.", ex); // NON-NLS
} finally { }
CentralRepoDbUtil.closeStatement(preparedStatement);
CentralRepoDbUtil.closeConnection(conn);
}
} }
/**
* Gets the Central Repo account for the given account type and account ID.
* Create a new account first, if one doesn't exist
*
* @param accountType account type
* @param accountUniqueID unique account identifier
*
* @return A matching account, either existing or newly created.
*
* @throws TskCoreException exception thrown if a critical error occurs
* within TSK core
*/
@Override
public CentralRepoAccount getOrCreateAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
// Get the account fom the accounts table
CentralRepoAccount account = getAccount(crAccountType, accountUniqueID);
// account not found in the table, create it
if (null == account) {
String query = "INSERT INTO accounts (account_type_id, account_unique_identifier) "
+ "VALUES ( " + crAccountType.getAccountTypeId() + ", '"
+ accountUniqueID + "' )";
try (Connection connection = connect();
Statement s = connection.createStatement();) {
s.execute(query);
// get the account from the db - should exist now.
account = getAccount(crAccountType, accountUniqueID);
} catch (SQLException ex) {
throw new CentralRepoException("Error adding an account to CR database.", ex);
}
}
return account;
}
@Override
public CentralRepoAccountType getAccountTypeByName(String accountTypeName) throws CentralRepoException {
try {
return accountTypesCache.get(accountTypeName, () -> getCRAccountTypeFromDb(accountTypeName));
} catch (CacheLoader.InvalidCacheLoadException | ExecutionException ex) {
throw new CentralRepoException("Error looking up CR account type in cache.", ex);
}
}
/**
* Gets the CR account type for the specified type name.
*
* @param accountTypeName account type name to look for
* @return CR account type
*
* @throws CentralRepoException
*/
private CentralRepoAccountType getCRAccountTypeFromDb(String accountTypeName) throws CentralRepoException {
String sql = "SELECT * FROM account_types WHERE type_name = ?";
try ( Connection conn = connect();
PreparedStatement preparedStatement = conn.prepareStatement(sql);) {
preparedStatement.setString(1, accountTypeName);
try (ResultSet resultSet = preparedStatement.executeQuery();) {
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;
} else {
throw new CentralRepoException("Failed to find entry for account type = " + accountTypeName);
}
}
} catch (SQLException ex) {
throw new CentralRepoException("Error getting correlation type by id.", ex); // NON-NLS
}
}
/**
* Get the CR account with the given account type and the unique account identifier.
* Looks in the cache first.
* If not found in cache, reads from the database and saves in cache.
*
* Returns null if the account is not found in the cache and not in the database.
*
* @param crAccountType account type to look for
* @param accountUniqueID unique account id
* @return CentralRepoAccount for the give type/id. May return null if not found.
*
* @throws CentralRepoException
*/
private CentralRepoAccount getAccount(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
CentralRepoAccount crAccount = accountsCache.getIfPresent(Pair.of(crAccountType, accountUniqueID));
if (crAccount == null) {
crAccount = getCRAccountFromDb(crAccountType, accountUniqueID);
if (crAccount != null) {
accountsCache.put(Pair.of(crAccountType, accountUniqueID), crAccount);
}
}
return crAccount;
}
/**
* Get the Account with the given account type and account identifier,
* from the database.
*
* @param accountType account type
* @param accountUniqueID unique account identifier
*
* @return Account, returns NULL is no matching account found
*
* @throws TskCoreException exception thrown if a critical error occurs
* within TSK core
*/
private CentralRepoAccount getCRAccountFromDb(CentralRepoAccountType crAccountType, String accountUniqueID) throws CentralRepoException {
CentralRepoAccount account = null;
String sql = "SELECT * FROM accounts WHERE account_type_id = ? AND account_unique_identifier = ?";
try ( Connection connection = connect();
PreparedStatement preparedStatement = connection.prepareStatement(sql);) {
preparedStatement.setInt(1, crAccountType.getAccountTypeId());
preparedStatement.setString(2, accountUniqueID);
try (ResultSet resultSet = preparedStatement.executeQuery();) {
if (resultSet.next()) {
account = new CentralRepoAccount(resultSet.getInt("id"), crAccountType, resultSet.getString("account_unique_identifier")); //NON-NLS
}
}
} catch (SQLException ex) {
throw new CentralRepoException("Error getting account type id", ex);
}
return account;
}
private void checkAddArtifactInstanceNulls(CorrelationAttributeInstance eamArtifact) throws CentralRepoException { private void checkAddArtifactInstanceNulls(CorrelationAttributeInstance eamArtifact) throws CentralRepoException {
if (eamArtifact == null) { if (eamArtifact == null) {
throw new CentralRepoException("CorrelationAttribute is null"); throw new CentralRepoException("CorrelationAttribute is null");

View File

@ -37,10 +37,10 @@ import org.sleuthkit.autopsy.coreutils.PlatformUtil;
* NOTE: This is public scope because the options panel calls it directly to * NOTE: This is public scope because the options panel calls it directly to
* set/get * set/get
*/ */
public final class SqliteCentralRepoSettings { public final class SqliteCentralRepoSettings implements CentralRepoDbSettings {
public final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS
private final static Logger LOGGER = Logger.getLogger(SqliteCentralRepoSettings.class.getName()); private final static Logger LOGGER = Logger.getLogger(SqliteCentralRepoSettings.class.getName());
private final static String DEFAULT_DBNAME = "central_repository.db"; // NON-NLS
private final static String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS private final static String DEFAULT_DBDIRECTORY = PlatformUtil.getUserDirectory() + File.separator + "central_repository"; // NON-NLS
private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS private final static String JDBC_DRIVER = "org.sqlite.JDBC"; // NON-NLS
private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS private final static String JDBC_BASE_URI = "jdbc:sqlite:"; // NON-NLS
@ -80,6 +80,18 @@ public final class SqliteCentralRepoSettings {
this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD; this.bulkThreshold = RdbmsCentralRepo.DEFAULT_BULK_THRESHHOLD;
} }
} }
public String toString() {
return String.format("SqliteCentralRepoSettings: [db type: sqlite, directory: %s, name: %s]", getDbDirectory(), getDbName());
}
/**
* sets database directory and name to defaults
*/
public void setupDefaultSettings() {
dbName = DEFAULT_DBNAME;
dbDirectory = DEFAULT_DBDIRECTORY;
}
public void saveSettings() { public void saveSettings() {
createDbDirectory(); createDbDirectory();
@ -103,6 +115,13 @@ public final class SqliteCentralRepoSettings {
return (!dbFile.isDirectory()); return (!dbFile.isDirectory());
} }
@Override
public boolean verifyDatabaseExists() {
return dbDirectoryExists();
}
/** /**
* Verify that the db directory path exists. * Verify that the db directory path exists.
* *
@ -122,6 +141,16 @@ public final class SqliteCentralRepoSettings {
} }
/**
* creates database directory for sqlite database if it does not exist
* @return whether or not operation occurred successfully
*/
@Override
public boolean createDatabase() {
return createDbDirectory();
}
/** /**
* Create the db directory if it does not exist. * Create the db directory if it does not exist.
* *
@ -325,5 +354,4 @@ public final class SqliteCentralRepoSettings {
String getJDBCBaseURI() { String getJDBCBaseURI() {
return JDBC_BASE_URI; return JDBC_BASE_URI;
} }
} }

View File

@ -7,3 +7,6 @@ IngestEventsListener.prevCount.text=Number of previous {0}: {1}
IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository) IngestEventsListener.prevExists.text=Previously Seen Devices (Central Repository)
IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository) IngestEventsListener.prevTaggedSet.text=Previously Tagged As Notable (Central Repository)
Installer.centralRepoUpgradeFailed.title=Central repository disabled Installer.centralRepoUpgradeFailed.title=Central repository disabled
Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. You can use this to ignore previously seen files and make connections between cases.
Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to?
Installer.initialCreateSqlite.title=Enable Central Repository?

View File

@ -18,15 +18,20 @@
*/ */
package org.sleuthkit.autopsy.centralrepository.eventlisteners; package org.sleuthkit.autopsy.centralrepository.eventlisteners;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import java.util.logging.Level;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import org.openide.modules.ModuleInstall; import org.openide.modules.ModuleInstall;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbUtil;
import org.sleuthkit.autopsy.core.RuntimeProperties; import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings;
/** /**
* Install event listeners during module initialization * Install event listeners during module initialization
@ -51,26 +56,109 @@ public class Installer extends ModuleInstall {
super(); super();
} }
@NbBundle.Messages({"Installer.centralRepoUpgradeFailed.title=Central repository disabled"}) @NbBundle.Messages({
"Installer.initialCreateSqlite.title=Enable Central Repository?",
"Installer.initialCreateSqlite.messageHeader=The Central Repository is not enabled. Would you like to enable it?",
"Installer.initialCreateSqlite.messageDesc=It will store information about all hashes and identifiers that you process. " +
"You can use this to ignore previously seen files and make connections between cases."
})
@Override @Override
public void restored() { public void restored() {
Case.addPropertyChangeListener(pcl); Case.addPropertyChangeListener(pcl);
ieListener.installListeners(); ieListener.installListeners();
// Perform the database upgrade and inform the user if it fails
try { Map<String, String> centralRepoSettings = ModuleSettings.getConfigSettings("CentralRepository");
CentralRepoDbUtil.upgradeDatabase(); String initializedStr = centralRepoSettings.get("initialized");
} catch (CentralRepoException ex) {
if (RuntimeProperties.runningWithGUI()) { // check to see if the repo has been initialized asking to setup cr
WindowManager.getDefault().invokeWhenUIReady(() -> { boolean initialized = Boolean.parseBoolean(initializedStr);
JOptionPane.showMessageDialog(null,
ex.getUserMessage(), // if it hasn't received that flag, check for a previous install where cr is already setup
NbBundle.getMessage(this.getClass(), if (!initialized) {
"Installer.centralRepoUpgradeFailed.title"), boolean prevRepo = Boolean.parseBoolean(centralRepoSettings.get("db.useCentralRepo"));
JOptionPane.ERROR_MESSAGE); // if it has been previously set up and is in use, mark as previously initialized and save the settings
}); if (prevRepo) {
initialized = true;
ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true");
} }
} }
// if central repository hasn't been previously initialized, initialize it
if (!initialized) {
// if running with a GUI, prompt the user
if (RuntimeProperties.runningWithGUI()) {
try {
SwingUtilities.invokeAndWait(() -> {
try {
String dialogText =
"<html><body>" +
"<div style='width: 400px;'>" +
"<p>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageHeader") + "</p>" +
"<p style='margin-top: 10px'>" + NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.messageDesc") + "</p>" +
"</div>" +
"</body></html>";
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(),
dialogText,
NbBundle.getMessage(this.getClass(), "Installer.initialCreateSqlite.title"),
JOptionPane.YES_NO_OPTION)) {
setupDefaultSqlite();
}
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
reportUpgradeError(ex);
}
});
} catch (InterruptedException | InvocationTargetException ex) {
LOGGER.log(Level.SEVERE, "There was an error while running the swing utility invoke later while creating the central repository database", ex);
}
} // if no GUI, just initialize
else {
try {
setupDefaultSqlite();
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "There was an error while initializing the central repository database", ex);
reportUpgradeError(ex);
}
}
ModuleSettings.setConfigSetting("CentralRepository", "initialized", "true");
}
// now run regular module startup code
try {
CentralRepoDbManager.upgradeDatabase();
} catch (CentralRepoException ex) {
LOGGER.log(Level.SEVERE, "There was an error while upgrading the central repository database", ex);
if (RuntimeProperties.runningWithGUI()) {
reportUpgradeError(ex);
}
}
}
private void setupDefaultSqlite() throws CentralRepoException {
CentralRepoDbManager manager = new CentralRepoDbManager();
manager.setupDefaultSqliteDb();
}
@NbBundle.Messages({ "Installer.centralRepoUpgradeFailed.title=Central repository disabled" })
private void reportUpgradeError(CentralRepoException ex) {
try {
SwingUtilities.invokeAndWait(() -> {
JOptionPane.showMessageDialog(null,
ex.getUserMessage(),
NbBundle.getMessage(this.getClass(),
"Installer.centralRepoUpgradeFailed.title"),
JOptionPane.ERROR_MESSAGE);
});
} catch (InterruptedException | InvocationTargetException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
} }
@Override @Override

View File

@ -67,7 +67,7 @@ final class CentralRepoIngestModule implements FileIngestModule {
private static final String MODULE_NAME = CentralRepoIngestModuleFactory.getModuleName(); private static final String MODULE_NAME = CentralRepoIngestModuleFactory.getModuleName();
static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = true; static final boolean DEFAULT_FLAG_TAGGED_NOTABLE_ITEMS = true;
static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = true; static final boolean DEFAULT_FLAG_PREVIOUS_DEVICES = false;
static final boolean DEFAULT_CREATE_CR_PROPERTIES = true; static final boolean DEFAULT_CREATE_CR_PROPERTIES = true;
private final static Logger logger = Logger.getLogger(CentralRepoIngestModule.class.getName()); private final static Logger logger = Logger.getLogger(CentralRepoIngestModule.class.getName());

View File

@ -35,14 +35,15 @@ import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileFilter;
import org.netbeans.spi.options.OptionsPanelController; import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
import org.sleuthkit.autopsy.corecomponents.TextPrompt; import org.sleuthkit.autopsy.corecomponents.TextPrompt;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms;
import static org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoPlatforms.SQLITE; import org.sleuthkit.autopsy.centralrepository.datamodel.DatabaseTestResult;
import org.sleuthkit.autopsy.centralrepository.datamodel.PostgresCentralRepoSettings;
import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteCentralRepoSettings; import org.sleuthkit.autopsy.centralrepository.datamodel.SqliteCentralRepoSettings;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory; import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory;
@ -54,17 +55,12 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.RdbmsCentralRepoFactory
public class EamDbSettingsDialog extends JDialog { public class EamDbSettingsDialog extends JDialog {
private static final Logger logger = Logger.getLogger(EamDbSettingsDialog.class.getName()); private static final Logger logger = Logger.getLogger(EamDbSettingsDialog.class.getName());
private static final String CENTRAL_REPO_DB_NAME = "central_repository";
private static final String CENTRAL_REPO_SQLITE_EXT = ".db";
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Collection<JTextField> textBoxes; private final Collection<JTextField> textBoxes;
private final TextBoxChangedListener textBoxChangedListener; private final TextBoxChangedListener textBoxChangedListener;
private final CentralRepoDbManager manager = new CentralRepoDbManager();
private final PostgresCentralRepoSettings dbSettingsPostgres;
private final SqliteCentralRepoSettings dbSettingsSqlite;
private DatabaseTestResult testingStatus;
private CentralRepoPlatforms selectedPlatform;
private boolean configurationChanged = false;
/** /**
* Creates new form EamDbSettingsDialog * Creates new form EamDbSettingsDialog
@ -73,7 +69,6 @@ public class EamDbSettingsDialog extends JDialog {
"EamDbSettingsDialog.lbSingleUserSqLite.text=SQLite should only be used by one examiner at a time.", "EamDbSettingsDialog.lbSingleUserSqLite.text=SQLite should only be used by one examiner at a time.",
"EamDbSettingsDialog.lbDatabaseType.text=Database Type :", "EamDbSettingsDialog.lbDatabaseType.text=Database Type :",
"EamDbSettingsDialog.fcDatabasePath.title=Select location for central_repository.db"}) "EamDbSettingsDialog.fcDatabasePath.title=Select location for central_repository.db"})
public EamDbSettingsDialog() { public EamDbSettingsDialog() {
super((JFrame) WindowManager.getDefault().getMainWindow(), super((JFrame) WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_title_text(), Bundle.EamDbSettingsDialog_title_text(),
@ -81,12 +76,6 @@ public class EamDbSettingsDialog extends JDialog {
textBoxes = new ArrayList<>(); textBoxes = new ArrayList<>();
textBoxChangedListener = new TextBoxChangedListener(); textBoxChangedListener = new TextBoxChangedListener();
dbSettingsPostgres = new PostgresCentralRepoSettings();
dbSettingsSqlite = new SqliteCentralRepoSettings();
selectedPlatform = CentralRepoPlatforms.getSelectedPlatform();
if (selectedPlatform == null || selectedPlatform.equals(CentralRepoPlatforms.DISABLED)) {
selectedPlatform = CentralRepoPlatforms.POSTGRESQL;
}
initComponents(); initComponents();
fcDatabasePath.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); fcDatabasePath.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
@ -98,7 +87,7 @@ public class EamDbSettingsDialog extends JDialog {
if (pathname.isDirectory()) { if (pathname.isDirectory()) {
return true; return true;
} }
return pathname.getName().toLowerCase().equals((CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT).toLowerCase()); return pathname.getName().equalsIgnoreCase(SqliteCentralRepoSettings.DEFAULT_DBNAME);
} }
@Override @Override
@ -106,12 +95,77 @@ public class EamDbSettingsDialog extends JDialog {
return "Directories and Central Repository databases"; return "Directories and Central Repository databases";
} }
}); });
cbDatabaseType.setSelectedItem(selectedPlatform); cbDatabaseType.setSelectedItem(manager.getSelectedPlatform());
customizeComponents(); customizeComponents();
valid(); valid();
display(); display();
} }
/**
* prompts user based on testing status (i.e. failure to connect, invalid schema, db does not exist, etc.)
* @return whether or not the ultimate status after prompts is okay to continue
*/
@NbBundle.Messages({"EamDbSettingsDialog.okButton.corruptDatabaseExists.title=Error Loading Database",
"EamDbSettingsDialog.okButton.corruptDatabaseExists.message=Database exists but is not the right format. Manually delete it or choose a different path (if applicable).",
"EamDbSettingsDialog.okButton.createDbDialog.title=Database Does Not Exist",
"EamDbSettingsDialog.okButton.createDbDialog.message=Database does not exist, would you like to create it?",
"EamDbSettingsDialog.okButton.databaseConnectionFailed.title=Database Connection Failed",
"EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database please check your settings and try again.",
"EamDbSettingsDialog.okButton.createSQLiteDbError.message=Unable to create SQLite Database, please ensure location exists and you have write permissions and try again.",
"EamDbSettingsDialog.okButton.createPostgresDbError.message=Unable to create Postgres Database, please ensure address, port, and login credentials are correct for Postgres server and try again.",
"EamDbSettingsDialog.okButton.createDbError.title=Unable to Create Database"})
private boolean promptTestStatusWarnings() {
if (manager.getStatus() == DatabaseTestResult.CONNECTION_FAILED) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_message(),
Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_title(),
JOptionPane.WARNING_MESSAGE);
} else if (manager.getStatus() == DatabaseTestResult.SCHEMA_INVALID) {
// There's an existing database or file, but it's not in our format.
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_message(),
Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_title(),
JOptionPane.WARNING_MESSAGE);
} else if (manager.getStatus() == DatabaseTestResult.DB_DOES_NOT_EXIST) {
//database doesn't exist. do you want to create?
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_createDbDialog_message(),
Bundle.EamDbSettingsDialog_okButton_createDbDialog_title(),
JOptionPane.YES_NO_OPTION)) {
try {
manager.createDb();
}
catch (CentralRepoException e) {
// in the event that there is a failure to connect, notify user with corresponding message
String errorMessage;
switch (manager.getSelectedPlatform()) {
case POSTGRESQL:
errorMessage = Bundle.EamDbSettingsDialog_okButton_createPostgresDbError_message();
break;
case SQLITE:
errorMessage = Bundle.EamDbSettingsDialog_okButton_createSQLiteDbError_message();
break;
default:
errorMessage = "";
break;
}
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
errorMessage,
Bundle.EamDbSettingsDialog_okButton_createDbError_title(),
JOptionPane.WARNING_MESSAGE);
}
valid();
}
}
return (manager.getStatus() == DatabaseTestResult.TESTEDOK);
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
@ -359,21 +413,16 @@ public class EamDbSettingsDialog extends JDialog {
private void customizeComponents() { private void customizeComponents() {
setTextPrompts(); setTextPrompts();
setTextBoxListeners(); setTextBoxListeners();
switch (selectedPlatform) { manager.clearStatus();
case SQLITE: if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) {
testingStatus = DatabaseTestResult.UNTESTED; updatePostgresFields(false);
updatePostgresFields(false); updateSqliteFields(true);
updateSqliteFields(true);
break;
default:
POSTGRESQL:
testingStatus = DatabaseTestResult.UNTESTED;
updatePostgresFields(true);
updateSqliteFields(false);
break;
} }
displayDatabaseSettings(selectedPlatform.equals(CentralRepoPlatforms.POSTGRESQL)); else {
updatePostgresFields(true);
updateSqliteFields(false);
}
displayDatabaseSettings(CentralRepoPlatforms.POSTGRESQL.equals(manager.getSelectedPlatform()));
} }
private void display() { private void display() {
@ -383,7 +432,7 @@ public class EamDbSettingsDialog extends JDialog {
@Messages({"EamDbSettingsDialog.chooserPath.failedToGetDbPathMsg=Selected database path is invalid. Try again."}) @Messages({"EamDbSettingsDialog.chooserPath.failedToGetDbPathMsg=Selected database path is invalid. Try again."})
private void bnDatabasePathFileOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDatabasePathFileOpenActionPerformed private void bnDatabasePathFileOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnDatabasePathFileOpenActionPerformed
fcDatabasePath.setSelectedFile(new File(dbSettingsSqlite.getDbDirectory())); fcDatabasePath.setSelectedFile(new File(manager.getDbSettingsSqlite().getDbDirectory()));
if (fcDatabasePath.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { if (fcDatabasePath.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
File databaseFile = fcDatabasePath.getSelectedFile(); File databaseFile = fcDatabasePath.getSelectedFile();
if (databaseFile.isFile()) { if (databaseFile.isFile()) {
@ -399,110 +448,38 @@ public class EamDbSettingsDialog extends JDialog {
} }
}//GEN-LAST:event_bnDatabasePathFileOpenActionPerformed }//GEN-LAST:event_bnDatabasePathFileOpenActionPerformed
private void testDbSettings() { @NbBundle.Messages({"EamDbSettingsDialog.okButton.errorTitle.text=Restart Required.",
switch (selectedPlatform) { "EamDbSettingsDialog.okButton.errorMsg.text=Please restart Autopsy to begin using the new database platform.",
case POSTGRESQL: "EamDbSettingsDialog.okButton.connectionErrorMsg.text=Failed to connect to central repository database."})
if (dbSettingsPostgres.verifyConnection()) { private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed
if (dbSettingsPostgres.verifyDatabaseExists()) { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
if (dbSettingsPostgres.verifyDatabaseSchema()) { manager.testStatus();
testingStatus = DatabaseTestResult.TESTEDOK;
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST;
}
} else {
testingStatus = DatabaseTestResult.CONNECTION_FAILED;
}
break;
case SQLITE:
if (dbSettingsSqlite.dbFileExists()) {
if (dbSettingsSqlite.verifyConnection()) {
if (dbSettingsSqlite.verifyDatabaseSchema()) {
testingStatus = DatabaseTestResult.TESTEDOK;
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.SCHEMA_INVALID;
}
} else {
testingStatus = DatabaseTestResult.DB_DOES_NOT_EXIST;
}
break;
}
valid(); valid();
}
boolean testedOk = promptTestStatusWarnings();
@Messages({"EamDbSettingsDialog.okButton.createDbError.title=Unable to Create Database", if (!testedOk) {
"EamDbSettingsDialog.okButton.createSQLiteDbError.message=Unable to create SQLite Database, please ensure location exists and you have write permissions and try again.", setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
"EamDbSettingsDialog.okButton.createPostgresDbError.message=Unable to create Postgres Database, please ensure address, port, and login credentials are correct for Postgres server and try again."}) return;
private void createDb() {
boolean result = false;
boolean dbCreated = true;
switch (selectedPlatform) {
case POSTGRESQL:
if (!dbSettingsPostgres.verifyDatabaseExists()) {
dbCreated = dbSettingsPostgres.createDatabase();
}
if (dbCreated) {
try {
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsPostgres);
result = centralRepoSchemaFactory.initializeDatabaseSchema()
&& centralRepoSchemaFactory.insertDefaultDatabaseContent();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Unable to initialize database schema or insert contents into Postgres central repository.", ex);
}
}
if (!result) {
// Remove the incomplete database
if (dbCreated) {
dbSettingsPostgres.deleteDatabase();
}
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_createPostgresDbError_message(),
Bundle.EamDbSettingsDialog_okButton_createDbError_title(),
JOptionPane.WARNING_MESSAGE);
logger.severe("Unable to initialize database schema or insert contents into central repository.");
return;
}
break;
case SQLITE:
if (!dbSettingsSqlite.dbDirectoryExists()) {
dbCreated = dbSettingsSqlite.createDbDirectory();
}
if (dbCreated) {
try {
RdbmsCentralRepoFactory centralRepoSchemaFactory = new RdbmsCentralRepoFactory(selectedPlatform, dbSettingsSqlite);
result = centralRepoSchemaFactory.initializeDatabaseSchema()
&& centralRepoSchemaFactory.insertDefaultDatabaseContent();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Unable to initialize database schema or insert contents into SQLite central repository.", ex);
}
}
if (!result) {
if (dbCreated) {
dbSettingsSqlite.deleteDatabase();
}
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_createSQLiteDbError_message(),
Bundle.EamDbSettingsDialog_okButton_createDbError_title(),
JOptionPane.WARNING_MESSAGE);
logger.severe("Unable to initialize database schema or insert contents into central repository.");
return;
}
break;
} }
testingStatus = DatabaseTestResult.TESTEDOK;
valid(); try{
} manager.saveNewCentralRepo();
}
catch (CentralRepoException e) {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(this,
Bundle.EamDbSettingsDialog_okButton_errorMsg_text(),
Bundle.EamDbSettingsDialog_okButton_errorTitle_text(),
JOptionPane.WARNING_MESSAGE);
});
}
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
dispose();
}//GEN-LAST:event_bnOkActionPerformed
/** /**
* Returns if changes to the central repository configuration were * Returns if changes to the central repository configuration were
* successfully applied * successfully applied
@ -510,128 +487,22 @@ public class EamDbSettingsDialog extends JDialog {
* @return true if the database configuration was successfully changed false * @return true if the database configuration was successfully changed false
* if it was not * if it was not
*/ */
boolean wasConfigurationChanged() { public boolean wasConfigurationChanged() {
return configurationChanged; return manager.wasConfigurationChanged();
} }
@Messages({"EamDbSettingsDialog.okButton.errorTitle.text=Restart Required.",
"EamDbSettingsDialog.okButton.errorMsg.text=Please restart Autopsy to begin using the new database platform.",
"EamDbSettingsDialog.okButton.connectionErrorMsg.text=Failed to connect to central repository database.",
"EamDbSettingsDialog.okButton.corruptDatabaseExists.title=Error Loading Database",
"EamDbSettingsDialog.okButton.corruptDatabaseExists.message=Database exists but is not the right format. Manually delete it or choose a different path (if applicable).",
"EamDbSettingsDialog.okButton.createDbDialog.title=Database Does Not Exist",
"EamDbSettingsDialog.okButton.createDbDialog.message=Database does not exist, would you like to create it?",
"EamDbSettingsDialog.okButton.databaseConnectionFailed.title=Database Connection Failed",
"EamDbSettingsDialog.okButton.databaseConnectionFailed.message=Unable to connect to database please check your settings and try again."})
private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
testDbSettings();
if (testingStatus == DatabaseTestResult.CONNECTION_FAILED) {
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_message(),
Bundle.EamDbSettingsDialog_okButton_databaseConnectionFailed_title(),
JOptionPane.WARNING_MESSAGE);
} else if (testingStatus == DatabaseTestResult.SCHEMA_INVALID) {
// There's an existing database or file, but it's not in our format.
JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_message(),
Bundle.EamDbSettingsDialog_okButton_corruptDatabaseExists_title(),
JOptionPane.WARNING_MESSAGE);
} else if (testingStatus == DatabaseTestResult.DB_DOES_NOT_EXIST) {
//database doesn't exist do you want to create
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(),
Bundle.EamDbSettingsDialog_okButton_createDbDialog_message(),
Bundle.EamDbSettingsDialog_okButton_createDbDialog_title(),
JOptionPane.YES_NO_OPTION)) {
createDb();
}
}
if (testingStatus != DatabaseTestResult.TESTEDOK) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
return;
}
/**
* We have to shutdown the previous platform's connection pool first;
* assuming it wasn't DISABLED. This will close any existing idle
* connections.
*
* The next use of an EamDb API method will start a new connection pool
* using those new settings.
*/
try {
CentralRepository previousDbManager = CentralRepository.getInstance();
if (null != previousDbManager) {
// NOTE: do not set/save the seleted platform before calling this.
CentralRepository.getInstance().shutdownConnections();
}
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Failed to close database connections in previously selected platform.", ex); // NON-NLS
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(this,
Bundle.EamDbSettingsDialog_okButton_errorMsg_text(),
Bundle.EamDbSettingsDialog_okButton_errorTitle_text(),
JOptionPane.WARNING_MESSAGE);
});
}
// Even if we fail to close the existing connections, make sure that we
// save the new connection settings, so an Autopsy restart will correctly
// start with the new settings.
CentralRepoPlatforms.setSelectedPlatform(selectedPlatform.name());
CentralRepoPlatforms.saveSelectedPlatform();
switch (selectedPlatform) {
case POSTGRESQL:
// save the new PostgreSQL settings
dbSettingsPostgres.saveSettings();
// Load those newly saved settings into the postgres db manager instance
// in case we are still using the same instance.
try {
CentralRepository.getInstance().updateSettings();
configurationChanged = true;
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, Bundle.EamDbSettingsDialog_okButton_connectionErrorMsg_text(), ex); //NON-NLS
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
return;
}
break;
case SQLITE:
// save the new SQLite settings
dbSettingsSqlite.saveSettings();
// Load those newly saved settings into the sqlite db manager instance
// in case we are still using the same instance.
try {
CentralRepository.getInstance().updateSettings();
configurationChanged = true;
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, Bundle.EamDbSettingsDialog_okButton_connectionErrorMsg_text(), ex); //NON-NLS
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
return;
}
break;
case DISABLED:
break;
}
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
dispose();
}//GEN-LAST:event_bnOkActionPerformed
private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed
dispose(); dispose();
}//GEN-LAST:event_bnCancelActionPerformed }//GEN-LAST:event_bnCancelActionPerformed
private void cbDatabaseTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDatabaseTypeActionPerformed private void cbDatabaseTypeActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cbDatabaseTypeActionPerformed
selectedPlatform = (CentralRepoPlatforms) cbDatabaseType.getSelectedItem(); manager.setSelectedPlatform((CentralRepoPlatforms) cbDatabaseType.getSelectedItem());
customizeComponents(); customizeComponents();
}//GEN-LAST:event_cbDatabaseTypeActionPerformed }//GEN-LAST:event_cbDatabaseTypeActionPerformed
private void updateFullDbPath() { private void updateFullDbPath() {
dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT); dataBaseFileTextArea.setText(tfDatabasePath.getText() + File.separator + SqliteCentralRepoSettings.DEFAULT_DBNAME);
dataBaseFileTextArea.setCaretPosition(dataBaseFileTextArea.getText().length()); dataBaseFileTextArea.setCaretPosition(dataBaseFileTextArea.getText().length());
} }
@ -669,13 +540,13 @@ public class EamDbSettingsDialog extends JDialog {
} }
private void updatePostgresFields(boolean enabled) { private void updatePostgresFields(boolean enabled) {
tbDbHostname.setText(enabled ? dbSettingsPostgres.getHost() : ""); tbDbHostname.setText(enabled ? manager.getDbSettingsPostgres().getHost() : "");
tbDbHostname.setEnabled(enabled); tbDbHostname.setEnabled(enabled);
tbDbPort.setText(enabled ? Integer.toString(dbSettingsPostgres.getPort()) : ""); tbDbPort.setText(enabled ? Integer.toString(manager.getDbSettingsPostgres().getPort()) : "");
tbDbPort.setEnabled(enabled); tbDbPort.setEnabled(enabled);
tbDbUsername.setText(enabled ? dbSettingsPostgres.getUserName() : ""); tbDbUsername.setText(enabled ? manager.getDbSettingsPostgres().getUserName() : "");
tbDbUsername.setEnabled(enabled); tbDbUsername.setEnabled(enabled);
jpDbPassword.setText(enabled ? dbSettingsPostgres.getPassword() : ""); jpDbPassword.setText(enabled ? manager.getDbSettingsPostgres().getPassword() : "");
jpDbPassword.setEnabled(enabled); jpDbPassword.setEnabled(enabled);
} }
@ -686,7 +557,7 @@ public class EamDbSettingsDialog extends JDialog {
* @param enabled * @param enabled
*/ */
private void updateSqliteFields(boolean enabled) { private void updateSqliteFields(boolean enabled) {
tfDatabasePath.setText(enabled ? dbSettingsSqlite.getDbDirectory() : ""); tfDatabasePath.setText(enabled ? manager.getDbSettingsSqlite().getDbDirectory() : "");
tfDatabasePath.setEnabled(enabled); tfDatabasePath.setEnabled(enabled);
bnDatabasePathFileOpen.setEnabled(enabled); bnDatabasePathFileOpen.setEnabled(enabled);
} }
@ -739,22 +610,15 @@ public class EamDbSettingsDialog extends JDialog {
@Messages({"EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database."}) @Messages({"EamDbSettingsDialog.validation.incompleteFields=Fill in all values for the selected database."})
private boolean databaseFieldsArePopulated() { private boolean databaseFieldsArePopulated() {
boolean result = true; boolean result = true;
switch (selectedPlatform) { if (manager.getSelectedPlatform() == CentralRepoPlatforms.POSTGRESQL) {
case POSTGRESQL: result = !tbDbHostname.getText().trim().isEmpty()
result = !tbDbHostname.getText().trim().isEmpty() && !tbDbPort.getText().trim().isEmpty()
&& !tbDbPort.getText().trim().isEmpty() // && !tbDbName.getText().trim().isEmpty()
// && !tbDbName.getText().trim().isEmpty() && !tbDbUsername.getText().trim().isEmpty()
&& !tbDbUsername.getText().trim().isEmpty() && 0 < jpDbPassword.getPassword().length;
&& 0 < jpDbPassword.getPassword().length;
break;
case SQLITE:
result = !tfDatabasePath.getText().trim().isEmpty();
break;
} }
else if (manager.getSelectedPlatform() == CentralRepoPlatforms.SQLITE) {
if (!result) { result = !tfDatabasePath.getText().trim().isEmpty();
} }
return result; return result;
@ -770,66 +634,6 @@ public class EamDbSettingsDialog extends JDialog {
&& databaseSettingsAreValid(); && databaseSettingsAreValid();
} }
/**
* Tests whether or not the database settings are valid.
*
* @return True or false.
*/
private boolean databaseSettingsAreValid() {
boolean result = true;
StringBuilder guidanceText = new StringBuilder();
switch (selectedPlatform) {
case POSTGRESQL:
try {
dbSettingsPostgres.setHost(tbDbHostname.getText().trim());
} catch (CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
try {
dbSettingsPostgres.setPort(Integer.valueOf(tbDbPort.getText().trim()));
} catch (NumberFormatException | CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
try {
dbSettingsPostgres.setDbName(CENTRAL_REPO_DB_NAME);
} catch (CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
try {
dbSettingsPostgres.setUserName(tbDbUsername.getText().trim());
} catch (CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
try {
dbSettingsPostgres.setPassword(new String(jpDbPassword.getPassword()));
} catch (CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
break;
case SQLITE:
try {
File databasePath = new File(tfDatabasePath.getText());
dbSettingsSqlite.setDbName(CENTRAL_REPO_DB_NAME + CENTRAL_REPO_SQLITE_EXT);
dbSettingsSqlite.setDbDirectory(databasePath.getPath());
} catch (CentralRepoException ex) {
guidanceText.append(ex.getMessage());
result = false;
}
break;
}
return result;
}
/** /**
* Validates that the form is filled out correctly for our usage. * Validates that the form is filled out correctly for our usage.
@ -856,6 +660,29 @@ public class EamDbSettingsDialog extends JDialog {
return true; return true;
} }
/**
* Tests whether or not the database settings are valid.
*
* @return True or false.
*/
private boolean databaseSettingsAreValid() {
try {
manager.testDatabaseSettingsAreValid(
tbDbHostname.getText().trim(),
tbDbPort.getText().trim(),
tbDbUsername.getText().trim(),
tfDatabasePath.getText().trim(),
new String(jpDbPassword.getPassword()));
}
catch (CentralRepoException | NumberFormatException | IllegalStateException e) {
return false;
}
return true;
}
/** /**
* Used to listen for changes in text boxes. It lets the panel know things * Used to listen for changes in text boxes. It lets the panel know things
@ -866,7 +693,7 @@ public class EamDbSettingsDialog extends JDialog {
@Override @Override
public void changedUpdate(DocumentEvent e) { public void changedUpdate(DocumentEvent e) {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
testingStatus = DatabaseTestResult.UNTESTED; manager.clearStatus();
updateFullDbPath(); updateFullDbPath();
valid(); valid();
} }
@ -874,7 +701,7 @@ public class EamDbSettingsDialog extends JDialog {
@Override @Override
public void insertUpdate(DocumentEvent e) { public void insertUpdate(DocumentEvent e) {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
testingStatus = DatabaseTestResult.UNTESTED; manager.clearStatus();
updateFullDbPath(); updateFullDbPath();
valid(); valid();
} }
@ -882,20 +709,13 @@ public class EamDbSettingsDialog extends JDialog {
@Override @Override
public void removeUpdate(DocumentEvent e) { public void removeUpdate(DocumentEvent e) {
firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null); firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
testingStatus = DatabaseTestResult.UNTESTED; manager.clearStatus();
updateFullDbPath(); updateFullDbPath();
valid(); valid();
} }
} }
private enum DatabaseTestResult {
UNTESTED,
CONNECTION_FAILED,
SCHEMA_INVALID,
DB_DOES_NOT_EXIST,
TESTEDOK;
}
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton bnCancel; private javax.swing.JButton bnCancel;

View File

@ -31,6 +31,7 @@ import org.netbeans.spi.options.OptionsPanelController;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoDbManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.corecomponents.OptionsPanel; import org.sleuthkit.autopsy.corecomponents.OptionsPanel;
import org.sleuthkit.autopsy.events.AutopsyEvent; import org.sleuthkit.autopsy.events.AutopsyEvent;
@ -86,7 +87,7 @@ public final class GlobalSettingsPanel extends IngestModuleGlobalSettingsPanel i
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
CentralRepoDbUtil.upgradeDatabase(); CentralRepoDbManager.upgradeDatabase();
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
} catch (CentralRepoException ex) { } catch (CentralRepoException ex) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

View File

@ -293,13 +293,21 @@ class FileSearch {
* @return The beginning of text from the specified AbstractFile. * @return The beginning of text from the specified AbstractFile.
*/ */
private static String getFirstLines(AbstractFile file) { private static String getFirstLines(AbstractFile file) {
try (Reader reader = TextExtractorFactory.getExtractor(file, null).getReader()) { TextExtractor extractor;
try {
extractor = TextExtractorFactory.getExtractor(file, null);
} catch (TextExtractorFactory.NoTextExtractorFound ignored) {
//no extractor found, use Strings Extractor
extractor = TextExtractorFactory.getStringsExtractor(file, null);
}
try (Reader reader = extractor.getReader()) {
char[] cbuf = new char[PREVIEW_SIZE]; char[] cbuf = new char[PREVIEW_SIZE];
reader.read(cbuf, 0, PREVIEW_SIZE); reader.read(cbuf, 0, PREVIEW_SIZE);
return new String(cbuf); return new String(cbuf);
} catch (IOException ex) { } catch (IOException ex) {
return Bundle.FileSearch_documentSummary_noBytes(); return Bundle.FileSearch_documentSummary_noBytes();
} catch (TextExtractorFactory.NoTextExtractorFound | TextExtractor.InitReaderException ex) { } catch (TextExtractor.InitReaderException ex) {
return Bundle.FileSearch_documentSummary_noPreview(); return Bundle.FileSearch_documentSummary_noPreview();
} }
} }

View File

@ -3,6 +3,9 @@ GEOTrack_point_label_header=Trackpoint for track: {0}
LastKnownWaypoint_Label=Last Known Location LastKnownWaypoint_Label=Last Known Location
Route_End_Label=End Route_End_Label=End
Route_Label=As-the-crow-flies Route Route_Label=As-the-crow-flies Route
Route_point_label=Waypoints for route
Route_Start_Label=Start Route_Start_Label=Start
SearchWaypoint_DisplayLabel=GPS Search SearchWaypoint_DisplayLabel=GPS Search
Track_distanceFromHome_displayName=Distance from home point
Track_distanceTraveled_displayName=Distance traveled
TrackpointWaypoint_DisplayLabel=GPS Trackpoint TrackpointWaypoint_DisplayLabel=GPS Trackpoint

View File

@ -25,19 +25,25 @@ import java.util.Map;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil;
import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList.GeoWaypoint;
import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil.GeoWaypointList;
/** /**
* A Route represents a TSK_GPS_ROUTE artifact which has a start and end point * A Route represents a TSK_GPS_ROUTE artifact which has a start and end point
* however the class was written with the assumption that routes may have * however the class was written with the assumption that routes may have more
* more that two points. * than two points.
* *
*/ */
public class Route extends GeoPath{ public class Route extends GeoPath {
private final Long timestamp; private final Long timestamp;
// This list is not expected to change after construction so the // This list is not expected to change after construction so the
// constructor will take care of creating an unmodifiable List // constructor will take care of creating an unmodifiable List
private final List<Waypoint.Property> propertiesList; private final List<Waypoint.Property> propertiesList;
private static final TskGeoWaypointsUtil attributeUtil = new TskGeoWaypointsUtil();
/** /**
* Construct a route for the given artifact. * Construct a route for the given artifact.
@ -51,12 +57,11 @@ public class Route extends GeoPath{
}) })
Route(BlackboardArtifact artifact) throws GeoLocationDataException { Route(BlackboardArtifact artifact) throws GeoLocationDataException {
super(artifact, Bundle.Route_Label()); super(artifact, Bundle.Route_Label());
Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap = Waypoint.getAttributesFromArtifactAsMap(artifact); Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap = Waypoint.getAttributesFromArtifactAsMap(artifact);
addToPath(getRouteStartPoint(artifact, attributeMap)); createRoute(artifact, attributeMap);
addToPath(getRouteEndPoint(artifact, attributeMap));
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME); BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
timestamp = attribute != null ? attribute.getValueLong() : null; timestamp = attribute != null ? attribute.getValueLong() : null;
@ -82,19 +87,61 @@ public class Route extends GeoPath{
return Collections.unmodifiableList(propertiesList); return Collections.unmodifiableList(propertiesList);
} }
/**
* Returns the route timestamp.
*
* @return Route timestamp
*/
public Long getTimestamp() { public Long getTimestamp() {
return timestamp; return timestamp;
} }
/**
* Gets the route waypoint attributes from the map and creates the list of
* route waypoints.
*
* @param artifact Route artifact
* @param attributeMap Map of artifact attributes
*
* @throws GeoLocationDataException
*/
@Messages({
"Route_point_label=Waypoints for route"
})
private void createRoute(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS);
String label = getLabel();
if (label == null || label.isEmpty()) {
label = Bundle.Route_point_label();
} else {
label = String.format("%s: %s", Bundle.Route_point_label(), label);
}
if (attribute != null) {
GeoWaypointList waypoints = attributeUtil.fromAttribute(attribute);
for(GeoWaypoint waypoint: waypoints) {
addToPath(new Waypoint(artifact, label, null, waypoint.getLatitude(), waypoint.getLongitude(), waypoint.getAltitude(), null, attributeMap, this));
}
} else {
Waypoint start = getRouteStartPoint(artifact, attributeMap);
Waypoint end = getRouteEndPoint(artifact, attributeMap);
addToPath(start);
addToPath(end);
}
}
/** /**
* Get the route start point. * Get the route start point.
* *
* @param artifact * @param artifact
* @param attributeMap Map of artifact attributes for this waypoint. * @param attributeMap Map of artifact attributes for this waypoint.
* *
* An exception will be thrown if longitude or latitude is null. * An exception will be thrown if longitude or latitude is null.
* *
* @return Start waypoint * @return Start waypoint
* *
* @throws GeoLocationDataException. * @throws GeoLocationDataException.
*/ */
@ -106,16 +153,14 @@ public class Route extends GeoPath{
BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START); BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START);
BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START);
BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
if (latitude != null && longitude != null) { if (latitude != null && longitude != null) {
return new Waypoint(artifact, return new RoutePoint(artifact,
Bundle.Route_Start_Label(), Bundle.Route_Start_Label(),
pointTimestamp != null ? pointTimestamp.getValueLong() : null, latitude.getValueDouble(),
latitude.getValueDouble(),
longitude.getValueDouble(), longitude.getValueDouble(),
altitude != null ? altitude.getValueDouble() : null, altitude != null ? altitude.getValueDouble() : null,
null, attributeMap, this); attributeMap);
} else { } else {
throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude"); throw new GeoLocationDataException("Unable to create route start point, invalid longitude and/or latitude");
} }
@ -123,8 +168,8 @@ public class Route extends GeoPath{
/** /**
* Get the route End point. * Get the route End point.
* *
* An exception will be thrown if longitude or latitude is null. * An exception will be thrown if longitude or latitude is null.
* *
* @param artifact * @param artifact
* @param attributeMap Map of artifact attributes for this waypoint * @param attributeMap Map of artifact attributes for this waypoint
@ -140,19 +185,52 @@ public class Route extends GeoPath{
BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END); BlackboardAttribute latitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END);
BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END); BlackboardAttribute longitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END);
BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE); BlackboardAttribute altitude = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE);
BlackboardAttribute pointTimestamp = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME);
if (latitude != null && longitude != null) { if (latitude != null && longitude != null) {
return new Waypoint(artifact, return new RoutePoint(artifact,
Bundle.Route_End_Label(), Bundle.Route_End_Label(),
pointTimestamp != null ? pointTimestamp.getValueLong() : null, latitude.getValueDouble(),
latitude.getValueDouble(),
longitude.getValueDouble(), longitude.getValueDouble(),
altitude != null ? altitude.getValueDouble() : null, altitude != null ? altitude.getValueDouble() : null,
null, attributeMap, this); attributeMap);
} else { } else {
throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude"); throw new GeoLocationDataException("Unable to create route end point, invalid longitude and/or latitude");
} }
} }
/**
* Route waypoint specific implementation of Waypoint.
*/
private class RoutePoint extends Waypoint {
/**
* Construct a RoutePoint
*
* @param artifact BlackboardArtifact for this waypoint
* @param label String waypoint label
* @param latitude Double waypoint latitude
* @param longitude Double waypoint longitude
*
* @param attributeMap A Map of attributes for the given artifact
*
* @throws GeoLocationDataException
*/
RoutePoint(BlackboardArtifact artifact, String label, Double latitude, Double longitude, Double altitude, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact,
label,
null,
latitude,
longitude,
altitude,
null,
attributeMap,
Route.this);
}
@Override
public Long getTimestamp() {
return ((Route) getParentGeoPath()).getTimestamp();
}
}
} }

View File

@ -26,8 +26,9 @@ import java.util.Map;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints; import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoint.GeoTrackPoint; import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList;
import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList.GeoTrackPoint;
/** /**
* A GPS track with which wraps the TSK_GPS_TRACK artifact. * A GPS track with which wraps the TSK_GPS_TRACK artifact.
@ -36,6 +37,8 @@ public final class Track extends GeoPath{
private final Long startTimestamp; private final Long startTimestamp;
private final Long endTimeStamp; private final Long endTimeStamp;
private static final TskGeoTrackpointsUtil attributeUtil = new TskGeoTrackpointsUtil();
/** /**
* Construct a new Track for the given artifact. * Construct a new Track for the given artifact.
@ -59,11 +62,11 @@ public final class Track extends GeoPath{
private Track(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException { private Track(BlackboardArtifact artifact, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) throws GeoLocationDataException {
super(artifact, getTrackName(attributeMap)); super(artifact, getTrackName(attributeMap));
List<GeoTrackPoint> points = getPointsList(attributeMap); GeoTrackPointList points = getPointsList(attributeMap);
buildPath(points); buildPath(points);
startTimestamp = findStartTime(points); startTimestamp = points.getStartTime();
endTimeStamp = findEndTime(points); endTimeStamp = points.getEndTime();
} }
/** /**
@ -111,8 +114,8 @@ public final class Track extends GeoPath{
"# {0} - track name", "# {0} - track name",
"GEOTrack_point_label_header=Trackpoint for track: {0}" "GEOTrack_point_label_header=Trackpoint for track: {0}"
}) })
private void buildPath(List<GeoTrackPoint> points) throws GeoLocationDataException { private void buildPath(GeoTrackPointList points) throws GeoLocationDataException {
for (GeoTrackPoint point : points) { for(GeoTrackPoint point: points) {
addToPath(new TrackWaypoint(Bundle.GEOTrack_point_label_header(getLabel()), point)); addToPath(new TrackWaypoint(Bundle.GEOTrack_point_label_header(getLabel()), point));
} }
} }
@ -125,52 +128,12 @@ public final class Track extends GeoPath{
* *
* @return GeoTrackPoint list empty list if the attribute was not found. * @return GeoTrackPoint list empty list if the attribute was not found.
*/ */
private List<GeoTrackPoint> getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) { private GeoTrackPointList getPointsList(Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap) {
BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS); BlackboardAttribute attribute = attributeMap.get(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_TRACKPOINTS);
if (attribute != null) { if (attribute != null) {
String value = attribute.getValueString(); return attributeUtil.fromAttribute(attribute);
return GeoTrackPoints.deserializePoints(value);
} }
return new ArrayList<>();
}
/**
* Return the start time for the track. Assumes the points are in time
* order.
*
* @param points List of GeoTrackPoints.
*
* @return First non-null time stamp or null, if one was not found.
*/
private Long findStartTime(List<GeoTrackPoint> points) {
if (points != null) {
for (GeoTrackPoint point : points) {
if (point.getTimeStamp() != null) {
return point.getTimeStamp();
}
}
}
return null;
}
/**
* Return the ends time for the track. Assumes the points are in time
* order.
*
* @param points List of GeoTrackPoints.
*
* @return First non-null time stamp or null, if one was not found.
*/
private Long findEndTime(List<GeoTrackPoint> points) {
if (points != null) {
for (int index = points.size() - 1; index >= 0; index--) {
GeoTrackPoint point = points.get(index);
if (point.getTimeStamp() != null) {
return point.getTimeStamp();
}
}
}
return null; return null;
} }
@ -219,6 +182,10 @@ public final class Track extends GeoPath{
* *
* @return A list of Waypoint.properies. * @return A list of Waypoint.properies.
*/ */
@Messages({
"Track_distanceTraveled_displayName=Distance traveled",
"Track_distanceFromHome_displayName=Distance from home point"
})
private List<Waypoint.Property> createPropertyList(GeoTrackPoint point) { private List<Waypoint.Property> createPropertyList(GeoTrackPoint point) {
List<Waypoint.Property> list = new ArrayList<>(); List<Waypoint.Property> list = new ArrayList<>();
@ -234,12 +201,12 @@ public final class Track extends GeoPath{
value = point.getDistanceTraveled(); value = point.getDistanceTraveled();
if (value != null) { if (value != null) {
list.add(new Property(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_DISTANCE_TRAVELED.getDisplayName(), value.toString())); list.add(new Property(Bundle.Track_distanceTraveled_displayName(), value.toString()));
} }
value = point.getDistanceFromHP(); value = point.getDistanceFromHomePoint();
if (value != null) { if (value != null) {
list.add(new Property(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_DISTANCE_FROM_HOME_POINT.getDisplayName(), value.toString())); list.add(new Property(Bundle.Track_distanceFromHome_displayName(), value.toString()));
} }
return list; return list;

View File

@ -43,7 +43,7 @@ public class Waypoint {
final private String label; final private String label;
final private AbstractFile image; final private AbstractFile image;
final private BlackboardArtifact artifact; final private BlackboardArtifact artifact;
final private GeoPath path; final private GeoPath parentGeoPath;
final private List<Waypoint.Property> propertiesList; final private List<Waypoint.Property> propertiesList;
@ -78,7 +78,7 @@ public class Waypoint {
* @throws GeoLocationDataException Exception will be thrown if artifact did * @throws GeoLocationDataException Exception will be thrown if artifact did
* not have a valid longitude and latitude. * not have a valid longitude and latitude.
*/ */
Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, GeoPath path) throws GeoLocationDataException { Waypoint(BlackboardArtifact artifact, String label, Long timestamp, Double latitude, Double longitude, Double altitude, AbstractFile image, Map<BlackboardAttribute.ATTRIBUTE_TYPE, BlackboardAttribute> attributeMap, GeoPath parentGeoPath) throws GeoLocationDataException {
if (longitude == null || latitude == null) { if (longitude == null || latitude == null) {
throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude"); throw new GeoLocationDataException("Invalid waypoint, null value passed for longitude or latitude");
} }
@ -90,7 +90,7 @@ public class Waypoint {
this.longitude = longitude; this.longitude = longitude;
this.latitude = latitude; this.latitude = latitude;
this.altitude = altitude; this.altitude = altitude;
this.path = path; this.parentGeoPath = parentGeoPath;
propertiesList = createGeolocationProperties(attributeMap); propertiesList = createGeolocationProperties(attributeMap);
} }
@ -173,13 +173,13 @@ public class Waypoint {
} }
/** /**
* Returns the route that this waypoint is apart of . * Returns the GeoPath that this waypoint is apart of .
* *
* @return The waypoint route or null if the waypoint is not apart of a * @return The waypoint route or null if the waypoint is not apart of a
* route. * route.
*/ */
public GeoPath getPath() { public GeoPath getParentGeoPath() {
return path; return parentGeoPath;
} }
/** /**
@ -231,6 +231,10 @@ public class Waypoint {
} }
for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) { for (BlackboardAttribute.ATTRIBUTE_TYPE type : keys) {
// Don't add JSON properties to this list.
if (type.getValueType() == BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.JSON) {
continue;
}
String key = type.getDisplayName(); String key = type.getDisplayName();
String value = attributeMap.get(type).getDisplayString(); String value = attributeMap.get(type).getDisplayString();

View File

@ -39,39 +39,49 @@ import org.sleuthkit.datamodel.DataSource;
public final class WaypointBuilder { public final class WaypointBuilder {
private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName()); private static final Logger logger = Logger.getLogger(WaypointBuilder.class.getName());
private final static String TIME_TYPE_IDS = String.format("%d, %d",
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID());
private final static String GEO_ATTRIBUTE_TYPE_IDS = String.format("%d, %d, %d",
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_WAYPOINTS.getTypeID());
// SELECT statement for getting a list of waypoints. // SELECT statement for getting a list of waypoints where %s is a comma separated list
final static String GEO_ARTIFACT_QUERY // of attribute type ids.
private final static String GEO_ARTIFACT_QUERY
= "SELECT artifact_id, artifact_type_id " = "SELECT artifact_id, artifact_type_id "
+ "FROM blackboard_attributes " + "FROM blackboard_attributes "
+ "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + "WHERE attribute_type_id IN (%s) "; //NON-NLS
// SELECT statement to get only artifact_ids // SELECT statement to get only artifact_ids
final static String GEO_ARTIFACT_QUERY_ID_ONLY private final static String GEO_ARTIFACT_QUERY_ID_ONLY
= "SELECT artifact_id " = "SELECT artifact_id "
+ "FROM blackboard_attributes " + "FROM blackboard_attributes "
+ "WHERE attribute_type_id IN (%d, %d) "; //NON-NLS + "WHERE attribute_type_id IN (%s) "; //NON-NLS
// This Query will return a list of waypoint artifacts // This Query will return a list of waypoint artifacts
final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY private final static String GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY
= "SELECT blackboard_attributes.artifact_id " = "SELECT blackboard_attributes.artifact_id "
+ "FROM blackboard_attributes, blackboard_artifacts " + "FROM blackboard_attributes, blackboard_artifacts "
+ "WHERE blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id " + "WHERE blackboard_attributes.artifact_id = blackboard_artifacts.artifact_id "
+ "AND blackboard_attributes.attribute_type_id IN(%d, %d) " + "AND blackboard_attributes.attribute_type_id IN(%s) "
+ "AND data_source_obj_id IN (%s)"; //NON-NLS + "AND data_source_obj_id IN (%s)"; //NON-NLS
// Select will return the "most recent" timestamp from all waypoings // Select will return the "most recent" timestamp from all waypoings
final static String MOST_RECENT_TIME private final static String MOST_RECENT_TIME
= "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day. = "SELECT MAX(value_int64) - (%d * 86400)" //86400 is the number of seconds in a day.
+ "FROM blackboard_attributes " + "FROM blackboard_attributes "
+ "WHERE attribute_type_id IN(%d, %d) " + "WHERE attribute_type_id IN(%s) "
+ "AND artifact_id " + "AND artifact_id "
+ "IN ( " + "IN ( "
+ "%s" //GEO_ARTIFACT with or without data source + "%s" //GEO_ARTIFACT with or without data source
+ " )"; + " )";
// Returns a list of artifacts with no time stamp // Returns a list of artifacts with no time stamp
final static String SELECT_WO_TIMESTAMP private final static String SELECT_WO_TIMESTAMP
= "SELECT DISTINCT artifact_id, artifact_type_id " = "SELECT DISTINCT artifact_id, artifact_type_id "
+ "FROM blackboard_attributes " + "FROM blackboard_attributes "
+ "WHERE artifact_id NOT IN (%s) " + "WHERE artifact_id NOT IN (%s) "
@ -132,7 +142,7 @@ public final class WaypointBuilder {
public static List<Route> getRoutes(List<Waypoint> waypoints) { public static List<Route> getRoutes(List<Waypoint> waypoints) {
List<Route> routeList = new ArrayList<>(); List<Route> routeList = new ArrayList<>();
for (Waypoint point : waypoints) { for (Waypoint point : waypoints) {
GeoPath path = point.getPath(); GeoPath path = point.getParentGeoPath();
if (path instanceof Route) { if (path instanceof Route) {
Route route = (Route) path; Route route = (Route) path;
if (!routeList.contains(route)) { if (!routeList.contains(route)) {
@ -154,7 +164,7 @@ public final class WaypointBuilder {
public static List<Track> getTracks(List<Waypoint> waypoints) { public static List<Track> getTracks(List<Waypoint> waypoints) {
List<Track> trackList = new ArrayList<>(); List<Track> trackList = new ArrayList<>();
for (Waypoint point : waypoints) { for (Waypoint point : waypoints) {
GeoPath path = point.getPath(); GeoPath path = point.getParentGeoPath();
if (path instanceof Track) { if (path instanceof Track) {
Track route = (Track) path; Track route = (Track) path;
if (!trackList.contains(route)) { if (!trackList.contains(route)) {
@ -507,9 +517,7 @@ public final class WaypointBuilder {
// FROM blackboard_attributes // FROM blackboard_attributes
// WHERE attribute_type_id IN (%d, %d) // WHERE attribute_type_id IN (%d, %d)
return String.format(SELECT_WO_TIMESTAMP, return String.format(SELECT_WO_TIMESTAMP,
String.format(GEO_ARTIFACT_QUERY_ID_ONLY, String.format(GEO_ARTIFACT_QUERY_ID_ONLY,TIME_TYPE_IDS),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID()),
getWaypointListQuery(dataSources)); getWaypointListQuery(dataSources));
} }
@ -542,15 +550,13 @@ public final class WaypointBuilder {
// MOST_RECENT_TIME // MOST_RECENT_TIME
// SELECT MAX(value_int64) - (%d * 86400) // SELECT MAX(value_int64) - (%d * 86400)
// FROM blackboard_attributes // FROM blackboard_attributes
// WHERE attribute_type_id IN(%d, %d) // WHERE attribute_type_id IN(%s)
// AND artifact_id // AND artifact_id
// IN ( %s ) // IN ( %s )
// //
mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS mostRecentQuery = String.format("AND value_int64 > (%s)", //NON-NLS
String.format(MOST_RECENT_TIME, String.format(MOST_RECENT_TIME,
cntDaysFromRecent, cntDaysFromRecent, TIME_TYPE_IDS,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(),
getWaypointListQuery(dataSources) getWaypointListQuery(dataSources)
)); ));
} }
@ -558,10 +564,8 @@ public final class WaypointBuilder {
// GEO_ARTIFACT_QUERY // GEO_ARTIFACT_QUERY
// SELECT artifact_id, artifact_type_id // SELECT artifact_id, artifact_type_id
// FROM blackboard_attributes // FROM blackboard_attributes
// WHERE attribute_type_id IN (%d, %d) // WHERE attribute_type_id IN (%s)
String query = String.format(GEO_ARTIFACT_QUERY, String query = String.format(GEO_ARTIFACT_QUERY, TIME_TYPE_IDS);
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID());
// That are in the list of artifacts for the given data Sources // That are in the list of artifacts for the given data Sources
query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS query += String.format("AND artifact_id IN(%s)", getWaypointListQuery(dataSources)); //NON-NLS
@ -592,10 +596,8 @@ public final class WaypointBuilder {
// GEO_ARTIFACT_QUERY // GEO_ARTIFACT_QUERY
// SELECT artifact_id, artifact_type_id // SELECT artifact_id, artifact_type_id
// FROM blackboard_attributes // FROM blackboard_attributes
// WHERE attribute_type_id IN (%d, %d) // WHERE attribute_type_id IN (%s)
return String.format(GEO_ARTIFACT_QUERY, return String.format(GEO_ARTIFACT_QUERY, GEO_ATTRIBUTE_TYPE_IDS);
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID());
} }
String dataSourceList = ""; String dataSourceList = "";
@ -608,9 +610,7 @@ public final class WaypointBuilder {
dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1); dataSourceList = dataSourceList.substring(0, dataSourceList.length() - 1);
} }
return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, return String.format(GEO_ARTIFACT_WITH_DATA_SOURCES_QUERY, GEO_ATTRIBUTE_TYPE_IDS,
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START.getTypeID(),
dataSourceList); dataSourceList);
} }

View File

@ -41,10 +41,11 @@ import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoint.GeoTrackPoint; import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList.GeoTrackPoint;
import org.sleuthkit.datamodel.blackboardutils.GeoArtifactsHelper; import org.sleuthkit.datamodel.blackboardutils.GeoArtifactsHelper;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.Blackboard.BlackboardException; import org.sleuthkit.datamodel.Blackboard.BlackboardException;
import org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoTrackpointsUtil.GeoTrackPointList;
/** /**
* Extract drone position data from DJI Phantom drones. * Extract drone position data from DJI Phantom drones.
@ -110,10 +111,10 @@ final class DATExtractor extends DroneExtractor {
} }
// Process the csv file // Process the csv file
List<GeoTrackPoint> trackPoints = processCSVFile(context, DATFile, csvFilePath); GeoTrackPointList trackPoints = processCSVFile(context, DATFile, csvFilePath);
if (trackPoints != null && !trackPoints.isEmpty()) { if (trackPoints != null && !trackPoints.isEmpty()) {
(new GeoArtifactsHelper(getSleuthkitCase(), getName(), DATFile)).addTrack(DATFile.getName(), trackPoints); (new GeoArtifactsHelper(getSleuthkitCase(), getName(), "DatCon", DATFile)).addTrack(DATFile.getName(), trackPoints, null);
} else { } else {
logger.log(Level.INFO, String.format("No trackpoints with valid longitude or latitude found in %s", DATFile.getName())); //NON-NLS logger.log(Level.INFO, String.format("No trackpoints with valid longitude or latitude found in %s", DATFile.getName())); //NON-NLS
} }
@ -187,8 +188,8 @@ final class DATExtractor extends DroneExtractor {
* *
* @throws DroneIngestException * @throws DroneIngestException
*/ */
private List<GeoTrackPoint> processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException { private GeoTrackPointList processCSVFile(IngestJobContext context, AbstractFile DATFile, String csvFilePath) throws DroneIngestException {
List<GeoTrackPoint> trackPoints = new ArrayList<>(); GeoTrackPointList trackPoints = new GeoTrackPointList();
try (BufferedReader reader = new BufferedReader(new FileReader(new File(csvFilePath)))) { try (BufferedReader reader = new BufferedReader(new FileReader(new File(csvFilePath)))) {
// First read in the header line and process // First read in the header line and process
String line = reader.readLine(); String line = reader.readLine();
@ -202,7 +203,7 @@ final class DATExtractor extends DroneExtractor {
String[] values = line.split(","); //NON-NLS String[] values = line.split(","); //NON-NLS
GeoTrackPoint point = createTrackPoint(headerMap, values); GeoTrackPoint point = createTrackPoint(headerMap, values);
if (point != null) { if (point != null) {
trackPoints.add(point); trackPoints.addPoint(point);
} }
} }
@ -258,6 +259,7 @@ final class DATExtractor extends DroneExtractor {
return new GeoTrackPoint(latitude, return new GeoTrackPoint(latitude,
longitude, longitude,
getDoubleValue(columnLookup.get(HEADER_ALTITUDE), values), getDoubleValue(columnLookup.get(HEADER_ALTITUDE), values),
null,
getDoubleValue(columnLookup.get(HEADER_VELOCITY), values), getDoubleValue(columnLookup.get(HEADER_VELOCITY), values),
getDoubleValue(columnLookup.get(HEADER_DISTANCE_FROM_HP), values), getDoubleValue(columnLookup.get(HEADER_DISTANCE_FROM_HP), values),
getDoubleValue(columnLookup.get(HEADER_DISTANCE_TRAVELED), values), getDoubleValue(columnLookup.get(HEADER_DISTANCE_TRAVELED), values),

View File

@ -1,5 +0,0 @@
iOSModuleFactory.moduleName=iOS Analyzer
iOSModuleFactory.moduleDescription=Extracts system and 3rd party app data
TextMessageAnalyzer.bbAttribute.incoming=Incoming
TextMessageAnalyzer.bbAttribute.outgoing=Outgoing
TextMessageAnalyzer.bbAttribute.smsMessage=SMS Message

View File

@ -1,8 +0,0 @@
CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search.
ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search.
iOSModuleFactory.moduleName=iOS Analyzer
iOSModuleFactory.moduleDescription=Extracts system and 3rd party app data
TextMessageAnalyzer.bbAttribute.incoming=Incoming
TextMessageAnalyzer.bbAttribute.outgoing=Outgoing
TextMessageAnalyzer.bbAttribute.smsMessage=SMS Message
TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search.

View File

@ -1,8 +0,0 @@
CallLogAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u901a\u8a71\u30ed\u30b0\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
ContactAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u9023\u7d61\u5148\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002
iOSModuleFactory.moduleName=iOS Analyzer
iOSModuleFactory.moduleDescription=\u30b7\u30b9\u30c6\u30e0\u3068\u30b5\u30fc\u30c9\u30d1\u30fc\u30c6\u30a3\u88fd\u30a2\u30d7\u30ea\u30c7\u30fc\u30bf\u3092\u62bd\u51fa
TextMessageAnalyzer.bbAttribute.incoming=\u53d7\u4fe1
TextMessageAnalyzer.bbAttribute.outgoing=\u9001\u4fe1
TextMessageAnalyzer.bbAttribute.smsMessage=SMS\u30e1\u30c3\u30bb\u30fc\u30b8
TextMessageAnalyzer.indexError.message=\u30ad\u30fc\u30ef\u30fc\u30c9\u3092\u691c\u7d22\u3059\u308b\u305f\u3081\u306e\u3001\u30c6\u30ad\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u30a2\u30fc\u30c6\u30a3\u30d5\u30a1\u30af\u30c8\u3092\u30a4\u30f3\u30c7\u30c3\u30af\u30b9\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002

View File

@ -1,192 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2018 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.modules.iOS;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Look for call logs and allow resulting blackboard artifacts to be generated.
*/
final class CallLogAnalyzer {
private Connection connection = null;
private ResultSet resultSet = null;
private Statement statement = null;
private long fileId = 0;
private java.io.File jFile = null;
private final String moduleName = iOSModuleFactory.getModuleName();
private static final Logger logger = Logger.getLogger(CallLogAnalyzer.class.getName());
private Blackboard blackboard;
/**
* Find call logs given an ingest job context and index the results.
*
* @param context The ingest job context.
*/
public void findCallLogs(IngestJobContext context) {
Case openCase;
try {
openCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
blackboard = openCase.getSleuthkitCase().getBlackboard();
List<AbstractFile> absFiles;
try {
SleuthkitCase skCase = openCase.getSleuthkitCase();
absFiles = skCase.findAllFilesWhere("name ='contacts2.db' OR name ='contacts.db'"); //NON-NLS //get exact file names
if (absFiles.isEmpty()) {
return;
}
for (AbstractFile file : absFiles) {
String dbPath = "";
try {
jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", ""));
dbPath = jFile.toString(); //path of file as string
fileId = file.getId();
ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled);
findCallLogsInDB(dbPath, fileId);
} catch (ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS
} catch (Exception ex) {
logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS
}
}
} catch (TskCoreException e) {
logger.log(Level.SEVERE, "Error finding Call logs", e); //NON-NLS
}
}
/**
* Index results for call logs found in the database.
*
* @param DatabasePath The path to the database.
* @param fileId The ID of the file associated with artifacts.
*/
@Messages({"CallLogAnalyzer.indexError.message=Failed to index call log artifact for keyword search."})
private void findCallLogsInDB(String DatabasePath, long fileId) {
if (DatabasePath == null || DatabasePath.isEmpty()) {
return;
}
try {
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS
statement = connection.createStatement();
} catch (ClassNotFoundException | SQLException e) {
logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS
}
Case currentCase;
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
SleuthkitCase skCase = currentCase.getSleuthkitCase();
try {
AbstractFile file = skCase.getAbstractFileById(fileId);
if (file == null) {
logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS
return;
}
try {
resultSet = statement.executeQuery(
"SELECT number,date,duration,type, name FROM calls ORDER BY date DESC;"); //NON-NLS
BlackboardArtifact bba;
String name; // name of person dialed or called. null if unregistered
String number; //string phone number
String duration; //duration of call in seconds
String date; // Unix time
String type; // 1 incoming, 2 outgoing, 3 missed
while (resultSet.next()) {
name = resultSet.getString("name"); //NON-NLS
number = resultSet.getString("number"); //NON-NLS
duration = resultSet.getString("duration"); //NON-NLS
date = resultSet.getString("date"); //NON-NLS
type = resultSet.getString("type"); //NON-NLS
bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); //create a call log and then add attributes from result set.
Collection<BlackboardAttribute> attributes = new ArrayList<>();
if (type.equalsIgnoreCase("outgoing")) { //NON-NLS
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, number));
} else { /// Covers INCOMING and MISSED
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, number));
}
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, moduleName, date)); // RC: Should be long!
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, moduleName, duration + date)); // RC: Should be long!
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, type));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, moduleName, name));
bba.addAttributes(attributes);
try {
/*
* post the artifact which will index the artifact for
* keyword search, and fire an event to notify UI of
* this new artifact
*/
blackboard.postArtifact(bba, moduleName);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
MessageNotifyUtil.Notify.error(
Bundle.CallLogAnalyzer_indexError_message(), bba.getDisplayName());
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing Call logs to the Blackboard", e); //NON-NLS
} finally {
try {
resultSet.close();
statement.close();
connection.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Error closing the database", e); //NON-NLS
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing Call logs to the Blackboard", e); //NON-NLS
}
}
}

View File

@ -1,251 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2019 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.modules.iOS;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Look for call logs and allow resulting blackboard artifacts to be generated.
*/
final class ContactAnalyzer {
private Connection connection = null;
private String dbPath = "";
private long fileId = 0;
private java.io.File jFile = null;
private final String moduleName = iOSModuleFactory.getModuleName();
private static final Logger logger = Logger.getLogger(ContactAnalyzer.class.getName());
private Blackboard blackboard;
/**
* Find contacts given an ingest job context and index the results.
*
* @param context The ingest job context.
*/
public void findContacts(IngestJobContext context) {
Case openCase;
try {
openCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
blackboard = openCase.getSleuthkitCase().getBlackboard();
List<AbstractFile> absFiles;
try {
SleuthkitCase skCase = openCase.getSleuthkitCase();
absFiles = skCase.findAllFilesWhere("LOWER(name) LIKE LOWER('%call_history%') "); //NON-NLS //get exact file names
if (absFiles.isEmpty()) {
return;
}
for (AbstractFile file : absFiles) {
try {
jFile = new java.io.File(openCase.getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", ""));
dbPath = jFile.toString(); //path of file as string
fileId = file.getId();
ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS
} catch (Exception ex) {
logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS
}
}
} catch (TskCoreException e) {
logger.log(Level.SEVERE, "Error finding Contacts", e); //NON-NLS
}
}
/**
* Create blackboard artifacts and index results for call logs found in the
* database.
*
* @param DatabasePath The path to the database.
* @param fileId The ID of the file associated with artifacts.
*/
@Messages({"ContactAnalyzer.indexError.message=Failed to index contact artifact for keyword search."})
private void findContactsInDB(String DatabasePath, long fileId) {
if (DatabasePath == null || DatabasePath.isEmpty()) {
return;
}
Case currentCase;
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
Statement statement = null;
try {
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS
statement = connection.createStatement();
} catch (ClassNotFoundException | SQLException e) {
logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS
}
SleuthkitCase skCase = currentCase.getSleuthkitCase();
try {
AbstractFile file = skCase.getAbstractFileById(fileId);
if (file == null) {
logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS
return;
}
ResultSet resultSet = null;
try {
// get display_name, mimetype(email or phone number) and data1 (phonenumber or email address depending on mimetype)
//sorted by name, so phonenumber/email would be consecutive for a person if they exist.
resultSet = statement.executeQuery(
"SELECT mimetype,data1, name_raw_contact.display_name AS display_name \n" //NON-NLS
+ "FROM raw_contacts JOIN contacts ON (raw_contacts.contact_id=contacts._id) \n" //NON-NLS
+ "JOIN raw_contacts AS name_raw_contact ON(name_raw_contact_id=name_raw_contact._id) " //NON-NLS
+ "LEFT OUTER JOIN data ON (data.raw_contact_id=raw_contacts._id) \n" //NON-NLS
+ "LEFT OUTER JOIN mimetypes ON (data.mimetype_id=mimetypes._id) \n" //NON-NLS
+ "WHERE mimetype = 'vnd.android.cursor.item/phone_v2' OR mimetype = 'vnd.android.cursor.item/email_v2'\n" //NON-NLS
+ "ORDER BY name_raw_contact.display_name ASC;"); //NON-NLS
BlackboardArtifact bba;
bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
Collection<BlackboardAttribute> attributes = new ArrayList<>();
String name;
String oldName = "";
String mimetype; // either phone or email
String data1; // the phone number or email
while (resultSet.next()) {
name = resultSet.getString("display_name"); //NON-NLS
data1 = resultSet.getString("data1"); //NON-NLS
mimetype = resultSet.getString("mimetype"); //NON-NLS
if (name.equals(oldName) == false) {
bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
attributes = new ArrayList<>();
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, moduleName, name));
}
if (mimetype.equals("vnd.android.cursor.item/phone_v2")) { //NON-NLS
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, moduleName, data1));
} else {
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL, moduleName, data1));
}
// TODO: If this code comes back to life, add code to create the account
// and relationship between the phone numbers & emails. Also
// investigate if the mimetype "vnd.android.cursor.item/phone_v2"
// makes sense in an ios word
oldName = name;
bba.addAttributes(attributes);
try {
// index the artifact for keyword search
blackboard.postArtifact(bba, moduleName);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
MessageNotifyUtil.Notify.error(
Bundle.ContactAnalyzer_indexError_message(), bba.getDisplayName());
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing Contacts to Blackboard", e); //NON-NLS
} finally {
try {
resultSet.close();
statement.close();
connection.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Error closing database", e); //NON-NLS
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing Contacts to Blackboard", e); //NON-NLS
}
}
public static void copyFileUsingStream(AbstractFile file, File jFile) throws IOException {
InputStream is = new ReadContentInputStream(file);
OutputStream os = new FileOutputStream(jFile);
byte[] buffer = new byte[8192];
int length;
try {
while ((length = is.read(buffer)) != -1) {
os.write(buffer, 0, length);
os.flush();
}
} finally {
is.close();
os.close();
}
}
public static void copyFileUsingStreams(AbstractFile file, File jFile) {
InputStream istream;
OutputStream ostream = null;
int c;
final int EOF = -1;
istream = new ReadContentInputStream(file);
try {
ostream = new FileOutputStream(jFile);
while ((c = istream.read()) != EOF) {
ostream.write(c);
}
} catch (IOException e) {
logger.log(Level.WARNING, "Error copying file", e);
} finally {
try {
istream.close();
ostream.close();
} catch (IOException e) {
logger.log(Level.WARNING, "File did not close", e);
}
}
}
}

View File

@ -1,198 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014-2018 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.modules.iOS;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.datamodel.ContentUtils;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Blackboard;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Look for text messages and allow resulting blackboard artifacts to be
* generated.
*/
class TextMessageAnalyzer {
private Connection connection = null;
private ResultSet resultSet = null;
private Statement statement = null;
private String dbPath = "";
private long fileId = 0;
private java.io.File jFile = null;
List<AbstractFile> absFiles;
private final String moduleName = iOSModuleFactory.getModuleName();
private static final Logger logger = Logger.getLogger(TextMessageAnalyzer.class.getName());
private Blackboard blackboard;
/**
* Find text messages given an ingest job context and index the results.
*
* @param context The ingest job context.
*/
void findTexts(IngestJobContext context) {
Case openCase;
try {
openCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
blackboard = openCase.getSleuthkitCase().getBlackboard();
try {
SleuthkitCase skCase = openCase.getSleuthkitCase();
absFiles = skCase.findAllFilesWhere("name ='mmssms.db'"); //NON-NLS //get exact file name
if (absFiles.isEmpty()) {
return;
}
for (AbstractFile file : absFiles) {
try {
jFile = new java.io.File(Case.getCurrentCaseThrows().getTempDirectory(), file.getName().replaceAll("[<>%|\"/:*\\\\]", ""));
dbPath = jFile.toString(); //path of file as string
fileId = file.getId();
ContentUtils.writeToFile(file, jFile, context::dataSourceIngestIsCancelled);
findTextsInDB(dbPath, fileId);
} catch (ReadContentInputStream.ReadContentInputStreamException ex) {
logger.log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", file.getName(), fileId), ex); //NON-NLS
} catch (Exception ex) {
logger.log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", file.getName(), fileId, dbPath), ex); //NON-NLS
}
}
} catch (TskCoreException e) {
logger.log(Level.SEVERE, "Error finding text messages", e); //NON-NLS
}
}
/**
* Create blackboard artifacts and index results for text messages found in
* the database.
*
* @param DatabasePath The path to the database.
* @param fileId The ID of the file associated with artifacts.
*/
@Messages({"TextMessageAnalyzer.indexError.message=Failed to index text message artifact for keyword search."})
private void findTextsInDB(String DatabasePath, long fileId) {
if (DatabasePath == null || DatabasePath.isEmpty()) {
return;
}
Case currentCase;
try {
currentCase = Case.getCurrentCaseThrows();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return;
}
try {
Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver
connection = DriverManager.getConnection("jdbc:sqlite:" + DatabasePath); //NON-NLS
statement = connection.createStatement();
} catch (ClassNotFoundException | SQLException e) {
logger.log(Level.SEVERE, "Error opening database", e); //NON-NLS
}
SleuthkitCase skCase = currentCase.getSleuthkitCase();
try {
AbstractFile file = skCase.getAbstractFileById(fileId);
if (file == null) {
logger.log(Level.SEVERE, "Error getting abstract file {0}", fileId); //NON-NLS
return;
}
try {
resultSet = statement.executeQuery(
"SELECT address,date,type,subject,body FROM sms;"); //NON-NLS
BlackboardArtifact bba;
String address; // may be phone number, or other addresses
String date;//unix time
String type; // message received in inbox = 1, message sent = 2
String subject;//message subject
String body; //message body
while (resultSet.next()) {
address = resultSet.getString("address"); //NON-NLS
date = resultSet.getString("date"); //NON-NLS
type = resultSet.getString("type"); //NON-NLS
subject = resultSet.getString("subject"); //NON-NLS
body = resultSet.getString("body"); //NON-NLS
bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); //create Message artifact and then add attributes from result set.
Collection<BlackboardAttribute> attributes = new ArrayList<>();
// @@@ NEed to put into more specific TO or FROM
if (type.equals("1")) {
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.incoming")));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, address));
} else {
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.outgoing")));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, address));
}
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, moduleName, date));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, type));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, moduleName, subject));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, moduleName, body));
attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, NbBundle.getMessage(this.getClass(), "TextMessageAnalyzer.bbAttribute.smsMessage")));
bba.addAttributes(attributes);
try {
/*
* post the artifact which will index the artifact for
* keyword search, and fire an event to notify UI of
* this new artifact
*/ blackboard.postArtifact(bba, moduleName);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bba.getArtifactID(), ex); //NON-NLS
MessageNotifyUtil.Notify.error(
Bundle.TextMessageAnalyzer_indexError_message(), bba.getDisplayName());
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing text messages to Blackboard", e); //NON-NLS
} finally {
try {
resultSet.close();
statement.close();
connection.close();
} catch (Exception e) {
logger.log(Level.SEVERE, "Error closing database", e); //NON-NLS
}
}
} catch (Exception e) {
logger.log(Level.SEVERE, "Error parsing text messages to Blackboard", e); //NON-NLS
}
}
}

View File

@ -1,50 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 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.modules.iOS;
import java.util.HashMap;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
import org.sleuthkit.autopsy.ingest.IngestModule;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter;
import org.sleuthkit.autopsy.ingest.IngestServices;
class iOSIngestModule implements DataSourceIngestModule {
private static final HashMap<Long, Long> fileCountsForIngestJobs = new HashMap<>();
private IngestJobContext context = null;
private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter();
private static final Logger logger = Logger.getLogger(iOSModuleFactory.class.getName());
private IngestServices services = IngestServices.getInstance();
@Override
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
this.context = context;
}
@Override
public IngestModule.ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
ContactAnalyzer FindContacts = new ContactAnalyzer();
FindContacts.findContacts(context);
return IngestModule.ProcessResult.OK;
}
}

View File

@ -1,59 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2014 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.modules.iOS;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Version;
import org.sleuthkit.autopsy.ingest.DataSourceIngestModule;
import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
//@ServiceProvider(service = IngestModuleFactory.class) //
public class iOSModuleFactory extends IngestModuleFactoryAdapter {
static String getModuleName() {
return NbBundle.getMessage(iOSModuleFactory.class, "iOSModuleFactory.moduleName");
}
@Override
public String getModuleDisplayName() {
return getModuleName();
}
@Override
public String getModuleDescription() {
return NbBundle.getMessage(iOSModuleFactory.class, "iOSModuleFactory.moduleDescription");
}
@Override
public String getModuleVersionNumber() {
return Version.getVersion();
}
@Override
public boolean isDataSourceIngestModuleFactory() {
return true;
}
@Override
public DataSourceIngestModule createDataSourceIngestModule(IngestModuleIngestJobSettings settings) {
return new iOSIngestModule();
}
}

View File

@ -41,6 +41,8 @@ from org.sleuthkit.datamodel import BlackboardArtifact
from org.sleuthkit.datamodel import BlackboardAttribute from org.sleuthkit.datamodel import BlackboardAttribute
from org.sleuthkit.datamodel import Content from org.sleuthkit.datamodel import Content
from org.sleuthkit.datamodel import TskCoreException from org.sleuthkit.datamodel import TskCoreException
from org.sleuthkit.datamodel.blackboardutils import GeoArtifactsHelper
from org.sleuthkit.datamodel.blackboardutils.attributes.TskGeoWaypointsUtil import GeoWaypointList
import traceback import traceback
import general import general
@ -52,15 +54,25 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer):
def __init__(self): def __init__(self):
self._logger = Logger.getLogger(self.__class__.__name__) self._logger = Logger.getLogger(self.__class__.__name__)
self.current_case = None
self.PROGRAM_NAME = "Google Maps History"
self.CAT_DESTINATION = "Destination"
def analyze(self, dataSource, fileManager, context): def analyze(self, dataSource, fileManager, context):
try:
self.current_case = Case.getCurrentCaseThrows()
except NoCurrentCaseException as ex:
self._logger.log(Level.WARNING, "No case currently open.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
return
try: try:
absFiles = fileManager.findFiles(dataSource, "da_destination_history") absFiles = fileManager.findFiles(dataSource, "da_destination_history")
if absFiles.isEmpty(): if absFiles.isEmpty():
return return
for abstractFile in absFiles: for abstractFile in absFiles:
try: try:
jFile = File(Case.getCurrentCase().getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName()) jFile = File(self.current_case.getTempDirectory(), str(abstractFile.getId()) + abstractFile.getName())
ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled) ContentUtils.writeToFile(abstractFile, jFile, context.dataSourceIngestIsCancelled)
self.__findGeoLocationsInDB(jFile.toString(), abstractFile) self.__findGeoLocationsInDB(jFile.toString(), abstractFile)
except Exception as ex: except Exception as ex:
@ -75,6 +87,8 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer):
return return
try: try:
artifactHelper = GeoArtifactsHelper(self.current_case.getSleuthkitCase(),
general.MODULE_NAME, self.PROGRAM_NAME, abstractFile)
Class.forName("org.sqlite.JDBC") # load JDBC driver Class.forName("org.sqlite.JDBC") # load JDBC driver
connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath) connection = DriverManager.getConnection("jdbc:sqlite:" + databasePath)
statement = connection.createStatement() statement = connection.createStatement()
@ -101,31 +115,21 @@ class GoogleMapLocationAnalyzer(general.AndroidComponentAnalyzer):
source_lat = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lat")) source_lat = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lat"))
source_lng = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lng")) source_lng = GoogleMapLocationAnalyzer.convertGeo(resultSet.getString("source_lng"))
attributes = ArrayList() waypointlist = GeoWaypointList()
artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE) waypointlist.addPoint(source_lat, source_lng, None, None)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, general.MODULE_NAME, "Destination")) waypointlist.addPoint(dest_lat, dest_lng, None, dest_address)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, general.MODULE_NAME, time))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_END, general.MODULE_NAME, dest_lat)) artifactHelper.addRoute(dest_title, time, waypointlist, None)
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_END, general.MODULE_NAME, dest_lng))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE_START, general.MODULE_NAME, source_lat))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE_START, general.MODULE_NAME, source_lng))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, general.MODULE_NAME, dest_title))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, general.MODULE_NAME, dest_address))
attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, general.MODULE_NAME, "Google Maps History"))
artifact.addAttributes(attributes)
try:
# index the artifact for keyword search
blackboard = Case.getCurrentCase().getSleuthkitCase().getBlackboard()
blackboard.postArtifact(artifact, general.MODULE_NAME)
except Blackboard.BlackboardException as ex:
self._logger.log(Level.SEVERE, "Unable to index blackboard artifact " + str(artifact.getArtifactID()), ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
MessageNotifyUtil.Notify.error("Failed to index GPS route artifact for keyword search.", artifact.getDisplayName())
except SQLException as ex: except SQLException as ex:
# Unable to execute Google map locations SQL query against database. # Unable to execute Google map locations SQL query against database.
pass pass
except TskCoreException as ex:
self._logger.log(Level.SEVERE, "Failed to add route artifacts.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc())
except BlackboardException as ex:
self._logger.log(Level.WARNING, "Failed to post artifacts.", ex)
self._logger.log(Level.WARNING, traceback.format_exc())
except Exception as ex: except Exception as ex:
self._logger.log(Level.SEVERE, "Error processing google maps history.", ex) self._logger.log(Level.SEVERE, "Error processing google maps history.", ex)
self._logger.log(Level.SEVERE, traceback.format_exc()) self._logger.log(Level.SEVERE, traceback.format_exc())

View File

@ -276,8 +276,8 @@
<binary-origin>release/modules/ext/icu4j-3.8.jar</binary-origin> <binary-origin>release/modules/ext/icu4j-3.8.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension> <class-path-extension>
<runtime-relative-path>ext/guava-17.0.jar</runtime-relative-path> <runtime-relative-path>ext/guava-19.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/guava-17.0.jar</binary-origin> <binary-origin>release/modules/ext/guava-19.0.jar</binary-origin>
</class-path-extension> </class-path-extension>
<class-path-extension> <class-path-extension>
<runtime-relative-path>ext/language-detector-0.6.jar</runtime-relative-path> <runtime-relative-path>ext/language-detector-0.6.jar</runtime-relative-path>