19 package org.sleuthkit.autopsy.thunderbirdparser;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
33 import java.util.logging.Level;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
36 import org.apache.james.mime4j.MimeException;
37 import org.openide.util.NbBundle;
38 import org.openide.util.NbBundle.Messages;
59 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
68 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
69 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
70 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
96 @Messages({
"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."})
103 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
114 if (abstractFile.getKnown().equals(TskData.FileKnown.KNOWN)) {
119 if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS))
120 || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
124 if ((abstractFile.isFile() ==
false)) {
129 boolean isMbox =
false;
130 boolean isEMLFile =
false;
133 byte[] t =
new byte[64];
134 if (abstractFile.getSize() > 64) {
135 int byteRead = abstractFile.read(t, 0, 64);
137 isMbox = MboxParser.isValidMimeTypeMbox(t, abstractFile);
138 isEMLFile = EMLParser.isEMLFile(abstractFile, t);
141 }
catch (TskException ex) {
142 logger.log(Level.WARNING, null, ex);
145 boolean isPstFile = PstParser.isPstFile(abstractFile);
146 boolean isVcardFile = VcardParser.isVcardFile(abstractFile);
152 if (isMbox || isEMLFile || isPstFile || isVcardFile) {
154 communicationArtifactsHelper =
new CommunicationArtifactsHelper(currentCase.
getSleuthkitCase(),
156 }
catch (TskCoreException ex) {
157 logger.log(Level.SEVERE, String.format(
"Failed to create CommunicationArtifactsHelper for file with object id = %d", abstractFile.getId()), ex);
188 @Messages({
"ThunderbirdMboxFileIngestModule.processPst.indexError.message=Failed to index encryption detected artifact for keyword search."})
192 fileName = getTempPath() + File.separator + abstractFile.getName()
193 +
"-" + String.valueOf(abstractFile.getId());
195 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
198 File file =
new File(fileName);
202 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
204 NbBundle.getMessage(this.getClass(),
205 "ThunderbirdMboxFileIngestModule.processPst.errMsg.outOfDiskSpace",
206 abstractFile.getName()));
211 try (PstParser parser =
new PstParser(services)) {
214 }
catch (IOException ex) {
215 logger.log(Level.WARNING,
"Failed writing pst file to disk.", ex);
219 PstParser.ParseResult result = parser.open(file, abstractFile.getId());
223 Iterator<EmailMessage> pstMsgIterator = parser.getEmailMessageIterator();
224 if (pstMsgIterator != null) {
225 processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile);
232 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
233 abstractFile.getName()),
234 NbBundle.getMessage(
this.getClass(),
235 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
236 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
245 String encryptionFileLevel = NbBundle.getMessage(this.getClass(),
246 "ThunderbirdMboxFileIngestModule.encryptionFileLevel");
247 BlackboardArtifact artifact = abstractFile.newAnalysisResult(
248 BlackboardArtifact.Type.TSK_ENCRYPTION_DETECTED,
249 Score.SCORE_NOTABLE, null, null, encryptionFileLevel, Arrays.asList(
250 new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
254 .getAnalysisResult();
259 }
catch (Blackboard.BlackboardException ex) {
261 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
263 }
catch (TskCoreException ex) {
264 logger.log(Level.INFO,
"Failed to add encryption attribute to file: {0}", abstractFile.getName());
270 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
271 abstractFile.getName()),
272 NbBundle.getMessage(
this.getClass(),
273 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
274 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
277 }
catch (Exception ex) {
278 logger.log(Level.WARNING, String.format(
"Failed to close temp pst file %s", file.getAbsolutePath()));
293 String mboxFileName = abstractFile.getName();
294 String mboxParentDir = abstractFile.getParentPath();
296 String emailFolder =
"";
298 if (mboxParentDir.contains(
"/Mail/")) {
299 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/Mail/") + 5);
300 }
else if (mboxParentDir.contains(
"/ImapMail/")) {
301 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/ImapMail/") + 9);
303 emailFolder += mboxFileName;
304 emailFolder = emailFolder.replaceAll(
".sbd",
"");
308 fileName = getTempPath() + File.separator + abstractFile.getName()
309 +
"-" + String.valueOf(abstractFile.getId());
311 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
314 File file =
new File(fileName);
318 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
320 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg",
321 abstractFile.getName()),
322 NbBundle.getMessage(
this.getClass(),
323 "ThunderbirdMboxFileIngestModule.processMBox.errProfFile.details"));
331 }
catch (IOException ex) {
332 logger.log(Level.WARNING,
"Failed writing mbox file to disk.", ex);
346 List<Long> mboxSplitOffsets =
new ArrayList<>();
349 }
catch (IOException ex) {
350 logger.log(Level.WARNING, String.format(
"Failed finding split offsets for mbox file {0}.", fileName), ex);
354 long startingOffset = 0;
355 for (Long mboxSplitOffset : mboxSplitOffsets) {
356 File splitFile =
new File(fileName +
"-" + mboxSplitOffset);
358 ContentUtils.
writeToFile(abstractFile, splitFile, context::fileIngestIsCancelled, startingOffset, mboxSplitOffset);
359 }
catch (IOException ex) {
360 logger.log(Level.WARNING,
"Failed writing split mbox file to disk.", ex);
365 startingOffset = mboxSplitOffset;
381 List<Long> mboxSplitOffset =
new ArrayList<>();
383 byte[] buffer =
new byte[7];
384 ReadContentInputStream in =
new ReadContentInputStream(abstractFile);
385 in.skip(MBOX_SIZE_TO_SPLIT);
386 int len = in.read(buffer);
388 len = in.read(buffer);
389 if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114
390 && buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) {
391 mboxSplitOffset.add(in.getCurPosition() - 5);
392 in.skip(MBOX_SIZE_TO_SPLIT);
396 return mboxSplitOffset;
400 private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) {
402 try (MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId())) {
403 List<EmailMessage> emails =
new ArrayList<>();
404 if (emailIterator != null) {
405 while (emailIterator.hasNext()) {
409 EmailMessage emailMessage = emailIterator.next();
410 if (emailMessage != null) {
411 emails.add(emailMessage);
415 String errors = emailIterator.getErrors();
416 if (!errors.isEmpty()) {
418 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2",
419 abstractFile.getName()), errors);
422 processEmails(emails, MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId()), abstractFile);
423 }
catch (Exception ex) {
424 logger.log(Level.WARNING, String.format(
"Failed to close mbox temp file %s", file.getAbsolutePath()));
440 "ThunderbirdMboxFileIngestModule.errorMessage.outOfDiskSpace=Out of disk space. Cannot copy '{0}' (id={1}) to parse."
444 VcardParser parser =
new VcardParser(currentCase, context);
445 parser.parse(abstractFile);
447 logger.log(Level.WARNING, String.format(
"Exception while parsing the file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex);
455 EmailMessage message = EMLParser.parse(abstractFile);
457 if (message == null) {
461 List<AbstractFile> derivedFiles =
new ArrayList<>();
464 createEmailArtifact(message, abstractFile, accountFileInstanceCache, derivedFiles);
465 accountFileInstanceCache.clear();
467 if (derivedFiles.isEmpty() ==
false) {
468 for (AbstractFile derived : derivedFiles) {
474 }
catch (IOException ex) {
475 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
477 }
catch (MimeException ex) {
478 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
494 File dir =
new File(tmpDir);
495 if (dir.exists() ==
false) {
508 static String getModuleOutputPath() throws NoCurrentCaseException {
509 String outDir = Case.getCurrentCaseThrows().getModuleDirectory() + File.separator
510 + EmailParserModuleFactory.getModuleName();
511 File dir =
new File(outDir);
512 if (dir.exists() ==
false) {
524 static String getRelModuleOutputPath() throws NoCurrentCaseException {
525 return Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath() + File.separator
526 + EmailParserModuleFactory.getModuleName();
537 private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator,
538 AbstractFile abstractFile) {
546 EmailMessageThreader.threadMessages(partialEmailsForThreading);
547 }
catch (Exception ex) {
548 logger.log(Level.WARNING, String.format(
"Exception thrown parsing emails from %s", abstractFile.getName()), ex);
551 List<AbstractFile> derivedFiles =
new ArrayList<>();
554 while (fullMessageIterator.hasNext()) {
559 EmailMessage current = fullMessageIterator.next();
561 if (current == null) {
565 if (partialEmailsForThreading.size() > msgCnt) {
566 EmailMessage threaded = partialEmailsForThreading.get(msgCnt++);
568 if (threaded.getMessageID().equals(current.getMessageID())
569 && threaded.getSubject().equals(current.getSubject())) {
570 current.setMessageThreadID(threaded.getMessageThreadID());
573 createEmailArtifact(current, abstractFile, accountFileInstanceCache, derivedFiles);
576 if (derivedFiles.isEmpty() ==
false) {
577 for (AbstractFile derived : derivedFiles) {
587 void createEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache, List<AbstractFile> derivedFiles) {
588 BlackboardArtifact msgArtifact =
addEmailArtifact(email, abstractFile, accountFileInstanceCache);
590 if ((msgArtifact != null) && (email.hasAttachment())) {
591 derivedFiles.addAll(
handleAttachments(email.getAttachments(), abstractFile, msgArtifact));
593 for (EmailMessage.Attachment attach : email.getAttachments()) {
594 if (attach instanceof AttachedEmailMessage) {
595 createEmailArtifact(((AttachedEmailMessage) attach).getEmailMessage(), abstractFile, accountFileInstanceCache, derivedFiles);
612 "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message."
614 private List<AbstractFile>
handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) {
615 List<AbstractFile> files =
new ArrayList<>();
616 List<FileAttachment> fileAttachments =
new ArrayList<>();
617 for (EmailMessage.Attachment attach : attachments) {
618 String filename = attach.getName();
619 long crTime = attach.getCrTime();
620 long mTime = attach.getmTime();
621 long aTime = attach.getaTime();
622 long cTime = attach.getcTime();
623 String relPath = attach.getLocalPath();
624 long size = attach.getSize();
625 TskData.EncodingType encodingType = attach.getEncodingType();
629 size, cTime, crTime, aTime, mTime,
true, abstractFile,
"",
634 fileAttachments.add(
new FileAttachment(df));
635 }
catch (TskCoreException ex) {
637 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.errMsg",
638 abstractFile.getName()),
639 NbBundle.getMessage(
this.getClass(),
640 "ThunderbirdMboxFileIngestModule.handleAttch.errMsg.details", filename));
641 logger.log(Level.INFO,
"", ex);
646 communicationArtifactsHelper.addAttachments(messageArtifact,
new MessageAttachments(fileAttachments, Collections.emptyList()));
647 }
catch (TskCoreException ex) {
649 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"),
651 logger.log(Level.INFO,
"Failed to add attachments to email message.", ex);
666 Pattern p = Pattern.compile(
"\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b",
667 Pattern.CASE_INSENSITIVE);
668 Matcher m = p.matcher(input);
669 Set<String> emailAddresses =
new HashSet<>();
671 emailAddresses.add(m.group());
673 return emailAddresses;
685 @Messages({
"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
687 BlackboardArtifact bbart = null;
688 List<BlackboardAttribute> bbattributes =
new ArrayList<>();
689 String to = email.getRecipients();
690 String cc = email.getCc();
691 String bcc = email.getBcc();
692 String from = email.getSender();
693 long dateL = email.getSentDate();
694 String headers = email.getHeaders();
695 String body = email.getTextBody();
696 String bodyHTML = email.getHtmlBody();
697 String rtf = email.getRtfBody();
698 String subject = email.getSubject();
699 long id = email.getId();
700 String localPath = email.getLocalPath();
701 String threadID = email.getMessageThreadID();
703 List<String> senderAddressList =
new ArrayList<>();
704 String senderAddress;
711 AccountFileInstance senderAccountInstance = null;
713 if (senderAddressList.size() == 1) {
714 senderAddress = senderAddressList.get(0);
716 senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress);
717 }
catch (TskCoreException ex) {
718 logger.log(Level.WARNING,
"Failed to create account for email address " + senderAddress, ex);
721 logger.log(Level.WARNING,
"Failed to find sender address, from = {0}", from);
728 List<String> recipientAddresses =
new ArrayList<>();
733 List<AccountFileInstance> recipientAccountInstances =
new ArrayList<>();
734 for (String addr : recipientAddresses) {
739 AccountFileInstance recipientAccountInstance = accountFileInstanceCache.getAccountInstance(addr);
740 recipientAccountInstances.add(recipientAccountInstance);
741 }
catch (TskCoreException ex) {
742 logger.log(Level.WARNING,
"Failed to create account for email address " + addr, ex);
746 addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes);
747 addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes);
748 addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes);
749 addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes);
751 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes);
752 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes);
754 addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes);
756 addArtifactAttribute(((
id < 0L) ? NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(
id)),
757 ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes);
759 addArtifactAttribute(((localPath.isEmpty() ==
false) ? localPath :
""),
760 ATTRIBUTE_TYPE.TSK_PATH, bbattributes);
762 addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes);
763 addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes);
764 addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes);
765 addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes);
772 bbart = abstractFile.newDataArtifact(
773 new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG),
781 currentCase.
getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL);
790 }
catch (Blackboard.BlackboardException ex) {
791 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + bbart.getArtifactID(), ex);
794 }
catch (TskCoreException | TskDataException ex) {
795 logger.log(Level.WARNING, null, ex);
808 static void addArtifactAttribute(String stringVal, BlackboardAttribute.Type attrType, Collection<BlackboardAttribute> bbattributes) {
809 if (stringVal.isEmpty() ==
false) {
821 static void addArtifactAttribute(String stringVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
822 if (stringVal.isEmpty() ==
false) {
823 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal));
834 static void addArtifactAttribute(
long longVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
836 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal));
847 private final Map<String, AccountFileInstance>
cacheMap;
848 private final AbstractFile
file;
858 cacheMap =
new HashMap<>();
872 AccountFileInstance getAccountInstance(String email)
throws TskCoreException {
873 if (cacheMap.containsKey(email)) {
874 return cacheMap.get(email);
877 AccountFileInstance accountInstance
878 = currentCase.
getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email,
880 cacheMap.put(email, accountInstance);
881 return accountInstance;
898 void postErrorMessage(String subj, String details) {
899 IngestMessage ingestMessage = IngestMessage.createErrorMessage(EmailParserModuleFactory.getModuleVersion(), subj, details);
908 IngestServices getServices() {
List< Long > findMboxSplitOffset(AbstractFile abstractFile, File file)
Set< String > findEmailAddresess(String input)
static final Logger logger
FileManager getFileManager()
String getTempDirectory()
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
void processEmails(List< EmailMessage > partialEmailsForThreading, Iterator< EmailMessage > fullMessageIterator, AbstractFile abstractFile)
final IngestServices services
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
final Map< String, AccountFileInstance > cacheMap
ProcessResult processMBox(AbstractFile abstractFile)
static final int DISK_FREE_SPACE_UNKNOWN
ProcessResult processVcard(AbstractFile abstractFile)
ProcessResult processEMLFile(AbstractFile abstractFile)
void addFilesToJob(List< AbstractFile > files)
void postMessage(final IngestMessage message)
ProcessResult process(AbstractFile abstractFile)
SleuthkitCase getSleuthkitCase()
boolean fileIngestIsCancelled()
BlackboardArtifact addEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache)
ProcessResult processPst(AbstractFile abstractFile)
void startUp(IngestJobContext context)
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
void processMboxFile(File file, AbstractFile abstractFile, String emailFolder)
static final int MBOX_SIZE_TO_SPLIT
static void error(String title, String message)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
List< AbstractFile > handleAttachments(List< EmailMessage.Attachment > attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact)
CommunicationArtifactsHelper communicationArtifactsHelper
DerivedFile addDerivedFile(String fileName, String localPath, long size, long ctime, long crtime, long atime, long mtime, boolean isFile, Content parentObj, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType)
static synchronized IngestServices getInstance()