none
Retreiving attachment from digitally signed messages RRS feed

  • Question

  • Managed API 2.0 / VS2012 / Exchange 2010SP2

    I'm working for an insurance company and we receive many signed emails. We have automatic document storage solutions that extract documents from emails and store them in a separate server. We have a problem with signed emails where the extraction cannot manage to get at the attachments in signed emails (multipart/signed mime type).

    When we traverse the attachment collections on these emails they invariably only contain one attachment, which is the smime content. When we load that content into a new instance of EmailMessage and save it, that email also only contains that very same attachment, making it all look a bit like a pandora's box.

    Following is a code snippet that I've been tinkering with to try to resolve this issue.

                var msg = new EmailMessage(service);
                // Create and load a new message with the mime content of the p7m attachment (containerAttachment)
                msg.Save(containerMessage.ParentFolderId);
                msg.Load(PropertySet);
                msg.MimeContent = new MimeContent("ascii", containerAttachment.Content);
                // Update the message to the server
                msg.Update(ConflictResolutionMode.AlwaysOverwrite);
                msg.Load(PropertySet);
                foreach (FileAttachment attachment in msg.Attachments)
                {
                    // Now lets get at the juicy attachments within
                    var m = new EmailMessage(service);
                    m.Save(containerMessage.ParentFolderId);
                    m.Load(PropertySet);
                    m.MimeContent = new MimeContent("ascii", attachment.Content);
                    m.Update(ConflictResolutionMode.AlwaysOverwrite);
                    m.Load(PropertySet);
                    // Oh, no, at this point the only attachment is identical to the original container attachment....
                    var c = msg.Attachments.Count;
                }
    

    I'd be grateful for any pointers in the right direction


    Friday, December 7, 2012 3:20 PM

All replies

  • I've just run into this same issue while testing an application I'm working on, because I sign my email messages. Would love to know if anyone has figured out how to pull individual file attachments out of the smime.p7m attachment.
    Tuesday, June 11, 2013 10:33 PM
  • Alright, I found a CodeProject project here, which has basically done the trick for me: http://www.codeproject.com/Articles/11380/A-C-Implementation-of-Mime-De-encode

    It is a "C# implementation of MIME De/encode." It is mostly-functional for the purpose of reading the content of smime.p7m on signed email messages. There are a few bugs, some of which are mentioned in the comments on the CodeProject page, but I've found one or two more in practice. Nothing deal-breaking, though. It also has a function for writing the attachments out to files, but I did not require or test this function.

    Here is the section of my code that uses the CodeProject MIME decode functions to work with smime.p7m:

    // We're finding out if this message is signed. If it is, then the first &
    // only file attachment will be smime.p7m, and it'll contain all of the
    // actual file attachments (if there are any).
    bool bMessageIsSignedOrEncrypted = false;
    if ((emailMessage.Attachments.Count == 1) &&
        (emailMessage.Attachments[0] is FileAttachment))
    {
        FileAttachment fileAttachment = emailMessage.Attachments[0] as FileAttachment;
        // If the first and only attachment is called smime.p7m, then the message
        // is signed or encrypted.
        if (string.Equals(fileAttachment.Name, "smime.p7m",
            StringComparison.OrdinalIgnoreCase))
        {
            bMessageIsSignedOrEncrypted = true;
        }
    }
    
    // The message is signed or encrypted.
    // This block uses some code from CodeProject which is marked UNSAFE because
    // it uses pointers.
    // 'Allow unsafe code' must be checked in project properties build options.
    // http://www.codeproject.com/Articles/11380/A-C-Implementation-of-Mime-De-encode?msg=1562614
    if (bMessageIsSignedOrEncrypted) // Begin UNSAFE.
    {
        _eventLogWrapper.WriteEntry(
            string.Concat("The message appears to be signed or encrypted. Trying to parse contents of 'smime.p7m'."),
            sCurrentMethodName,
            LogEntryType.Information);
    
        // Get smime.p7m. We'll extract the data from this attachment to check for
        // attachments inside of it.
        FileAttachment fileAttachment = emailMessage.Attachments[0] as FileAttachment;
    
        MemoryStream memoryStream = null;
        StreamReader streamReader = null;
        string sMimeContent = string.Empty;
        try
        {
            // Load & read 'smime.p7m' into a string.
            fileAttachment.Load();
            memoryStream = new MemoryStream(fileAttachment.Content);
            streamReader = new StreamReader(memoryStream);
            sMimeContent = streamReader.ReadToEnd();
        }
        catch (Exception ex)
        {
            _eventLogWrapper.WriteEntry(
                ex,
                string.Concat("Exception encountered reading 'smime.p7m' file attachment content."),
                sCurrentMethodName,
                LogEntryType.Error);
        }
        finally
        {
            if (memoryStream != null)
            {
                memoryStream.Close();
                memoryStream.Dispose();
            }
            if (streamReader != null)
            {
                streamReader.Close();
                streamReader.Dispose();
            }
        }
    
        // If the string data contained in the smime.p7m attachment is not empty or we did not
        // fail attempting to extract it.
        if (!string.IsNullOrWhiteSpace(sMimeContent))
        {
            // Load the content of smime.p7m into a mimemessage.
            MimeMessage mimeMessage = new MimeMessage();
            System.Collections.ArrayList bodyPartList = new System.Collections.ArrayList();
            try
            {
                mimeMessage.LoadBody(sMimeContent);
                mimeMessage.GetBodyPartList(bodyPartList);
            }
            catch (Exception ex)
            {
                _eventLogWrapper.WriteEntry(
                    ex,
                    string.Concat("Exception encountered loading MIME content into a MimeMessage."),
                    sCurrentMethodName,
                    LogEntryType.Error);
            }
    
            // We'll increment this number as we find attached mail messages.
            // This is to provide a simple means of naming them, since the name is
            // not being returned by bodyPart.GetName(), and I don't really care to figure
            // out why/how to make it return the email subject (or to parse the
            // message itself for the subject).
            int iAttachedEmailMessageCount = 0;
            int iBodyPartListCount = bodyPartList.Count;
            for (int i = 0; i < iBodyPartListCount; i++)
            {
                MimeBody bodyPart = null;
                try
                {
                    bodyPart = bodyPartList[i] as MimeBody;
                }
                catch (Exception ex)
                {
                    _eventLogWrapper.WriteEntry(
                        ex,
                        string.Concat("Exception encountered casting bodyPartList[", i, "] as MimeBody"),
                        sCurrentMethodName,
                        LogEntryType.Error);
    
                    // We failed, go to next bodypart.
                    continue;
                }
    
                // DEBUG. This needs cleaned up. We probably only need the 'attachment' and 'message' cases.
                if (bodyPart.IsText())
                {
                    _eventLogWrapper.WriteEntry(
                        string.Concat("BodyPart ", i + 1, " of ", iBodyPartListCount, " for email message: ", sEmailSubject,
                            Environment.NewLine, "Type: Text",
                            Environment.NewLine, "Text: ", bodyPart.GetText()),
                        sCurrentMethodName,
                        LogEntryType.Information);
                }
                else if (bodyPart.IsAttachment())
                {
                    string sAttachmentName = string.Empty;
                    string sAttachmentFilename = string.Empty;
                    string sAttachmentMimeType = string.Empty;
    
                    // DEBUG: We don't need to be loading all of these properties here;
                    // we can load them once we're sure we need them (file is not p7s).
                    try
                    {
                        sAttachmentName = bodyPart.GetName();
                        sAttachmentFilename = bodyPart.GetFilename();
    
                        // sAttachmentMimeType will also contain the name of the file, e.g.:
                        // application/msword;
                        //	name="Travel Laptop Tags.doc"
                        // We're going to want to keep only the bit up to the semicolon.
                        sAttachmentMimeType = bodyPart.GetContentType();
                    }
                    catch (Exception ex)
                    {
                        _eventLogWrapper.WriteEntry(
                            ex,
                            string.Concat("Exception encountered getting properties for attachment:",
                                          Environment.NewLine, "Email message: ", sEmailSubject),
                            sCurrentMethodName,
                            LogEntryType.Error);
    
                        // Failed, stop processing this attachment & go to next.
                        continue;
                    }                                        
                    // Removing 'name' from mimetype - just keep the mimetype. This is an issue
                    // w/ the value being returned from the third-party mime code that I couldn't
                    // figure out how to resolve, so I'll just do it the quick & dirty way here.
                    sAttachmentMimeType = fixMimeType(sAttachmentMimeType);
    
                    _eventLogWrapper.WriteEntry(
                        string.Concat("BodyPart ", i + 1, " of ", iBodyPartListCount, " for email message: ", sEmailSubject,
                                      Environment.NewLine, "Type: Attachment",
                                      Environment.NewLine, "Name: ", sAttachmentName,
                                      Environment.NewLine, "Filename: ", sAttachmentFilename,
                                      Environment.NewLine, "MimeType: ", sAttachmentMimeType),
                        sCurrentMethodName,
                        LogEntryType.Information);
    
                    // If this is not a 'smime.p7s' file attachment, but is in fact an
                    // attachment that we care about (in theory)...
                    if (!string.Equals(sAttachmentFilename, "smime.p7s",
                        StringComparison.OrdinalIgnoreCase))
                    {
                        try
                        {
                            // Add attachment to list.
                            HelpdeskAttachment helpdeskAttachment = new HelpdeskAttachment(
                                bodyPart.GetByteArray(),
                                sAttachmentFilename,
                                sAttachmentMimeType);
    
                            listHelpdeskAttachments.Add(helpdeskAttachment);
                        }
                        catch (Exception ex)
                        {
                            _eventLogWrapper.WriteEntry(
                                ex,
                                string.Concat("Exception encountered loading byte array for attachment:",
                                              Environment.NewLine, "Email message: ", sEmailSubject,
                                              Environment.NewLine, "Name: ", sAttachmentName,
                                              Environment.NewLine, "Filename: ", sAttachmentFilename,
                                              Environment.NewLine, "MimeType: ", sAttachmentMimeType),
                                sCurrentMethodName,
                                LogEntryType.Error);
                        }
                    }
                }
                else if (bodyPart.IsMessage())
                {
                    string sAttachmentMimeType = string.Empty;
               
                    try
                    {
                        sAttachmentMimeType = bodyPart.GetContentType();
                    }
                    catch (Exception ex)
                    {
                        _eventLogWrapper.WriteEntry(
                            ex,
                            string.Concat("Exception encountered getting ContentType for message attachment:",
                                          Environment.NewLine, "Email message: ", sEmailSubject),
                            sCurrentMethodName,
                            LogEntryType.Error);
    
                        continue;
                    }                                        
                    // Removing 'name' from mimetype (if present) - just keep the mimetype. This is an issue
                    // w/ the value being returned from the third-party mime code that I couldn't
                    // figure out how to resolve, so I'll just do it the quick & dirty way here.
                    // Name & FileName don't actually seem to be present for message attachments, so we likely
                    // don't need to 'fix' the mimetype, but we'll just pass it to the function anyway as
                    // a precaution.
                    sAttachmentMimeType = fixMimeType(sAttachmentMimeType);
    
                    _eventLogWrapper.WriteEntry(
                        string.Concat("BodyPart ", i + 1, " of ", iBodyPartListCount, " for email message: ", sEmailSubject,
                                      Environment.NewLine, "Type: Message",
                                      Environment.NewLine, "MimeType: ", sAttachmentMimeType),
                        sCurrentMethodName,
                        LogEntryType.Information);
    
                    // Name / FileName are never populated for 'Messages', as returned by this third-party
                    // MIME code. Just don't bother trying to get them. We'll name the email messages 
                    // we find attached something generic.
                    string sAttachedEmailMessageCount = string.Empty;
                    if (++iAttachedEmailMessageCount < 10)
                    {
                        // Add leading zero for < 10.
                        sAttachedEmailMessageCount = string.Concat("0", iAttachedEmailMessageCount.ToString());
                    }
                    else
                    {
                        sAttachedEmailMessageCount = iAttachedEmailMessageCount.ToString();
                    }
    
                    // The message returned by GetByteArray() is functional, but not quite as complete as one
                    // that we'd find on an unsigned message in ItemAttachment format. It is plaintext, and 
                    // is missing header & style information. It's perhaps possible to locate all of that info
                    // somewhere inside p7m, but i don't really care about it.
                    HelpdeskAttachment helpdeskAttachment = new HelpdeskAttachment(
                        bodyPart.GetByteArray(),
                        string.Concat("Attached_email_message_", sAttachedEmailMessageCount, ".eml"), // This is our made-up filename.
                        sAttachmentMimeType);
    
                    listHelpdeskAttachments.Add(helpdeskAttachment);
                }
                else if (bodyPart.IsMultiPart())
                {
                    _eventLogWrapper.WriteEntry(
                        string.Concat("BodyPart ", i + 1, " of ", iBodyPartListCount, " for email message: ", sEmailSubject,
                                      Environment.NewLine, "Type: MultiPart",
                                      Environment.NewLine, "Name: ", bodyPart.GetName(),
                                      Environment.NewLine, "Filename: ", bodyPart.GetFilename(),
                                      Environment.NewLine, "MimeType: ", bodyPart.GetContentType()),
                        sCurrentMethodName,
                        LogEntryType.Information);
                }
            }
        }
    } // End UNSAFE. 

    Friday, June 21, 2013 9:48 PM
  • I should add that the bodyPart.GetByteArray() function used in the above code was added by me as a slightly-modified version of the built-in function that writes out to a file. I wanted the byte[] info. So, since that is not included in the CodeProject code itself, I'll just paste that here, too:

    // Added by LEM to get a byte array (based on writetofile function, just
    // gets byte array for passing to helpdesk attachment service).
    public byte[] GetByteArray()
    {
        string encoding = GetTransferEncoding();
        if (encoding == null)
        {
            encoding = MimeConst.Encoding7Bit;
        }
    
        MimeCode aCode = MimeCodeManager.Instance.GetCode(encoding);
        aCode.Charset = GetCharset();
        byte[] byteContent = aCode.DecodeToBytes(mContent);
    
        return byteContent;
    }

    Friday, June 21, 2013 9:53 PM