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.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
30 import java.util.logging.Level;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import org.apache.james.mime4j.MimeException;
34 import org.openide.util.NbBundle;
35 import org.openide.util.NbBundle.Messages;
55 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
63 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
64 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
65 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
90 @Messages ({
"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."})
97 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
108 if (abstractFile.getKnown().equals(TskData.FileKnown.KNOWN)) {
113 if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)) ||
114 (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
118 if ((abstractFile.isFile() ==
false)) {
123 boolean isMbox =
false;
124 boolean isEMLFile =
false;
127 byte[] t =
new byte[64];
128 if (abstractFile.getSize() > 64) {
129 int byteRead = abstractFile.read(t, 0, 64);
131 isMbox = MboxParser.isValidMimeTypeMbox(t);
132 isEMLFile = EMLParser.isEMLFile(abstractFile, t);
135 }
catch (TskException ex) {
136 logger.log(Level.WARNING, null, ex);
139 boolean isPstFile = PstParser.isPstFile(abstractFile);
140 boolean isVcardFile = VcardParser.isVcardFile(abstractFile);
142 if (isMbox || isEMLFile || isPstFile || isVcardFile ) {
144 communicationArtifactsHelper =
new CommunicationArtifactsHelper(currentCase.
getSleuthkitCase(),
146 }
catch (TskCoreException ex) {
147 logger.log(Level.SEVERE, String.format(
"Failed to create CommunicationArtifactsHelper for file with object id = %d", abstractFile.getId()), ex);
178 @Messages({
"ThunderbirdMboxFileIngestModule.processPst.indexError.message=Failed to index encryption detected artifact for keyword search."})
182 fileName = getTempPath() + File.separator + abstractFile.getName()
183 +
"-" + String.valueOf(abstractFile.getId());
185 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
188 File file =
new File(fileName);
192 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
194 NbBundle.getMessage(this.getClass(),
195 "ThunderbirdMboxFileIngestModule.processPst.errMsg.outOfDiskSpace",
196 abstractFile.getName()));
203 }
catch (IOException ex) {
204 logger.log(Level.WARNING,
"Failed writing pst file to disk.", ex);
208 PstParser parser =
new PstParser(services);
209 PstParser.ParseResult result = parser.open(file, abstractFile.getId());
213 Iterator<EmailMessage> pstMsgIterator = parser.getEmailMessageIterator();
214 if (pstMsgIterator != null) {
215 processEmails(parser.getPartialEmailMessages(), pstMsgIterator , abstractFile);
219 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
220 abstractFile.getName()),
221 NbBundle.getMessage(
this.getClass(),
222 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
223 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
225 if (file.delete() ==
false) {
226 logger.log(Level.INFO,
"Failed to delete temp file: {0}", file.getName());
236 BlackboardArtifact artifact = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED);
237 artifact.addAttribute(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
EmailParserModuleFactory.getModuleName(), NbBundle.getMessage(this.getClass(),
"ThunderbirdMboxFileIngestModule.encryptionFileLevel")));
242 }
catch (Blackboard.BlackboardException ex) {
244 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + artifact.getArtifactID(), ex);
246 }
catch (TskCoreException ex) {
247 logger.log(Level.INFO,
"Failed to add encryption attribute to file: {0}", abstractFile.getName());
253 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processPst.errProcFile.msg",
254 abstractFile.getName()),
255 NbBundle.getMessage(
this.getClass(),
256 "ThunderbirdMboxFileIngestModule.processPst.errProcFile.details"));
257 logger.log(Level.INFO,
"PSTParser failed to parse {0}", abstractFile.getName());
259 if (file.delete() ==
false) {
260 logger.log(Level.INFO,
"Failed to delete temp file: {0}", file.getName());
265 if (file.delete() ==
false) {
266 logger.log(Level.INFO,
"Failed to delete temp file: {0}", file.getName());
280 String mboxFileName = abstractFile.getName();
281 String mboxParentDir = abstractFile.getParentPath();
283 String emailFolder =
"";
285 if (mboxParentDir.contains(
"/Mail/")) {
286 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/Mail/") + 5);
287 }
else if (mboxParentDir.contains(
"/ImapMail/")) {
288 emailFolder = mboxParentDir.substring(mboxParentDir.indexOf(
"/ImapMail/") + 9);
290 emailFolder += mboxFileName;
291 emailFolder = emailFolder.replaceAll(
".sbd",
"");
295 fileName = getTempPath() + File.separator + abstractFile.getName()
296 +
"-" + String.valueOf(abstractFile.getId());
298 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
301 File file =
new File(fileName);
305 logger.log(Level.WARNING,
"Not enough disk space to write file to disk.");
307 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg",
308 abstractFile.getName()),
309 NbBundle.getMessage(
this.getClass(),
310 "ThunderbirdMboxFileIngestModule.processMBox.errProfFile.details"));
318 }
catch (IOException ex) {
319 logger.log(Level.WARNING,
"Failed writing mbox file to disk.", ex);
325 if (file.delete() ==
false) {
326 logger.log(Level.INFO,
"Failed to delete temp file: {0}", file.getName());
330 List<Long> mboxSplitOffsets =
new ArrayList<>();
333 }
catch (IOException ex) {
334 logger.log(Level.WARNING, String.format(
"Failed finding split offsets for mbox file {0}.", fileName), ex);
338 long startingOffset = 0;
339 for (Long mboxSplitOffset : mboxSplitOffsets) {
340 File splitFile =
new File(fileName +
"-" + mboxSplitOffset);
342 ContentUtils.
writeToFile(abstractFile, splitFile, context::fileIngestIsCancelled, startingOffset, mboxSplitOffset);
343 }
catch (IOException ex) {
344 logger.log(Level.WARNING,
"Failed writing split mbox file to disk.", ex);
348 startingOffset = mboxSplitOffset;
349 if (splitFile.delete() ==
false) {
350 logger.log(Level.INFO,
"Failed to delete temp file: {0}", splitFile);
361 List<Long> mboxSplitOffset =
new ArrayList<>();
363 byte[] buffer =
new byte[7];
364 ReadContentInputStream in =
new ReadContentInputStream(abstractFile);
365 in.skip(MBOX_SIZE_TO_SPLIT);
366 int len = in.read(buffer);
368 len = in.read(buffer);
369 if (buffer[0] == 13 && buffer[1] == 10 && buffer[2] == 70 && buffer[3] == 114 &&
370 buffer[4] == 111 && buffer[5] == 109 && buffer[6] == 32) {
371 mboxSplitOffset.add(in.getCurPosition() - 5 );
372 in.skip(MBOX_SIZE_TO_SPLIT);
376 return mboxSplitOffset;
381 private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) {
384 MboxParser emailIterator = MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId());
385 List<EmailMessage> emails =
new ArrayList<>();
386 if(emailIterator != null) {
387 while(emailIterator.hasNext()) {
388 EmailMessage emailMessage = emailIterator.next();
389 if(emailMessage != null) {
390 emails.add(emailMessage);
394 String errors = emailIterator.getErrors();
395 if (!errors.isEmpty()) {
397 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2",
398 abstractFile.getName()), errors);
401 processEmails(emails, MboxParser.getEmailIterator( emailFolder, file, abstractFile.getId()), abstractFile);
416 "ThunderbirdMboxFileIngestModule.errorMessage.outOfDiskSpace=Out of disk space. Cannot copy '{0}' (id={1}) to parse."
420 VcardParser parser =
new VcardParser(currentCase, context);
421 parser.parse(abstractFile);
423 logger.log(Level.WARNING, String.format(
"Exception while parsing the file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex);
431 EmailMessage message = EMLParser.parse(abstractFile);
433 if (message == null) {
437 List<AbstractFile> derivedFiles =
new ArrayList<>();
441 if ((msgArtifact != null) && (message.hasAttachment())) {
442 derivedFiles.addAll(
handleAttachments(message.getAttachments(), abstractFile, msgArtifact));
445 if (derivedFiles.isEmpty() ==
false) {
446 for (AbstractFile derived : derivedFiles) {
452 }
catch (IOException ex) {
453 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
455 }
catch (MimeException ex) {
456 logger.log(Level.WARNING, String.format(
"Error reading eml file %s", abstractFile.getName()), ex);
472 File dir =
new File(tmpDir);
473 if (dir.exists() ==
false) {
486 static String getModuleOutputPath() throws NoCurrentCaseException {
487 String outDir = Case.getCurrentCaseThrows().getModuleDirectory() + File.separator
488 + EmailParserModuleFactory.getModuleName();
489 File dir =
new File(outDir);
490 if (dir.exists() ==
false) {
502 static String getRelModuleOutputPath() throws NoCurrentCaseException {
503 return Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath() + File.separator
504 + EmailParserModuleFactory.getModuleName();
515 private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator, AbstractFile abstractFile) {
520 EmailMessageThreader.threadMessages(partialEmailsForThreading);
521 }
catch(Exception ex) {
522 logger.log(Level.WARNING, String.format(
"Exception thrown parsing emails from %s", abstractFile.getName()), ex);
525 List<AbstractFile> derivedFiles =
new ArrayList<>();
528 while(fullMessageIterator.hasNext()) {
529 EmailMessage current = fullMessageIterator.next();
531 if(current == null) {
535 if(partialEmailsForThreading.size() > msgCnt) {
536 EmailMessage threaded = partialEmailsForThreading.get(msgCnt++);
538 if(threaded.getMessageID().equals(current.getMessageID()) &&
539 threaded.getSubject().equals(current.getSubject())) {
540 current.setMessageThreadID(threaded.getMessageThreadID());
546 if ((msgArtifact != null) && (current.hasAttachment())) {
547 derivedFiles.addAll(
handleAttachments(current.getAttachments(), abstractFile, msgArtifact ));
551 if (derivedFiles.isEmpty() ==
false) {
552 for (AbstractFile derived : derivedFiles) {
569 "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message."
571 private List<AbstractFile>
handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) {
572 List<AbstractFile> files =
new ArrayList<>();
573 List<FileAttachment> fileAttachments =
new ArrayList<>();
574 for (EmailMessage.Attachment attach : attachments) {
575 String filename = attach.getName();
576 long crTime = attach.getCrTime();
577 long mTime = attach.getmTime();
578 long aTime = attach.getaTime();
579 long cTime = attach.getcTime();
580 String relPath = attach.getLocalPath();
581 long size = attach.getSize();
582 TskData.EncodingType encodingType = attach.getEncodingType();
586 size, cTime, crTime, aTime, mTime,
true, abstractFile,
"",
593 fileAttachments.add(
new FileAttachment(df));
594 }
catch (TskCoreException ex) {
596 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.errMsg",
597 abstractFile.getName()),
598 NbBundle.getMessage(
this.getClass(),
599 "ThunderbirdMboxFileIngestModule.handleAttch.errMsg.details", filename));
600 logger.log(Level.INFO,
"", ex);
606 communicationArtifactsHelper.addAttachments(messageArtifact,
new MessageAttachments(fileAttachments, Collections.emptyList()));
607 }
catch (TskCoreException ex) {
609 NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"),
611 logger.log(Level.INFO,
"Failed to add attachments to email message.", ex);
622 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
623 attributes.add(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT,
EmailParserModuleFactory.getModuleName(), message.getArtifactID()));
625 BlackboardArtifact bba = attachedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
626 bba.addAttributes(attributes);
638 Pattern p = Pattern.compile(
"\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}\\b",
639 Pattern.CASE_INSENSITIVE);
640 Matcher m = p.matcher(input);
641 Set<String> emailAddresses =
new HashSet<>();
643 emailAddresses.add( m.group());
645 return emailAddresses;
656 @Messages({
"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
657 private BlackboardArtifact
addEmailArtifact(EmailMessage email, AbstractFile abstractFile) {
658 BlackboardArtifact bbart = null;
659 List<BlackboardAttribute> bbattributes =
new ArrayList<>();
660 String to = email.getRecipients();
661 String cc = email.getCc();
662 String bcc = email.getBcc();
663 String from = email.getSender();
664 long dateL = email.getSentDate();
665 String headers = email.getHeaders();
666 String body = email.getTextBody();
667 String bodyHTML = email.getHtmlBody();
668 String rtf = email.getRtfBody();
669 String subject = email.getSubject();
670 long id = email.getId();
671 String localPath = email.getLocalPath();
672 String threadID = email.getMessageThreadID();
674 List<String> senderAddressList =
new ArrayList<>();
675 String senderAddress;
678 AccountFileInstance senderAccountInstance = null;
680 if (senderAddressList.size() == 1) {
681 senderAddress = senderAddressList.get(0);
683 senderAccountInstance = currentCase.
getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, senderAddress,
EmailParserModuleFactory.getModuleName(), abstractFile);
685 catch(TskCoreException ex) {
686 logger.log(Level.WARNING,
"Failed to create account for email address " + senderAddress, ex);
690 logger.log(Level.WARNING,
"Failed to find sender address, from = {0}", from);
693 List<String> recipientAddresses =
new ArrayList<>();
698 List<AccountFileInstance> recipientAccountInstances =
new ArrayList<>();
699 recipientAddresses.forEach((addr) -> {
701 AccountFileInstance recipientAccountInstance =
702 currentCase.
getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, addr,
704 recipientAccountInstances.add(recipientAccountInstance);
706 catch(TskCoreException ex) {
707 logger.log(Level.WARNING,
"Failed to create account for email address " + addr, ex);
711 addArtifactAttribute(headers, ATTRIBUTE_TYPE.TSK_HEADERS, bbattributes);
712 addArtifactAttribute(from, ATTRIBUTE_TYPE.TSK_EMAIL_FROM, bbattributes);
713 addArtifactAttribute(to, ATTRIBUTE_TYPE.TSK_EMAIL_TO, bbattributes);
714 addArtifactAttribute(subject, ATTRIBUTE_TYPE.TSK_SUBJECT, bbattributes);
716 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes);
717 addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes);
719 addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes);
721 addArtifactAttribute(((
id < 0L) ? NbBundle.getMessage(
this.getClass(),
"ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(
id)),
722 ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes);
724 addArtifactAttribute(((localPath.isEmpty() ==
false) ? localPath :
""),
725 ATTRIBUTE_TYPE.TSK_PATH, bbattributes);
727 addArtifactAttribute(cc, ATTRIBUTE_TYPE.TSK_EMAIL_CC, bbattributes);
728 addArtifactAttribute(bodyHTML, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML, bbattributes);
729 addArtifactAttribute(rtf, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, bbattributes);
730 addArtifactAttribute(threadID, ATTRIBUTE_TYPE.TSK_THREAD_ID, bbattributes);
735 bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG);
736 bbart.addAttributes(bbattributes);
739 currentCase.
getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart,Relationship.Type.MESSAGE, dateL);
744 }
catch (Blackboard.BlackboardException ex) {
745 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + bbart.getArtifactID(), ex);
748 }
catch (TskCoreException | TskDataException ex) {
749 logger.log(Level.WARNING, null, ex);
762 static void addArtifactAttribute(String stringVal, BlackboardAttribute.Type attrType, Collection<BlackboardAttribute> bbattributes) {
763 if (stringVal.isEmpty() ==
false) {
775 static void addArtifactAttribute(String stringVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
776 if (stringVal.isEmpty() ==
false) {
777 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal));
788 static void addArtifactAttribute(
long longVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
790 bbattributes.add(
new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal));
800 void postErrorMessage(String subj, String details) {
801 IngestMessage ingestMessage = IngestMessage.createErrorMessage(EmailParserModuleFactory.getModuleVersion(), subj, details);
810 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)
BlackboardArtifact addEmailArtifact(EmailMessage email, 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)
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()
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()