Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ThunderbirdMboxFileIngestModule.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.thunderbirdparser;
20 
21 import java.io.File;
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;
31 import java.util.Map;
32 import java.util.Set;
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;
53 import org.sleuthkit.datamodel.AbstractFile;
54 import org.sleuthkit.datamodel.Account;
55 import org.sleuthkit.datamodel.AccountFileInstance;
56 import org.sleuthkit.datamodel.Blackboard;
57 import org.sleuthkit.datamodel.BlackboardArtifact;
58 import org.sleuthkit.datamodel.BlackboardAttribute;
59 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
60 import org.sleuthkit.datamodel.DerivedFile;
61 import org.sleuthkit.datamodel.ReadContentInputStream;
62 import org.sleuthkit.datamodel.Relationship;
63 import org.sleuthkit.datamodel.Score;
64 import org.sleuthkit.datamodel.TskCoreException;
65 import org.sleuthkit.datamodel.TskData;
66 import org.sleuthkit.datamodel.TskDataException;
67 import org.sleuthkit.datamodel.TskException;
68 import org.sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
69 import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
70 import org.sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
71 
77 public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
78 
79  private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestModule.class.getName());
83  private Blackboard blackboard;
84  private CommunicationArtifactsHelper communicationArtifactsHelper;
85 
86  private static final int MBOX_SIZE_TO_SPLIT = 1048576000;
87  private Case currentCase;
88 
93  }
94 
95  @Override
96  @Messages({"ThunderbirdMboxFileIngestModule.noOpenCase.errMsg=Exception while getting open case."})
97  public void startUp(IngestJobContext context) throws IngestModuleException {
98  this.context = context;
99  try {
100  currentCase = Case.getCurrentCaseThrows();
102  } catch (NoCurrentCaseException ex) {
103  logger.log(Level.SEVERE, "Exception while getting open case.", ex);
104  throw new IngestModuleException(Bundle.ThunderbirdMboxFileIngestModule_noOpenCase_errMsg(), ex);
105  }
106  }
107 
108  @Override
109  public ProcessResult process(AbstractFile abstractFile) {
110 
111  blackboard = currentCase.getSleuthkitCase().getBlackboard();
112 
113  // skip known
114  if (abstractFile.getKnown().equals(TskData.FileKnown.KNOWN)) {
115  return ProcessResult.OK;
116  }
117 
118  //skip unalloc
119  if ((abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS))
120  || (abstractFile.getType().equals(TskData.TSK_DB_FILES_TYPE_ENUM.SLACK))) {
121  return ProcessResult.OK;
122  }
123 
124  if ((abstractFile.isFile() == false)) {
125  return ProcessResult.OK;
126  }
127 
128  // check its signature
129  boolean isMbox = false;
130  boolean isEMLFile = false;
131 
132  try {
133  byte[] t = new byte[64];
134  if (abstractFile.getSize() > 64) {
135  int byteRead = abstractFile.read(t, 0, 64);
136  if (byteRead > 0) {
137  isMbox = MboxParser.isValidMimeTypeMbox(t, abstractFile);
138  isEMLFile = EMLParser.isEMLFile(abstractFile, t);
139  }
140  }
141  } catch (TskException ex) {
142  logger.log(Level.WARNING, null, ex);
143  }
144 
145  boolean isPstFile = PstParser.isPstFile(abstractFile);
146  boolean isVcardFile = VcardParser.isVcardFile(abstractFile);
147 
148  if (context.fileIngestIsCancelled()) {
149  return ProcessResult.OK;
150  }
151 
152  if (isMbox || isEMLFile || isPstFile || isVcardFile) {
153  try {
154  communicationArtifactsHelper = new CommunicationArtifactsHelper(currentCase.getSleuthkitCase(),
155  EmailParserModuleFactory.getModuleName(), abstractFile, Account.Type.EMAIL);
156  } catch (TskCoreException ex) {
157  logger.log(Level.SEVERE, String.format("Failed to create CommunicationArtifactsHelper for file with object id = %d", abstractFile.getId()), ex);
158  return ProcessResult.ERROR;
159  }
160  }
161 
162  if (isMbox) {
163  return processMBox(abstractFile);
164  }
165 
166  if (isEMLFile) {
167  return processEMLFile(abstractFile);
168  }
169 
170  if (isPstFile) {
171  return processPst(abstractFile);
172  }
173 
174  if (isVcardFile) {
175  return processVcard(abstractFile);
176  }
177 
178  return ProcessResult.OK;
179  }
180 
188  @Messages({"ThunderbirdMboxFileIngestModule.processPst.indexError.message=Failed to index encryption detected artifact for keyword search."})
189  private ProcessResult processPst(AbstractFile abstractFile) {
190  String fileName;
191  try {
192  fileName = getTempPath() + File.separator + abstractFile.getName()
193  + "-" + String.valueOf(abstractFile.getId());
194  } catch (NoCurrentCaseException ex) {
195  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
196  return ProcessResult.ERROR;
197  }
198  File file = new File(fileName);
199 
200  long freeSpace = services.getFreeDiskSpace();
201  if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (abstractFile.getSize() >= freeSpace)) {
202  logger.log(Level.WARNING, "Not enough disk space to write file to disk."); //NON-NLS
204  NbBundle.getMessage(this.getClass(),
205  "ThunderbirdMboxFileIngestModule.processPst.errMsg.outOfDiskSpace",
206  abstractFile.getName()));
207  services.postMessage(msg);
208  return ProcessResult.OK;
209  }
210 
211  try (PstParser parser = new PstParser(services)) {
212  try {
213  ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled);
214  } catch (IOException ex) {
215  logger.log(Level.WARNING, "Failed writing pst file to disk.", ex); //NON-NLS
216  return ProcessResult.OK;
217  }
218 
219  PstParser.ParseResult result = parser.open(file, abstractFile.getId());
220 
221  switch (result) {
222  case OK:
223  Iterator<EmailMessage> pstMsgIterator = parser.getEmailMessageIterator();
224  if (pstMsgIterator != null) {
225  processEmails(parser.getPartialEmailMessages(), pstMsgIterator, abstractFile);
226  if (context.fileIngestIsCancelled()) {
227  return ProcessResult.OK;
228  }
229  } else {
230  // sometimes parser returns ParseResult=OK but there are no messages
231  postErrorMessage(
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()); //NON-NLS
237  return ProcessResult.ERROR;
238  }
239  break;
240 
241  case ENCRYPT:
242  // encrypted pst: Add encrypted file artifact
243  try {
244 
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,
251  EmailParserModuleFactory.getModuleName(),
252  encryptionFileLevel)
253  ))
254  .getAnalysisResult();
255 
256  try {
257  // index the artifact for keyword search
258  blackboard.postArtifact(artifact, EmailParserModuleFactory.getModuleName());
259  } catch (Blackboard.BlackboardException ex) {
260  MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_processPst_indexError_message(), artifact.getDisplayName());
261  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + artifact.getArtifactID(), ex); //NON-NLS
262  }
263  } catch (TskCoreException ex) {
264  logger.log(Level.INFO, "Failed to add encryption attribute to file: {0}", abstractFile.getName()); //NON-NLS
265  }
266  break;
267  default:
268  // parsing error: log message
269  postErrorMessage(
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()); //NON-NLS
275  return ProcessResult.ERROR;
276  }
277  } catch (Exception ex) {
278  logger.log(Level.WARNING, String.format("Failed to close temp pst file %s", file.getAbsolutePath()));
279  } finally {
280  file.delete();
281  }
282  return ProcessResult.OK;
283  }
284 
292  private ProcessResult processMBox(AbstractFile abstractFile) {
293  String mboxFileName = abstractFile.getName();
294  String mboxParentDir = abstractFile.getParentPath();
295  // use the local path to determine the e-mail folder structure
296  String emailFolder = "";
297  // email folder is everything after "Mail" or ImapMail
298  if (mboxParentDir.contains("/Mail/")) { //NON-NLS
299  emailFolder = mboxParentDir.substring(mboxParentDir.indexOf("/Mail/") + 5); //NON-NLS
300  } else if (mboxParentDir.contains("/ImapMail/")) { //NON-NLS
301  emailFolder = mboxParentDir.substring(mboxParentDir.indexOf("/ImapMail/") + 9); //NON-NLS
302  }
303  emailFolder += mboxFileName;
304  emailFolder = emailFolder.replaceAll(".sbd", ""); //NON-NLS
305 
306  String fileName;
307  try {
308  fileName = getTempPath() + File.separator + abstractFile.getName()
309  + "-" + String.valueOf(abstractFile.getId());
310  } catch (NoCurrentCaseException ex) {
311  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
312  return ProcessResult.ERROR;
313  }
314  File file = new File(fileName);
315 
316  long freeSpace = services.getFreeDiskSpace();
317  if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (abstractFile.getSize() >= freeSpace)) {
318  logger.log(Level.WARNING, "Not enough disk space to write file to disk."); //NON-NLS
319  postErrorMessage(
320  NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg",
321  abstractFile.getName()),
322  NbBundle.getMessage(this.getClass(),
323  "ThunderbirdMboxFileIngestModule.processMBox.errProfFile.details"));
324  return ProcessResult.OK;
325  }
326 
327  if (abstractFile.getSize() < MBOX_SIZE_TO_SPLIT) {
328 
329  try {
330  ContentUtils.writeToFile(abstractFile, file, context::fileIngestIsCancelled);
331  } catch (IOException ex) {
332  logger.log(Level.WARNING, "Failed writing mbox file to disk.", ex); //NON-NLS
333  return ProcessResult.OK;
334  }
335 
336  try {
337  processMboxFile(file, abstractFile, emailFolder);
338  if (context.fileIngestIsCancelled()) {
339  return ProcessResult.OK;
340  }
341  } finally {
342  file.delete();
343  }
344  } else {
345 
346  List<Long> mboxSplitOffsets = new ArrayList<>();
347  try {
348  mboxSplitOffsets = findMboxSplitOffset(abstractFile, file);
349  } catch (IOException ex) {
350  logger.log(Level.WARNING, String.format("Failed finding split offsets for mbox file {0}.", fileName), ex); //NON-NLS
351  return ProcessResult.OK;
352  }
353 
354  long startingOffset = 0;
355  for (Long mboxSplitOffset : mboxSplitOffsets) {
356  File splitFile = new File(fileName + "-" + mboxSplitOffset);
357  try {
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); //NON-NLS
361  return ProcessResult.OK;
362  }
363  try {
364  processMboxFile(splitFile, abstractFile, emailFolder);
365  startingOffset = mboxSplitOffset;
366  } finally {
367  splitFile.delete();
368  }
369 
370  if (context.fileIngestIsCancelled()) {
371  return ProcessResult.OK;
372  }
373  }
374  }
375 
376  return ProcessResult.OK;
377  }
378 
379  private List<Long> findMboxSplitOffset(AbstractFile abstractFile, File file) throws IOException {
380 
381  List<Long> mboxSplitOffset = new ArrayList<>();
382 
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);
387  while (len != -1) {
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);
393  }
394  }
395 
396  return mboxSplitOffset;
397 
398  }
399 
400  private void processMboxFile(File file, AbstractFile abstractFile, String emailFolder) {
401 
402  try (MboxParser emailIterator = MboxParser.getEmailIterator(emailFolder, file, abstractFile.getId())) {
403  List<EmailMessage> emails = new ArrayList<>();
404  if (emailIterator != null) {
405  while (emailIterator.hasNext()) {
406  if (context.fileIngestIsCancelled()) {
407  return;
408  }
409  EmailMessage emailMessage = emailIterator.next();
410  if (emailMessage != null) {
411  emails.add(emailMessage);
412  }
413  }
414 
415  String errors = emailIterator.getErrors();
416  if (!errors.isEmpty()) {
417  postErrorMessage(
418  NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.processMBox.errProcFile.msg2",
419  abstractFile.getName()), errors);
420  }
421  }
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()));
425  }
426 
427  }
428 
437  @Messages({
438  "# {0} - file name",
439  "# {1} - file ID",
440  "ThunderbirdMboxFileIngestModule.errorMessage.outOfDiskSpace=Out of disk space. Cannot copy '{0}' (id={1}) to parse."
441  })
442  private ProcessResult processVcard(AbstractFile abstractFile) {
443  try {
444  VcardParser parser = new VcardParser(currentCase, context);
445  parser.parse(abstractFile);
446  } catch (IOException | NoCurrentCaseException ex) {
447  logger.log(Level.WARNING, String.format("Exception while parsing the file '%s' (id=%d).", abstractFile.getName(), abstractFile.getId()), ex); //NON-NLS
448  return ProcessResult.OK;
449  }
450  return ProcessResult.OK;
451  }
452 
453  private ProcessResult processEMLFile(AbstractFile abstractFile) {
454  try {
455  EmailMessage message = EMLParser.parse(abstractFile);
456 
457  if (message == null) {
458  return ProcessResult.OK;
459  }
460 
461  List<AbstractFile> derivedFiles = new ArrayList<>();
462 
463  AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase);
464  createEmailArtifact(message, abstractFile, accountFileInstanceCache, derivedFiles);
465  accountFileInstanceCache.clear();
466 
467  if (derivedFiles.isEmpty() == false) {
468  for (AbstractFile derived : derivedFiles) {
469  services.fireModuleContentEvent(new ModuleContentEvent(derived));
470  }
471  }
472  context.addFilesToJob(derivedFiles);
473 
474  } catch (IOException ex) {
475  logger.log(Level.WARNING, String.format("Error reading eml file %s", abstractFile.getName()), ex);
476  return ProcessResult.ERROR;
477  } catch (MimeException ex) {
478  logger.log(Level.WARNING, String.format("Error reading eml file %s", abstractFile.getName()), ex);
479  return ProcessResult.ERROR;
480  }
481 
482  return ProcessResult.OK;
483  }
484 
491  static String getTempPath() throws NoCurrentCaseException {
492  String tmpDir = Case.getCurrentCaseThrows().getTempDirectory() + File.separator
493  + "EmailParser"; //NON-NLS
494  File dir = new File(tmpDir);
495  if (dir.exists() == false) {
496  dir.mkdirs();
497  }
498  return tmpDir;
499  }
500 
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) {
513  dir.mkdirs();
514  }
515  return outDir;
516  }
517 
524  static String getRelModuleOutputPath() throws NoCurrentCaseException {
525  return Case.getCurrentCaseThrows().getModuleOutputDirectoryRelativePath() + File.separator
526  + EmailParserModuleFactory.getModuleName();
527  }
528 
537  private void processEmails(List<EmailMessage> partialEmailsForThreading, Iterator<EmailMessage> fullMessageIterator,
538  AbstractFile abstractFile) {
539 
540  // Create cache for accounts
541  AccountFileInstanceCache accountFileInstanceCache = new AccountFileInstanceCache(abstractFile, currentCase);
542 
543  // Putting try/catch around this to catch any exception and still allow
544  // the creation of the artifacts to continue.
545  try {
546  EmailMessageThreader.threadMessages(partialEmailsForThreading);
547  } catch (Exception ex) {
548  logger.log(Level.WARNING, String.format("Exception thrown parsing emails from %s", abstractFile.getName()), ex);
549  }
550 
551  List<AbstractFile> derivedFiles = new ArrayList<>();
552 
553  int msgCnt = 0;
554  while (fullMessageIterator.hasNext()) {
555  if (context.fileIngestIsCancelled()) {
556  return;
557  }
558 
559  EmailMessage current = fullMessageIterator.next();
560 
561  if (current == null) {
562  continue;
563  }
564 
565  if (partialEmailsForThreading.size() > msgCnt) {
566  EmailMessage threaded = partialEmailsForThreading.get(msgCnt++);
567 
568  if (threaded.getMessageID().equals(current.getMessageID())
569  && threaded.getSubject().equals(current.getSubject())) {
570  current.setMessageThreadID(threaded.getMessageThreadID());
571  }
572  }
573  createEmailArtifact(current, abstractFile, accountFileInstanceCache, derivedFiles);
574  }
575 
576  if (derivedFiles.isEmpty() == false) {
577  for (AbstractFile derived : derivedFiles) {
578  if (context.fileIngestIsCancelled()) {
579  return;
580  }
581  services.fireModuleContentEvent(new ModuleContentEvent(derived));
582  }
583  }
584  context.addFilesToJob(derivedFiles);
585  }
586 
587  void createEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache, List<AbstractFile> derivedFiles) {
588  BlackboardArtifact msgArtifact = addEmailArtifact(email, abstractFile, accountFileInstanceCache);
589 
590  if ((msgArtifact != null) && (email.hasAttachment())) {
591  derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile, msgArtifact));
592 
593  for (EmailMessage.Attachment attach : email.getAttachments()) {
594  if (attach instanceof AttachedEmailMessage) {
595  createEmailArtifact(((AttachedEmailMessage) attach).getEmailMessage(), abstractFile, accountFileInstanceCache, derivedFiles);
596  }
597  }
598  }
599  }
600 
611  @NbBundle.Messages({
612  "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg=Failed to add attachments to email message."
613  })
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();
626 
627  try {
628  DerivedFile df = fileManager.addDerivedFile(filename, relPath,
629  size, cTime, crTime, aTime, mTime, true, abstractFile, "",
630  EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType);
631 
632  files.add(df);
633 
634  fileAttachments.add(new FileAttachment(df));
635  } catch (TskCoreException ex) {
636  postErrorMessage(
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);
642  }
643  }
644 
645  try {
646  communicationArtifactsHelper.addAttachments(messageArtifact, new MessageAttachments(fileAttachments, Collections.emptyList()));
647  } catch (TskCoreException ex) {
648  postErrorMessage(
649  NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.handleAttch.addAttachmentsErrorMsg"),
650  "");
651  logger.log(Level.INFO, "Failed to add attachments to email message.", ex);
652  }
653 
654  return files;
655  }
656 
665  private Set<String> findEmailAddresess(String input) {
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<>();
670  while (m.find()) {
671  emailAddresses.add(m.group());
672  }
673  return emailAddresses;
674  }
675 
685  @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."})
686  private BlackboardArtifact addEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache) {
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();
702 
703  List<String> senderAddressList = new ArrayList<>();
704  String senderAddress;
705  senderAddressList.addAll(findEmailAddresess(from));
706 
707  if (context.fileIngestIsCancelled()) {
708  return null;
709  }
710 
711  AccountFileInstance senderAccountInstance = null;
712 
713  if (senderAddressList.size() == 1) {
714  senderAddress = senderAddressList.get(0);
715  try {
716  senderAccountInstance = accountFileInstanceCache.getAccountInstance(senderAddress);
717  } catch (TskCoreException ex) {
718  logger.log(Level.WARNING, "Failed to create account for email address " + senderAddress, ex); //NON-NLS
719  }
720  } else {
721  logger.log(Level.WARNING, "Failed to find sender address, from = {0}", from); //NON-NLS
722  }
723 
724  if (context.fileIngestIsCancelled()) {
725  return null;
726  }
727 
728  List<String> recipientAddresses = new ArrayList<>();
729  recipientAddresses.addAll(findEmailAddresess(to));
730  recipientAddresses.addAll(findEmailAddresess(cc));
731  recipientAddresses.addAll(findEmailAddresess(bcc));
732 
733  List<AccountFileInstance> recipientAccountInstances = new ArrayList<>();
734  for (String addr : recipientAddresses) {
735  if (context.fileIngestIsCancelled()) {
736  return null;
737  }
738  try {
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); //NON-NLS
743  }
744  }
745 
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);
750 
751  addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_RCVD, bbattributes);
752  addArtifactAttribute(dateL, ATTRIBUTE_TYPE.TSK_DATETIME_SENT, bbattributes);
753 
754  addArtifactAttribute(body, ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN, bbattributes);
755 
756  addArtifactAttribute(((id < 0L) ? NbBundle.getMessage(this.getClass(), "ThunderbirdMboxFileIngestModule.notAvail") : String.valueOf(id)),
757  ATTRIBUTE_TYPE.TSK_MSG_ID, bbattributes);
758 
759  addArtifactAttribute(((localPath.isEmpty() == false) ? localPath : ""),
760  ATTRIBUTE_TYPE.TSK_PATH, bbattributes);
761 
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);
766 
767  try {
768  if (context.fileIngestIsCancelled()) {
769  return null;
770  }
771 
772  bbart = abstractFile.newDataArtifact(
773  new BlackboardArtifact.Type(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG),
774  bbattributes);
775 
776  if (context.fileIngestIsCancelled()) {
777  return null;
778  }
779 
780  // Add account relationships
781  currentCase.getSleuthkitCase().getCommunicationsManager().addRelationships(senderAccountInstance, recipientAccountInstances, bbart, Relationship.Type.MESSAGE, dateL);
782 
783  if (context.fileIngestIsCancelled()) {
784  return null;
785  }
786 
787  try {
788  // index the artifact for keyword search
789  blackboard.postArtifact(bbart, EmailParserModuleFactory.getModuleName());
790  } catch (Blackboard.BlackboardException ex) {
791  logger.log(Level.SEVERE, "Unable to index blackboard artifact " + bbart.getArtifactID(), ex); //NON-NLS
792  MessageNotifyUtil.Notify.error(Bundle.ThunderbirdMboxFileIngestModule_addArtifact_indexError_message(), bbart.getDisplayName());
793  }
794  } catch (TskCoreException | TskDataException ex) {
795  logger.log(Level.WARNING, null, ex);
796  }
797 
798  return bbart;
799  }
800 
808  static void addArtifactAttribute(String stringVal, BlackboardAttribute.Type attrType, Collection<BlackboardAttribute> bbattributes) {
809  if (stringVal.isEmpty() == false) {
810  bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), stringVal));
811  }
812  }
813 
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));
824  }
825  }
826 
834  static void addArtifactAttribute(long longVal, ATTRIBUTE_TYPE attrType, Collection<BlackboardAttribute> bbattributes) {
835  if (longVal > 0) {
836  bbattributes.add(new BlackboardAttribute(attrType, EmailParserModuleFactory.getModuleName(), longVal));
837  }
838  }
839 
845  static private class AccountFileInstanceCache {
846 
847  private final Map<String, AccountFileInstance> cacheMap;
848  private final AbstractFile file;
849  private final Case currentCase;
850 
857  AccountFileInstanceCache(AbstractFile file, Case currentCase) {
858  cacheMap = new HashMap<>();
859  this.file = file;
860  this.currentCase = currentCase;
861  }
862 
872  AccountFileInstance getAccountInstance(String email) throws TskCoreException {
873  if (cacheMap.containsKey(email)) {
874  return cacheMap.get(email);
875  }
876 
877  AccountFileInstance accountInstance
878  = currentCase.getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email,
879  EmailParserModuleFactory.getModuleName(), file);
880  cacheMap.put(email, accountInstance);
881  return accountInstance;
882  }
883 
887  void clear() {
888  cacheMap.clear();
889  }
890  }
891 
898  void postErrorMessage(String subj, String details) {
899  IngestMessage ingestMessage = IngestMessage.createErrorMessage(EmailParserModuleFactory.getModuleVersion(), subj, details);
900  services.postMessage(ingestMessage);
901  }
902 
908  IngestServices getServices() {
909  return services;
910  }
911 
912  @Override
913  public void shutDown() {
914  // nothing to shut down
915  }
916 
917 }
static IngestMessage createErrorMessage(String source, String subject, String detailsHtml)
void processEmails(List< EmailMessage > partialEmailsForThreading, Iterator< EmailMessage > fullMessageIterator, AbstractFile abstractFile)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)
void addFilesToJob(List< AbstractFile > files)
void postMessage(final IngestMessage message)
BlackboardArtifact addEmailArtifact(EmailMessage email, AbstractFile abstractFile, AccountFileInstanceCache accountFileInstanceCache)
void fireModuleContentEvent(ModuleContentEvent moduleContentEvent)
void processMboxFile(File file, AbstractFile abstractFile, String emailFolder)
static void error(String title, String message)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
List< AbstractFile > handleAttachments(List< EmailMessage.Attachment > attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact)
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()

Copyright © 2012-2021 Basis Technology. Generated on: Thu Sep 30 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.