diff --git a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellType.java b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellType.java index d491fcfadf..685c083eda 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellType.java +++ b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellType.java @@ -16,14 +16,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.interestingdatabases; +package org.sleuthkit.autopsy.modules.databaseselector; /** - * - * @author dsmyda + * Cell types used by the CellTypeDetector class */ public enum CellType { EMAIL, NOT_INTERESTING -} - +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellTypeDetector.java index e3301bccb8..903bccca37 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/CellTypeDetector.java @@ -16,18 +16,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.interestingdatabases; +package org.sleuthkit.autopsy.modules.databaseselector; + +import org.apache.commons.validator.routines.EmailValidator; /** - * - * @author dsmyda + * Determines the type of a database cell (EMAIL, PHONE, GPS COORD, MAC ADDRESS) */ public class CellTypeDetector { + + private final EmailValidator emailValidator; - public CellTypeDetector() { + public CellTypeDetector() { + emailValidator = EmailValidator.getInstance(); } public CellType getType(String cell) { - return CellType.EMAIL; + if(emailValidator.isValid(cell)) { + return CellType.EMAIL; + } else { + return CellType.NOT_INTERESTING; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModule.java index b3c9d78ea6..043e85eaaa 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModule.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.interestingdatabases; +package org.sleuthkit.autopsy.modules.databaseselector; import java.io.File; import java.io.IOException; @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException; import org.sleuthkit.autopsy.sqlitereader.SQLiteReader; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -46,197 +47,248 @@ import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.TskCoreException; /** - * - * @author dsmyda + * Parses database files and marks them as having interesting records (emails, + * phone numbers, mac addresses, gps coordinates). */ -public class InterestingDatabasesIngestModule extends FileIngestModuleAdapter { +public class DatabaseSelectorIngestModule extends FileIngestModuleAdapter { private static final String SUPPORTED_MIME_TYPE = "application/x-sqlite3"; - private static final String MODULE_NAME = InterestingDatabasesIngestModuleFactory.getModuleName(); + private static final String MODULE_NAME = DatabaseSelectorIngestModuleFactory.getModuleName(); private final IngestServices services = IngestServices.getInstance(); private final Logger logger = services.getLogger( - InterestingDatabasesIngestModuleFactory.getModuleName()); + DatabaseSelectorIngestModuleFactory.getModuleName()); private FileTypeDetector fileTypeDetector; private CellTypeDetector cellTypeDetector; private Blackboard blackboard; + private String localDiskPath; + - /** - * - * @param context - * @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException - */ @NbBundle.Messages({ - "CannotRunFileTypeDetection=Unable to initialize file type detection.", + "DatabaseSelectorIngestModule.CannotRunFileTypeDetection=Unable to initialize file type detection.", }) @Override public void startUp(IngestJobContext context) throws IngestModuleException { try { fileTypeDetector = new FileTypeDetector(); cellTypeDetector = new CellTypeDetector(); - } catch (FileTypeDetector.FileTypeDetectorInitException ex) { - throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); + } catch (FileTypeDetectorInitException ex) { + throw new IngestModuleException(Bundle.DatabaseSelectorIngestModule_CannotRunFileTypeDetection(), ex); } } - /** - * - * @param file - * @return - */ @Override - @NbBundle.Messages({ - "InterestingDatabasesIngestModule.indexError.message=Failed to index interesting artifact hit for keyword search." - }) public ProcessResult process(AbstractFile file) { + if(getBlackboardInstanceFromServices().equals(ProcessResult.ERROR)) { + return ProcessResult.ERROR; + } + + //Qualify the MIMEType + String dataSourceMimeType = fileTypeDetector.getMIMEType(file); + if(SUPPORTED_MIME_TYPE.equals(dataSourceMimeType)) { + if(createLocalDiskPathFromCurrentCase(file).equals(ProcessResult.ERROR)) { + return ProcessResult.ERROR; + } + + try (SQLiteReader sqliteReader = new SQLiteReader(file, localDiskPath)){ + try { + Set databaseCellTypes = getCellTypesInDatabase(sqliteReader); + //No interesting hits, don't flag this database, skip artifact creation. + if(!databaseCellTypes.isEmpty()) { + String cellTypesComment = createCellTypeCommentString(databaseCellTypes); + try { + BlackboardArtifact artifact = createArtifactGivenCellTypes( + file, cellTypesComment); + indexArtifactAndFireModuleDataEvent(artifact); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error creating blackboard artifact", ex); //NON-NLS + } + } + } catch(SQLException ex) { + logger.log(Level.WARNING, "Error attempting to read sqlite " + + "file in DatabaseSelectorIngestModule", ex); + } + } catch (ClassNotFoundException | SQLException | IOException | + NoCurrentCaseException | TskCoreException ex) { + logger.log(Level.SEVERE, "Cannot initialize sqliteReader class " + + "in DatabaseSelectorIngestModule", ex); + return ProcessResult.ERROR; + } + } + + //Whether we successfully read the sqlite database or determined the mime + //type is not supported, the process is OK. + return ProcessResult.OK; + } + + /** + * Get a pointer to the current case blackboard for indexing of artifacts + * + * @return ProcessResult indicating a success or failure in getting current case + */ + private ProcessResult getBlackboardInstanceFromServices() { try { - blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); + blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); } catch (NoCurrentCaseException ex) { logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS return ProcessResult.ERROR; } - String dataSourceMimeType = fileTypeDetector.getMIMEType(file); - if(SUPPORTED_MIME_TYPE.equals(dataSourceMimeType)) { - - String localDiskPath; - try { - localDiskPath = Case.getCurrentCaseThrows() - .getTempDirectory() + File.separator + file.getName(); - } catch (NoCurrentCaseException ex) { - // TODO -- personal note log about current case being closed or - //current case not existing. - Exceptions.printStackTrace(ex); - return ProcessResult.ERROR; - } - - - try (SQLiteReader sqliteReader = - new SQLiteReader(file, localDiskPath)){ - - Set aggregatedCellTypes = cellTypesInDatabase(sqliteReader); - String cellTypesMessage = aggregatedCellTypes.toString() - .replace("]", "") - .replace("[", ""); - - try { - BlackboardArtifact artifact = createArtifactGivenCellTypes( - file, cellTypesMessage); - - try { - // index the artifact for keyword search - blackboard.indexArtifact(artifact); - } catch (Blackboard.BlackboardException ex) { - logger.log(Level.SEVERE, - "Unable to index blackboard artifact " + //NON-NLS - artifact.getArtifactID(), ex); - MessageNotifyUtil.Notify.error( - Bundle.InterestingDatabasesIngestModule_indexError_message(), - artifact.getDisplayName()); - } - - services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, - BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, - Collections.singletonList(artifact))); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error posting to the blackboard", ex); //NOI18N NON-NLS - } - - } catch (SQLException ex) { - //Could be thrown in init or close.. need to fix. - Exceptions.printStackTrace(ex); - } catch (ClassNotFoundException ex) { - Exceptions.printStackTrace(ex); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } catch (NoCurrentCaseException ex) { - Exceptions.printStackTrace(ex); - } catch (TskCoreException ex) { - Exceptions.printStackTrace(ex); - } + return ProcessResult.OK; + } + + /** + * Generates a local disk path for abstract file contents to be copied. + * All database sources must be copied to local disk to be opened by + * SQLiteReader + * + * @param file The database abstract file + * @return ProcessResult indicating a success or failure in creating a disk path + */ + private ProcessResult createLocalDiskPathFromCurrentCase(AbstractFile file) { + try { + localDiskPath = Case.getCurrentCaseThrows() + .getTempDirectory() + File.separator + file.getName(); + } catch (NoCurrentCaseException ex) { + // TODO -- personal note log about current case being closed or + //current case not existing. + Exceptions.printStackTrace(ex); + return ProcessResult.ERROR; } return ProcessResult.OK; } /** + * Creates and populates a set of all CellTypes in the sqlite database * - * @param sqliteReader - * @return - * @throws SQLException + * @param sqliteReader Reader currently connected to database file + * @return A Set of distinct CellTypes + * @throws SQLException Caught during attempting to read sqlite database */ - private Set cellTypesInDatabase(SQLiteReader sqliteReader) throws SQLException { + private Set getCellTypesInDatabase(SQLiteReader sqliteReader) throws SQLException { Map tables = sqliteReader.getTableSchemas(); Set aggregateCellTypes = new TreeSet<>(); - //Aggregate all cell types from each table + //Aggregate cell types from all tables for(String tableName : tables.keySet()) { - addCellTypesInTable(sqliteReader, tableName, aggregateCellTypes); + aggregateCellTypes.addAll(getCellTypesInTable(sqliteReader, tableName)); } return aggregateCellTypes; } /** + * Creates and populates a set of all CellTypes in a sqlite table * - * @param sqliteReader - * @param table - * @return + * @param sqliteReader Reader currently connected to database file + * @param tableName database table to be opened and read + * @return Set of all unique cell types in table + * @throws SQLException Caught during attempting to read sqlite database */ - private void addCellTypesInTable(SQLiteReader sqliteReader, String tableName, - Set aggregateCellTypes) throws SQLException { - + private Set getCellTypesInTable(SQLiteReader sqliteReader, + String tableName) throws SQLException { + + Set tableCellTypes = new TreeSet<>(); List> tableValues = sqliteReader.getRowsFromTable(tableName); + + //Aggregate cell types from all table rows tableValues.forEach((row) -> { - addCellTypeInRow(row, aggregateCellTypes); + tableCellTypes.addAll(getCellTypesInRow(row)); }); + return tableCellTypes; } /** + * Creates and populates a set of all CellTypes in a table row * - * @param row + * @param row Table row is represented as a column-value map * @return */ - private void addCellTypeInRow(Map row, - Set aggregateCellTypes) { + private Set getCellTypesInRow(Map row) { + Set rowCellTypes = new TreeSet<>(); + + //Aggregate cell types from a row row.values().forEach((Object cell) -> { if(cell instanceof String) { - aggregateCellTypes.add(cellTypeDetector.getType( (String) cell)); + CellType type = cellTypeDetector.getType((String) cell); + if(!type.equals(CellType.NOT_INTERESTING)) { + rowCellTypes.add(type); + } } }); + return rowCellTypes; } /** + * Creates a comma seperated string of all the cell types found in a database + * file. Used as the comment string for the blackboard artifact. * - * @param type + * @param databaseCellTypes The set of all database cell types detected + * @return + */ + private String createCellTypeCommentString(Set databaseCellTypes) { + return databaseCellTypes.toString().replace("]", "").replace("[", ""); + } + + /** + * Initializes a new interesting file hit artifact and provides name and + * comment attributes + * + * @param file The database abstract file + * @param cellTypesComment String of all the cell types found in a database + * file + * @return Interesting file hit artifact + * @throws TskCoreException Thrown if the abstract file cannot create a blackboard + * artifact */ @NbBundle.Messages({ - "InterestingDatabasesIngestModule.FlagDatabases.setName=Selectors identified" + "DatabaseSelectorIngestModule.FlagDatabases.setName=Selectors identified" }) private BlackboardArtifact createArtifactGivenCellTypes(AbstractFile file, - String cellTypesMessage) throws TskCoreException { + String cellTypesComment) throws TskCoreException { BlackboardArtifact artifact = file.newArtifact( BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); BlackboardAttribute setNameAttribute = new BlackboardAttribute( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, - Bundle.InterestingDatabasesIngestModule_FlagDatabases_setName()); + Bundle.DatabaseSelectorIngestModule_FlagDatabases_setName()); artifact.addAttribute(setNameAttribute); BlackboardAttribute commentAttribute = new BlackboardAttribute( BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, - cellTypesMessage); + cellTypesComment); artifact.addAttribute(commentAttribute); return artifact; } /** + * Pass the artifact to blackboard for indexing and fire module data event + * in the IngestServices * + * @param artifact Blackboard artifact created for the interesting file hit */ - @Override - public void shutDown() { - + @NbBundle.Messages({ + "DatabaseSelectorIngestModule.indexError.message=" + + "Failed to index interesting file hit artifact for keyword search." + }) + private void indexArtifactAndFireModuleDataEvent(BlackboardArtifact artifact) { + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, + "Unable to index blackboard artifact " + //NON-NLS + artifact.getArtifactID(), ex); + MessageNotifyUtil.Notify.error( + Bundle.DatabaseSelectorIngestModule_indexError_message(), + artifact.getDisplayName()); + } + + services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME, + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT, + Collections.singletonList(artifact))); } -} +} \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModuleFactory.java index 28c3e5eb2d..155b26c425 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/databaseselector/DatabaseSelectorIngestModuleFactory.java @@ -16,7 +16,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.sleuthkit.autopsy.modules.interestingdatabases; +package org.sleuthkit.autopsy.modules.databaseselector; import org.sleuthkit.autopsy.coreutils.Version; import org.openide.util.NbBundle; @@ -27,19 +27,18 @@ import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; /** - * - * @author dsmyda + * A factory that creates a DatabaseSelectorModule to parse database files and + * mark them as having interesting records (emails, phone numbers, mac addresses, gps coordinates). */ - @NbBundle.Messages({ - "InterestingDatabasesIngestModuleFactory.FlagDatabases.moduleName=Interesting Databases", - "InterestingDatabasesIngestModuleFactory.FlagDatabases.moduleDesc.text=Flags databases with interesting items (emails, phone numbers, gps coordinates, ip/mac addresses)" + "DatabaseSelectorIngestModuleFactory.FlagDatabases.moduleName=Database Selector", + "DatabaseSelectorIngestModuleFactory.FlagDatabases.moduleDesc.text=Flag databases with interesting items (emails, phone numbers, gps coordinates, ip/mac addresses)" }) @ServiceProvider(service = IngestModuleFactory.class) -public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactoryAdapter { +public class DatabaseSelectorIngestModuleFactory extends IngestModuleFactoryAdapter { static String getModuleName() { - return Bundle.InterestingDatabasesIngestModuleFactory_FlagDatabases_moduleName(); + return Bundle.DatabaseSelectorIngestModuleFactory_FlagDatabases_moduleName(); } @Override @@ -49,7 +48,7 @@ public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactory @Override public String getModuleDescription() { - return Bundle.InterestingDatabasesIngestModuleFactory_FlagDatabases_moduleDesc_text(); + return Bundle.DatabaseSelectorIngestModuleFactory_FlagDatabases_moduleDesc_text(); } @Override @@ -64,6 +63,6 @@ public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactory @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { - return new InterestingDatabasesIngestModule(); + return new DatabaseSelectorIngestModule(); } -} +} \ No newline at end of file