diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AppDBParserHelper.java b/Core/src/org/sleuthkit/autopsy/coreutils/AppDBParserHelper.java index 32848c6437..0b6f567b87 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/AppDBParserHelper.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/AppDBParserHelper.java @@ -56,7 +56,48 @@ public final class AppDBParserHelper { UNREAD, /// message has not been read READ /// message has been read } + + /** + * Enum for call/message direction + */ + public enum CommunicationDirection + { + UNKNOWN("Unknown"), + INCOMING("Incoming"), + OUTGOING("Outgoing"); + private final String dirStr; + + CommunicationDirection(String dir) { + this.dirStr = dir; + } + + public String getString() { + return dirStr; + } + } + + /** + * Enum for call media type + */ + public enum CallMediaType + { + UNKNOWN("Unknown"), + AUDIO("Audio"), + VIDEO("Video"); + + private final String typeStr; + + CallMediaType(String type) { + this.typeStr = type; + } + + public String getString() { + return typeStr; + } + } + + private final AbstractFile dbAbstractFile; private final String moduleName; @@ -248,13 +289,15 @@ public final class AppDBParserHelper { * @param relationshipType type of relationship * @param dateTime date/time of relationship */ - private void addRelationship(AccountFileInstance selfAccount, AccountFileInstance otherAccount, + private void addRelationship(AccountFileInstance selfAccountInstance, AccountFileInstance otherAccountInstance, BlackboardArtifact sourceArtifact, Relationship.Type relationshipType, long dateTime) { try { - Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(selfAccount, - Collections.singletonList(otherAccount), sourceArtifact, relationshipType, dateTime); + if (selfAccountInstance.getAccount() != otherAccountInstance.getAccount()) { + Case.getCurrentCase().getSleuthkitCase().getCommunicationsManager().addRelationships(selfAccountInstance, + Collections.singletonList(otherAccountInstance), sourceArtifact, relationshipType, dateTime); + } } catch (TskCoreException | TskDataException ex) { - logger.log(Level.SEVERE, String.format("Unable to add relationship between account %s and account %s", selfAccount.toString(), otherAccount.toString()), ex); //NON-NLS + logger.log(Level.SEVERE, String.format("Unable to add relationship between account %s and account %s", selfAccountInstance.toString(), otherAccountInstance.toString()), ex); //NON-NLS } } @@ -278,7 +321,8 @@ public final class AppDBParserHelper { * @return message artifact */ public BlackboardArtifact addMessage( - String messageType, String direction, + String messageType, + CommunicationDirection direction, Account.Address fromAddress, Account.Address toAddress, long dateTime, MessageReadStatusEnum readStatus, @@ -309,7 +353,8 @@ public final class AppDBParserHelper { * * @return message artifact */ - public BlackboardArtifact addMessage( String messageType, String direction, + public BlackboardArtifact addMessage( String messageType, + CommunicationDirection direction, Account.Address fromAddress, Account.Address toAddress, long dateTime, MessageReadStatusEnum readStatus, String subject, @@ -345,7 +390,8 @@ public final class AppDBParserHelper { * * @return message artifact */ - public BlackboardArtifact addMessage( String messageType, String direction, + public BlackboardArtifact addMessage( String messageType, + CommunicationDirection direction, Account.Address fromAddress, List recipientsList, long dateTime, MessageReadStatusEnum readStatus, @@ -358,7 +404,8 @@ public final class AppDBParserHelper { } - public BlackboardArtifact addMessage( String messageType, String direction, + public BlackboardArtifact addMessage( String messageType, + CommunicationDirection direction, Account.Address fromAddress, List recipientsList, long dateTime, MessageReadStatusEnum readStatus, @@ -366,17 +413,6 @@ public final class AppDBParserHelper { String threadId, Collection otherAttributesList) { - - // Create a comma separated string of recipients - String toAddresses = null; - if (recipientsList != null && (!recipientsList.isEmpty())) { - StringBuilder toAddressesSb = new StringBuilder(); - for(Account.Address recipient : recipientsList) { - toAddressesSb = toAddressesSb.length() > 0 ? toAddressesSb.append(",").append(recipient.getDisplayName()) : toAddressesSb.append(recipient.getDisplayName()); - } - toAddresses = toAddressesSb.toString(); - } - // Created message artifact. BlackboardArtifact msgArtifact = null; try { @@ -393,12 +429,14 @@ public final class AppDBParserHelper { if (!StringUtils.isEmpty(messageType)) { msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, moduleName, messageType)); } - if (!StringUtils.isEmpty(direction)) { - msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, direction)); + if (direction != CommunicationDirection.UNKNOWN) { + msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, direction.getString())); } if (fromAddress != null && !StringUtils.isEmpty(fromAddress.getDisplayName())) { msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, fromAddress.getDisplayName())); } + // Create a comma separated string of recipients + String toAddresses = addressListToString(recipientsList); if (toAddresses != null && !StringUtils.isEmpty(toAddresses)) { msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, toAddresses)); } @@ -413,7 +451,6 @@ public final class AppDBParserHelper { msgArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_THREAD_ID, moduleName, threadId)); } - // Add other specified attributes for (BlackboardAttribute otherAttribute: otherAttributesList) { msgArtifact.addAttribute(otherAttribute); @@ -444,8 +481,6 @@ public final class AppDBParserHelper { // post artifact Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifact(msgArtifact, this.moduleName); - - } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Unable to add message artifact", ex); //NON-NLS return null; @@ -461,24 +496,45 @@ public final class AppDBParserHelper { /** * Adds a TSK_CALLLOG artifact. * - * Also creates an account instance for the caller/receiver, and creates a - * relationship between the self account and the caller/receiver account. + * Also creates an account instance for the caller/callee, and creates a + * relationship between the self account and the caller/callee account. * - * @param otherAccountUniqueID unique id for the caller/receiver account * @param direction call direction - * @param fromPhoneNumber originating phone number, may be empty - * @param toPhoneNumber recipient phone number, may be empty + * @param fromAddress caller address, may be empty + * @param toAddress callee address, may be empty * @param startDateTime start date/time * @param endDateTime end date/time - * @param contactName contact name, may be empty * * @return call log artifact */ - public BlackboardArtifact addCalllog( String otherAccountUniqueID, - String direction, String fromPhoneNumber, String toPhoneNumber, - long startDateTime, long endDateTime, String contactName) { - return addCalllog(otherAccountUniqueID, direction, fromPhoneNumber, toPhoneNumber, - startDateTime, endDateTime, contactName, + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, Account.Address toAddress, + long startDateTime, long endDateTime) { + return addCalllog(direction, fromAddress, toAddress, + startDateTime, endDateTime, + CallMediaType.UNKNOWN); + } + + /** + * Adds a TSK_CALLLOG artifact. + * + * Also creates an account instance for the caller/callee, and creates a + * relationship between the self account and the caller/callee account. + * + * @param direction call direction + * @param fromAddress caller address, may be empty + * @param toAddress callee address, may be empty + * @param startDateTime start date/time + * @param endDateTime end date/time + * @param mediaType media type + * + * @return call log artifact + */ + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, Account.Address toAddress, + long startDateTime, long endDateTime, CallMediaType mediaType) { + return addCalllog(direction, fromAddress, toAddress, + startDateTime, endDateTime, mediaType, Collections.emptyList()); } @@ -486,23 +542,108 @@ public final class AppDBParserHelper { * Adds a TSK_CALLLOG artifact. * * Also creates an account instance for the caller/receiver, and creates a - * relationship between the device owner account and the caller/receiver account. + * relationship between the self account and the caller/receiver account. * - * @param otherAccountUniqueID - * @param direction - * @param fromPhoneNumber - * @param toPhoneNumber - * @param startDateTime - * @param endDateTime - * @param contactName - * @param otherAttributesList + * @param direction call direction + * @param fromAddress caller address, may be empty + * @param toAddress callee address, may be empty + * @param startDateTime start date/time + * @param endDateTime end date/time + * @param mediaType media type + * @param otherAttributesList other attributes + * + * @return call log artifact + */ + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, + Account.Address toAddress, + long startDateTime, long endDateTime, + CallMediaType mediaType, + Collection otherAttributesList) { + return addCalllog(direction, + fromAddress, + Arrays.asList(toAddress), + startDateTime, endDateTime, + mediaType, + otherAttributesList); + } + + /** + * Adds a TSK_CALLLOG artifact. + * + * Also creates an account instance for the caller/callees, + * and creates a relationship between the device owner account and the caller account + * as well between the device owner account and each callee account + * + * @param direction call direction + * @param fromAddress caller address, may be empty + * @param toAddressList callee address list, may be empty + * @param startDateTime start date/time + * @param endDateTime end date/time + * + * @return call log artifact + */ + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, + Collection toAddressList, + long startDateTime, long endDateTime) { + + return addCalllog(direction, fromAddress, toAddressList, + startDateTime, endDateTime, + CallMediaType.UNKNOWN); + } + + /** + * Adds a TSK_CALLLOG artifact. + * + * Also creates an account instance for the caller/callees, + * and creates a relationship between the device owner account and the caller account + * as well between the device owner account and each callee account + * + * @param direction call direction + * @param fromAddress caller address, may be empty + * @param toAddressList callee address list, may be empty + * @param startDateTime start date/time + * @param endDateTime end date/time + * @param mediaType call media type + * + * @return call log artifact + */ + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, + Collection toAddressList, + long startDateTime, long endDateTime, + CallMediaType mediaType) { + + return addCalllog(direction, fromAddress, toAddressList, + startDateTime, endDateTime, + mediaType, + Collections.emptyList()); + } + + /** + * Adds a TSK_CALLLOG artifact. + * + * Also creates an account instance for the caller/callees, + * and creates a relationship between the device owner account and the caller account + * as well between the device owner account and each callee account + * + * @param direction call direction + * @param fromAddress caller address, may be empty + * @param toAddressList callee address list, may be empty + * @param startDateTime start date/time + * @param endDateTime end date/time + * @param mediaType called media type + * @param otherAttributesList other attributes * * @return calllog artifact */ - public BlackboardArtifact addCalllog(String otherAccountUniqueID, - String direction, String fromPhoneNumber, String toPhoneNumber, - long startDateTime, long endDateTime, String contactName, - Collection otherAttributesList) { + public BlackboardArtifact addCalllog(CommunicationDirection direction, + Account.Address fromAddress, + Collection toAddressList, + long startDateTime, long endDateTime, + CallMediaType mediaType, + Collection otherAttributesList) { BlackboardArtifact callLogArtifact = null; try { // Create TSK_CALLLOG artifact @@ -516,17 +657,20 @@ public final class AppDBParserHelper { callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_END, moduleName, endDateTime)); } - if (!StringUtils.isEmpty(direction)) { - callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, direction)); + if (direction != CommunicationDirection.UNKNOWN) { + callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DIRECTION, moduleName, direction.getString())); } - if (!StringUtils.isEmpty(fromPhoneNumber)) { - callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, fromPhoneNumber)); + if (fromAddress != null) { + callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, moduleName, fromAddress.getUniqueID())); + if (!StringUtils.isEmpty(fromAddress.getDisplayName())) { + callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, moduleName, fromAddress.getDisplayName())); + } } - if (!StringUtils.isEmpty(toPhoneNumber)) { - callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, toPhoneNumber)); - } - if (!StringUtils.isEmpty(contactName)) { - callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, moduleName, contactName)); + + // Create a comma separated string of recipients + String toAddresses = addressListToString(toAddressList); + if (!StringUtils.isEmpty(toAddresses)) { + callLogArtifact.addAttribute(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, moduleName, toAddresses)); } // Add other specified attributes @@ -534,13 +678,24 @@ public final class AppDBParserHelper { callLogArtifact.addAttribute(otherAttribute); } - // Find/Create an account instance for the sender/recipient - // Create a relationship between selfAccount and contactAccount - AccountFileInstance contactAccountInstance = createAccountInstance(accountsType, otherAccountUniqueID); - if (selfAccountInstance != null) { - addRelationship (selfAccountInstance, contactAccountInstance, callLogArtifact, Relationship.Type.CALL_LOG, 0 ); + // Create a relationship between selfAccount and caller + if (fromAddress != null) { + AccountFileInstance callerAccountInstance = createAccountInstance(accountsType, fromAddress.getUniqueID()); + if (selfAccountInstance != null) { + addRelationship (selfAccountInstance, callerAccountInstance, callLogArtifact, Relationship.Type.CALL_LOG, (startDateTime > 0) ? startDateTime : 0 ); + } } + // Create a relationship between selfAccount and each callee + if (toAddressList != null) { + for(Account.Address callee : toAddressList) { + AccountFileInstance calleeAccountInstance = createAccountInstance(accountsType, callee.getUniqueID()); + if (selfAccountInstance != null) { + addRelationship (selfAccountInstance, calleeAccountInstance, callLogArtifact, Relationship.Type.CALL_LOG, (startDateTime > 0) ? startDateTime : 0 ); + } + } + } + // post artifact Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifact(callLogArtifact, this.moduleName); } catch (TskCoreException ex) { @@ -1142,4 +1297,25 @@ public final class AppDBParserHelper { return gpsTrackpointArtifact; } + /** + * Converts a list of addresses into a single comma separated string of + * addresses. + * + * @param addressList + * @return comma separated string of addresses + */ + private String addressListToString(Collection addressList) { + + String toAddresses = ""; + if (addressList != null && (!addressList.isEmpty())) { + StringBuilder toAddressesSb = new StringBuilder(); + for(Account.Address address : addressList) { + String displayAddress = !StringUtils.isEmpty(address.getDisplayName()) ? address.getDisplayName() : address.getUniqueID(); + toAddressesSb = toAddressesSb.length() > 0 ? toAddressesSb.append(",").append(displayAddress) : toAddressesSb.append(displayAddress); + } + toAddresses = toAddressesSb.toString(); + } + + return toAddresses; + } } diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/AppSQLiteDB.java b/Core/src/org/sleuthkit/autopsy/coreutils/AppSQLiteDB.java index f514072433..6812f6daf3 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/AppSQLiteDB.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/AppSQLiteDB.java @@ -44,35 +44,53 @@ import org.sleuthkit.datamodel.TskCoreException; /** * An abstraction around an SQLite app DB found in a data source. - * This class makes a copy of it, opens a SQLite connection to it - * and runs queries on it. + * This class makes a copy of it, along with any meta files (WAL, SHM), + * opens a SQLite connection to it, and runs queries on it. */ public final class AppSQLiteDB implements Closeable { private final Logger logger = Logger.getLogger(AppSQLiteDB.class.getName()); private final AbstractFile dbAbstractFile; // AbstractFile for the DB file - private Connection connection = null; - private Statement statement = null; + private final Connection connection; + private final Statement statement; - private AppSQLiteDB(AbstractFile dbAbstractFile, File dbFileCopy) { - this.dbAbstractFile = dbAbstractFile; + + /** + * Class to abstract the abstract file for a DB file and its on disk copy + * + */ + private static final class AppSQLiteDBFileBundle { + private final AbstractFile dbAbstractFile; + private final File dbFileCopy; - try { - Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver - connection = DriverManager.getConnection("jdbc:sqlite:" + dbFileCopy.getPath()); //NON-NLS - statement = connection.createStatement(); - } catch (ClassNotFoundException | SQLException e) { - logger.log(Level.SEVERE, "Error opening database " + dbFileCopy.getPath(), e); //NON-NLS - connection = null; - statement = null; + AppSQLiteDBFileBundle(AbstractFile dbAbstractFile, File dbFileCopy) { + this.dbAbstractFile = dbAbstractFile; + this.dbFileCopy = dbFileCopy; } + + AbstractFile getAbstractFile() { + return dbAbstractFile; + } + + File getFileCopy() { + return dbFileCopy; + } + + } + + private AppSQLiteDB(AppSQLiteDBFileBundle appSQLiteDBFileBundle) throws ClassNotFoundException, SQLException { + this.dbAbstractFile = appSQLiteDBFileBundle.getAbstractFile(); + + Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver + connection = DriverManager.getConnection("jdbc:sqlite:" + appSQLiteDBFileBundle.getFileCopy().getPath()); //NON-NLS + statement = connection.createStatement(); } /** * Looks for the given SQLIte database filename, with matching path substring. - * It looks for exact name or a pattern match based on + * It looks for exact name or a pattern match based on a input parameter. * It makes a copy of each matching file, and creates an instance of * AppSQLiteDB to help query the DB. * @@ -84,57 +102,27 @@ public final class AppSQLiteDB implements Closeable { * @param matchExactName whether to look for exact file name or a pattern match * @param parentPathSubstr path substring to match * - * @return AbstractFile for the DB if the database file is found. - * Returns NULL if no such database is found. + * @return A list of abstract files matching the specified name and path. + * Returns an empty list if no matching database is found. */ - public static Collection findAppDatabases(DataSource dataSource, String dbName, boolean matchExactName, String parentPathSubstr) { + public static Collection findAppDatabases(DataSource dataSource, + String dbName, boolean matchExactName, String parentPathSubstr) { List appDbs = new ArrayList<> (); - Case openCase; - try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS - return appDbs; - } - - List absFiles; - long fileId = 0; - String localDiskPath = ""; - try { - SleuthkitCase skCase = openCase.getSleuthkitCase(); - String parentPath = parentPathSubstr.replace("\\", "/"); - parentPath = SleuthkitCase.escapeSingleQuotes(parentPath); - String whereClause; - if (matchExactName) { - whereClause = String.format("LOWER(name) = LOWER('%s') AND LOWER(parent_path) LIKE LOWER('%%%s%%') AND data_source_obj_id = %s", dbName, parentPath, dataSource.getId()); - } else { - whereClause = String.format("LOWER(name) LIKE LOWER('%%%s%%') AND LOWER(name) NOT LIKE LOWER('%%journal%%') AND LOWER(parent_path) LIKE LOWER('%%%s%%') AND data_source_obj_id = %s", dbName, parentPath, dataSource.getId() ); - } - absFiles = skCase.findAllFilesWhere(whereClause); - for (AbstractFile absFile : absFiles) { + Collection dbFileBundles = findAndCopySQLiteDB( dataSource, dbName, matchExactName, parentPathSubstr, false); + dbFileBundles.forEach((dbFileBundle) -> { try { - localDiskPath = openCase.getTempDirectory() - + File.separator + absFile.getId() + absFile.getName(); - File jFile = new java.io.File(localDiskPath); - fileId = absFile.getId(); - ContentUtils.writeToFile(absFile, jFile); - - //Find and copy both WAL and SHM meta files - findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-wal"); - findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-shm"); - - appDbs.add(new AppSQLiteDB(absFile, jFile) ); - } catch (ReadContentInputStream.ReadContentInputStreamException ex) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", absFile.getName(), fileId), ex); //NON-NLS - } catch (IOException | NoCurrentCaseException | TskCoreException ex) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", absFile.getName(), fileId, localDiskPath), ex); //NON-NLS + AppSQLiteDB appSQLiteDB = new AppSQLiteDB(dbFileBundle); + appDbs.add(appSQLiteDB); + } catch (ClassNotFoundException | SQLException ex) { + Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Failed to open a DB connection for file = '%s' and path = '%s'.", dbFileBundle.dbAbstractFile.getName(), dbFileBundle.getFileCopy().getPath()), ex); //NON-NLS } - } - } catch (TskCoreException e) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, "Error finding application DB file.", e); //NON-NLS + }); + } catch (TskCoreException ex) { + Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Error finding App database files with name = '%s' and path = '%s'.", dbName, parentPathSubstr), ex); //NON-NLS } + return appDbs; } @@ -151,120 +139,117 @@ public final class AppSQLiteDB implements Closeable { * * @param dataSource data source in which to look file the db file * @param dbName name of db file to look for - * @param matchExactName specified whether the name is an exact name or a pattern * @param dbPath path in which to look for the db file * @param dbAlias alias name to attach the database as * * @return abstract file for the matching db file. - * - * @throws TskCoreException in case of an error. + * null if no match is found. + * + * @throws SQLException in case of an SQL error */ public AbstractFile attachDatabase(DataSource dataSource, String dbName, - boolean matchExactName, String dbPath, String dbAlias) throws TskCoreException { - - Case openCase; + String dbPath, String dbAlias) throws SQLException { try { - openCase = Case.getCurrentCaseThrows(); - } catch (NoCurrentCaseException ex) { - throw new TskCoreException("Exception while getting open case.", ex); - } - - List absFiles; - long fileId = 0; - String localFilePath = ""; - try { - SleuthkitCase skCase = openCase.getSleuthkitCase(); - String parentPath = dbPath.replace("\\", "/"); - parentPath = SleuthkitCase.escapeSingleQuotes(parentPath); - String whereClause; - if (matchExactName) { - whereClause = String.format("LOWER(name) = LOWER('%s') AND LOWER(parent_path) = LOWER('%s') AND data_source_obj_id = %s", dbName, parentPath, dataSource.getId()); //NON-NLS - } else { - whereClause = String.format("LOWER(name) LIKE LOWER('%%%s%%') AND LOWER(name) NOT LIKE LOWER('%%journal%%') AND LOWER(parent_path) = LOWER('%s') AND data_source_obj_id = %s", dbName, parentPath, dataSource.getId()); //NON-NLS - } - absFiles = skCase.findAllFilesWhere(whereClause); - for (AbstractFile absFile : absFiles) { - try { - localFilePath = openCase.getTempDirectory() - + File.separator + absFile.getId() + absFile.getName(); - File jFile = new java.io.File(localFilePath); - fileId = absFile.getId(); - ContentUtils.writeToFile(absFile, jFile); + // find and copy DB files with exact name and path. + Collection dbFileBundles = findAndCopySQLiteDB(dataSource, dbName, true, dbPath, true); + if (!dbFileBundles.isEmpty()) { + AppSQLiteDBFileBundle dbFileBundle = dbFileBundles.iterator().next(); + String attachDbSql = String.format("ATTACH DATABASE '%s' AS '%s'", dbFileBundle.getFileCopy().getPath(), dbAlias); //NON-NLS + statement.executeUpdate(attachDbSql); - //Find and copy both WAL and SHM meta files - findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-wal"); - findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-shm"); - - //run the ATTACH DATABASE sql command - try { - String attachDbSql = String.format("ATTACH DATABASE '%s' AS '%s'", localFilePath, dbAlias); //NON-NLS - statement.executeUpdate(attachDbSql); - } - catch (SQLException ex) { - throw new TskCoreException("Error running ATTACH DATABASE SQL. " + ex.getMessage(), ex); - } - - return absFile; - } catch (ReadContentInputStream.ReadContentInputStreamException ex) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", absFile.getName(), fileId), ex); //NON-NLS - } catch (IOException | NoCurrentCaseException ex) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Error writing content from file '%s' (id=%d) to '%s'.", absFile.getName(), fileId, localFilePath), ex); //NON-NLS - } + return dbFileBundle.getAbstractFile(); } - } catch (TskCoreException e) { - Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, "Error finding application DB file.", e); //NON-NLS + } catch (TskCoreException ex) { + Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Error attaching to App database files with name = '%s' and path = '%s'.", dbName, dbPath), ex); //NON-NLS } return null; } + /** + * Finds database file with the specified name, makes a copy of the file in the case directory, + * and returns the AbstractFile as well as the file copy. + * + * @param dataSource data source to search in + * @param dbName db file name to search + * @param matchExactName whether to look for exact file name or a pattern match + * @param dbPath path to match + * @param matchExactName whether to look for exact path name or a substring match + * + * @return a collection of AppSQLiteDBFileBundle + * + * @throws TskCoreException + */ + private static Collection findAndCopySQLiteDB(DataSource dataSource, String dbName, + boolean matchExactName, String dbPath, boolean matchExactPath) throws TskCoreException { + + Case openCase; + try { + openCase = Case.getCurrentCaseThrows(); + } catch (NoCurrentCaseException ex) { + throw new TskCoreException("Failed to get current case.", ex); + } + + List dbFileBundles = new ArrayList<> (); + long fileId = 0; + String localDiskPath = ""; + + SleuthkitCase skCase = openCase.getSleuthkitCase(); + String parentPath = dbPath.replace("\\", "/"); + parentPath = SleuthkitCase.escapeSingleQuotes(parentPath); + + String whereClause; + if (matchExactName) { + whereClause = String.format("LOWER(name) = LOWER('%s')", dbName); + } else { + whereClause = String.format("LOWER(name) LIKE LOWER('%%%s%%') AND LOWER(name) NOT LIKE LOWER('%%journal%%')", dbName ); + } + if (matchExactPath) { + whereClause += String.format(" AND LOWER(parent_path) = LOWER('%s')", parentPath ); + } else { + whereClause += String.format(" AND LOWER(parent_path) LIKE LOWER('%%%s%%')", parentPath ); + } + whereClause += String.format(" AND data_source_obj_id = %s", dataSource.getId()); + + List absFiles = skCase.findAllFilesWhere(whereClause); + for (AbstractFile absFile : absFiles) { + try { + localDiskPath = openCase.getTempDirectory() + + File.separator + absFile.getId() + absFile.getName(); + File jFile = new java.io.File(localDiskPath); + fileId = absFile.getId(); + ContentUtils.writeToFile(absFile, jFile); + + //Find and copy both WAL and SHM meta files + findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-wal"); + findAndCopySQLiteMetaFile(absFile, absFile.getName() + "-shm"); + + AppSQLiteDBFileBundle dbFileBundle = new AppSQLiteDBFileBundle(absFile, jFile); + dbFileBundles.add(dbFileBundle); + + } catch (ReadContentInputStream.ReadContentInputStreamException ex) { + Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.WARNING, String.format("Error reading content from file '%s' (id=%d).", absFile.getName(), fileId), ex); //NON-NLS + } catch (IOException | NoCurrentCaseException | TskCoreException ex) { + Logger.getLogger(AppSQLiteDB.class.getName()).log(Level.SEVERE, String.format("Error creating AppSQLiteDB for file '%s' (id=%d) to copied to '%s'.", absFile.getName(), fileId, localDiskPath), ex); //NON-NLS + } + } + + return dbFileBundles; + } + /** * Detaches the specified database from the connection * * @param dbAlias alias for database to detach * - * @throws TskCoreException + * @throws SQLException */ - public void detachDatabase(String dbAlias) throws TskCoreException { - - try { - String detachDbSql = String.format("DETACH DATABASE '%s'", dbAlias); - statement.executeUpdate(detachDbSql); //NON-NLS - } - catch (SQLException ex) { - throw new TskCoreException("Error running DETACH DATABASE SQL. " + ex.getMessage(), ex); - } + public void detachDatabase(String dbAlias) throws SQLException { + String detachDbSql = String.format("DETACH DATABASE '%s'", dbAlias); + statement.executeUpdate(detachDbSql); //NON-NLS } - /** - * Checks if the specified table exists in the given database file. - * - * @param tableName table name to check - * - * @return - */ - public boolean tableExists(String tableName) { - // RAMAN TBD - return false; - - } - - /** - * Checks if the specified column exists. - * - * @param tableName table name to check - * @param columnName column name to check - * @return - */ - public boolean columnExists(String tableName, String columnName) { - // RAMAN TBD - return false; - } - - - - /** * Runs the given query on the database and returns result set. @@ -272,18 +257,15 @@ public final class AppSQLiteDB implements Closeable { * * @return ResultSet from running the query. * - * @throws TskCoreException in case of an error. + * @throws SQLException in case of an error. * */ - public ResultSet runQuery(String queryStr) throws TskCoreException { + public ResultSet runQuery(String queryStr) throws SQLException { ResultSet resultSet = null; - try { + + if (null != queryStr) { resultSet = statement.executeQuery(queryStr); //NON-NLS - } - catch (SQLException ex) { - throw new TskCoreException("Error running app SQLite query. " + ex.getMessage(), ex); - } - + } return resultSet; } diff --git a/InternalPythonModules/android/imo.py b/InternalPythonModules/android/imo.py index 2407ff552d..0bb1b913b2 100644 --- a/InternalPythonModules/android/imo.py +++ b/InternalPythonModules/android/imo.py @@ -33,6 +33,8 @@ from org.sleuthkit.autopsy.coreutils import Logger from org.sleuthkit.autopsy.coreutils import MessageNotifyUtil from org.sleuthkit.autopsy.coreutils import AppSQLiteDB from org.sleuthkit.autopsy.coreutils import AppDBParserHelper +from org.sleuthkit.autopsy.coreutils.AppDBParserHelper import MessageReadStatusEnum +from org.sleuthkit.autopsy.coreutils.AppDBParserHelper import CommunicationDirection from org.sleuthkit.autopsy.datamodel import ContentUtils from org.sleuthkit.autopsy.ingest import IngestJobContext from org.sleuthkit.datamodel import AbstractFile @@ -97,20 +99,20 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer): uniqueId = messagesResultSet.getString("buid") if (messagesResultSet.getInt("message_type") == 1): - direction = "Incoming" + direction = CommunicationDirection.INCOMING fromAddress = Account.Address(uniqueId, name) else: - direction = "Outgoing" + direction = CommunicationDirection.OUTGOING toAddress = Account.Address(uniqueId, name) message_read = messagesResultSet.getInt("message_read") if (message_read == 1): - msgReadStatus = AppDBParserHelper.MessageReadStatusEnum.READ + msgReadStatus = MessageReadStatusEnum.READ elif (message_read == 0): - msgReadStatus = AppDBParserHelper.MessageReadStatusEnum.UNREAD + msgReadStatus = MessageReadStatusEnum.UNREAD else: - msgReadStatus = AppDBParserHelper.MessageReadStatusEnum.UNKNOWN + msgReadStatus = MessageReadStatusEnum.UNKNOWN timeStamp = messagesResultSet.getLong("timestamp") / 1000000000 @@ -129,9 +131,11 @@ class IMOAnalyzer(general.AndroidComponentAnalyzer): # TBD: parse the imdata JSON structure to figure out if there is an attachment. # If one exists, add the attachment as a derived file and a child of the message artifact. - + except SQLException as ex: - self._logger.log(Level.SEVERE, "Error processing query result for IMO friends", ex) + self._logger.log(Level.SEVERE, "Error processing query result for IMO friends", ex) + except TskCoreException as ex: + self._logger.log(Level.SEVERE, "Failed to create AppDBParserHelper for adding artifacts.", ex) finally: friendsDb.close()