19 package org.sleuthkit.autopsy.thunderbirdparser;
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.HashSet;
28 import java.util.Iterator;
29 import java.util.List;
32 import java.util.logging.Level;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
35 import org.apache.james.mime4j.MimeException;
36 import org.openide.util.NbBundle;
37 import org.openide.util.NbBundle.Messages;
57 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
65 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
66 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
67 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
92 @Messages ({
"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."})
99 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
110 if (abstractFile.getKnown().equals(TskData.FileKnown.KNOWN)) {
115 if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) ||
116 (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
120 if ((abstractFile.isFile() ==
false)) {
125 boolean isMbox =
false;
126 boolean isEMLFile =
false;
129 byte[] t =
new byte[64];
130 if (abstractFile.getSize() > 64) {
131 int byteRead = abstractFile.read(t, 0, 64);
133 isMbox = MboxParser.isValidMimeTypeMbox(t, abstractFile);
134 isEMLFile = EMLParser.isEMLFile(abstractFile, t);
137 }
catch (TskException ex) {
138 logger.log(Level.WARNING, null, ex);
141 boolean isPstFile = PstParser.isPstFile(abstractFile);
142 boolean isVcardFile = VcardParser.isVcardFile(abstractFile);
148 if (isMbox || isEMLFile || isPstFile || isVcardFile ) {
150 communicationArtifactsHelper =
new CommunicationArtifactsHelper(currentCase.
getSleuthkitCase(),
152 }
catch (TskCoreException ex) {
153 logger.log(Level.SEVERE, String.format(
"Failed to create CommunicationArtifactsHelper for file with object id = %d", abstractFile.getId()), ex);
184 @Messages({
"ThunderbirdMboxFileIngestModule.processPst.indexError.message=Failed to index encryption detected artifact for keyword search."})
188 fileName = getTempPath() + File.separator + abstractFile.getName()
189 +
"-" + String.valueOf(abstractFile.getId());
191 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
194 File file =
new File(fileName);
198 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
200 NbBundle.getMessage(this.getClass(),
201 "ThunderbirdMboxFileIngestModule.processPst.errMsg.outOfDiskSpace",
202 abstractFile.getName()));
207 try (PstParser parser =
new PstParser(services)){
210 }
catch (IOException ex) {
211 logger.log(Level.WARNING,
"Failed writing pst file to disk.", ex);
215 PstParser.ParseResult result = parser.open(file, abstractFile.getId());
219 Iterator<EmailMessage> pstMsgIterator = parser.getEmailMessageIterator();
220 if (pstMsgIterator != null) {
221 processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile);
228 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
229 abstractFile.getName()),
230 NbBundle.getMessage(
this.getClass(),
231 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
232 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
241 BlackboardArtifact artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
242 artifact.addAttribute(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
EmailParserModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(),
"ThunderbirdMboxFileIngestModule.encryptionFileLevel")));
247 }
catch (Blackboard.BlackboardException ex) {
249 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
251 }
catch (TskCoreException ex) {
252 logger.log(Level.INFO,
"Failed to add encryption attribute to file: {0}", abstractFile.getName());
258 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
259 abstractFile.getName()),
260 NbBundle.getMessage(
this.getClass(),
261 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
262 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
265 }
catch(Exception ex) {
266 logger.log(Level.WARNING, String.format(
"Failed to close temp pst file %s", file.getAbsolutePath()));
281 String mboxFileName = abstractFile.getName();
282 String mboxParentDir = abstractFile.getParentPath();
284 String emailFolder =
"";
286 if (mboxParentDir.contains(
"/Mail/")) {
287 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/Mail/") + 5);
288 }
else if (mboxParentDir.contains(
"/ImapMail/")) {
289 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/ImapMail/") + 9);
291 emailFolder += mboxFileName;
292 emailFolder = emailFolder.replaceAll(
".sbd",
"");
296 fileName = getTempPath() + File.separator + abstractFile.getName()
297 +
"-" + String.valueOf(abstractFile.getId());
299 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
302 File file =
new File(fileName);
306 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
308 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg",
309 abstractFile.getName()),
310 NbBundle.getMessage(
this.getClass(),
311 "ThunderbirdMboxFileIngestModule.processMBox.errProfFile.details"));
319 }
catch (IOException ex) {
320 logger.log(Level.WARNING,
"Failed writing mbox file to disk.", ex);
334 List<Long> mboxSplitOffsets =
new ArrayList<>();
337 }
catch (IOException ex) {
338 logger.log(Level.WARNING, String.format(
"Failed finding split offsets for mbox file {0}.", fileName), ex);
342 long startingOffset = 0;
343 for (Long mboxSplitOffset : mboxSplitOffsets) {
344 File splitFile =
new File(fileName +
"-" + mboxSplitOffset);
346 ContentUtils.
writeToFile(abstractFile, splitFile, context::fileIngestIsCancelled, startingOffset, mboxSplitOffset);
347 }
catch (IOException ex) {
348 logger.log(Level.WARNING,
"Failed writing split mbox file to disk.", ex);
353 startingOffset = mboxSplitOffset;
369 List<Long> mboxSplitOffset =
new ArrayList<>();
371 byte[] buffer =
new byte[7];
372 ReadContentInputStream in =
new ReadContentInputStream(abstractFile);
373 in.skip(MBOX_SIZE_TO_SPLIT);
374 int len = in.read(buffer);
376 len = in.read(buffer);
377 if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114 &&
378 buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) {
379 mboxSplitOffset.add(in.getCurPosition() - 5 );
380 in.skip(MBOX_SIZE_TO_SPLIT);
384 return mboxSplitOffset;
389 private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) {
391 try(MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId())) {
392 List<EmailMessage> emails =
new ArrayList<>();
393 if(emailIterator != null) {
394 while(emailIterator.hasNext()) {
398 EmailMessage emailMessage = emailIterator.next();
399 if(emailMessage != null) {
400 emails.add(emailMessage);
404 String errors = emailIterator.getErrors();
405 if (!errors.isEmpty()) {
407 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2",
408 abstractFile.getName()), errors);
411 processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile);
412 }
catch(Exception ex) {
413 logger.log(Level.WARNING, String.format(
"Failed to close mbox temp file %s", file.getAbsolutePath()));
429 "ThunderbirdMboxFileIngestModule.errorMessage.outOfDiskSpace=Out of disk space. Cannot copy '{0}' (id={1}) to parse."
433 VcardParser parser =
new VcardParser(currentCase, context);
434 parser.parse(abstractFile);
436 logger.log(Level.WARNING, String.format(
"Exception while parsing the file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex);
444 EmailMessage message = EMLParser.parse(abstractFile);
446 if (message == null) {
450 List<AbstractFile> derivedFiles =
new ArrayList<>();
453 BlackboardArtifact msgArtifact =
addEmailArtifact(message, abstractFile, accountFileInstanceCache);
454 accountFileInstanceCache.clear();
456 if ((msgArtifact != null) && (message.hasAttachment())) {
457 derivedFiles.addAll(
handleAttachments(message.getAttachments(), abstractFile, msgArtifact));
460 if (derivedFiles.isEmpty() ==
false) {
461 for (AbstractFile derived : derivedFiles) {
467 }
catch (IOException ex) {
468 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
470 }
catch (MimeException ex) {
471 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
487 File dir =
new File(tmpDir);
488 if (dir.exists() ==
false) {
501 static String getModuleOutputPath() throws NoCurrentCaseException {
502 String outDir = Case.getCurrentCaseThrows().getModuleDirectory() + File.separator
503 + EmailParserModuleFactory.getModuleName();
504 File dir =
new File(outDir);
505 if (dir.exists() ==
false) {
517 static String getRelModuleOutputPath() throws NoCurrentCaseException {
518 return Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath() + File.separator
519 + EmailParserModuleFactory.getModuleName();
530 private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator,
531 AbstractFile abstractFile) {
539 EmailMessageThreader.threadMessages(partialEmailsForThreading);
540 }
catch(Exception ex) {
541 logger.log(Level.WARNING, String.format(
"Exception thrown parsing emails from %s", abstractFile.getName()), ex);
544 List<AbstractFile> derivedFiles =
new ArrayList<>();
547 while(fullMessageIterator.hasNext()) {
552 EmailMessage current = fullMessageIterator.next();
554 if(current == null) {
558 if(partialEmailsForThreading.size() > msgCnt) {
559 EmailMessage threaded = partialEmailsForThreading.get(msgCnt++);
561 if(threaded.getMessageID().equals(current.getMessageID()) &&
562 threaded.getSubject().equals(current.getSubject())) {
563 current.setMessageThreadID(threaded.getMessageThreadID());
567 BlackboardArtifact msgArtifact =
addEmailArtifact(current, abstractFile, accountFileInstanceCache);
569 if ((msgArtifact != null) && (current.hasAttachment())) {
570 derivedFiles.addAll(
handleAttachments(current.getAttachments(), abstractFile, msgArtifact ));
574 if (derivedFiles.isEmpty() ==
false) {
575 for (AbstractFile derived : derivedFiles) {
595 "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message."
597 private List<AbstractFile>
handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) {
598 List<AbstractFile> files =
new ArrayList<>();
599 List<FileAttachment> fileAttachments =
new ArrayList<>();
600 for (EmailMessage.Attachment attach : attachments) {
601 String filename = attach.getName();
602 long crTime = attach.getCrTime();
603 long mTime = attach.getmTime();
604 long aTime = attach.getaTime();
605 long cTime = attach.getcTime();
606 String relPath = attach.getLocalPath();
607 long size = attach.getSize();
608 TskData.EncodingType encodingType = attach.getEncodingType();
612 size, cTime, crTime, aTime, mTime,
true, abstractFile,
"",
619 fileAttachments.add(
new FileAttachment(df));
620 }
catch (TskCoreException ex) {
622 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.errMsg",
623 abstractFile.getName()),
624 NbBundle.getMessage(
this.getClass(),
625 "ThunderbirdMboxFileIngestModule.handleAttch.errMsg.details", filename));
626 logger.log(Level.INFO,
"", ex);
632 communicationArtifactsHelper.addAttachments(messageArtifact,
new MessageAttachments(fileAttachments, Collections.emptyList()));
633 }
catch (TskCoreException ex) {
635 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"),
637 logger.log(Level.INFO,
"Failed to add attachments to email message.", ex);
648 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
649 attributes.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
EmailParserModuleFactory.getModuleName(), message.getArtifactID()));
651 BlackboardArtifact bba = attachedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
652 bba.addAttributes(attributes);
664 Pattern p = Pattern.compile(
"\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b",
665 Pattern.CASE_INSENSITIVE);
666 Matcher m = p.matcher(input);
667 Set<String> emailAddresses =
new HashSet<>();
669 emailAddresses.add( m.group());
671 return emailAddresses;
683 @Messages({
"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
685 BlackboardArtifact bbart = null;
686 List<BlackboardAttribute> bbattributes =
new ArrayList<>();
687 String to = email.getRecipients();
688 String cc = email.getCc();
689 String bcc = email.getBcc();
690 String from = email.getSender();
691 long dateL = email.getSentDate();
692 String headers = email.getHeaders();
693 String body = email.getTextBody();
694 String bodyHTML = email.getHtmlBody();
695 String rtf = email.getRtfBody();
696 String subject = email.getSubject();
697 long id = email.getId();
698 String localPath = email.getLocalPath();
699 String threadID = email.getMessageThreadID();
701 List<String> senderAddressList =
new ArrayList<>();
702 String senderAddress;
709 AccountFileInstance senderAccountInstance = null;
711 if (senderAddressList.size() == 1) {
712 senderAddress = senderAddressList.get(0);
714 senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress);
716 catch(TskCoreException ex) {
717 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);
742 catch(TskCoreException ex) {
743 logger.log(Level.WARNING,
"Failed to create account for email address " + addr, ex);
747 addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes);
748 addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes);
749 addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes);
750 addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes);
752 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes);
753 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes);
755 addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes);
757 addArtifactAttribute(((
id < 0L) ? NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(
id)),
758 ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes);
760 addArtifactAttribute(((localPath.isEmpty() ==
false) ? localPath :
""),
761 ATTRIBUTE_TYPE.TSK_PATH, bbattributes);
763 addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes);
764 addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes);
765 addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes);
766 addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes);
774 bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG);
775 bbart.addAttributes(bbattributes);
782 currentCase.
getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL);
791 }
catch (Blackboard.BlackboardException ex) {
792 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + bbart.getArtifactID(), ex);
795 }
catch (TskCoreException | TskDataException ex) {
796 logger.log(Level.WARNING, null, ex);
809 static void addArtifactAttribute(String stringVal, BlackboardAttribute.Type attrType, Collection<BlackboardAttribute> bbattributes) {
810 if (stringVal.isEmpty() ==
false) {
822 static void addArtifactAttribute(String stringVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
823 if (stringVal.isEmpty() ==
false) {
824 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal));
835 static void addArtifactAttribute(
long longVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
837 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal));
847 private final Map<String, AccountFileInstance>
cacheMap;
848 private final AbstractFile
file;
857 cacheMap=
new HashMap<>();
871 AccountFileInstance getAccountInstance(String email)
throws TskCoreException {
872 if (cacheMap.containsKey(email)) {
873 return cacheMap.get(email);
876 AccountFileInstance accountInstance =
877 currentCase.
getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email,
879 cacheMap.put(email, accountInstance);
880 return accountInstance;
897 void postErrorMessage(String subj, String details) {
898 IngestMessage ingestMessage = IngestMessage.createErrorMessage(EmailParserModuleFactory.getModuleVersion(), subj, details);
907 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)
synchronized 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)
final Map< String, AccountFileInstance > cacheMap
BlackboardArtifact associateAttachmentWithMesssge(BlackboardArtifact message, AbstractFile attachedFile)
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
static synchronized IngestServices getInstance()