Autopsy  4.11.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
PstParser.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2014 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 com.pff.PSTAttachment;
22 import com.pff.PSTException;
23 import com.pff.PSTFile;
24 import com.pff.PSTFolder;
25 import com.pff.PSTMessage;
26 import java.io.File;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.nio.ByteBuffer;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.Scanner;
34 import java.util.logging.Level;
36 import org.openide.util.NbBundle;
40 import static org.sleuthkit.autopsy.thunderbirdparser.ThunderbirdMboxFileIngestModule.getRelModuleOutputPath;
41 import org.sleuthkit.datamodel.AbstractFile;
42 import org.sleuthkit.datamodel.EncodedFileOutputStream;
43 import org.sleuthkit.datamodel.TskCoreException;
44 import org.sleuthkit.datamodel.TskData;
45 
51 class PstParser {
52 
53  private static final Logger logger = Logger.getLogger(PstParser.class.getName());
57  private static int PST_HEADER = 0x2142444E;
58  private IngestServices services;
63  private List<EmailMessage> results;
64  private StringBuilder errors;
65 
66  PstParser(IngestServices services) {
67  results = new ArrayList<>();
68  this.services = services;
69  errors = new StringBuilder();
70  }
71 
72  enum ParseResult {
73 
74  OK, ERROR, ENCRYPT;
75  }
76 
85  ParseResult parse(File file, long fileID) {
86  PSTFile pstFile;
87  long failures;
88  try {
89  pstFile = new PSTFile(file);
90  failures = processFolder(pstFile.getRootFolder(), "\\", true, fileID);
91  if (failures > 0) {
92  addErrorMessage(
93  NbBundle.getMessage(this.getClass(), "PstParser.parse.errMsg.failedToParseNMsgs", failures));
94  }
95  return ParseResult.OK;
96  } catch (PSTException | IOException ex) {
97  String msg = file.getName() + ": Failed to create internal java-libpst PST file to parse:\n" + ex.getMessage(); //NON-NLS
98  logger.log(Level.WARNING, msg);
99  return ParseResult.ERROR;
100  } catch (IllegalArgumentException ex) {
101  logger.log(Level.INFO, "Found encrypted PST file."); //NON-NLS
102  return ParseResult.ENCRYPT;
103  }
104  }
105 
111  List<EmailMessage> getResults() {
112  return results;
113  }
114 
115  String getErrors() {
116  return errors.toString();
117  }
118 
131  private long processFolder(PSTFolder folder, String path, boolean root, long fileID) {
132  String newPath = (root ? path : path + "\\" + folder.getDisplayName());
133  long failCount = 0L; // Number of emails that failed
134  if (folder.hasSubfolders()) {
135  List<PSTFolder> subFolders;
136  try {
137  subFolders = folder.getSubFolders();
138  } catch (PSTException | IOException ex) {
139  subFolders = new ArrayList<>();
140  logger.log(Level.INFO, "java-libpst exception while getting subfolders: {0}", ex.getMessage()); //NON-NLS
141  }
142 
143  for (PSTFolder f : subFolders) {
144  failCount += processFolder(f, newPath, false, fileID);
145  }
146  }
147 
148  if (folder.getContentCount() != 0) {
149  PSTMessage email;
150  // A folder's children are always emails, never other folders.
151  try {
152  while ((email = (PSTMessage) folder.getNextChild()) != null) {
153  results.add(extractEmailMessage(email, newPath, fileID));
154  }
155  } catch (PSTException | IOException ex) {
156  failCount++;
157  logger.log(Level.INFO, "java-libpst exception while getting emails from a folder: {0}", ex.getMessage()); //NON-NLS
158  }
159  }
160 
161  return failCount;
162  }
163 
172  private EmailMessage extractEmailMessage(PSTMessage msg, String localPath, long fileID) {
173  EmailMessage email = new EmailMessage();
174  email.setRecipients(msg.getDisplayTo());
175  email.setCc(msg.getDisplayCC());
176  email.setBcc(msg.getDisplayBCC());
177  email.setSender(getSender(msg.getSenderName(), msg.getSenderEmailAddress()));
178  email.setSentDate(msg.getMessageDeliveryTime());
179  email.setTextBody(msg.getBody());
180  if(false == msg.getTransportMessageHeaders().isEmpty()) {
181  email.setHeaders("\n-----HEADERS-----\n\n" + msg.getTransportMessageHeaders() + "\n\n---END HEADERS--\n\n");
182  }
183 
184  email.setHtmlBody(msg.getBodyHTML());
185  String rtf = "";
186  try {
187  rtf = msg.getRTFBody();
188  } catch (PSTException | IOException ex) {
189  logger.log(Level.INFO, "Failed to get RTF content from pst email."); //NON-NLS
190  }
191  email.setRtfBody(rtf);
192  email.setLocalPath(localPath);
193  email.setSubject(msg.getSubject());
194  email.setId(msg.getDescriptorNodeId());
195  email.setMessageID(msg.getInternetMessageId());
196 
197  String inReplyToID = msg.getInReplyToId();
198  email.setInReplyToID(inReplyToID);
199 
200  if (msg.hasAttachments()) {
201  extractAttachments(email, msg, fileID);
202  }
203 
204  List<String> references = extractReferences(msg.getTransportMessageHeaders());
205  if (inReplyToID != null && !inReplyToID.isEmpty()) {
206  if (references == null) {
207  references = new ArrayList<>();
208  references.add(inReplyToID);
209  } else if (!references.contains(inReplyToID)) {
210  references.add(inReplyToID);
211  }
212  }
213  email.setReferences(references);
214 
215  return email;
216  }
217 
224  @NbBundle.Messages({"PstParser.noOpenCase.errMsg=Exception while getting open case."})
225  private void extractAttachments(EmailMessage email, PSTMessage msg, long fileID) {
226  int numberOfAttachments = msg.getNumberOfAttachments();
227  String outputDirPath;
228  try {
229  outputDirPath = ThunderbirdMboxFileIngestModule.getModuleOutputPath() + File.separator;
230  } catch (NoCurrentCaseException ex) {
231  logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
232  return;
233  }
234  for (int x = 0; x < numberOfAttachments; x++) {
235  String filename = "";
236  try {
237  PSTAttachment attach = msg.getAttachment(x);
238  long size = attach.getAttachSize();
239  long freeSpace = services.getFreeDiskSpace();
240  if ((freeSpace != IngestMonitor.DISK_FREE_SPACE_UNKNOWN) && (size >= freeSpace)) {
241  continue;
242  }
243  // both long and short filenames can be used for attachments
244  filename = attach.getLongFilename();
245  if (filename.isEmpty()) {
246  filename = attach.getFilename();
247  }
248  String uniqueFilename = fileID + "-" + msg.getDescriptorNodeId() + "-" + attach.getContentId() + "-" + filename;
249  String outPath = outputDirPath + uniqueFilename;
250  saveAttachmentToDisk(attach, outPath);
251 
252  EmailMessage.Attachment attachment = new EmailMessage.Attachment();
253 
254  long crTime = attach.getCreationTime() != null ? attach.getCreationTime().getTime() / 1000 : 0;
255  long mTime = attach.getModificationTime() != null ? attach.getModificationTime().getTime() / 1000 : 0;
256  String relPath = getRelModuleOutputPath() + File.separator + uniqueFilename;
257  attachment.setName(filename);
258  attachment.setCrTime(crTime);
259  attachment.setmTime(mTime);
260  attachment.setLocalPath(relPath);
261  attachment.setSize(attach.getFilesize());
262  attachment.setEncodingType(TskData.EncodingType.XOR1);
263  email.addAttachment(attachment);
264  } catch (PSTException | IOException | NullPointerException ex) {
269  addErrorMessage(
270  NbBundle.getMessage(this.getClass(), "PstParser.extractAttch.errMsg.failedToExtractToDisk",
271  filename));
272  logger.log(Level.WARNING, "Failed to extract attachment from pst file.", ex); //NON-NLS
273  } catch (NoCurrentCaseException ex) {
274  addErrorMessage(Bundle.PstParser_noOpenCase_errMsg());
275  logger.log(Level.SEVERE, Bundle.PstParser_noOpenCase_errMsg(), ex); //NON-NLS
276  }
277  }
278  }
279 
291  private void saveAttachmentToDisk(PSTAttachment attach, String outPath) throws IOException, PSTException {
292  try (InputStream attachmentStream = attach.getFileInputStream();
293  EncodedFileOutputStream out = new EncodedFileOutputStream(new FileOutputStream(outPath), TskData.EncodingType.XOR1)) {
294  // 8176 is the block size used internally and should give the best performance
295  int bufferSize = 8176;
296  byte[] buffer = new byte[bufferSize];
297  int count = attachmentStream.read(buffer);
298 
299  if (count == -1) {
300  throw new IOException("attachmentStream invalid (read() fails). File " + attach.getLongFilename() + " skipped");
301  }
302 
303  while (count == bufferSize) {
304  out.write(buffer);
305  count = attachmentStream.read(buffer);
306  }
307  if (count != -1) {
308  byte[] endBuffer = new byte[count];
309  System.arraycopy(buffer, 0, endBuffer, 0, count);
310  out.write(endBuffer);
311  }
312  }
313  }
314 
323  private String getSender(String name, String addr) {
324  if (name.isEmpty() && addr.isEmpty()) {
325  return "";
326  } else if (name.isEmpty()) {
327  return addr;
328  } else if (addr.isEmpty()) {
329  return name;
330  } else {
331  return name + ": " + addr;
332  }
333  }
334 
342  public static boolean isPstFile(AbstractFile file) {
343  byte[] buffer = new byte[4];
344  try {
345  int read = file.read(buffer, 0, 4);
346  if (read != 4) {
347  return false;
348  }
349  ByteBuffer bb = ByteBuffer.wrap(buffer);
350  return bb.getInt() == PST_HEADER;
351  } catch (TskCoreException ex) {
352  logger.log(Level.WARNING, "Exception while detecting if a file is a pst file."); //NON-NLS
353  return false;
354  }
355  }
356 
357  private void addErrorMessage(String msg) {
358  errors.append("<li>").append(msg).append("</li>"); //NON-NLS
359  }
360 
368  private List<String> extractReferences(String emailHeader) {
369  Scanner scanner = new Scanner(emailHeader);
370  StringBuilder buffer = null;
371  while (scanner.hasNextLine()) {
372  String token = scanner.nextLine();
373 
374  if (token.matches("^References:.*")) {
375  buffer = new StringBuilder();
376  buffer.append((token.substring(token.indexOf(':') + 1)).trim());
377  } else if (buffer != null) {
378  if (token.matches("^\\w+:.*$")) {
379  List<String> references = new ArrayList<>();
380  for (String id : buffer.toString().split(">")) {
381  references.add(id.trim() + ">");
382  }
383  return references;
384  } else {
385  buffer.append(token.trim());
386  }
387  }
388  }
389 
390  return null;
391  }
392 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Jun 21 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.