19 package org.sleuthkit.autopsy.modules.leappanalyzers;
21 import com.fasterxml.jackson.databind.MappingIterator;
22 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
23 import com.fasterxml.jackson.dataformat.csv.CsvParser;
24 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
25 import com.google.common.collect.ImmutableMap;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.io.UncheckedIOException;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.text.DateFormat;
33 import java.text.ParseException;
34 import java.text.SimpleDateFormat;
35 import java.util.List;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import static java.util.Locale.US;
45 import java.util.logging.Level;
46 import java.util.stream.Collectors;
47 import java.util.stream.IntStream;
48 import java.util.stream.Stream;
49 import javax.xml.parsers.DocumentBuilder;
50 import javax.xml.parsers.DocumentBuilderFactory;
51 import javax.xml.parsers.ParserConfigurationException;
52 import org.apache.commons.collections4.CollectionUtils;
53 import org.apache.commons.collections4.MapUtils;
54 import org.apache.commons.io.FilenameUtils;
55 import org.apache.commons.lang3.StringUtils;
56 import org.openide.util.NbBundle;
57 import org.openide.util.NbBundle.Messages;
72 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
75 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
80 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
81 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CallMediaType;
82 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationDirection;
83 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.MessageReadStatus;
84 import org.
sleuthkit.datamodel.blackboardutils.GeoArtifactsHelper;
85 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
86 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints.TrackPoint;
87 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoints;
88 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoints.Waypoint;
89 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
90 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
91 import org.w3c.dom.Document;
92 import org.w3c.dom.NamedNodeMap;
93 import org.w3c.dom.NodeList;
94 import org.xml.sax.SAXException;
128 BlackboardAttribute.Type getAttributeType() {
135 String getColumnName() {
142 boolean isRequired() {
162 .put(
"TSK_IP_DHCP",
"DHCP Information")
166 .put(
"zapya.tsv",
"message")
167 .put(
"sms messages.tsv",
"message")
168 .put(
"mms messages.tsv",
"message")
169 .put(
"viber - messages.tsv",
"message")
170 .put(
"viber - contacts.tsv",
"contact")
171 .put(
"viber - call logs.tsv",
"calllog")
172 .put(
"xender file transfer - messages.tsv",
"message")
173 .put(
"xender file transfer - contacts.tsv",
"contact")
174 .put(
"whatsapp - contacts.tsv",
"contact")
175 .put(
"whatsapp - group call logs.tsv",
"calllog")
176 .put(
"whatsapp - single call logs.tsv",
"calllog")
177 .put(
"whatsapp - messages logs.tsv",
"message")
178 .put(
"shareit file transfer.tsv",
"message")
179 .put(
"tangomessages messages.tsv",
"message")
180 .put(
"contacts.tsv",
"contact")
181 .put(
"imo - accountid.tsv",
"contact")
182 .put(
"imo - messages.tsv",
"message")
183 .put(
"textnow - contacts.tsv",
"contact")
184 .put(
"textnow - messages.tsv",
"message")
185 .put(
"line - messages.tsv",
"message")
186 .put(
"line - contacts.tsv",
"contact")
187 .put(
"line - calllogs.tsv",
"calllog")
188 .put(
"skype - messages logs.tsv",
"message")
189 .put(
"skype - contacts.tsv",
"contact")
190 .put(
"skype - call logs.tsv",
"calllog")
191 .put(
"facebook messenger - chats.tsv",
"message")
192 .put(
"facebook messenger - contacts.tsv",
"contact")
193 .put(
"facebook messenger - calls.tsv",
"calllog")
194 .put(
"call logs2.tsv",
"calllog")
195 .put(
"call logs.tsv",
"calllog")
196 .put(
"oruxmaps tracks.tsv",
"trackpoint")
197 .put(
"google map locations.tsv",
"route")
198 .put(
"Contacts.tsv",
"contact")
199 .put(
"sms - imessage.tsv",
"message")
200 .put(
"call history.tsv",
"calllog")
206 this.tsvFiles =
new HashMap<>();
208 this.tsvFileArtifactComments =
new HashMap<>();
209 this.tsvFileAttributes =
new HashMap<>();
232 return StringUtils.defaultString(origKey).trim().toLowerCase();
236 "LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.",
237 "LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.",
238 "LeappFileProcessor.starting.Leapp=Starting Leapp",
239 "LeappFileProcessor.running.Leapp=Running Leapp",
240 "LeappFileProcessor.has.run=Leapp",
241 "LeappFileProcessor.Leapp.cancelled=Leapp run was canceled",
242 "LeappFileProcessor.completed=Leapp Processing Completed",
243 "LeappFileProcessor.findTsv=Finding all Leapp ouput",
244 "LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory"
252 progress.
progress(Bundle.LeappFileProcessor_findTsv());
253 List<String> LeappTsvOutputFiles =
findTsvFiles(moduleOutputPath);
255 }
catch (IngestModuleException ex) {
256 logger.log(Level.SEVERE, String.format(
"Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex);
269 progress.
progress(Bundle.LeappFileProcessor_findTsv());
270 List<String> LeappTsvOutputFiles =
findTsvFiles(moduleOutputPath);
272 }
catch (IngestModuleException ex) {
273 logger.log(Level.SEVERE, String.format(
"Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex);
284 private List<String>
findTsvFiles(Path LeappOutputDir)
throws IngestModuleException {
285 List<String> allTsvFiles;
286 List<String> foundTsvFiles =
new ArrayList<>();
288 try (Stream<Path> walk = Files.walk(LeappOutputDir)) {
290 allTsvFiles = walk.map(x -> x.toString())
291 .filter(f -> f.toLowerCase().endsWith(
".tsv")).collect(Collectors.toList());
293 for (String tsvFile : allTsvFiles) {
294 if (tsvFiles.containsKey(
normalizeKey(FilenameUtils.getName(tsvFile)))) {
295 foundTsvFiles.add(tsvFile);
299 }
catch (IOException | UncheckedIOException e) {
300 throw new IngestModuleException(Bundle.LeappFileProcessor_error_reading_Leapp_directory() + LeappOutputDir.toString(), e);
303 return foundTsvFiles;
309 logger.log(Level.INFO,
"Leapp File processing module run was cancelled");
328 "LeappFileProcessor.tsvProcessed=Processing LEAPP output file: {0}"
333 for (
int i = 0; i < LeappFilesToProcess.size(); i++) {
338 String LeappFileName = LeappFilesToProcess.get(i);
339 String fileName = FilenameUtils.getName(LeappFileName);
340 progress.
progress(Bundle.LeappFileProcessor_tsvProcessed(fileName), i);
342 File LeappFile =
new File(LeappFileName);
343 String fileKey = fileName.toLowerCase().trim();
344 if (tsvFileAttributes.containsKey(
normalizeKey(fileKey))) {
345 List<TsvColumn> attrList = tsvFileAttributes.get(
normalizeKey(fileKey));
349 processFile(LeappFile, attrList, fileName, artifactType, dataSource);
350 }
catch (TskCoreException | IOException ex) {
351 logger.log(Level.SEVERE, String.format(
"Error processing file at %s", LeappFile.toString()), ex);
358 private void processFile(File LeappFile, List<TsvColumn> attrList, String fileName,
359 BlackboardArtifact.Type artifactType, Content dataSource)
throws FileNotFoundException, IOException, IngestModuleException,
362 String trackpointSegmentName = null;
363 GeoTrackPoints pointList =
new GeoTrackPoints();
364 AbstractFile geoAbstractFile = null;
366 if (LeappFile == null || !LeappFile.exists() || fileName == null) {
367 logger.log(Level.WARNING, String.format(
"Leap file: %s is null or does not exist", LeappFile != null ? LeappFile.toString() :
"<null>"));
369 }
else if (attrList == null || artifactType == null || dataSource == null) {
370 logger.log(Level.WARNING, String.format(
"attribute list, artifact type or dataSource not provided for %s", LeappFile.toString()));
374 List<BlackboardArtifact> bbartifacts =
new ArrayList<>();
377 try (MappingIterator<List<String>> iterator =
new CsvMapper()
378 .enable(CsvParser.Feature.WRAP_AS_ARRAY)
379 .readerFor(List.class)
380 .with(CsvSchema.emptySchema().withColumnSeparator(
'\t'))
381 .readValues(LeappFile)) {
383 if (iterator.hasNext()) {
384 List<String> headerItems = iterator.next();
385 Map<String, Integer> columnIndexes = IntStream.range(0, headerItems.size())
386 .mapToObj(idx -> idx)
387 .collect(Collectors.toMap(
388 idx -> headerItems.get(idx) == null ? null : headerItems.get(idx).trim().toLowerCase(),
390 (val1, val2) -> val1));
393 while (iterator.hasNext()) {
394 List<String> columnItems = iterator.next();
395 Collection<BlackboardAttribute> bbattributes =
processReadLine(columnItems, columnIndexes, attrList, fileName, lineNum);
397 if (!bbattributes.isEmpty()) {
398 switch (ACCOUNT_RELATIONSHIPS.getOrDefault(fileName.toLowerCase(),
"norelationship").toLowerCase()) {
412 geoAbstractFile =
createTrackpoint(bbattributes, dataSource, fileName, trackpointSegmentName, pointList);
416 if (bbartifact != null) {
417 bbartifacts.add(bbartifact);
429 if (ACCOUNT_RELATIONSHIPS.getOrDefault(fileName.toLowerCase(),
"norelationship").toLowerCase().equals(
"trackpoint")) {
433 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex);
436 if (!bbartifacts.isEmpty()) {
437 postArtifacts(bbartifacts);
442 "LeappFileProcessor.cannot.create.waypoint.relationship=Cannot create TSK_WAYPOINT artifact."
444 private void createRoute(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
446 Double startLatitude = Double.valueOf(0);
447 Double startLongitude = Double.valueOf(0);
448 Double endLatitude = Double.valueOf(0);
449 Double endLongitude = Double.valueOf(0);
450 Double zeroValue = Double.valueOf(0);
451 String destinationName =
"";
452 String locationName =
"";
453 Long dateTime = Long.valueOf(0);
454 Collection<BlackboardAttribute> otherAttributes =
new ArrayList<>();
455 String sourceFile = null;
456 AbstractFile absFile;
460 for (BlackboardAttribute bba : bbattributes) {
461 switch (bba.getAttributeType().getTypeName()) {
462 case "TSK_GEO_LATITUDE_START":
463 startLatitude = bba.getValueDouble();
465 case "TSK_GEO_LONGITUDE_START":
466 startLongitude = bba.getValueDouble();
468 case "TSK_GEO_LATITUDE_END":
469 startLatitude = bba.getValueDouble();
471 case "TSK_GEO_LONGITUDE_END":
472 startLongitude = bba.getValueDouble();
475 dateTime = bba.getValueLong();
478 destinationName = bba.getValueString();
481 locationName = bba.getValueString();
483 case "TSK_TEXT_FILE":
484 sourceFile = bba.getValueString();
487 comment = bba.getValueString();
490 otherAttributes.add(bba);
495 if (absFile == null) {
496 absFile = (AbstractFile) dataSource;
498 GeoWaypoints waypointList =
new GeoWaypoints();
499 waypointList.addPoint(
new Waypoint(startLatitude, startLongitude, zeroValue,
""));
500 waypointList.addPoint(
new Waypoint(endLatitude, endLongitude, zeroValue, locationName));
504 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_waypoint_relationship() + ex.getLocalizedMessage(), ex);
510 "LeappFileProcessor.cannot.create.trackpoint.relationship=Cannot create TSK_TRACK_POINT artifact."
512 private AbstractFile
createTrackpoint(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName, String trackpointSegmentName, GeoTrackPoints pointList)
throws IngestModuleException {
514 Double latitude = Double.valueOf(0);
515 Double longitude = Double.valueOf(0);
516 Double altitude = Double.valueOf(0);
517 Double zeroValue = Double.valueOf(0);
518 String segmentName = null;
519 Long dateTime = Long.valueOf(0);
520 Collection<BlackboardAttribute> otherAttributes =
new ArrayList<>();
521 String sourceFile = null;
522 String comment = null;
523 AbstractFile absFile = null;
526 for (BlackboardAttribute bba : bbattributes) {
527 switch (bba.getAttributeType().getTypeName()) {
528 case "TSK_GEO_LATITUDE":
529 latitude = bba.getValueDouble();
531 case "TSK_GEO_LONGITUDE":
532 longitude = bba.getValueDouble();
534 case "TSK_GEO_ALTITUDE":
535 altitude = bba.getValueDouble();
538 dateTime = bba.getValueLong();
541 segmentName = bba.getValueString();
543 case "TSK_TEXT_FILE":
544 sourceFile = bba.getValueString();
547 comment = bba.getValueString();
548 otherAttributes.add(bba);
551 otherAttributes.add(bba);
556 if (absFile == null) {
557 absFile = (AbstractFile) dataSource;
559 if ((trackpointSegmentName == null) || (trackpointSegmentName.equals(segmentName))) {
560 pointList.addPoint(
new TrackPoint(latitude, longitude, altitude, segmentName, zeroValue, zeroValue, zeroValue, dateTime));
563 pointList.addPoint(
new TrackPoint(latitude, longitude, altitude, segmentName, zeroValue, zeroValue, zeroValue, dateTime));
567 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_trackpoint_relationship() + ex.getLocalizedMessage(), ex);
575 "LeappFileProcessor.cannot.create.message.relationship=Cannot create TSK_MESSAGE Relationship."
577 private void createMessageRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
579 String messageType = null;
580 String alternateId = null;
581 CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
582 String senderId = null;
583 String receipentId = null;
584 String[] receipentIdList = null;
585 Long dateTime = Long.valueOf(0);
586 MessageReadStatus messageStatus = MessageReadStatus.UNKNOWN;
587 String subject = null;
588 String messageText = null;
589 String threadId = null;
590 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
591 List<FileAttachment> fileAttachments =
new ArrayList<>();
592 String sourceFile = null;
593 MessageAttachments messageAttachments;
596 for (BlackboardAttribute bba : bbattributes) {
597 switch (bba.getAttributeType().getTypeName()) {
598 case "TSK_DIRECTION":
599 if (bba.getValueString().toLowerCase().equals(
"outgoing")) {
600 communicationDirection = CommunicationDirection.OUTGOING;
601 }
else if (bba.getValueString().toLowerCase().equals(
"incoming")) {
602 communicationDirection = CommunicationDirection.INCOMING;
605 case "TSK_PHONE_NUMBER_FROM":
606 if (!bba.getValueString().isEmpty()) {
607 senderId = bba.getValueString();
610 case "TSK_PHONE_NUMBER_TO":
611 if (!bba.getValueString().isEmpty()) {
612 receipentIdList = bba.getValueString().split(
",", 0);
616 dateTime = bba.getValueLong();
619 messageType = bba.getValueString();
621 case "TSK_ATTACHMENTS":
622 if (!bba.getValueString().isEmpty()) {
626 case "TSK_TEXT_FILE":
627 sourceFile = bba.getValueString();
629 case "TSK_READ_STATUS":
630 if (bba.getValueInt() == 1) {
631 messageStatus = MessageReadStatus.READ;
633 messageStatus = MessageReadStatus.UNREAD;
637 messageText = bba.getValueString();
640 subject = bba.getValueString();
643 alternateId = bba.getValueString();
644 otherAttributes.add(bba);
647 otherAttributes.add(bba);
652 if (absFile == null) {
653 absFile = (AbstractFile) dataSource;
655 CommunicationArtifactsHelper accountHelper;
657 if (alternateId == null) {
664 BlackboardArtifact messageArtifact = accountHelper.addMessage(messageType, communicationDirection, senderId,
665 receipentId, dateTime, messageStatus, subject,
666 messageText, threadId, otherAttributes);
667 if (!fileAttachments.isEmpty()) {
668 messageAttachments =
new MessageAttachments(fileAttachments,
new ArrayList<>());
669 accountHelper.addAttachments(messageArtifact, messageAttachments);
672 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex);
678 "LeappFileProcessor.cannot.create.contact.relationship=Cannot create TSK_CONTACT Relationship."
680 private void createContactRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
682 String alternateId = null;
683 String contactName = null;
684 String phoneNumber = null;
685 String homePhoneNumber = null;
686 String mobilePhoneNumber = null;
687 String emailAddr = null;
688 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
689 String sourceFile = null;
692 for (BlackboardAttribute bba : bbattributes) {
693 switch (bba.getAttributeType().getTypeName()) {
694 case "TSK_PHONE_NUMBER":
695 if (!bba.getValueString().isEmpty()) {
696 phoneNumber = bba.getValueString();
700 if (!bba.getValueString().isEmpty()) {
701 contactName = bba.getValueString();
704 case "TSK_TEXT_FILE":
705 sourceFile = bba.getValueString();
707 case "TSK_PHONE_NUMBER_HOME":
708 homePhoneNumber = bba.getValueString();
710 case "TSK_PHONE_NUMBER_MOBILE":
711 mobilePhoneNumber = bba.getValueString();
714 emailAddr = bba.getValueString();
717 alternateId = bba.getValueString();
718 otherAttributes.add(bba);
721 otherAttributes.add(bba);
726 if (absFile == null) {
727 absFile = (AbstractFile) dataSource;
730 if (accountType != null) {
732 CommunicationArtifactsHelper accountHelper;
733 if (alternateId == null) {
740 BlackboardArtifact messageArtifact = accountHelper.addContact(contactName, phoneNumber, homePhoneNumber, mobilePhoneNumber, emailAddr, otherAttributes);
743 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_contact_relationship() + ex.getLocalizedMessage(), ex);
748 "LeappFileProcessor.cannot.create.calllog.relationship=Cannot create TSK_CALLLOG Relationship."
750 private void createCalllogRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
752 String callerId = null;
753 String alternateId = null;
754 List<String> calleeId = Arrays.asList();
755 CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
756 Long startDateTime = Long.valueOf(0);
757 Long endDateTime = Long.valueOf(0);
758 CallMediaType mediaType = CallMediaType.UNKNOWN;
759 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
760 String sourceFile = null;
763 for (BlackboardAttribute bba : bbattributes) {
764 switch (bba.getAttributeType().getTypeName()) {
765 case "TSK_TEXT_FILE":
766 sourceFile = bba.getValueString();
768 case "TSK_DATETIME_START":
769 startDateTime = bba.getValueLong();
771 case "TSK_DATETIME_END":
772 startDateTime = bba.getValueLong();
774 case "TSK_DIRECTION":
775 if (bba.getValueString().toLowerCase().equals(
"outgoing")) {
776 communicationDirection = CommunicationDirection.OUTGOING;
777 }
else if (bba.getValueString().toLowerCase().equals(
"incoming")) {
778 communicationDirection = CommunicationDirection.INCOMING;
781 case "TSK_PHONE_NUMBER_FROM":
782 if (!bba.getValueString().isEmpty()) {
783 callerId = bba.getValueString();
786 case "TSK_PHONE_NUMBER_TO":
787 if (!bba.getValueString().isEmpty()) {
788 String[] calleeTempList = bba.getValueString().split(
",", 0);
789 calleeId = Arrays.asList(calleeTempList);
793 alternateId = bba.getValueString();
794 otherAttributes.add(bba);
797 otherAttributes.add(bba);
802 if (calleeId.isEmpty() && communicationDirection == CommunicationDirection.OUTGOING && callerId != null) {
803 String[] calleeTempList = callerId.split(
",", 0);
804 calleeId = Arrays.asList(calleeTempList);
808 if (absFile == null) {
809 absFile = (AbstractFile) dataSource;
812 CommunicationArtifactsHelper accountHelper;
813 if (accountType != null) {
820 accountHelper.addCalllog(communicationDirection, callerId, calleeId, startDateTime, endDateTime, mediaType, otherAttributes);
822 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_calllog_relationship() + ex.getLocalizedMessage(), ex);
828 switch (AccountTypeName.toLowerCase()) {
830 return Account.Type.ZAPYA;
831 case "sms messages.tsv":
832 return Account.Type.PHONE;
834 return Account.Type.PHONE;
835 case "imo - accountid.tsv":
836 return Account.Type.IMO;
837 case "imo - messages.tsv":
838 return Account.Type.IMO;
839 case "textnow - contacts.tsv":
840 return Account.Type.TEXTNOW;
841 case "textnow - messages.tsv":
842 return Account.Type.TEXTNOW;
843 case "mms messages.tsv":
844 return Account.Type.PHONE;
845 case "viber - call logs.tsv":
846 return Account.Type.VIBER;
847 case "viber - contacts.tsv":
848 return Account.Type.VIBER;
849 case "viber - messages.tsv":
850 return Account.Type.VIBER;
851 case "xender file transfer - messages.tsv":
852 return Account.Type.XENDER;
853 case "xender file transfer - contacts.tsv":
854 return Account.Type.XENDER;
855 case "whatsapp - single call logs.tsv":
856 return Account.Type.WHATSAPP;
857 case "whatsapp - messages logs.tsv":
858 return Account.Type.WHATSAPP;
859 case "whatsapp - group call logs.tsv":
860 return Account.Type.WHATSAPP;
861 case "whatsapp - contacts.tsv":
862 return Account.Type.WHATSAPP;
863 case "tangomessages messages.tsv":
864 return Account.Type.TANGO;
865 case "shareit file transfer.tsv":
866 return Account.Type.SHAREIT;
867 case "line - calllogs.tsv":
868 return Account.Type.LINE;
869 case "line - contacts.tsv":
870 return Account.Type.LINE;
871 case "line - messages.tsv":
872 return Account.Type.LINE;
873 case "skype - call logs.tsv":
874 return Account.Type.SKYPE;
875 case "skype - contacts.tsv":
876 return Account.Type.SKYPE;
877 case "skype - messages logs.tsv":
878 return Account.Type.SKYPE;
879 case "facebook messenger - calls.tsv":
880 return Account.Type.FACEBOOK;
881 case "facebook messenger - contacts.tsv":
882 return Account.Type.FACEBOOK;
883 case "facebook messenger - chats.tsv":
884 return Account.Type.FACEBOOK;
885 case "call logs2.tsv":
886 return Account.Type.PHONE;
887 case "call logs.tsv":
888 return Account.Type.PHONE;
889 case "sms - imessage.tsv":
890 return Account.Type.PHONE;
892 return Account.Type.PHONE;
913 private Collection<BlackboardAttribute>
processReadLine(List<String> lineValues, Map<String, Integer> columnIndexes,
914 List<TsvColumn> attrList, String fileName,
int lineNum)
throws IngestModuleException {
917 if (MapUtils.isEmpty(columnIndexes) || CollectionUtils.isEmpty(lineValues)
918 || (lineValues.size() == 1 && StringUtils.isEmpty(lineValues.get(0)))) {
919 return Collections.emptyList();
922 List<BlackboardAttribute> attrsToRet =
new ArrayList<>();
925 if (colAttr.getAttributeType() == null) {
930 Integer columnIdx = columnIndexes.get(colAttr.getColumnName());
931 if (columnIdx == null) {
932 logger.log(Level.WARNING, String.format(
"No column mapping found for %s in file %s. Omitting column.", colAttr.getColumnName(), fileName));
936 String value = (columnIdx >= lineValues.size() || columnIdx < 0) ? null : lineValues.get(columnIdx);
939 if (colAttr.isRequired()) {
940 logger.log(Level.WARNING, String.format(
"No value found for required column %s at line %d in file %s. Omitting row.", colAttr.getColumnName(), lineNum, fileName));
941 return Collections.emptyList();
944 logger.log(Level.WARNING, String.format(
"No value found for column %s at line %d in file %s. Omitting column.", colAttr.getColumnName(), lineNum, fileName));
951 BlackboardAttribute attr =
getAttribute(colAttr.getAttributeType(), formattedValue, fileName);
953 attrsToRet.add(attr);
954 }
else if (colAttr.isRequired()) {
955 logger.log(Level.WARNING, String.format(
"Blackboard attribute could not be parsed column %s at line %d in file %s. Omitting row.", colAttr.getColumnName(), lineNum, fileName));
956 return Collections.emptyList();
960 if (tsvFileArtifactComments.containsKey(
normalizeKey(fileName))) {
961 attrsToRet.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, moduleName, tsvFileArtifactComments.get(
normalizeKey(fileName))));
977 if (colAttr.getAttributeType().getTypeName().equals(
"TSK_DOMAIN")) {
987 private static final DateFormat
TIMESTAMP_FORMAT =
new SimpleDateFormat(
"yyyy-MM-d HH:mm:ss", US);
1000 private BlackboardAttribute
getAttribute(BlackboardAttribute.Type attrType, String value, String fileName) {
1001 if (attrType == null || value == null) {
1002 logger.log(Level.WARNING, String.format(
"Unable to parse attribute type %s for value '%s' in fileName %s",
1003 attrType == null ?
"<null>" : attrType.toString(),
1004 value == null ?
"<null>" : value,
1005 fileName == null ?
"<null>" : fileName));
1009 switch (attrType.getValueType()) {
1013 (v) ->
new BlackboardAttribute(attrType, moduleName, v));
1015 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
1016 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v).intValue()));
1018 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
1019 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v).longValue()));
1021 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
1022 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v)));
1024 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
1025 (v) ->
new BlackboardAttribute(attrType, moduleName,
new byte[]{Byte.valueOf(v)}));
1027 return parseAttrValue(value.trim(), attrType, fileName,
true,
true,
1028 (v) ->
new BlackboardAttribute(attrType, moduleName, TIMESTAMP_FORMAT.parse(v).getTime() / 1000));
1031 logger.log(Level.WARNING, String.format(
"Attribute Type %s for file %s not defined.", attrType, fileName));
1052 BlackboardAttribute
apply(String orig)
throws ParseException, NumberFormatException;
1073 String sanitizedValue = value.replaceAll(
"\\p{C}",
"");
1075 if (blankIsNull && StringUtils.isBlank(sanitizedValue)) {
1079 if (zeroIsNull && sanitizedValue.matches(
"^\\s*[0\\.]*\\s*$")) {
1084 return valueConverter.
apply(sanitizedValue);
1085 }
catch (NumberFormatException | ParseException ex) {
1086 logger.log(Level.WARNING, String.format(
"Unable to format '%s' as value type %s while converting to attributes from %s.", sanitizedValue, attrType.getValueType().getLabel(), fileName), ex);
1098 if (
new File(userPath).exists()) {
1106 @NbBundle.Messages({
1107 "LeappFileProcessor.cannot.load.artifact.xml=Cannot load xml artifact file.",
1108 "LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
1109 "LeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
1110 "LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
1111 "LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts."
1116 File f =
new File(path);
1117 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1118 DocumentBuilder db = dbf.newDocumentBuilder();
1119 xmlinput = db.parse(f);
1121 }
catch (IOException e) {
1122 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_load_artifact_xml() + e.getLocalizedMessage(), e);
1123 }
catch (ParserConfigurationException pce) {
1124 throw new IngestModuleException(Bundle.LeappFileProcessor_cannotBuildXmlParser() + pce.getLocalizedMessage(), pce);
1125 }
catch (SAXException sxe) {
1126 throw new IngestModuleException(Bundle.LeappFileProcessor_cannotParseXml() + sxe.getLocalizedMessage(), sxe);
1137 NodeList nlist = xmlinput.getElementsByTagName(
"FileName");
1139 for (
int i = 0; i < nlist.getLength(); i++) {
1140 NamedNodeMap nnm = nlist.item(i).getAttributes();
1141 tsvFiles.put(
normalizeKey(nnm.getNamedItem(
"filename").getNodeValue()), nnm.getNamedItem(
"description").getNodeValue());
1149 NodeList artifactNlist = xmlinput.getElementsByTagName(
"ArtifactName");
1150 for (
int k = 0; k < artifactNlist.getLength(); k++) {
1151 NamedNodeMap nnm = artifactNlist.item(k).getAttributes();
1152 String artifactName = nnm.getNamedItem(
"artifactname").getNodeValue();
1153 String comment = nnm.getNamedItem(
"comment").getNodeValue();
1154 String parentName = artifactNlist.item(k).getParentNode().getAttributes().getNamedItem(
"filename").getNodeValue();
1156 BlackboardArtifact.Type foundArtifactType = null;
1159 }
catch (TskCoreException ex) {
1160 logger.log(Level.SEVERE, String.format(
"There was an issue that arose while trying to fetch artifact type for %s.", artifactName), ex);
1163 if (foundArtifactType == null) {
1164 logger.log(Level.SEVERE, String.format(
"No known artifact mapping found for [artifact: %s, %s]",
1170 if (!comment.toLowerCase().matches(
"null")) {
1171 tsvFileArtifactComments.put(
normalizeKey(parentName), comment);
1178 return String.format(
"file: %s, filename: %s",
1179 this.xmlFile == null ?
"<null>" : this.xmlFile,
1180 fileName == null ?
"<null>" : fileName);
1184 return String.format(
"attribute: %s %s",
1185 attributeName == null ?
"<null>" : attributeName,
1191 NodeList attributeNlist = xmlinput.getElementsByTagName(
"AttributeName");
1192 for (
int k = 0; k < attributeNlist.getLength(); k++) {
1193 NamedNodeMap nnm = attributeNlist.item(k).getAttributes();
1194 String attributeName = nnm.getNamedItem(
"attributename").getNodeValue();
1196 if (!attributeName.toLowerCase().matches(
"null")) {
1197 String columnName = nnm.getNamedItem(
"columnName").getNodeValue();
1198 String required = nnm.getNamedItem(
"required").getNodeValue();
1199 String parentName = attributeNlist.item(k).getParentNode().getParentNode().getAttributes().getNamedItem(
"filename").getNodeValue();
1201 BlackboardAttribute.Type foundAttrType = null;
1204 }
catch (TskCoreException ex) {
1205 logger.log(Level.SEVERE, String.format(
"There was an issue that arose while trying to fetch attribute type for %s.", attributeName), ex);
1208 if (foundAttrType == null) {
1209 logger.log(Level.SEVERE, String.format(
"No known attribute mapping found for [%s]",
getXmlAttrIdentifier(parentName, attributeName)));
1212 if (required != null && required.compareToIgnoreCase(
"yes") != 0 && required.compareToIgnoreCase(
"no") != 0) {
1213 logger.log(Level.SEVERE, String.format(
"Required value %s did not match 'yes' or 'no' for [%s]",
1217 if (columnName == null) {
1218 logger.log(Level.SEVERE, String.format(
"No column name provided for [%s]",
getXmlAttrIdentifier(parentName, attributeName)));
1220 }
else if (columnName.trim().length() != columnName.length()) {
1221 logger.log(Level.SEVERE, String.format(
"Column name '%s' starts or ends with whitespace for [%s]", columnName,
getXmlAttrIdentifier(parentName, attributeName)));
1223 }
else if (columnName.matches(
"[^ \\S]")) {
1224 logger.log(Level.SEVERE, String.format(
"Column name '%s' contains invalid characters [%s]", columnName,
getXmlAttrIdentifier(parentName, attributeName)));
1230 columnName.trim().toLowerCase(),
1231 "yes".compareToIgnoreCase(required) == 0);
1233 if (tsvFileAttributes.containsKey(
normalizeKey(parentName))) {
1234 List<TsvColumn> attrList = tsvFileAttributes.get(
normalizeKey(parentName));
1235 attrList.add(thisCol);
1236 tsvFileAttributes.replace(parentName, attrList);
1238 List<TsvColumn> attrList =
new ArrayList<>();
1239 attrList.add(thisCol);
1240 tsvFileAttributes.put(
normalizeKey(parentName), attrList);
1259 private BlackboardArtifact
createArtifactWithAttributes(BlackboardArtifact.Type artType, Content dataSource, Collection<BlackboardAttribute> bbattributes) {
1261 switch (artType.getCategory()) {
1263 return dataSource.newDataArtifact(artType, bbattributes);
1264 case ANALYSIS_RESULT:
1265 return dataSource.newAnalysisResult(artType, Score.SCORE_UNKNOWN, null, null, null, bbattributes).getAnalysisResult();
1267 logger.log(Level.SEVERE, String.format(
"Unknown category type: %s", artType.getCategory().getDisplayName()));
1270 }
catch (TskException ex) {
1271 logger.log(Level.WARNING, Bundle.LeappFileProcessor_error_creating_new_artifacts(), ex);
1282 void postArtifacts(Collection<BlackboardArtifact> artifacts) {
1283 if (artifacts == null || artifacts.isEmpty()) {
1288 Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(artifacts, moduleName, context.
getJobId());
1289 }
catch (Blackboard.BlackboardException ex) {
1290 logger.log(Level.SEVERE, Bundle.LeappFileProcessor_postartifacts_error(), ex);
1313 static List<AbstractFile> findLeappFilesToProcess(Content dataSource) {
1315 List<AbstractFile> leappFiles =
new ArrayList<>();
1317 FileManager fileManager = getCurrentCase().getServices().getFileManager();
1321 leappFiles = fileManager.
findFiles(dataSource,
"%",
"/");
1322 }
catch (TskCoreException ex) {
1323 logger.log(Level.WARNING,
"No files found to process");
1327 List<AbstractFile> leappFilesToProcess =
new ArrayList<>();
1328 for (AbstractFile leappFile : leappFiles) {
1329 if (((leappFile.getLocalAbsPath() != null)
1330 && !leappFile.isVirtual())
1331 && leappFile.getNameExtension() != null
1332 && ALLOWED_EXTENSIONS.contains(leappFile.getNameExtension().toLowerCase())) {
1333 leappFilesToProcess.add(leappFile);
1337 return leappFilesToProcess;
1346 for (Map.Entry<String, String> customArtifact : CUSTOM_ARTIFACT_MAP.entrySet()) {
1347 String artifactName = customArtifact.getKey();
1348 String artifactDescription = customArtifact.getValue();
1353 if (customFilePath.exists()) {
1354 try (MappingIterator<List<String>> iterator =
new CsvMapper()
1355 .enable(CsvParser.Feature.WRAP_AS_ARRAY)
1356 .readerFor(List.class)
1357 .with(CsvSchema.emptySchema().withColumnSeparator(
','))
1358 .readValues(customFilePath)) {
1360 if (iterator.hasNext()) {
1362 List<String> headerItems = iterator.next();
1364 while (iterator.hasNext()) {
1365 List<String> columnItems = iterator.next();
1366 if (columnItems.size() > 3) {
1373 }
catch (IOException ex) {
1374 logger.log(Level.WARNING, String.format(
"Failed to read/open file %s.", customFilePath), ex);
1385 if (atType.toLowerCase().equals(
"artifact")) {
1387 BlackboardArtifact.Type customArtifactType = blkBoard.getOrAddArtifactType(atName.toUpperCase(), atDescription);
1388 }
catch (Blackboard.BlackboardException ex) {
1389 logger.log(Level.WARNING, String.format(
"Failed to create custom artifact type %s.", atName), ex);
1394 switch (attrType.toLowerCase()) {
1398 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, atDescription);
1399 }
catch (Blackboard.BlackboardException ex) {
1400 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1405 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, atDescription);
1406 }
catch (Blackboard.BlackboardException ex) {
1407 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1412 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, atDescription);
1413 }
catch (Blackboard.BlackboardException ex) {
1414 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1419 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING, atDescription);
1420 }
catch (Blackboard.BlackboardException ex) {
1421 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1426 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.BYTE, atDescription);
1427 }
catch (Blackboard.BlackboardException ex) {
1428 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1433 BlackboardAttribute.Type customAttrbiuteType = blkBoard.getOrAddAttributeType(atName.toUpperCase(), BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME, atDescription);
1434 }
catch (Blackboard.BlackboardException ex) {
1435 logger.log(Level.WARNING, String.format(
"Failed to create custom attribute type %s.", atName), ex);
1439 logger.log(Level.WARNING, String.format(
"Attribute Type %s for file %s not defined.", attrType, atName));
1451 for (Map.Entry<String, String> customArtifact : CUSTOM_ARTIFACT_MAP.entrySet()) {
1452 String artifactName = customArtifact.getKey();
1453 String artifactDescription = customArtifact.getValue();
1456 BlackboardArtifact.Type customArtifactType = blkBoard.getOrAddArtifactType(artifactName, artifactDescription);
1457 }
catch (Blackboard.BlackboardException ex) {
1458 logger.log(Level.WARNING, String.format(
"Failed to create custom artifact type %s.", artifactName), ex);
1465 if (fileNamePath == null) {
1469 List<AbstractFile> files;
1471 String fileName = FilenameUtils.getName(fileNamePath);
1472 String filePath = FilenameUtils.normalize(FilenameUtils.getPath(fileNamePath),
true);
1477 files = fileManager.
findFiles(dataSource, fileName);
1479 }
catch (TskCoreException ex) {
1480 logger.log(Level.WARNING,
"Unable to find prefetch files.", ex);
1484 for (AbstractFile pFile : files) {
1486 if (pFile.getParentPath().toLowerCase().endsWith(filePath.toLowerCase())) {
void createMessageRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
final IngestJobContext context
BlackboardArtifact createArtifactWithAttributes(BlackboardArtifact.Type artType, Content dataSource, Collection< BlackboardAttribute > bbattributes)
String getXmlFileIdentifier(String fileName)
final String ARTIFACT_ATTRIBUTE_REFERENCE_USER
final BlackboardAttribute.Type attributeType
FileManager getFileManager()
void createContactRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static String extractDomain(String urlString)
List< String > findTsvFiles(Path LeappOutputDir)
List< AbstractFile > findFiles(String fileName)
void createCalllogRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static final Logger logger
void processFile(File LeappFile, List< TsvColumn > attrList, String fileName, BlackboardArtifact.Type artifactType, Content dataSource)
void loadCustomArtifactsAttributes(Blackboard blkBoard, String leapModule)
static final DateFormat TIMESTAMP_FORMAT
final Map< String, BlackboardArtifact.Type > tsvFileArtifacts
static final Set< String > ALLOWED_EXTENSIONS
void getArtifactNode(Document xmlinput)
AbstractFile findAbstractFile(Content dataSource, String fileNamePath)
void createRoute(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static final Map< String, String > CUSTOM_ARTIFACT_MAP
Collection< BlackboardAttribute > processReadLine(List< String > lineValues, Map< String, Integer > columnIndexes, List< TsvColumn > attrList, String fileName, int lineNum)
void switchToIndeterminate()
BlackboardAttribute parseAttrValue(String value, BlackboardAttribute.Type attrType, String fileName, boolean blankIsNull, boolean zeroIsNull, ParseExceptionFunction valueConverter)
void processLeappFiles(List< String > LeappFilesToProcess, Content dataSource, DataSourceIngestModuleProgress progress)
void createCustomAttributesArtifacts(Blackboard blkBoard, String atType, String atName, String atDescription, String attrType)
final Map< String, String > tsvFiles
void loadIndividualConfigFile(String path)
final String CUSTOM_ARTIFACTS_ATTRIBUTES_FILE
static final Map< String, String > ACCOUNT_RELATIONSHIPS
SleuthkitCase getSleuthkitCase()
final Map< String, String > tsvFileArtifactComments
BlackboardAttribute getAttribute(BlackboardAttribute.Type attrType, String value, String fileName)
final Map< String, List< TsvColumn > > tsvFileAttributes
boolean dataSourceIngestIsCancelled()
static String normalizeKey(String origKey)
ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath, DataSourceIngestModuleProgress progress)
final Blackboard blkBoard
void switchToDeterminate(int workUnits)
void createCustomArtifacts(Blackboard blkBoard)
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
BlackboardAttribute apply(String orig)
AbstractFile createTrackpoint(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName, String trackpointSegmentName, GeoTrackPoints pointList)
Account.Type getAccountType(String AccountTypeName)
void getAttributeNodes(Document xmlinput)
LeappFileProcessor(String xmlFile, String moduleName, String leapModule, IngestJobContext context)
void progress(int workUnits)
String getXmlAttrIdentifier(String fileName, String attributeName)
ProcessResult processFiles(Content dataSource, Path moduleOutputPath, AbstractFile LeappFile, DataSourceIngestModuleProgress progress)
String formatValueBasedOnAttrType(TsvColumn colAttr, String value)
void getFileNode(Document xmlinput)