diff --git a/src/mail.cpp b/src/mail.cpp index 45e252e..db137b1 100644 --- a/src/mail.cpp +++ b/src/mail.cpp @@ -1,3921 +1,3927 @@ /* @file mail.h * @brief High level class to work with Outlook Mailitems. * * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "categorymanager.h" #include "dialogs.h" #include "common.h" #include "mail.h" #include "eventsinks.h" #include "attachment.h" #include "mapihelp.h" #include "mimemaker.h" #include "revert.h" #include "gpgoladdin.h" #include "mymapitags.h" #include "parsecontroller.h" #include "cryptcontroller.h" #include "windowmessages.h" #include "mlang-charset.h" #include "wks-helper.h" #include "keycache.h" #include "cpphelp.h" #include "addressbook.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #undef _ #define _(a) utf8_gettext (a) using namespace GpgME; static std::map s_mail_map; static std::map s_uid_map; static std::map s_folder_events_map; static std::set uids_searched; GPGRT_LOCK_DEFINE (mail_map_lock); GPGRT_LOCK_DEFINE (uid_map_lock); static Mail *s_last_mail; #define COPYBUFSIZE (8 * 1024) Mail::Mail (LPDISPATCH mailitem) : m_mailitem(mailitem), m_currentItemRef(nullptr), m_processed(false), m_needs_wipe(false), m_needs_save(false), m_crypt_successful(false), m_is_smime(false), m_is_smime_checked(false), m_is_signed(false), m_is_valid(false), m_close_triggered(false), m_is_html_alternative(false), m_needs_encrypt(false), m_moss_position(0), m_crypto_flags(0), m_cached_html_body(nullptr), m_cached_plain_body(nullptr), m_type(MSGTYPE_UNKNOWN), m_do_inline(false), m_is_gsuite(false), m_crypt_state(NoCryptMail), m_window(nullptr), m_async_crypt_disabled(false), m_is_forwarded_crypto_mail(false), m_is_reply_crypto_mail(false), m_is_send_again(false), m_disable_att_remove_warning(false), m_manual_crypto_opts(false), m_first_autosecure_check(true), m_locate_count(0), m_pass_write(false), m_locate_in_progress(false), m_is_junk(false) { TSTART; if (getMailForItem (mailitem)) { log_error ("Mail object for item: %p already exists. Bug.", mailitem); TRETURN; } m_event_sink = install_MailItemEvents_sink (mailitem); if (!m_event_sink) { /* Should not happen but in that case we don't add us to the list and just release the Mail item. */ log_error ("%s:%s: Failed to install MailItemEvents sink.", SRCNAME, __func__); gpgol_release(mailitem); TRETURN; } gpgol_lock (&mail_map_lock); s_mail_map.insert (std::pair (mailitem, this)); gpgol_unlock (&mail_map_lock); s_last_mail = this; memdbg_ctor ("Mail"); TRETURN; } GPGRT_LOCK_DEFINE(dtor_lock); // static void Mail::lockDelete () { TSTART; gpgol_lock (&dtor_lock); TRETURN; } // static void Mail::unlockDelete () { TSTART; gpgol_unlock (&dtor_lock); TRETURN; } Mail::~Mail() { TSTART; /* This should fix a race condition where the mail is deleted before the parser is accessed in the decrypt thread. The shared_ptr of the parser then ensures that the parser is alive even if the mail is deleted while parsing. */ gpgol_lock (&dtor_lock); memdbg_dtor ("Mail"); log_oom ("%s:%s: dtor: Mail: %p item: %p", SRCNAME, __func__, this, m_mailitem); std::map::iterator it; log_oom ("%s:%s: Detaching event sink", SRCNAME, __func__); detach_MailItemEvents_sink (m_event_sink); gpgol_release(m_event_sink); log_oom ("%s:%s: Erasing mail", SRCNAME, __func__); gpgol_lock (&mail_map_lock); it = s_mail_map.find(m_mailitem); if (it != s_mail_map.end()) { s_mail_map.erase (it); } gpgol_unlock (&mail_map_lock); if (!m_uuid.empty()) { gpgol_lock (&uid_map_lock); auto it2 = s_uid_map.find(m_uuid); if (it2 != s_uid_map.end()) { s_uid_map.erase (it2); } gpgol_unlock (&uid_map_lock); } log_oom ("%s:%s: removing categories", SRCNAME, __func__); removeCategories_o (); log_oom ("%s:%s: releasing mailitem", SRCNAME, __func__); gpgol_release(m_mailitem); xfree (m_cached_html_body); xfree (m_cached_plain_body); if (!m_uuid.empty()) { log_oom ("%s:%s: destroyed: %p uuid: %s", SRCNAME, __func__, this, m_uuid.c_str()); } else { log_oom ("%s:%s: non crypto (or sent) mail: %p destroyed", SRCNAME, __func__, this); } log_oom ("%s:%s: nulling shared pointer", SRCNAME, __func__); m_parser = nullptr; m_crypter = nullptr; releaseCurrentItem(); gpgol_unlock (&dtor_lock); log_oom ("%s:%s: returning", SRCNAME, __func__); TRETURN; } //static Mail * Mail::getMailForItem (LPDISPATCH mailitem) { TSTART; if (!mailitem) { TRETURN NULL; } std::map::iterator it; gpgol_lock (&mail_map_lock); it = s_mail_map.find(mailitem); gpgol_unlock (&mail_map_lock); if (it == s_mail_map.end()) { TRETURN NULL; } TRETURN it->second; } //static Mail * Mail::getMailForUUID (const char *uuid) { TSTART; if (!uuid) { TRETURN NULL; } gpgol_lock (&uid_map_lock); auto it = s_uid_map.find(std::string(uuid)); gpgol_unlock (&uid_map_lock); if (it == s_uid_map.end()) { TRETURN NULL; } TRETURN it->second; } //static bool Mail::isValidPtr (const Mail *mail) { TSTART; gpgol_lock (&mail_map_lock); auto it = s_mail_map.begin(); while (it != s_mail_map.end()) { if (it->second == mail) { gpgol_unlock (&mail_map_lock); TRETURN true; } ++it; } gpgol_unlock (&mail_map_lock); TRETURN false; } int Mail::preProcessMessage_m () { TSTART; LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); TRETURN 0; } log_oom ("%s:%s: GetBaseMessage OK for %p.", SRCNAME, __func__, m_mailitem); /* Change the message class here. It is important that we change the message class in the before read event regardless if it is already set to one of GpgOL's message classes. Changing the message class (even if we set it to the same value again that it already has) causes Outlook to reconsider what it "knows" about a message and reread data from the underlying base message. */ mapi_change_message_class (message, 1, &m_type); if (m_type == MSGTYPE_UNKNOWN) { /* For unknown messages we still need to check for autocrypt headers. If the mails are crypto messages the autocrypt stuff is handled in the parsecontroller. */ autocrypt_s ac; if (mapi_get_header_info (message, ac)) { if (ac.exists) { log_debug ("%s:%s: Importing autocrypt header from unencrypted " "mail.", SRCNAME, __func__); KeyCache::import_pgp_key_data (ac.data); } } gpgol_release (message); TRETURN 0; } /* Create moss attachments here so that they are properly hidden when the item is read into the model. */ m_moss_position = mapi_mark_or_create_moss_attach (message, m_type); if (!m_moss_position) { log_error ("%s:%s: Failed to find moss attachment.", SRCNAME, __func__); m_type = MSGTYPE_UNKNOWN; } gpgol_release (message); TRETURN 0; } static LPDISPATCH get_attachment_o (LPDISPATCH mailitem, int pos) { TSTART; LPDISPATCH attachment; LPDISPATCH attachments = get_oom_object (mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); TRETURN NULL; } std::string item_str; int count = get_oom_int (attachments, "Count"); if (count < 1) { log_debug ("%s:%s: Invalid attachment count: %i.", SRCNAME, __func__, count); gpgol_release (attachments); TRETURN NULL; } if (pos > 0) { item_str = std::string("Item(") + std::to_string(pos) + ")"; } else { item_str = std::string("Item(") + std::to_string(count) + ")"; } attachment = get_oom_object (attachments, item_str.c_str()); gpgol_release (attachments); TRETURN attachment; } /** Helper to check that all attachments are hidden, to be called before crypto. */ int Mail::checkAttachments_o () const { TSTART; LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { log_debug ("%s:%s: Failed to get attachments.", SRCNAME, __func__); TRETURN 1; } int count = get_oom_int (attachments, "Count"); if (!count) { gpgol_release (attachments); TRETURN 0; } std::string message; if (isEncrypted () && isSigned ()) { message += _("Not all attachments were encrypted or signed.\n" "The unsigned / unencrypted attachments are:\n\n"); } else if (isSigned ()) { message += _("Not all attachments were signed.\n" "The unsigned attachments are:\n\n"); } else if (isEncrypted ()) { message += _("Not all attachments were encrypted.\n" "The unencrypted attachments are:\n\n"); } else { gpgol_release (attachments); TRETURN 0; } bool foundOne = false; for (int i = 1; i <= count; i++) { std::string item_str; item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH oom_attach = get_oom_object (attachments, item_str.c_str ()); if (!oom_attach) { log_error ("%s:%s: Failed to get attachment.", SRCNAME, __func__); continue; } VARIANT var; VariantInit (&var); if (get_pa_variant (oom_attach, PR_ATTACHMENT_HIDDEN_DASL, &var) || (var.vt == VT_BOOL && var.boolVal == VARIANT_FALSE)) { foundOne = true; char *dispName = get_oom_string (oom_attach, "DisplayName"); message += dispName ? dispName : "Unknown"; xfree (dispName); message += "\n"; } VariantClear (&var); gpgol_release (oom_attach); } gpgol_release (attachments); if (foundOne) { message += "\n"; message += _("Note: The attachments may be encrypted or signed " "on a file level but the GpgOL status does not apply to them."); wchar_t *wmsg = utf8_to_wchar (message.c_str ()); wchar_t *wtitle = utf8_to_wchar (_("GpgOL Warning")); MessageBoxW (get_active_hwnd (), wmsg, wtitle, MB_ICONWARNING|MB_OK); xfree (wmsg); xfree (wtitle); } TRETURN 0; } /** Get the cipherstream of the mailitem. */ static LPSTREAM get_attachment_stream_o (LPDISPATCH mailitem, int pos) { TSTART; if (!pos) { log_debug ("%s:%s: Called with zero pos.", SRCNAME, __func__); TRETURN NULL; } LPDISPATCH attachment = get_attachment_o (mailitem, pos); LPSTREAM stream = NULL; if (!attachment) { // For opened messages that have ms-tnef type we // create the moss attachment but don't find it // in the OOM. Try to find it through MAPI. HRESULT hr; log_debug ("%s:%s: Failed to find MOSS Attachment. " "Fallback to MAPI.", SRCNAME, __func__); LPMESSAGE message = get_oom_message (mailitem); if (!message) { log_debug ("%s:%s: Failed to get MAPI Interface.", SRCNAME, __func__); TRETURN NULL; } hr = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0, (LPUNKNOWN*)&stream); gpgol_release (message); if (hr) { log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr); TRETURN NULL; } TRETURN stream; } LPATTACH mapi_attachment = NULL; mapi_attachment = (LPATTACH) get_oom_iunknown (attachment, "MapiObject"); gpgol_release (attachment); if (!mapi_attachment) { log_debug ("%s:%s: Failed to get MapiObject of attachment: %p", SRCNAME, __func__, attachment); TRETURN NULL; } if (FAILED (gpgol_openProperty (mapi_attachment, PR_ATTACH_DATA_BIN, &IID_IStream, 0, MAPI_MODIFY, (LPUNKNOWN*) &stream))) { log_debug ("%s:%s: Failed to open stream for mapi_attachment: %p", SRCNAME, __func__, mapi_attachment); gpgol_release (mapi_attachment); } gpgol_release (mapi_attachment); TRETURN stream; } #if 0 This should work. But Outlook says no. See the comment in set_pa_variant about this. I left the code here as an example how to work with safearrays and how this probably should work. static int copy_data_property(LPDISPATCH target, std::shared_ptr attach) { TSTART; VARIANT var; VariantInit (&var); /* Get the size */ off_t size = attach->get_data ().seek (0, SEEK_END); attach->get_data ().seek (0, SEEK_SET); if (!size) { TRACEPOINT; TRETURN 1; } if (!get_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { log_debug("Have variant. type: %x", var.vt); } else { log_debug("failed to get variant."); } /* Set the type to an array of unsigned chars (OLE SAFEARRAY) */ var.vt = VT_ARRAY | VT_UI1; /* Set up the bounds structure */ SAFEARRAYBOUND rgsabound[1]; rgsabound[0].cElements = static_cast (size); rgsabound[0].lLbound = 0; /* Create an OLE SAFEARRAY */ var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound); if (var.parray == NULL) { TRACEPOINT; VariantClear(&var); TRETURN 1; } void *buffer = NULL; /* Get a safe pointer to the array */ if (SafeArrayAccessData(var.parray, &buffer) != S_OK) { TRACEPOINT; VariantClear(&var); TRETURN 1; } /* Copy data to it */ size_t nread = attach->get_data ().read (buffer, static_cast (size)); if (nread != static_cast (size)) { TRACEPOINT; VariantClear(&var); TRETURN 1; } /*/ Unlock the variant data */ if (SafeArrayUnaccessData(var.parray) != S_OK) { TRACEPOINT; VariantClear(&var); TRETURN 1; } if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var)) { TRACEPOINT; VariantClear(&var); TRETURN 1; } VariantClear(&var); TRETURN 0; } #endif static int copy_attachment_to_file (std::shared_ptr att, HANDLE hFile) { TSTART; char copybuf[COPYBUFSIZE]; size_t nread; /* Security considerations: Writing the data to a temporary file is necessary as neither MAPI manipulation works in the read event to transmit the data nor Property Accessor works (see above). From a security standpoint there is a short time where the temporary files are on disk. Tempdir should be protected so that only the user can read it. Thus we have a local attack that could also take the data out of Outlook. FILE_SHARE_READ is necessary so that outlook can read the file. A bigger concern is that the file is manipulated by another software to fake the signature state. So we keep the write exlusive to us. We delete the file before closing the write file handle. */ /* Make sure we start at the beginning */ att->get_data ().seek (0, SEEK_SET); while ((nread = att->get_data ().read (copybuf, COPYBUFSIZE))) { DWORD nwritten; if (!WriteFile (hFile, copybuf, nread, &nwritten, NULL)) { log_error ("%s:%s: Failed to write in tmp attachment.", SRCNAME, __func__); TRETURN 1; } if (nread != nwritten) { log_error ("%s:%s: Write truncated.", SRCNAME, __func__); TRETURN 1; } } TRETURN 0; } /** Sets some meta data on the last attachment added. The meta data is taken from the attachment object. */ static int fixup_last_attachment_o (LPDISPATCH mail, std::shared_ptr attachment) { TSTART; /* Currently we only set content id */ if (attachment->get_content_id ().empty()) { log_debug ("%s:%s: Content id not found.", SRCNAME, __func__); TRETURN 0; } LPDISPATCH attach = get_attachment_o (mail, -1); if (!attach) { log_error ("%s:%s: No attachment.", SRCNAME, __func__); TRETURN 1; } const std::string cid = attachment->get_content_id (); int ret = put_pa_string (attach, PR_ATTACH_CONTENT_ID_DASL, cid.c_str()); log_debug ("%s:%s: Set attachment content id to: '%s'", SRCNAME, __func__, anonstr (cid.c_str())); if (ret) { log_error ("%s:%s: Failed.", SRCNAME, __func__); gpgol_release (attach); } #if 0 The following was an experiement to delete the ATTACH_FLAGS values so that we are not hiding attachments. LPATTACH mapi_attach = (LPATTACH) get_oom_iunknown (attach, "MAPIOBJECT"); if (mapi_attach) { SPropTagArray proparray; HRESULT hr; proparray.cValues = 1; proparray.aulPropTag[0] = 0x37140003; hr = mapi_attach->DeleteProps (&proparray, NULL); if (hr) { log_error ("%s:%s: can't delete property attach flags: hr=%#lx\n", SRCNAME, __func__, hr); ret = -1; } gpgol_release (mapi_attach); } else { log_error ("%s:%s: Failed to get mapi attachment.", SRCNAME, __func__); } #endif gpgol_release (attach); TRETURN ret; } /** Helper to update the attachments of a mail object in oom. does not modify the underlying mapi structure. */ static int add_attachments_o(LPDISPATCH mail, std::vector > attachments) { TSTART; bool anyError = false; for (auto att: attachments) { int err = 0; const auto dispName = att->get_display_name (); if (dispName.empty()) { log_error ("%s:%s: Ignoring attachment without display name.", SRCNAME, __func__); continue; } wchar_t* wchar_name = utf8_to_wchar (dispName.c_str()); if (!wchar_name) { log_error ("%s:%s: Failed to convert '%s' to wchar.", SRCNAME, __func__, anonstr (dispName.c_str())); continue; } HANDLE hFile; wchar_t* wchar_file = get_tmp_outfile (wchar_name, &hFile); if (!wchar_file) { log_error ("%s:%s: Failed to obtain a tmp filename for: %s", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } if (!err && copy_attachment_to_file (att, hFile)) { log_error ("%s:%s: Failed to copy attachment %s to temp file", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } if (!err && add_oom_attachment (mail, wchar_file, wchar_name)) { log_error ("%s:%s: Failed to add attachment: %s", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } if (hFile && hFile != INVALID_HANDLE_VALUE) { CloseHandle (hFile); } if (wchar_file && !DeleteFileW (wchar_file)) { log_error ("%s:%s: Failed to delete tmp attachment for: %s", SRCNAME, __func__, anonstr (dispName.c_str())); err = 1; } xfree (wchar_file); xfree (wchar_name); if (!err) { log_debug ("%s:%s: Added attachment '%s'", SRCNAME, __func__, anonstr (dispName.c_str())); err = fixup_last_attachment_o (mail, att); } if (err) { anyError = true; } } TRETURN anyError; } GPGRT_LOCK_DEFINE(parser_lock); static DWORD WINAPI do_parsing (LPVOID arg) { TSTART; gpgol_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: canceling parsing for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); TRETURN 0; } blockInv (); /* This takes a shared ptr of parser. So the parser is still valid when the mail is deleted. */ auto parser = mail->parser (); gpgol_unlock (&dtor_lock); gpgol_lock (&parser_lock); /* We lock the parser here to avoid too many decryption attempts if there are multiple mailobjects which might have already been deleted (e.g. by quick switches of the mailview.) Let's rather be a bit slower. */ log_debug ("%s:%s: preparing the parser for: %p", SRCNAME, __func__, arg); if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: cancel for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); unblockInv(); TRETURN 0; } if (!parser) { log_error ("%s:%s: no parser found for mail: %p", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); unblockInv(); TRETURN -1; } parser->parse(); if (!opt.sync_dec) { do_in_ui_thread (PARSING_DONE, arg); } gpgol_unlock (&parser_lock); unblockInv(); TRETURN 0; } /* How encryption is done: There are two modes of encryption. Synchronous and Async. If async is used depends on the value of mail->async_crypt_disabled. Synchronous crypto: > Send Event < | State NoCryptMail Needs Crypto ? (get_gpgol_draft_info_flags != 0) -> No: Pass send -> unencrypted mail. -> Yes: mail->update_oom_data State = Mail::NeedsFirstAfterWrite check_inline_response invoke_oom_method (m_object, "Save", NULL); > Write Event < Pass because is_crypto_mail is false (not a decrypted mail) > AfterWrite Event < | State NeedsFirstAfterWrite State = NeedsActualCrypo encrypt_sign_start collect_input_data -> Check if Inline PGP should be used do_crypt -> Resolve keys / do crypto State = NeedsUpdateInMAPI update_crypt_mapi crypter->update_mail_mapi if (inline) (Meaning PGP/Inline) <-- do nothing. else build MSOXSMIME attachment and clear body / attachments. State = NeedsUpdateInOOM <- Back to Send Event update_crypt_oom -> Cleans body or sets PGP/Inline body. (inline_body_to_body) State = WantsSendMIME or WantsSendInline -> Saftey check "has_crypted_or_empty_body" -> If MIME Mail do the T3656 check. Send. State order for "inline_response" (sync) Mails. NoCryptMail NeedsFirstAfterWrite NeedsActualCrypto NeedsUpdateInMAPI NeedsUpdateInOOM WantsSendMIME (or inline for PGP Inline) -> Send. State order for async Mails NoCryptMail NeedsFirstAfterWrite NeedsActualCrypto -> Cancel Send. Windowmessages -> Crypto Done NeedsUpdateInOOM NeedsSecondAfterWrite trigger Save. NeedsUpdateInMAPI WantsSendMIME trigger Send. */ static DWORD WINAPI do_crypt (LPVOID arg) { TSTART; gpgol_lock (&dtor_lock); /* We lock with mail dtors so we can be sure the mail->parser call is valid. */ Mail *mail = (Mail *)arg; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: canceling crypt for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); TRETURN 0; } if (mail->cryptState () != Mail::NeedsActualCrypt) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, mail->cryptState ()); mail->setWindowEnabled_o (true); gpgol_unlock (&dtor_lock); TRETURN -1; } /* This takes a shared ptr of crypter. So the crypter is still valid when the mail is deleted. */ auto crypter = mail->cryper (); gpgol_unlock (&dtor_lock); if (!crypter) { log_error ("%s:%s: no crypter found for mail: %p", SRCNAME, __func__, arg); gpgol_unlock (&parser_lock); mail->setWindowEnabled_o (true); TRETURN -1; } GpgME::Error err; int rc = crypter->do_crypto(err); gpgol_lock (&dtor_lock); if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: aborting crypt for: %p already deleted", SRCNAME, __func__, arg); gpgol_unlock (&dtor_lock); TRETURN 0; } mail->setWindowEnabled_o (true); if (rc == -1 || err) { mail->resetCrypter (); crypter = nullptr; if (err) { char *buf = nullptr; gpgrt_asprintf (&buf, _("Crypto operation failed:\n%s"), err.asString()); memdbg_alloc (buf); gpgol_message_box (mail->getWindow (), buf, _("GpgOL"), MB_OK); xfree (buf); } else { gpgol_bug (mail->getWindow (), ERR_CRYPT_RESOLVER_FAILED); } } if (rc || err.isCanceled()) { log_debug ("%s:%s: crypto failed for: %p with: %i err: %i", SRCNAME, __func__, arg, rc, err.code()); mail->setCryptState (Mail::NoCryptMail); mail->resetCrypter (); crypter = nullptr; gpgol_unlock (&dtor_lock); TRETURN rc; } if (!mail->isAsyncCryptDisabled ()) { mail->setCryptState (Mail::NeedsUpdateInOOM); gpgol_unlock (&dtor_lock); // This deletes the Mail in Outlook 2010 do_in_ui_thread (CRYPTO_DONE, arg); log_debug ("%s:%s: UI thread finished for %p", SRCNAME, __func__, arg); } else { mail->setCryptState (Mail::NeedsUpdateInMAPI); mail->updateCryptMAPI_m (); if (mail->cryptState () == Mail::WantsSendMIME) { // For sync crypto we need to switch this. mail->setCryptState (Mail::NeedsUpdateInOOM); } else { // A bug! log_debug ("%s:%s: Resetting crypter because of state mismatch. %p", SRCNAME, __func__, arg); crypter = nullptr; mail->resetCrypter (); } gpgol_unlock (&dtor_lock); } /* This works around a bug in pinentry that it might bring the wrong window to front. So after encryption / signing we bring outlook back to front. See GnuPG-Bug-Id: T3732 */ do_in_ui_thread_async (BRING_TO_FRONT, nullptr, 250); log_debug ("%s:%s: crypto thread for %p finished", SRCNAME, __func__, arg); TRETURN 0; } bool Mail::isCryptoMail () const { TSTART; if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL || m_type == MSGTYPE_SMIME) { /* Not a message for us. */ TRETURN false; } TRETURN true; } int Mail::decryptVerify_o () { TSTART; if (!isCryptoMail ()) { log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.", SRCNAME, __func__, m_mailitem); TRETURN 0; } auto cipherstream = get_attachment_stream_o (m_mailitem, m_moss_position); if (!cipherstream) { m_is_junk = is_junk_mail (m_mailitem); if (m_is_junk) { log_debug ("%s:%s: Detected: %p as junk", SRCNAME, __func__, m_mailitem); auto mngr = CategoryManager::instance (); m_store_id = mngr->addCategoryToMail (this, CategoryManager::getJunkMailCategory (), 3 /* peach */); installFolderEventHandler_o (); TRETURN 0; } log_debug ("%s:%s: Failed to get cipherstream. Aborting handling.", SRCNAME, __func__); m_type = MSGTYPE_UNKNOWN; TRETURN 1; } setUUID_o (); m_processed = true; m_pass_write = false; /* Insert placeholder */ char *placeholder_buf = nullptr; if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, "OpenPGP", _("Pubkey directory confirmation"), _("This is a confirmation request to publish your Pubkey in the " "directory for your domain.\n\n" "

If you did not request to publish your Pubkey in your providers " "directory, simply ignore this message.

\n")); } else if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html : decrypt_template, isSMIME_m () ? "S/MIME" : "OpenPGP", _("message"), _("Please wait while the message is being decrypted / verified...")) == -1) { log_error ("%s:%s: Failed to format placeholder.", SRCNAME, __func__); TRETURN 1; } if (opt.prefer_html) { char *tmp = get_oom_string (m_mailitem, "HTMLBody"); if (!tmp) { TRACEPOINT; TRETURN 1; } m_orig_body = tmp; xfree (tmp); if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf)) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } else { char *tmp = get_oom_string (m_mailitem, "Body"); if (!tmp) { TRACEPOINT; TRETURN 1; } m_orig_body = tmp; xfree (tmp); if (put_oom_string (m_mailitem, "Body", placeholder_buf)) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } } memdbg_alloc (placeholder_buf); xfree (placeholder_buf); if (m_type == MSGTYPE_GPGOL_WKS_CONFIRMATION) { WKSHelper::instance ()->handle_confirmation_read (this, cipherstream); TRETURN 0; } m_parser = std::shared_ptr (new ParseController (cipherstream, m_type)); m_parser->setSender(GpgME::UserID::addrSpecFromString(getSender_o ().c_str())); if (opt.autoimport) { /* Handle autocrypt header. As we want to have the import of the header in the same thread as the parser we leave it to the parser. */ auto message = MAKE_SHARED (get_oom_message (m_mailitem)); if (!message) { /* Hmmm */ STRANGEPOINT; } else { autocrypt_s ainfo; if (!mapi_get_header_info ((LPMESSAGE)message.get(), ainfo)) { STRANGEPOINT; } else if (ainfo.exists) { m_parser->setAutocryptInfo (ainfo); } } } log_data ("%s:%s: Parser for \"%s\" is %p", SRCNAME, __func__, getSubject_o ().c_str(), m_parser.get()); gpgol_release (cipherstream); if (!opt.sync_dec) { HANDLE parser_thread = CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0, NULL); if (!parser_thread) { log_error ("%s:%s: Failed to create decrypt / verify thread.", SRCNAME, __func__); } CloseHandle (parser_thread); TRETURN 0; } else { /* Parse synchronously */ do_parsing ((LPVOID) this); parsing_done (); TRETURN 0; } } void find_and_replace(std::string& source, const std::string &find, const std::string &replace) { TSTART; for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;) { source.replace(i, find.length(), replace); i += replace.length(); } TRETURN; } void Mail::updateBody_o () { TSTART; if (!m_parser) { TRACEPOINT; TRETURN; } const auto error = m_parser->get_formatted_error (); if (!error.empty()) { if (opt.prefer_html) { if (put_oom_string (m_mailitem, "HTMLBody", error.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } else { log_debug ("%s:%s: Set error html to: '%s'", SRCNAME, __func__, error.c_str ()); } } else { if (put_oom_string (m_mailitem, "Body", error.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } else { log_debug ("%s:%s: Set error plain to: '%s'", SRCNAME, __func__, error.c_str ()); } } TRETURN; } if (m_verify_result.error()) { log_error ("%s:%s: Verification failed. Restoring Body.", SRCNAME, __func__); if (opt.prefer_html) { if (put_oom_string (m_mailitem, "HTMLBody", m_orig_body.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } else { if (put_oom_string (m_mailitem, "Body", m_orig_body.c_str ())) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } } TRETURN; } // No need to carry body anymore m_orig_body = std::string(); auto html = m_parser->get_html_body (); auto body = m_parser->get_body (); /** Outlook does not show newlines if \r\r\n is a newline. We replace these as apparently some other buggy MUA sends this. */ find_and_replace (html, "\r\r\n", "\r\n"); if (opt.prefer_html && !html.empty()) { if (!m_block_html) { const auto charset = m_parser->get_html_charset(); int codepage = 0; if (charset.empty()) { codepage = get_oom_int (m_mailitem, "InternetCodepage"); log_debug ("%s:%s: Did not find html charset." " Using internet Codepage %i.", SRCNAME, __func__, codepage); } char *converted = ansi_charset_to_utf8 (charset.c_str(), html.c_str(), html.size(), codepage); TRACEPOINT; int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); TRACEPOINT; xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); } TRETURN; } else if (!body.empty()) { /* We had a multipart/alternative mail but html should be blocked. So we prefer the text/plain part and warn once about this so that we hopefully don't get too many bugreports about this. */ if (!opt.smime_html_warn_shown) { std::string caption = _("GpgOL") + std::string (": ") + std::string (_("HTML display disabled.")); std::string buf = _("HTML content in unsigned S/MIME mails " "is insecure."); buf += "\n"; buf += _("GpgOL will only show such mails as text."); buf += "\n\n"; buf += _("This message is shown only once."); gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(), MB_OK); opt.smime_html_warn_shown = true; write_options (); } } } if (body.empty () && m_block_html && !html.empty()) { #if 0 Sadly the following code still offers to load external references it might also be too dangerous if Outlook somehow autoloads the references as soon as the Body is put into HTML // Fallback to show HTML as plaintext if HTML display // is blocked. log_error ("%s:%s: No text body. Putting HTML into plaintext.", SRCNAME, __func__); char *converted = ansi_charset_to_utf8 (m_parser->get_html_charset().c_str(), html.c_str(), html.size()); int ret = put_oom_string (m_mailitem, "HTMLBody", converted ? converted : ""); xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify html body of item.", SRCNAME, __func__); body = html; } else { char *plainBody = get_oom_string (m_mailitem, "Body"); if (!plainBody) { log_error ("%s:%s: Failed to obtain converted plain body.", SRCNAME, __func__); body = html; } else { ret = put_oom_string (m_mailitem, "HTMLBody", plainBody); xfree (plainBody); if (ret) { log_error ("%s:%s: Failed to put plain into html body of item.", SRCNAME, __func__); body = html; } else { TRETURN; } } } #endif body = html; std::string caption = _("GpgOL") + std::string (": ") + std::string (_("HTML display disabled.")); std::string buf = _("HTML content in unsigned S/MIME mails " "is insecure."); buf += "\n"; buf += _("GpgOL will only show such mails as text."); buf += "\n\n"; buf += _("Please ask the sender to sign the message or\n" "to send it with a plain text alternative."); gpgol_message_box (getWindow (), buf.c_str(), caption.c_str(), MB_OK); } find_and_replace (body, "\r\r\n", "\r\n"); const auto plain_charset = m_parser->get_body_charset(); int codepage = 0; if (plain_charset.empty()) { codepage = get_oom_int (m_mailitem, "InternetCodepage"); log_debug ("%s:%s: Did not find body charset. " "Using internet Codepage %i.", SRCNAME, __func__, codepage); } char *converted = ansi_charset_to_utf8 (plain_charset.c_str(), body.c_str(), body.size(), codepage); TRACEPOINT; int ret = put_oom_string (m_mailitem, "Body", converted ? converted : ""); TRACEPOINT; xfree (converted); if (ret) { log_error ("%s:%s: Failed to modify body of item.", SRCNAME, __func__); } TRETURN; } static int parsed_count; void Mail::parsing_done() { TSTART; TRACEPOINT; log_oom ("Mail %p Parsing done for parser num %i: %p", this, parsed_count++, m_parser.get()); if (!m_parser) { /* This should not happen but it happens when outlook sends multiple ItemLoad events for the same Mail Object. In that case it could happen that one parser was already done while a second is now returning for the wrong mail (as it's looked up by uuid.) We have a check in get_uuid that the uuid was not in the map before (and the parser is replaced). So this really really should not happen. We handle it anyway as we crash otherwise. It should not happen because the parser is only created in decrypt_verify which is called in the read event. And even in there we check if the parser was set. */ log_error ("%s:%s: No parser obj. For mail: %p", SRCNAME, __func__, this); TRETURN; } /* Store the results. */ m_decrypt_result = m_parser->decrypt_result (); m_verify_result = m_parser->verify_result (); + const auto subject = m_parser->get_internal_subject (); + if (!subject.empty ()) + { + put_oom_string (m_mailitem, "Subject", subject.c_str ()); + } + m_crypto_flags = 0; if (!m_decrypt_result.isNull()) { m_crypto_flags |= 1; } if (m_verify_result.numSignatures()) { m_crypto_flags |= 2; } TRACEPOINT; updateSigstate (); m_needs_wipe = !m_is_send_again; TRACEPOINT; /* Set categories according to the result. */ updateCategories_o (); TRACEPOINT; m_block_html = m_parser->shouldBlockHtml (); if (m_block_html) { // Just to be careful. setBlockStatus_m (); } TRACEPOINT; /* Update the body */ updateBody_o (); TRACEPOINT; /* Check that there are no unsigned / unencrypted messages. */ checkAttachments_o (); /* Update attachments */ if (add_attachments_o (m_mailitem, m_parser->get_attachments())) { log_error ("%s:%s: Failed to update attachments.", SRCNAME, __func__); } if (m_is_send_again) { log_debug ("%s:%s: I think that this is the send again of a crypto mail.", SRCNAME, __func__); /* We no longer want to be treated like a crypto mail. */ m_type = MSGTYPE_UNKNOWN; LPMESSAGE msg = get_oom_base_message (m_mailitem); if (!msg) { TRACEPOINT; } else { set_gpgol_draft_info_flags (msg, m_crypto_flags); gpgol_release (msg); } removeOurAttachments_o (); } installFolderEventHandler_o (); log_debug ("%s:%s: Delayed invalidate to update sigstate.", SRCNAME, __func__); CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0, NULL)); TRACEPOINT; TRETURN; } int Mail::encryptSignStart_o () { TSTART; if (m_crypt_state != NeedsActualCrypt) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); TRETURN -1; } int flags = 0; if (!needs_crypto_m ()) { TRETURN 0; } LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message) { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); TRETURN -1; } flags = get_gpgol_draft_info_flags (message); gpgol_release (message); const auto window = get_active_hwnd (); if (m_is_gsuite) { auto att_table = mapi_create_attach_table (message, 0); int n_att_usable = count_usable_attachments (att_table); mapi_release_attach_table (att_table); /* Check for attachments if we have some abort. */ if (n_att_usable) { wchar_t *w_title = utf8_to_wchar (_( "GpgOL: Oops, G Suite Sync account detected")); wchar_t *msg = utf8_to_wchar ( _("G Suite Sync breaks outgoing crypto mails " "with attachments.\nUsing crypto and attachments " "with G Suite Sync is not supported.\n\n" "See: https://dev.gnupg.org/T3545 for details.")); MessageBoxW (window, msg, w_title, MB_ICONINFORMATION|MB_OK); xfree (msg); xfree (w_title); TRETURN -1; } } m_do_inline = m_is_gsuite ? true : opt.inline_pgp; GpgME::Protocol proto = opt.enable_smime ? GpgME::UnknownProtocol: GpgME::OpenPGP; m_crypter = std::shared_ptr (new CryptController (this, flags & 1, flags & 2, proto)); // Careful from here on we have to check every // error condition with window enabling again. setWindowEnabled_o (false); if (m_crypter->collect_data ()) { log_error ("%s:%s: Crypter for mail %p failed to collect data.", SRCNAME, __func__, this); setWindowEnabled_o (true); TRETURN -1; } if (!m_async_crypt_disabled) { CloseHandle(CreateThread (NULL, 0, do_crypt, (LPVOID) this, 0, NULL)); } else { do_crypt (this); } TRETURN 0; } int Mail::needs_crypto_m () const { TSTART; LPMESSAGE message = get_oom_message (m_mailitem); int ret; if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); TRETURN false; } ret = get_gpgol_draft_info_flags (message); gpgol_release(message); TRETURN ret; } int Mail::wipe_o (bool force) { TSTART; if (!m_needs_wipe && !force) { TRETURN 0; } log_debug ("%s:%s: Removing plaintext from mailitem: %p.", SRCNAME, __func__, m_mailitem); if (put_oom_string (m_mailitem, "HTMLBody", "")) { if (put_oom_string (m_mailitem, "Body", "")) { log_debug ("%s:%s: Failed to wipe mailitem: %p.", SRCNAME, __func__, m_mailitem); TRETURN -1; } TRETURN -1; } else { put_oom_string (m_mailitem, "Body", ""); } m_needs_wipe = false; TRETURN 0; } int Mail::updateOOMData_o () { TSTART; char *buf = nullptr; log_debug ("%s:%s", SRCNAME, __func__); if (!isCryptoMail ()) { /* Update the body format. */ m_is_html_alternative = get_oom_int (m_mailitem, "BodyFormat") > 1; /* Store the body. It was not obvious for me (aheinecke) how to access this through MAPI. */ if (m_is_html_alternative) { log_debug ("%s:%s: Is html alternative mail.", SRCNAME, __func__); xfree (m_cached_html_body); m_cached_html_body = get_oom_string (m_mailitem, "HTMLBody"); } xfree (m_cached_plain_body); m_cached_plain_body = get_oom_string (m_mailitem, "Body"); m_cached_recipients = getRecipients_o (); } /* For some reason outlook may store the recipient address in the send using account field. If we have SMTP we prefer the SenderEmailAddress string. */ if (isCryptoMail ()) { /* This is the case where we are reading a mail and not composing. When composing we need to use the SendUsingAccount because if you send from the folder of userA but change the from to userB outlook will keep the SenderEmailAddress of UserA. This is all so horrible. */ buf = get_sender_SenderEMailAddress (m_mailitem); if (!buf) { /* Try the sender Object */ buf = get_sender_Sender (m_mailitem); } /* We also want to cache sent representing email address so that we can use it for verification information. */ char *buf2 = get_sender_SentRepresentingAddress (m_mailitem); if (buf2) { m_sent_on_behalf = buf2; xfree (buf2); } } if (!buf) { buf = get_sender_SendUsingAccount (m_mailitem, &m_is_gsuite); } if (!buf && !isCryptoMail ()) { /* Try the sender Object */ buf = get_sender_Sender (m_mailitem); } if (!buf) { /* We don't have s sender object or SendUsingAccount, well, in that case fall back to the current user. */ buf = get_sender_CurrentUser (m_mailitem); } if (!buf) { log_debug ("%s:%s: All fallbacks failed.", SRCNAME, __func__); TRETURN -1; } m_sender = buf; xfree (buf); TRETURN 0; } std::string Mail::getSender_o () { TSTART; if (m_sender.empty()) updateOOMData_o (); TRETURN m_sender; } std::string Mail::getSender () const { TSTART; TRETURN m_sender; } int Mail::closeAllMails_o () { TSTART; int err = 0; /* Detach Folder sinks */ for (auto fit = s_folder_events_map.begin(); fit != s_folder_events_map.end(); ++fit) { detach_FolderEvents_sink (fit->second); gpgol_release (fit->second); } s_folder_events_map.clear(); std::map::iterator it; TRACEPOINT; gpgol_lock (&mail_map_lock); std::map mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { /* XXX For non racy code the is_valid_ptr check should not be necessary but we crashed sometimes closing a destroyed mail. */ if (!isValidPtr (it->second)) { log_debug ("%s:%s: Already deleted mail for %p", SRCNAME, __func__, it->first); continue; } if (!it->second->isCryptoMail ()) { continue; } if (closeInspector_o (it->second) || close (it->second)) { log_error ("Failed to close mail: %p ", it->first); /* Should not happen */ if (isValidPtr (it->second) && it->second->revert_o ()) { err++; } } } TRETURN err; } int Mail::revertAllMails_o () { TSTART; int err = 0; std::map::iterator it; gpgol_lock (&mail_map_lock); auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->revert_o ()) { log_error ("Failed to revert mail: %p ", it->first); err++; continue; } it->second->setNeedsSave (true); if (!invoke_oom_method (it->first, "Save", NULL)) { log_error ("Failed to save reverted mail: %p ", it->second); err++; continue; } } TRETURN err; } int Mail::wipeAllMails_o () { TSTART; int err = 0; std::map::iterator it; gpgol_lock (&mail_map_lock); auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->wipe_o ()) { log_error ("Failed to wipe mail: %p ", it->first); err++; } } TRETURN err; } int Mail::revert_o () { TSTART; int err = 0; if (!m_processed) { TRETURN 0; } m_disable_att_remove_warning = true; err = gpgol_mailitem_revert (m_mailitem); if (err == -1) { log_error ("%s:%s: Message revert failed falling back to wipe.", SRCNAME, __func__); TRETURN wipe_o (); } /* We need to reprocess the mail next time around. */ m_processed = false; m_needs_wipe = false; m_disable_att_remove_warning = false; TRETURN 0; } bool Mail::isSMIME_m () { TSTART; msgtype_t msgtype; LPMESSAGE message; if (m_is_smime_checked) { TRETURN m_is_smime; } message = get_oom_message (m_mailitem); if (!message) { log_error ("%s:%s: No message?", SRCNAME, __func__); TRETURN false; } msgtype = mapi_get_message_type (message); m_is_smime = msgtype == MSGTYPE_GPGOL_OPAQUE_ENCRYPTED || msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED; /* Check if it is an smime mail. Multipart signed can also be true. */ if (!m_is_smime && msgtype == MSGTYPE_GPGOL_MULTIPART_SIGNED) { char *proto; char *ct = mapi_get_message_content_type (message, &proto, NULL); if (ct && proto) { m_is_smime = (!strcmp (proto, "application/pkcs7-signature") || !strcmp (proto, "application/x-pkcs7-signature")); } else { log_error ("%s:%s: No protocol in multipart / signed mail.", SRCNAME, __func__); } xfree (proto); xfree (ct); } gpgol_release (message); m_is_smime_checked = true; TRETURN m_is_smime; } static std::string get_string_o (LPDISPATCH item, const char *str) { TSTART; char *buf = get_oom_string (item, str); if (!buf) { TRETURN std::string(); } std::string ret = buf; xfree (buf); TRETURN ret; } std::string Mail::getSubject_o () const { TSTART; TRETURN get_string_o (m_mailitem, "Subject"); } std::string Mail::getBody_o () const { TSTART; TRETURN get_string_o (m_mailitem, "Body"); } std::vector Mail::getRecipients_o () const { TSTART; LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients"); if (!recipients) { TRACEPOINT; std::vector(); } bool err = false; auto ret = get_oom_recipients (recipients, &err); gpgol_release (recipients); if (err) { log_debug ("%s:%s: Failed to resolve recipients at this time.", SRCNAME, __func__); } TRETURN ret; } int Mail::closeInspector_o (Mail *mail) { TSTART; LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector"); HRESULT hr; DISPID dispid; if (!inspector) { log_debug ("%s:%s: No inspector.", SRCNAME, __func__); TRETURN -1; } dispid = lookup_oom_dispid (inspector, "Close"); if (dispid != DISPID_UNKNOWN) { VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dispparams, NULL, NULL, NULL); if (hr != S_OK) { log_debug ("%s:%s: Failed to close inspector: %#lx", SRCNAME, __func__, hr); gpgol_release (inspector); TRETURN -1; } } gpgol_release (inspector); TRETURN 0; } /* static */ int Mail::close (Mail *mail) { TSTART; VARIANT aVariant[1]; DISPPARAMS dispparams; dispparams.rgvarg = aVariant; dispparams.rgvarg[0].vt = VT_INT; dispparams.rgvarg[0].intVal = 1; dispparams.cArgs = 1; dispparams.cNamedArgs = 0; log_oom ("%s:%s: Invoking close for: %p", SRCNAME, __func__, mail->item()); mail->setCloseTriggered (true); int rc = invoke_oom_method_with_parms (mail->item(), "Close", NULL, &dispparams); if (!rc) { log_debug ("%s:%s: Close successful. Next write may pass.", SRCNAME, __func__); mail->setPassWrite (true); } log_oom ("%s:%s: returned from close", SRCNAME, __func__); TRETURN rc; } void Mail::setCloseTriggered (bool value) { TSTART; m_close_triggered = value; TRETURN; } bool Mail::getCloseTriggered () const { TSTART; TRETURN m_close_triggered; } static const UserID get_uid_for_sender (const Key &k, const char *sender) { TSTART; UserID ret; if (!sender) { TRETURN ret; } if (!k.numUserIDs()) { log_debug ("%s:%s: Key without uids", SRCNAME, __func__); TRETURN ret; } for (const auto uid: k.userIDs()) { if (!uid.email() || !*(uid.email())) { /* This happens for S/MIME a lot */ log_debug ("%s:%s: skipping uid without email.", SRCNAME, __func__); continue; } auto normalized_uid = uid.addrSpec(); auto normalized_sender = UserID::addrSpecFromString(sender); if (normalized_sender.empty() || normalized_uid.empty()) { log_error ("%s:%s: normalizing '%s' or '%s' failed.", SRCNAME, __func__, anonstr (uid.email()), anonstr (sender)); continue; } if (normalized_sender == normalized_uid) { ret = uid; } } TRETURN ret; } void Mail::updateSigstate () { TSTART; std::string sender = getSender (); if (sender.empty()) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); TRETURN; } if (m_verify_result.isNull()) { log_debug ("%s:%s: No verify result.", SRCNAME, __func__); TRETURN; } if (m_verify_result.error()) { log_debug ("%s:%s: verify error.", SRCNAME, __func__); TRETURN; } for (const auto sig: m_verify_result.signatures()) { m_is_signed = true; const auto key = KeyCache::instance ()->getByFpr (sig.fingerprint(), true); m_uid = get_uid_for_sender (key, sender.c_str()); if (m_uid.isNull() && !m_sent_on_behalf.empty ()) { m_uid = get_uid_for_sender (key, m_sent_on_behalf.c_str ()); if (!m_uid.isNull()) { log_debug ("%s:%s: Using sent on behalf '%s' instead of '%s'", SRCNAME, __func__, anonstr (m_sent_on_behalf.c_str()), anonstr (sender.c_str ())); } } /* Sigsum valid or green is somehow not set in this case. * Which is strange as AFAIK this worked in the past. */ if ((sig.summary() & Signature::Summary::Valid) && m_uid.origin() == GpgME::Key::OriginWKD && (sig.validity() == Signature::Validity::Unknown || sig.validity() == Signature::Validity::Marginal)) { // WKD is a shortcut to Level 2 trust. log_debug ("%s:%s: Unknown or marginal from WKD -> Level 2", SRCNAME, __func__); } else if (m_uid.isNull() || (sig.validity() != Signature::Validity::Marginal && sig.validity() != Signature::Validity::Full && sig.validity() != Signature::Validity::Ultimate)) { /* For our category we only care about trusted sigs. And the UID needs to match.*/ continue; } else if (sig.validity() == Signature::Validity::Marginal) { const auto tofu = m_uid.tofuInfo(); if (!tofu.isNull() && (tofu.validity() != TofuInfo::Validity::BasicHistory && tofu.validity() != TofuInfo::Validity::LargeHistory)) { /* Marginal is only good enough without tofu. Otherwise we wait for basic trust. */ log_debug ("%s:%s: Discarding marginal signature." "With too little history.", SRCNAME, __func__); continue; } } log_debug ("%s:%s: Classified sender as verified uid validity: %i origin: %i", SRCNAME, __func__, m_uid.validity(), m_uid.origin()); m_sig = sig; m_is_valid = true; TRETURN; } log_debug ("%s:%s: No signature with enough trust. Using first", SRCNAME, __func__); m_sig = m_verify_result.signature(0); TRETURN; } bool Mail::isValidSig () const { TSTART; TRETURN m_is_valid; } void Mail::removeCategories_o () { TSTART; if (!m_store_id.empty () && !m_verify_category.empty ()) { log_oom ("%s:%s: Unreffing verify category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, m_verify_category); } if (!m_store_id.empty () && !m_decrypt_result.isNull()) { log_oom ("%s:%s: Unreffing dec category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, CategoryManager::getEncMailCategory ()); } if (m_is_junk) { log_oom ("%s:%s: Unreffing junk category", SRCNAME, __func__); CategoryManager::instance ()->removeCategory (this, CategoryManager::getJunkMailCategory ()); } TRETURN; } /* Now for some tasty hack: Outlook sometimes does not show the new categories properly but instead does some weird scrollbar thing. This can be avoided by resizing the message a bit. But somehow this only needs to be done once. Weird isn't it? But as this workaround worked let's do it programatically. Fun. Wan't some tomato sauce with this hack? */ static void resize_active_window () { TSTART; HWND wnd = get_active_hwnd (); static std::vector resized_windows; if(std::find(resized_windows.begin(), resized_windows.end(), wnd) != resized_windows.end()) { /* We only need to do this once per window. XXX But sometimes we also need to do this once per view of the explorer. So for now this might break but we reduce the flicker. A better solution would be to find the current view and track that. */ TRETURN; } if (!wnd) { TRACEPOINT; TRETURN; } RECT oldpos; if (!GetWindowRect (wnd, &oldpos)) { TRACEPOINT; TRETURN; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, /* Anything smaller then 19 was ignored when the window was * maximized on Windows 10 at least with a 1980*1024 * resolution. So I assume it's at least 1 percent. * This is all hackish and ugly but should work for 90%... * hopefully. */ (int)oldpos.right - oldpos.left - 20, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; TRETURN; } if (!SetWindowPos (wnd, nullptr, (int)oldpos.left, (int)oldpos.top, (int)oldpos.right - oldpos.left, (int)oldpos.bottom - oldpos.top, 0)) { TRACEPOINT; TRETURN; } resized_windows.push_back(wnd); TRETURN; } #if 0 static std::string pretty_id (const char *keyId) { /* Three spaces, four quads and a NULL */ char buf[20]; buf[19] = '\0'; if (!keyId) { return std::string ("null"); } size_t len = strlen (keyId); if (!len) { return std::string ("empty"); } if (len < 16) { return std::string (_("Invalid Key")); } const char *p = keyId + (len - 16); int j = 0; for (size_t i = 0; i < 16; i++) { if (i && i % 4 == 0) { buf[j++] = ' '; } buf[j++] = *(p + i); } return std::string (buf); } #endif void Mail::updateCategories_o () { TSTART; auto mngr = CategoryManager::instance (); if (isValidSig ()) { char *buf; /* Resolve to the primary fingerprint */ #if 0 const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (), true); const char *sigFpr; if (sigKey.isNull()) { sigFpr = m_sig.fingerprint (); } else { sigFpr = sigKey.primaryFingerprint (); } #endif /* If m_uid addrSpec would not return a result we would never * have gotten the UID. */ int lvl = get_signature_level (); /* TRANSLATORS: The first placeholder is for tranlsation of "Level". The second one is for the level number. The third is for the translation of "trust in" and the last one is for the mail address used for verification. The result is used as the text on the green bar for signed mails. e.g.: "GpgOL: Level 3 trust in 'john.doe@example.org'" */ gpgrt_asprintf (&buf, "GpgOL: %s %i %s '%s'", _("Level"), lvl, _("trust in"), m_uid.addrSpec ().c_str ()); memdbg_alloc (buf); int color = 0; if (lvl == 2) { color = 7; /* Olive */ } if (lvl == 3) { color = 5; /* Green */ } if (lvl == 4) { color = 20; /* Dark Green */ } m_store_id = mngr->addCategoryToMail (this, buf, color); m_verify_category = buf; xfree (buf); } else { remove_category (m_mailitem, "GpgOL: ", false); } if (!m_decrypt_result.isNull()) { const auto id = mngr->addCategoryToMail (this, CategoryManager::getEncMailCategory (), 8 /* Blue */); if (m_store_id.empty()) { m_store_id = id; } if (m_store_id != id) { log_error ("%s:%s unexpected store mismatch " "between '%s' and dec cat '%s'", SRCNAME, __func__, m_store_id.c_str(), id.c_str()); } } else { /* As a small safeguard against fakes we remove our categories */ remove_category (m_mailitem, CategoryManager::getEncMailCategory ().c_str (), true); } resize_active_window(); TRETURN; } bool Mail::isSigned () const { TSTART; TRETURN m_verify_result.numSignatures() > 0; } bool Mail::isEncrypted () const { TSTART; TRETURN !m_decrypt_result.isNull(); } int Mail::setUUID_o () { TSTART; char *uuid; if (!m_uuid.empty()) { /* This codepath is reached by decrypt again after a close with discard changes. The close discarded the uuid on the OOM object so we have to set it again. */ log_debug ("%s:%s: Resetting uuid for %p to %s", SRCNAME, __func__, this, m_uuid.c_str()); uuid = get_unique_id (m_mailitem, 1, m_uuid.c_str()); } else { uuid = get_unique_id (m_mailitem, 1, nullptr); log_debug ("%s:%s: uuid for %p set to %s", SRCNAME, __func__, this, uuid); } if (!uuid) { log_debug ("%s:%s: Failed to get/set uuid for %p", SRCNAME, __func__, m_mailitem); TRETURN -1; } if (m_uuid.empty()) { m_uuid = uuid; Mail *other = getMailForUUID (uuid); if (other) { /* According to documentation this should not happen as this means that multiple ItemLoad events occured for the same mailobject without unload / destruction of the mail. But it happens. If you invalidate the UI in the selection change event Outlook loads a new mailobject for the mail. Might happen in other surprising cases. We replace in that case as experiments have shown that the last mailobject is the one that is visible. Still troubling state so we log this as an error. */ log_error ("%s:%s: There is another mail for %p " "with uuid: %s replacing it.", SRCNAME, __func__, m_mailitem, uuid); delete other; } gpgol_lock (&uid_map_lock); s_uid_map.insert (std::pair (m_uuid, this)); gpgol_unlock (&uid_map_lock); log_debug ("%s:%s: uuid for %p is now %s", SRCNAME, __func__, this, m_uuid.c_str()); } xfree (uuid); TRETURN 0; } /* TRETURNs 2 if the userid is ultimately trusted. TRETURNs 1 if the userid is fully trusted but has a signature by a key for which we have a secret and which is ultimately trusted. (Direct trust) 0 otherwise */ static int level_4_check (const UserID &uid) { TSTART; if (uid.isNull()) { TRETURN 0; } if (uid.validity () == UserID::Validity::Ultimate) { TRETURN 2; } if (uid.validity () == UserID::Validity::Full) { const auto ultimate_keys = KeyCache::instance()->getUltimateKeys (); for (const auto sig: uid.signatures ()) { const char *sigID = sig.signerKeyID (); if (sig.isNull() || !sigID) { /* should not happen */ TRACEPOINT; continue; } /* Direct trust information is not available through gnupg so we cached the keys with ultimate trust during parsing and now see if we find a direct trust path.*/ for (const auto secKey: ultimate_keys) { /* Check that the Key id of the key matches */ const char *secKeyID = secKey.keyID (); if (!secKeyID || strcmp (secKeyID, sigID)) { continue; } /* Check that the userID of the signature is the ultimately trusted one. */ const char *sig_uid_str = sig.signerUserID(); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } for (const auto signer_uid: secKey.userIDs ()) { if (signer_uid.validity() != UserID::Validity::Ultimate) { TRACEPOINT; continue; } const char *signer_uid_str = signer_uid.id (); if (!sig_uid_str) { /* should not happen */ TRACEPOINT; continue; } if (!strcmp(sig_uid_str, signer_uid_str)) { /* We have a match */ log_debug ("%s:%s: classified %s as ultimate because " "it was signed by uid %s of key %s", SRCNAME, __func__, anonstr (signer_uid_str), anonstr (sig_uid_str), anonstr (secKeyID)); TRETURN 1; } } } } } TRETURN 0; } std::string Mail::getCryptoSummary () const { TSTART; const int level = get_signature_level (); bool enc = isEncrypted (); if (level == 4 && enc) { TRETURN _("Security Level 4"); } if (level == 4) { TRETURN _("Trust Level 4"); } if (level == 3 && enc) { TRETURN _("Security Level 3"); } if (level == 3) { TRETURN _("Trust Level 3"); } if (level == 2 && enc) { TRETURN _("Security Level 2"); } if (level == 2) { TRETURN _("Trust Level 2"); } if (enc) { TRETURN _("Encrypted"); } if (isSigned ()) { /* Even if it is signed, if it is not validly signed it's still completly insecure as anyone could have signed this. So we avoid the label "signed" here as this word already implies some security. */ TRETURN _("Insecure"); } TRETURN _("Insecure"); } std::string Mail::getCryptoOneLine () const { TSTART; bool sig = isSigned (); bool enc = isEncrypted (); if (sig || enc) { if (sig && enc) { TRETURN _("Signed and encrypted message"); } else if (sig) { TRETURN _("Signed message"); } else if (enc) { TRETURN _("Encrypted message"); } } TRETURN _("Insecure message"); } std::string Mail::getCryptoDetails_o () { TSTART; std::string message; /* No signature with keys but error */ if (!isEncrypted () && !isSigned () && m_verify_result.error()) { message = _("You cannot be sure who sent, " "modified and read the message in transit."); message += "\n\n"; message += _("The message was signed but the verification failed with:"); message += "\n"; message += m_verify_result.error().asString(); TRETURN message; } /* No crypo, what are we doing here? */ if (!isEncrypted () && !isSigned ()) { TRETURN _("You cannot be sure who sent, " "modified and read the message in transit."); } /* Handle encrypt only */ if (isEncrypted () && !isSigned ()) { if (in_de_vs_mode ()) { if (m_sig.isDeVs()) { message += _("The encryption was VS-NfD-compliant."); } else { message += _("The encryption was not VS-NfD-compliant."); } } message += "\n\n"; message += _("You cannot be sure who sent the message because " "it is not signed."); TRETURN message; } bool keyFound = true; const auto sigKey = KeyCache::instance ()->getByFpr (m_sig.fingerprint (), true); bool isOpenPGP = sigKey.isNull() ? !isSMIME_m () : sigKey.protocol() == Protocol::OpenPGP; char *buf; bool hasConflict = false; int level = get_signature_level (); log_debug ("%s:%s: Formatting sig. Validity: %x Summary: %x Level: %i", SRCNAME, __func__, m_sig.validity(), m_sig.summary(), level); if (level == 4) { /* level 4 check for direct trust */ int four_check = level_4_check (m_uid); if (four_check == 2 && sigKey.hasSecret ()) { message = _("You signed this message."); } else if (four_check == 1) { message = _("The senders identity was certified by yourself."); } else if (four_check == 2) { message = _("The sender is allowed to certify identities for you."); } else { log_error ("%s:%s:%i BUG: Invalid sigstate.", SRCNAME, __func__, __LINE__); TRETURN message; } } else if (level == 3 && isOpenPGP) { /* Level three is only reachable through web of trust and no direct signature. */ message = _("The senders identity was certified by several trusted people."); } else if (level == 3 && !isOpenPGP) { /* Level three is the only level for trusted S/MIME keys. */ gpgrt_asprintf (&buf, _("The senders identity is certified by the trusted issuer:\n'%s'\n"), sigKey.issuerName()); memdbg_alloc (buf); message = buf; xfree (buf); } else if (level == 2 && m_uid.origin () == GpgME::Key::OriginWKD) { message = _("The mail provider of the recipient served this key."); } else if (level == 2 && m_uid.tofuInfo ().isNull ()) { /* Marginal trust through pgp only */ message = _("Some trusted people " "have certified the senders identity."); } else if (level == 2) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); /* i18n note signcount is always pulral because with signcount 1 we * would not be in this branch. */ gpgrt_asprintf (&buf, _("The senders address is trusted, because " "you have established a communication history " "with this address starting on %s.\n" "You encrypted %i and verified %i messages since."), time, m_uid.tofuInfo().encrCount(), m_uid.tofuInfo().signCount ()); memdbg_alloc (buf); xfree (time); message = buf; xfree (buf); } else if (level == 1) { /* This could be marginal trust through pgp, or tofu with little history. */ if (m_uid.tofuInfo ().signCount() == 1) { message += _("The senders signature was verified for the first time."); } else if (m_uid.tofuInfo ().validity() == TofuInfo::Validity::LittleHistory) { unsigned long first_contact = std::max (m_uid.tofuInfo().signFirst(), m_uid.tofuInfo().encrFirst()); char *time = format_date_from_gpgme (first_contact); gpgrt_asprintf (&buf, _("The senders address is not trustworthy yet because " "you only verified %i messages and encrypted %i messages to " "it since %s."), m_uid.tofuInfo().signCount (), m_uid.tofuInfo().encrCount (), time); memdbg_alloc (buf); xfree (time); message = buf; xfree (buf); } } else { /* Now we are in level 0, this could be a technical problem, no key or just unkown. */ message = isEncrypted () ? _("But the sender address is not trustworthy because:") : _("The sender address is not trustworthy because:"); message += "\n"; keyFound = !(m_sig.summary() & Signature::Summary::KeyMissing); bool general_problem = true; /* First the general stuff. */ if (m_sig.summary() & Signature::Summary::Red) { message += _("The signature is invalid: \n"); if (m_sig.status().code() == GPG_ERR_BAD_SIGNATURE) { message += std::string("\n") + _("The signature does not match."); return message; } } else if (m_sig.summary() & Signature::Summary::SysError || m_verify_result.numSignatures() < 1) { message += _("There was an error verifying the signature.\n"); const auto err = m_sig.status (); if (err) { message += err.asString () + std::string ("\n"); } } else if (m_sig.summary() & Signature::Summary::SigExpired) { message += _("The signature is expired.\n"); } else { message += isOpenPGP ? _("The used key") : _("The used certificate"); message += " "; general_problem = false; } /* Now the key problems */ if ((m_sig.summary() & Signature::Summary::KeyMissing)) { message += _("is not available."); } else if ((m_sig.summary() & Signature::Summary::KeyRevoked)) { message += _("is revoked."); } else if ((m_sig.summary() & Signature::Summary::KeyExpired)) { message += _("is expired."); } else if ((m_sig.summary() & Signature::Summary::BadPolicy)) { message += _("is not meant for signing."); } else if ((m_sig.summary() & Signature::Summary::CrlMissing)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::CrlTooOld)) { message += _("could not be checked for revocation."); } else if ((m_sig.summary() & Signature::Summary::TofuConflict) || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { message += _("is not the same as the key that was used " "for this address in the past."); hasConflict = true; } else if (m_uid.isNull()) { gpgrt_asprintf (&buf, _("does not claim the address: \"%s\"."), getSender_o ().c_str()); memdbg_alloc (buf); message += buf; xfree (buf); } else if (((m_sig.validity() & Signature::Validity::Undefined) || (m_sig.validity() & Signature::Validity::Unknown) || (m_sig.summary() == Signature::Summary::None) || (m_sig.validity() == 0))&& !general_problem) { /* Bit of a catch all for weird results. */ if (isOpenPGP) { message += _("is not certified by any trustworthy key."); } else { message += _("is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown."); } } else if (m_uid.isRevoked()) { message += _("The sender marked this address as revoked."); } else if ((m_sig.validity() & Signature::Validity::Never)) { message += _("is marked as not trustworthy."); } } message += "\n\n"; if (in_de_vs_mode ()) { if (isSigned ()) { if (m_sig.isDeVs ()) { message += _("The signature is VS-NfD-compliant."); } else { message += _("The signature is not VS-NfD-compliant."); } message += "\n"; } if (isEncrypted ()) { if (m_decrypt_result.isDeVs ()) { message += _("The encryption is VS-NfD-compliant."); } else { message += _("The encryption is not VS-NfD-compliant."); } message += "\n\n"; } else { message += "\n"; } } if (hasConflict) { message += _("Click here to change the key used for this address."); } else if (keyFound) { message += isOpenPGP ? _("Click here for details about the key.") : _("Click here for details about the certificate."); } else { message += isOpenPGP ? _("Click here to search the key on the configured keyserver.") : _("Click here to search the certificate on the configured X509 keyserver."); } TRETURN message; } int Mail::get_signature_level () const { TSTART; if (!m_is_signed) { TRETURN 0; } if (m_uid.isNull ()) { /* No m_uid matches our sender. */ TRETURN 0; } if (m_is_valid && (m_uid.validity () == UserID::Validity::Ultimate || (m_uid.validity () == UserID::Validity::Full && level_4_check (m_uid))) && (!in_de_vs_mode () || m_sig.isDeVs())) { TRETURN 4; } if (m_is_valid && m_uid.validity () == UserID::Validity::Full && (!in_de_vs_mode () || m_sig.isDeVs())) { TRETURN 3; } if (m_is_valid) { TRETURN 2; } if (m_sig.validity() == Signature::Validity::Marginal) { TRETURN 1; } if (m_sig.summary() & Signature::Summary::TofuConflict || m_uid.tofuInfo().validity() == TofuInfo::Conflict) { TRETURN 0; } TRETURN 0; } int Mail::getCryptoIconID () const { TSTART; int level = get_signature_level (); int offset = isEncrypted () ? ENCRYPT_ICON_OFFSET : 0; TRETURN IDI_LEVEL_0 + level + offset; } const char* Mail::getSigFpr () const { TSTART; if (!m_is_signed || m_sig.isNull()) { TRETURN nullptr; } TRETURN m_sig.fingerprint(); } /** Try to locate the keys for all recipients */ void Mail::locateKeys_o () { TSTART; if (m_locate_in_progress) { /** XXX The strangest thing seems to happen here: In get_recipients the lookup for "AddressEntry" on an unresolved address might cause network traffic. So Outlook somehow "detaches" this call and keeps processing window messages while the call is running. So our do_delayed_locate might trigger a second locate. If we access the OOM in this call while we access the same object in the blocked "detached" call we crash. (T3931) After the window message is handled outlook retunrs in the original lookup. A better fix here might be a non recursive lock of the OOM. But I expect that if we lock the handling of the Windowmessage we might deadlock. */ log_debug ("%s:%s: Locate for %p already in progress.", SRCNAME, __func__, this); TRETURN; } m_locate_in_progress = true; Addressbook::check_o (this); if (opt.autoresolve) { // First update oom data to have recipients and sender updated. updateOOMData_o (); KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this); KeyCache::instance()->startLocate (getSender_o ().c_str (), this); KeyCache::instance()->startLocate (getCachedRecipients (), this); } autosecureCheck (); m_locate_in_progress = false; TRETURN; } bool Mail::isHTMLAlternative () const { TSTART; TRETURN m_is_html_alternative; } char * Mail::takeCachedHTMLBody () { TSTART; char *ret = m_cached_html_body; m_cached_html_body = nullptr; TRETURN ret; } char * Mail::takeCachedPlainBody () { TSTART; char *ret = m_cached_plain_body; m_cached_plain_body = nullptr; TRETURN ret; } int Mail::getCryptoFlags () const { TSTART; TRETURN m_crypto_flags; } void Mail::setNeedsEncrypt (bool value) { TSTART; m_needs_encrypt = value; TRETURN; } bool Mail::getNeedsEncrypt () const { TSTART; TRETURN m_needs_encrypt; } std::vector Mail::getCachedRecipients () { TSTART; TRETURN m_cached_recipients; } void Mail::appendToInlineBody (const std::string &data) { TSTART; m_inline_body += data; TRETURN; } int Mail::inlineBodyToBody_o () { TSTART; if (!m_crypter) { log_error ("%s:%s: No crypter.", SRCNAME, __func__); TRETURN -1; } const auto body = m_crypter->get_inline_data (); if (body.empty()) { TRETURN 0; } /* For inline we always work with UTF-8 */ put_oom_int (m_mailitem, "InternetCodepage", 65001); int ret = put_oom_string (m_mailitem, "Body", body.c_str ()); TRETURN ret; } void Mail::updateCryptMAPI_m () { TSTART; log_debug ("%s:%s: Update crypt mapi", SRCNAME, __func__); if (m_crypt_state != NeedsUpdateInMAPI) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); TRETURN; } if (!m_crypter) { if (!m_mime_data.empty()) { log_debug ("%s:%s: Have override mime data creating dummy crypter", SRCNAME, __func__); m_crypter = std::shared_ptr (new CryptController (this, false, false, GpgME::UnknownProtocol)); } else { log_error ("%s:%s: No crypter.", SRCNAME, __func__); m_crypt_state = NoCryptMail; TRETURN; } } if (m_crypter->update_mail_mapi ()) { log_error ("%s:%s: Failed to update MAPI after crypt", SRCNAME, __func__); m_crypt_state = NoCryptMail; } else { m_crypt_state = WantsSendMIME; } /** If sync we need the crypter in update_crypt_oom */ if (!isAsyncCryptDisabled ()) { // We don't need the crypter anymore. resetCrypter (); } TRETURN; } /** Checks in OOM if the body is either empty or contains the -----BEGIN tag. pair.first -> true if body starts with -----BEGIN pair.second -> true if body is empty. */ static std::pair has_crypt_or_empty_body_oom (Mail *mail) { TSTART; auto body = mail->getBody_o (); std::pair ret; ret.first = false; ret.second = false; ltrim (body); if (body.size() > 10 && !strncmp (body.c_str(), "-----BEGIN", 10)) { ret.first = true; TRETURN ret; } if (!body.size()) { ret.second = true; } else { log_data ("%s:%s: Body found in %p : \"%s\"", SRCNAME, __func__, mail, body.c_str ()); } TRETURN ret; } void Mail::updateCryptOOM_o () { TSTART; log_debug ("%s:%s: Update crypt oom for %p", SRCNAME, __func__, this); if (m_crypt_state != NeedsUpdateInOOM) { log_debug ("%s:%s: invalid state %i", SRCNAME, __func__, m_crypt_state); resetCrypter (); TRETURN; } if (getDoPGPInline ()) { if (inlineBodyToBody_o ()) { log_error ("%s:%s: Inline body to body failed %p.", SRCNAME, __func__, this); gpgol_bug (get_active_hwnd(), ERR_INLINE_BODY_TO_BODY); m_crypt_state = NoCryptMail; TRETURN; } } if (m_crypter->get_protocol () == GpgME::CMS && m_crypter->is_encrypter ()) { /* We put the PIDNameContentType headers here for exchange because this is the only way we found to inject the smime-type. */ if (put_pa_string (m_mailitem, PR_PIDNameContentType_DASL, "application/pkcs7-mime;smime-type=\"enveloped-data\";name=smime.p7m")) { log_debug ("%s:%s: Failed to put PIDNameContentType for %p.", SRCNAME, __func__, this); } } /** When doing async update_crypt_mapi follows and needs the crypter. */ if (isAsyncCryptDisabled ()) { resetCrypter (); } const auto pair = has_crypt_or_empty_body_oom (this); if (pair.first) { log_debug ("%s:%s: Looks like inline body. You can pass %p.", SRCNAME, __func__, this); m_crypt_state = WantsSendInline; TRETURN; } // We are in MIME land. Wipe the body. if (wipe_o (true)) { log_debug ("%s:%s: Cancel send for %p.", SRCNAME, __func__, this); wchar_t *title = utf8_to_wchar (_("GpgOL: Encryption not possible!")); wchar_t *msg = utf8_to_wchar (_( "Outlook returned an error when trying to send the encrypted mail.\n\n" "Please restart Outlook and try again.\n\n" "If it still fails consider using an encrypted attachment or\n" "switching to PGP/Inline in GpgOL's options.")); MessageBoxW (get_active_hwnd(), msg, title, MB_ICONERROR | MB_OK); xfree (msg); xfree (title); m_crypt_state = NoCryptMail; TRETURN; } m_crypt_state = NeedsSecondAfterWrite; TRETURN; } void Mail::setWindowEnabled_o (bool value) { TSTART; if (!value) { m_window = get_active_hwnd (); } log_debug ("%s:%s: enable window %p %i", SRCNAME, __func__, m_window, value); EnableWindow (m_window, value ? TRUE : FALSE); TRETURN; } bool Mail::check_inline_response () { TSTART; /* Async sending is known to cause instabilities. So we keep a hidden option to disable it. */ if (opt.sync_enc) { m_async_crypt_disabled = true; TRETURN m_async_crypt_disabled; } m_async_crypt_disabled = false; const auto subject = getSubject_o (); /* Check for an empty subject. Otherwise the question for it might be hidden behind our overlay. */ if (subject.empty()) { log_debug ("%s:%s: Detected empty subject. " "Disabling async crypt due to T4150.", SRCNAME, __func__); m_async_crypt_disabled = true; TRETURN m_async_crypt_disabled; } LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (attachments) { /* This is horrible. But. For some kinds of attachments (we got reports about Office attachments the write in the send event triggered by our crypto done code fails with an exception. There does not appear to be a detectable pattern when this happens. As we can't be sure and do not know for which attachments this really happens we do not use async crypt for any mails with attachments. :-/ Better be save (not crash) instead of nice (async). TODO: Figure this out. The log goes like this. We pass the send event. That triggers a write, which we pass. And then that fails. So it looks like moving to Outbox fails. Because we can save as much as we like before that. Using the IMessage::SubmitMessage MAPI interface works, but as it is unstable in our current implementation we do not want to use it. mailitem-events.cpp:Invoke: Passing send event for mime-encrypted message 12B7C6E0. application-events.cpp:Invoke: Unhandled Event: f002 mailitem-events.cpp:Invoke: Write : 0ED4D058 mailitem-events.cpp:Invoke: Passing write event. oomhelp.cpp:invoke_oom_method_with_parms_type: Method 'Send' invokation failed: 0x80020009 oomhelp.cpp:dump_excepinfo: Exception: wCode: 0x1000 wReserved: 0x0 source: Microsoft Outlook desc: The operation failed. The messaging interfaces have returned an unknown error. If the problem persists, restart Outlook. help: null helpCtx: 0x0 deferredFill: 00000000 scode: 0x80040119 */ int count = get_oom_int (attachments, "Count"); gpgol_release (attachments); if (count) { m_async_crypt_disabled = true; log_debug ("%s:%s: Detected attachments. " "Disabling async crypt due to T4131.", SRCNAME, __func__); TRETURN m_async_crypt_disabled; } } LPDISPATCH app = GpgolAddin::get_instance ()->get_application (); if (!app) { TRACEPOINT; TRETURN false; } LPDISPATCH explorer = get_oom_object (app, "ActiveExplorer"); if (!explorer) { TRACEPOINT; TRETURN false; } LPDISPATCH inlineResponse = get_oom_object (explorer, "ActiveInlineResponse"); gpgol_release (explorer); if (!inlineResponse) { TRETURN false; } // We have inline response // Check if we are it. It's a bit naive but meh. Worst case // is that we think inline response too often and do sync // crypt where we could do async crypt. char * inlineSubject = get_oom_string (inlineResponse, "Subject"); gpgol_release (inlineResponse); if (inlineResponse && !subject.empty() && !strcmp (subject.c_str (), inlineSubject)) { log_debug ("%s:%s: Detected inline response for '%p'", SRCNAME, __func__, this); m_async_crypt_disabled = true; } xfree (inlineSubject); TRETURN m_async_crypt_disabled; } // static Mail * Mail::getLastMail () { TSTART; if (!s_last_mail || !isValidPtr (s_last_mail)) { s_last_mail = nullptr; } TRETURN s_last_mail; } // static void Mail::clearLastMail () { TSTART; s_last_mail = nullptr; TRETURN; } // static void Mail::locateAllCryptoRecipients_o () { TSTART; gpgol_lock (&mail_map_lock); std::map::iterator it; auto mail_map_copy = s_mail_map; gpgol_unlock (&mail_map_lock); for (it = mail_map_copy.begin(); it != mail_map_copy.end(); ++it) { if (it->second->needs_crypto_m ()) { it->second->locateKeys_o (); } } TRETURN; } int Mail::removeAllAttachments_o () { TSTART; int ret = 0; LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { TRACEPOINT; TRETURN 0; } int count = get_oom_int (attachments, "Count"); LPDISPATCH to_delete[count]; /* Populate the array so that we don't get in an index mess */ for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; to_delete[i-1] = get_oom_object (attachments, item_str.c_str()); } gpgol_release (attachments); /* Now delete all attachments */ for (int i = 0; i < count; i++) { LPDISPATCH attachment = to_delete[i]; if (!attachment) { log_error ("%s:%s: No such attachment %i", SRCNAME, __func__, i); ret = -1; } /* Delete the attachments that are marked to delete */ if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Deleting attachment %i", SRCNAME, __func__, i); ret = -1; } gpgol_release (attachment); } TRETURN ret; } int Mail::removeOurAttachments_o () { TSTART; LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments"); if (!attachments) { TRACEPOINT; TRETURN 0; } int count = get_oom_int (attachments, "Count"); LPDISPATCH to_delete[count]; int del_cnt = 0; for (int i = 1; i <= count; i++) { auto item_str = std::string("Item(") + std::to_string (i) + ")"; LPDISPATCH attachment = get_oom_object (attachments, item_str.c_str()); if (!attachment) { TRACEPOINT; continue; } attachtype_t att_type; if (get_pa_int (attachment, GPGOL_ATTACHTYPE_DASL, (int*) &att_type)) { /* Not our attachment. */ gpgol_release (attachment); continue; } if (att_type == ATTACHTYPE_PGPBODY || att_type == ATTACHTYPE_MOSS || att_type == ATTACHTYPE_MOSSTEMPL) { /* One of ours to delete. */ to_delete[del_cnt++] = attachment; /* Dont' release yet */ continue; } gpgol_release (attachment); } gpgol_release (attachments); int ret = 0; for (int i = 0; i < del_cnt; i++) { LPDISPATCH attachment = to_delete[i]; /* Delete the attachments that are marked to delete */ if (invoke_oom_method (attachment, "Delete", NULL)) { log_error ("%s:%s: Error: deleting attachment %i", SRCNAME, __func__, i); ret = -1; } gpgol_release (attachment); } TRETURN ret; } /* We are very verbose because if we fail it might mean that we have leaked plaintext -> critical. */ bool Mail::hasCryptedOrEmptyBody_o () { TSTART; const auto pair = has_crypt_or_empty_body_oom (this); if (pair.first /* encrypted marker */) { log_debug ("%s:%s: Crypt Marker detected in OOM body. Return true %p.", SRCNAME, __func__, this); TRETURN true; } if (!pair.second) { log_debug ("%s:%s: Unexpected content detected. Return false %p.", SRCNAME, __func__, this); TRETURN false; } // Pair second == true (is empty) can happen on OOM error. LPMESSAGE message = get_oom_base_message (m_mailitem); if (!message && pair.second) { if (message) { gpgol_release (message); } TRETURN true; } size_t r_nbytes = 0; char *mapi_body = mapi_get_body (message, &r_nbytes); gpgol_release (message); if (!mapi_body || !r_nbytes) { // Body or bytes are null. we are empty. xfree (mapi_body); log_debug ("%s:%s: MAPI error or empty message. Return true. %p.", SRCNAME, __func__, this); TRETURN true; } if (r_nbytes > 10 && !strncmp (mapi_body, "-----BEGIN", 10)) { // Body is crypt. log_debug ("%s:%s: MAPI Crypt marker detected. Return true. %p.", SRCNAME, __func__, this); xfree (mapi_body); TRETURN true; } xfree (mapi_body); log_debug ("%s:%s: Found mapi body. Return false. %p.", SRCNAME, __func__, this); TRETURN false; } std::string Mail::getVerificationResultDump () { TSTART; std::stringstream ss; ss << m_verify_result; TRETURN ss.str(); } void Mail::setBlockStatus_m () { TSTART; SPropValue prop; LPMESSAGE message = get_oom_base_message (m_mailitem); prop.ulPropTag = PR_BLOCK_STATUS; prop.Value.l = 1; HRESULT hr = message->SetProps (1, &prop, NULL); if (hr) { log_error ("%s:%s: can't set block value: hr=%#lx\n", SRCNAME, __func__, hr); } gpgol_release (message); TRETURN; } void Mail::setBlockHTML (bool value) { TSTART; m_block_html = value; TRETURN; } void Mail::incrementLocateCount () { TSTART; m_locate_count++; TRETURN; } void Mail::decrementLocateCount () { TSTART; m_locate_count--; if (m_locate_count < 0) { log_error ("%s:%s: locate count mismatch.", SRCNAME, __func__); m_locate_count = 0; } if (!m_locate_count) { autosecureCheck (); } TRETURN; } void Mail::autosecureCheck () { TSTART; if (!opt.autosecure || !opt.autoresolve || m_manual_crypto_opts || m_locate_count) { TRETURN; } bool ret = KeyCache::instance()->isMailResolvable (this); log_debug ("%s:%s: status %i", SRCNAME, __func__, ret); /* As we are safe to call at any time, because we need * to be triggered by the locator threads finishing we * need to actually set the draft info flags in * the ui thread. */ do_in_ui_thread_async (ret ? DO_AUTO_SECURE : DONT_AUTO_SECURE, this); TRETURN; } void Mail::setDoAutosecure_m (bool value) { TSTART; TRACEPOINT; LPMESSAGE msg = get_oom_base_message (m_mailitem); if (!msg) { TRACEPOINT; TRETURN; } /* We need to set a uuid so that autosecure can be disabled manually */ setUUID_o (); int old_flags = get_gpgol_draft_info_flags (msg); if (old_flags && m_first_autosecure_check && /* Someone with always sign and autosecure active * will want to get autoencryption. */ !(old_flags == 2 && opt.sign_default)) { /* They were set explicily before us. This can be * because they were a draft (which is bad) or * because they are a reply/forward to a crypto mail * or because there are conflicting settings. */ log_debug ("%s:%s: Mail %p had already flags set.", SRCNAME, __func__, m_mailitem); m_first_autosecure_check = false; m_manual_crypto_opts = true; gpgol_release (msg); TRETURN; } m_first_autosecure_check = false; set_gpgol_draft_info_flags (msg, value ? 3 : opt.sign_default ? 2 : 0); gpgol_release (msg); gpgoladdin_invalidate_ui(); TRETURN; } void Mail::installFolderEventHandler_o() { TSTART; TRACEPOINT; LPDISPATCH folder = get_oom_object (m_mailitem, "Parent"); if (!folder) { TRACEPOINT; TRETURN; } char *objName = get_object_name (folder); if (!objName || strcmp (objName, "MAPIFolder")) { log_debug ("%s:%s: Mail %p parent is not a mapi folder.", SRCNAME, __func__, m_mailitem); xfree (objName); gpgol_release (folder); TRETURN; } xfree (objName); char *path = get_oom_string (folder, "FullFolderPath"); if (!path) { TRACEPOINT; path = get_oom_string (folder, "FolderPath"); } if (!path) { log_error ("%s:%s: Mail %p parent has no folder path.", SRCNAME, __func__, m_mailitem); gpgol_release (folder); TRETURN; } std::string strPath (path); xfree (path); if (s_folder_events_map.find (strPath) == s_folder_events_map.end()) { log_debug ("%s:%s: Install folder events watcher for %s.", SRCNAME, __func__, anonstr (strPath.c_str())); const auto sink = install_FolderEvents_sink (folder); s_folder_events_map.insert (std::make_pair (strPath, sink)); } /* Folder already registered */ gpgol_release (folder); TRETURN; } void Mail::refCurrentItem() { TSTART; if (m_currentItemRef) { log_debug ("%s:%s: Current item multi ref. Bug?", SRCNAME, __func__); TRETURN; } /* This prevents a crash in Outlook 2013 when sending a mail as it * would unload too early. * * As it didn't crash when the mail was opened in Outlook Spy this * mimics that the mail is inspected somewhere else. */ m_currentItemRef = get_oom_object (m_mailitem, "GetInspector.CurrentItem"); TRETURN; } void Mail::releaseCurrentItem() { TSTART; if (!m_currentItemRef) { TRETURN; } log_oom ("%s:%s: releasing CurrentItem ref %p", SRCNAME, __func__, m_currentItemRef); LPDISPATCH tmp = m_currentItemRef; m_currentItemRef = nullptr; /* This can cause our destruction */ gpgol_release (tmp); TRETURN; } diff --git a/src/mailitem-events.cpp b/src/mailitem-events.cpp index fa12d88..5be58e7 100644 --- a/src/mailitem-events.cpp +++ b/src/mailitem-events.cpp @@ -1,940 +1,942 @@ /* mailitem-events.h - Event handling for mails. * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "common.h" #include "eventsink.h" #include "eventsinks.h" #include "mymapi.h" #include "mymapitags.h" #include "oomhelp.h" #include "ocidl.h" #include "windowmessages.h" #include "mail.h" #include "mapihelp.h" #include "gpgoladdin.h" #include "wks-helper.h" #undef _ #define _(a) utf8_gettext (a) const wchar_t *prop_blacklist[] = { L"Body", L"HTMLBody", L"To", /* Somehow this is done when a mail is opened */ L"CC", /* Ditto */ L"BCC", /* Ditto */ L"Categories", L"UnRead", L"OutlookVersion", L"OutlookInternalVersion", L"ReceivedTime", L"InternetCodepage", + L"ConversationIndex", + L"Subject", NULL }; typedef enum { AfterWrite = 0xFC8D, AttachmentAdd = 0xF00B, AttachmentRead = 0xF00C, AttachmentRemove = 0xFBAE, BeforeAttachmentAdd = 0xFBB0, BeforeAttachmentPreview = 0xFBAF, BeforeAttachmentRead = 0xFBAB, BeforeAttachmentSave = 0xF00D, BeforeAttachmentWriteToTempFile = 0xFBB2, BeforeAutoSave = 0xFC02, BeforeCheckNames = 0xF00A, BeforeDelete = 0xFA75, BeforeRead = 0xFC8C, Close = 0xF004, CustomAction = 0xF006, CustomPropertyChange = 0xF008, Forward = 0xF468, Open = 0xF003, PropertyChange = 0xF009, Read = 0xF001, ReadComplete = 0xFC8F, Reply = 0xF466, ReplyAll = 0xF467, Send = 0xF005, Unload = 0xFBAD, Write = 0xF002 } MailEvent; /* Mail Item Events */ BEGIN_EVENT_SINK(MailItemEvents, IDispatch) /* We are still in the class declaration */ private: Mail * m_mail; /* The mail object related to this mailitem */ }; MailItemEvents::MailItemEvents() : m_object(NULL), m_pCP(NULL), m_cookie(0), m_ref(1), m_mail(NULL) { } MailItemEvents::~MailItemEvents() { if (m_pCP) m_pCP->Unadvise(m_cookie); if (m_object) gpgol_release (m_object); } static bool propchangeWarnShown = false; static bool attachRemoveWarnShown = false; static bool addinsLogged = false; static DWORD WINAPI do_delayed_locate (LPVOID arg) { Sleep(100); do_in_ui_thread (RECIPIENT_ADDED, arg); return 0; } /* The main Invoke function. The return value of this function does not appear to have any effect on outlook although I have read in an example somewhere that you should return S_OK so that outlook continues to handle the event I have not yet seen any effect by returning error values here and no MSDN documentation about the return values. */ EVENT_SINK_INVOKE(MailItemEvents) { USE_INVOKE_ARGS TSTART; if (!m_mail) { m_mail = Mail::getMailForItem (m_object); if (!m_mail) { log_error ("%s:%s: mail event without mail object known. Bug.", SRCNAME, __func__); TRETURN S_OK; } } bool is_reply = false; switch(dispid) { case Open: { log_oom ("%s:%s: Open : %p", SRCNAME, __func__, m_mail); int draft_flags = 0; if (!opt.encrypt_default && !opt.sign_default) { TRETURN S_OK; } LPMESSAGE message = get_oom_base_message (m_object); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); TBREAK; } if (opt.encrypt_default) { draft_flags = 1; } if (opt.sign_default) { draft_flags += 2; } set_gpgol_draft_info_flags (message, draft_flags); gpgol_release (message); TBREAK; } case BeforeRead: { log_oom ("%s:%s: BeforeRead : %p", SRCNAME, __func__, m_mail); if (GpgolAddin::get_instance ()->isShutdown()) { log_debug ("%s:%s: Ignoring read after shutdown.", SRCNAME, __func__); TBREAK; } if (m_mail->preProcessMessage_m ()) { log_error ("%s:%s: Pre process message failed.", SRCNAME, __func__); } TBREAK; } case Read: { log_oom ("%s:%s: Read : %p", SRCNAME, __func__, m_mail); if (!addinsLogged) { // We do it here as this nearly always comes and we want to remove // as much as possible from the startup time. log_addins (); addinsLogged = true; } if (!m_mail->isCryptoMail ()) { log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.", SRCNAME, __func__, m_mail); /* Ensure that no wrong sigstatus is shown */ CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0, NULL)); TBREAK; } if (m_mail->setUUID_o ()) { log_debug ("%s:%s: Failed to set uuid.", SRCNAME, __func__); delete m_mail; /* deletes this, too */ TRETURN S_OK; } if (m_mail->decryptVerify_o ()) { log_error ("%s:%s: Decrypt message failed.", SRCNAME, __func__); } if (!opt.enable_smime && m_mail->isSMIME_m ()) { /* We want to save the mail when it's an smime mail and smime is disabled to revert it. */ log_debug ("%s:%s: S/MIME mail but S/MIME is disabled." " Need save.", SRCNAME, __func__); m_mail->setNeedsSave (true); } TBREAK; } case PropertyChange: { if (!parms || parms->cArgs != 1 || parms->rgvarg[0].vt != VT_BSTR || !parms->rgvarg[0].bstrVal) { log_error ("%s:%s: Unexpected params.", SRCNAME, __func__); TBREAK; } const wchar_t *prop_name = parms->rgvarg[0].bstrVal; if (!m_mail->isCryptoMail ()) { if (m_mail->hasOverrideMimeData()) { /* This is a mail created by us. Ignore propchanges. */ TBREAK; } if (!wcscmp (prop_name, L"To") /* || !wcscmp (prop_name, L"BCC") || !wcscmp (prop_name, L"CC") Testing shows that Outlook always sends these three in a row */) { if (opt.autosecure || (m_mail->needs_crypto_m () & 1)) { /* XXX Racy race. This is a fix for crashes that happend if a resolved recipient is copied an pasted. If we then access the recipients object in the Property Change event we crash. Thus we do the delay dance. */ HANDLE thread = CreateThread (NULL, 0, do_delayed_locate, (LPVOID) m_mail, 0, NULL); CloseHandle(thread); } } TBREAK; } for (const wchar_t **cur = prop_blacklist; *cur; cur++) { if (!wcscmp (prop_name, *cur)) { log_oom ("%s:%s: Message %p propchange: %ls discarded.", SRCNAME, __func__, m_object, prop_name); TRETURN S_OK; } } log_oom ("%s:%s: Message %p propchange: %ls.", SRCNAME, __func__, m_object, prop_name); if (!wcscmp (prop_name, L"SendUsingAccount")) { bool sent = get_oom_bool (m_object, "Sent"); if (sent) { log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ", SRCNAME, __func__, m_object); TRETURN S_OK; } log_debug ("%s:%s: Message %p looks like send again.", SRCNAME, __func__, m_object); m_mail->setIsSendAgain (true); TRETURN S_OK; } /* We have tried several scenarios to handle propery changes. Only save the property in MAPI and call MAPI SaveChanges worked and did not leak plaintext but this caused outlook still to break the attachments of PGP/MIME Mails into two attachments and add them as winmail.dat so other clients are broken. Alternatively reverting the mail, saving the property and then decrypt again also worked a bit but there were some weird side effects and breakages. But this has the usual problem of a revert that the mail is created by outlook and e.g. multipart/signed signatures from most MUA's are broken. Some things to try out might be the close approach and then another open or a selection change. But for now we just warn. As a workardound a user should make property changes when the mail was not read by us. */ if (propchangeWarnShown) { TRETURN S_OK; } wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet")); char *fmt; gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n" "Property changes are not yet handled for crypto messages.\n\n" "To workaround this limitation please change the property when the " "message is not open in any window and not selected in the " "messagelist.\n\nFor example by right clicking but not selecting the message.\n"), wchar_to_utf8(prop_name)); memdbg_alloc (fmt); wchar_t *msg = utf8_to_wchar (fmt); xfree (fmt); MessageBoxW (get_active_hwnd(), msg, title, MB_ICONINFORMATION | MB_OK); xfree (msg); xfree (title); propchangeWarnShown = true; TRETURN S_OK; } case CustomPropertyChange: { log_oom ("%s:%s: CustomPropertyChange : %p", SRCNAME, __func__, m_mail); /* TODO */ TBREAK; } case Send: { /* This is the only event where we can cancel the send of a mailitem. But it is too early for us to encrypt as the MAPI structures are not yet filled. Crypto based on the Outlook Object Model data did not work as the messages were only sent out empty. See 2b376a48 for a try of this. This is why we store send_seen and invoke a save which may result in an error but only after triggering all the behavior we need -> filling mapi structures and invoking the AfterWrite handler where we encrypt. If this encryption is successful and we pass the send as then the encrypted data is sent. */ log_oom ("%s:%s: Send : %p", SRCNAME, __func__, m_mail); if (!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail) { log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p", SRCNAME, __func__, m_mail, m_object); TBREAK; } if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF)) { log_debug ("%s:%s: Uncancellable send event.", SRCNAME, __func__); TBREAK; } if (m_mail->cryptState () == Mail::NoCryptMail && m_mail->needs_crypto_m ()) { log_debug ("%s:%s: Send event for crypto mail %p saving and starting.", SRCNAME, __func__, m_mail); if (!m_mail->isAsyncCryptDisabled()) { /* Obtain a reference of the current item. This prevents * an early unload which would crash Outlook 2013 * * As it didn't crash when the mail was opened in Outlook Spy this * mimics that the mail is inspected somewhere else. */ m_mail->refCurrentItem (); } // First contact with a mail to encrypt update // state and oom data. m_mail->updateOOMData_o (); m_mail->setCryptState (Mail::NeedsFirstAfterWrite); // Check inline response state before the write. m_mail->check_inline_response (); // Save the Mail invoke_oom_method (m_object, "Save", NULL); if (!m_mail->isAsyncCryptDisabled ()) { // The afterwrite in the save should have triggered // the encryption. We cancel send for our asyncness. // Cancel send *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; TBREAK; } else { if (m_mail->cryptState () == Mail::NoCryptMail) { // Crypto failed or was canceled log_debug ("%s:%s: Message %p mail %p cancelling send - " "Crypto failed or canceled.", SRCNAME, __func__, m_object, m_mail); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; TBREAK; } // For inline response we can't trigger send programatically // so we do the encryption in sync. if (m_mail->cryptState () == Mail::NeedsUpdateInOOM) { m_mail->updateCryptOOM_o (); } if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite) { m_mail->setCryptState (Mail::WantsSendMIME); } if (m_mail->getDoPGPInline () && m_mail->cryptState () != Mail::WantsSendInline) { log_debug ("%s:%s: Message %p mail %p cancelling send - " "Invalid state.", SRCNAME, __func__, m_object, m_mail); gpgol_bug (m_mail->getWindow (), ERR_INLINE_BODY_INV_STATE); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; TBREAK; } } } if (m_mail->cryptState () == Mail::WantsSendInline) { if (!m_mail->hasCryptedOrEmptyBody_o ()) { log_debug ("%s:%s: Message %p mail %p cancelling send - " "not encrypted or not empty body detected.", SRCNAME, __func__, m_object, m_mail); gpgol_bug (m_mail->getWindow (), ERR_WANTS_SEND_INLINE_BODY); m_mail->setCryptState (Mail::NoCryptMail); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; TBREAK; } log_debug ("%s:%s: Passing send event for no-mime message %p.", SRCNAME, __func__, m_object); WKSHelper::instance()->allow_notify (1000); TBREAK; } if (m_mail->cryptState () == Mail::WantsSendMIME) { if (!m_mail->hasCryptedOrEmptyBody_o ()) { /* The safety checks here trigger too often. Somehow for some users the body is not empty after the encryption but when it is sent it is still sent with the crypto content because the encrypted MIME Structure is used because it is correct in MAPI land. For safety reasons enabling the checks might be better but until we figure out why for some users the body replacement does not work we have to disable them. Otherwise GpgOL is unusuable for such users. GnuPG-Bug-Id: T3875 */ #define DISABLE_SAFTEY_CHECKS #ifndef DISABLE_SAFTEY_CHECKS gpgol_bug (m_mail->getWindow (), ERR_WANTS_SEND_MIME_BODY); log_debug ("%s:%s: Message %p mail %p cancelling send mime - " "not encrypted or not empty body detected.", SRCNAME, __func__, m_object, m_mail); m_mail->setCryptState (Mail::NoCryptMail); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; TBREAK; #else log_debug ("%s:%s: Message %p mail %p - " "not encrypted or not empty body detected - MIME.", SRCNAME, __func__, m_object, m_mail); #endif } /* Now we adress T3656 if Outlooks internal S/MIME is somehow * mixed in (even if it is enabled and then disabled) it might * cause strange behavior in that it sends the plain message * and not the encrypted message. Tests have shown that we can * bypass that by calling submit message on our base * message. * * We do this conditionally as our other way of using OOM * to send is proven to work and we don't want to mess * with it. */ // Get the Message class. HRESULT hr; LPSPropValue propval = NULL; // It's important we use the _not_ base message here. LPMESSAGE message = get_oom_message (m_object); hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval); gpgol_release (message); if (FAILED (hr)) { log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n", SRCNAME, __func__, hr); gpgol_release (message); TBREAK; } if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL")) { // Does not have a message class by us. log_debug ("%s:%s: Message %p - No GpgOL Message class after encryption. cls is: '%s'", SRCNAME, __func__, m_object, propval->Value.lpszA); log_debug ("%s:%s: Message %p - Activating T3656 Workaround", SRCNAME, __func__, m_object); message = get_oom_base_message (m_object); if (message) { // It's important we use the _base_ message here. mapi_save_changes (message, FORCE_SAVE); message->SubmitMessage(0); gpgol_release (message); // Close the composer and trigger unloads CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0, NULL)); } else { gpgol_bug (nullptr, ERR_GET_BASE_MSG_FAILED); } // Cancel send *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; } MAPIFreeBuffer (propval); if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE) { TBREAK; } log_debug ("%s:%s: Passing send event for mime-encrypted message %p.", SRCNAME, __func__, m_object); WKSHelper::instance()->allow_notify (1000); TBREAK; } else { log_debug ("%s:%s: Message %p cancelling send - " "crypto or second save failed.", SRCNAME, __func__, m_object); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; } TRETURN S_OK; } case Write: { log_oom ("%s:%s: Write : %p", SRCNAME, __func__, m_mail); /* This is a bit strange. We sometimes get multiple write events without a read in between. When we access the message in the second event it fails and if we cancel the event outlook crashes. So we have keep the m_needs_wipe state variable to keep track of that. */ if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF)) { /* This happens in the weird case */ log_debug ("%s:%s: Uncancellable write event.", SRCNAME, __func__); TBREAK; } if (m_mail->passWrite()) { log_debug ("%s:%s: Passing write because passNextWrite was set for %p", SRCNAME, __func__, m_mail); TBREAK; } if (m_mail->isCryptoMail () && !m_mail->needsSave ()) { Mail *last_mail = Mail::getLastMail (); if (Mail::isValidPtr (last_mail)) { /* We want to identify here if there was a mail created that should receive the contents of this mail. For this we check for a write in the same loop as a mail creation. Now when switching from one mail to another this is also what happens. The new mail is loaded and the old mail is written. To distinguish the two we check that the new mail does not have an entryID, a Subject and No Size. Maybe just size or entryID would be enough but better save then sorry. Security consideration: Worst case we pass the write here but an unload follows before we get the scheduled revert. This would leak plaintext. But does not happen in our tests. Similarly if we crash or Outlook is closed before we see this revert. But as we immediately revert after the write this should also not happen. */ const std::string lastSubject = last_mail->getSubject_o (); char *lastEntryID = get_oom_string (last_mail->item (), "EntryID"); int lastSize = get_oom_int (last_mail->item (), "Size"); std::string lastEntryStr; if (lastEntryID) { lastEntryStr = lastEntryID; xfree (lastEntryID); } if (!lastSize && !lastEntryStr.size () && !lastSubject.size ()) { log_debug ("%s:%s: Write in the same loop as empty load." " Pass but schedule revert.", SRCNAME, __func__); /* This might be a forward. So don't invalidate yet. */ // Mail::clearLastMail (); do_in_ui_thread_async (REVERT_MAIL, m_mail); TRETURN S_OK; } } /* We cancel the write event to stop outlook from excessively syncing our changes. if smime support is disabled and we still have an smime mail we also don't want to cancel the write event to enable reverting this mails. */ *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; log_debug ("%s:%s: Canceling write event.", SRCNAME, __func__); TRETURN S_OK; } if (m_mail->isCryptoMail () && m_mail->needsSave () && m_mail->revert_o ()) { /* An error cleaning the mail should not happen normally. But just in case there is an error we cancel the write here. */ log_debug ("%s:%s: Failed to remove plaintext.", SRCNAME, __func__); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; } if (!m_mail->isCryptoMail () && m_mail->is_forwarded_crypto_mail () && !m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail) { /* We are sure now that while this is a forward of an encrypted * mail that the forward should not be signed or encrypted. So * it's not constructed by us. We need to remove our attachments * though so that they are not included in the forward. */ log_debug ("%s:%s: Writing unencrypted forward of crypt mail. " "Removing attachments. mail: %p item: %p", SRCNAME, __func__, m_mail, m_object); if (m_mail->removeOurAttachments_o ()) { // Worst case we forward some encrypted data here not // a security problem, so let it pass. log_error ("%s:%s: Failed to remove our attachments.", SRCNAME, __func__); } /* Remove marker because we did this now. */ m_mail->setIsForwardedCryptoMail (false); } log_debug ("%s:%s: Passing write event.", SRCNAME, __func__); m_mail->setNeedsSave (false); TBREAK; } case AfterWrite: { log_oom ("%s:%s: AfterWrite : %p", SRCNAME, __func__, m_mail); if (m_mail->cryptState () == Mail::NeedsFirstAfterWrite) { /* Seen the first after write. Advance the state */ m_mail->setCryptState (Mail::NeedsActualCrypt); if (m_mail->encryptSignStart_o ()) { log_debug ("%s:%s: Encrypt sign start failed.", SRCNAME, __func__); m_mail->setCryptState (Mail::NoCryptMail); } TRETURN S_OK; } if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite) { m_mail->setCryptState (Mail::NeedsUpdateInMAPI); m_mail->updateCryptMAPI_m (); TRETURN S_OK; } TBREAK; } case Close: { log_oom ("%s:%s: Close : %p", SRCNAME, __func__, m_mail); if (m_mail->isCryptoMail ()) { /* Close. This happens when an Opened mail is closed. To prevent the question of wether or not to save the changes (Which would save the decrypted data without an event to prevent it) we cancel the close and then either close it with discard changes or revert / save it. Contrary to documentation we can invoke close from close. */ if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF)) { /* This happens in the weird case */ log_debug ("%s:%s: Uncancellable close event.", SRCNAME, __func__); TBREAK; } if (m_mail->getCloseTriggered ()) { /* Our close with discard changes, pass through */ m_mail->setCloseTriggered (false); TRETURN S_OK; } *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; log_oom ("%s:%s: Canceling close event.", SRCNAME, __func__); if (Mail::close(m_mail)) { log_debug ("%s:%s: Close request failed.", SRCNAME, __func__); } } TRETURN S_OK; } case Unload: { log_oom ("%s:%s: Unload : %p", SRCNAME, __func__, m_mail); log_debug ("%s:%s: Removing Mail for message: %p.", SRCNAME, __func__, m_object); delete m_mail; log_oom ("%s:%s: deletion done", SRCNAME, __func__); memdbg_dump (); TRETURN S_OK; } /* Fallthrough */ case ReplyAll: case Reply: is_reply = true; __attribute__ ((fallthrough)); case Forward: { log_oom ("%s:%s: %s : %p", SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail); int draft_flags = 0; if (opt.encrypt_default) { draft_flags = 1; } if (opt.sign_default) { draft_flags += 2; } bool is_crypto_mail = m_mail->isCryptoMail (); /* If it is a crypto mail and the settings should not be taken * from the crypto mail and always encrypt / sign is on. Or * If it is not a crypto mail and we have automaticalls sign_encrypt. */ if ((is_crypto_mail && !opt.reply_crypt && draft_flags) || (!is_crypto_mail && draft_flags)) { /* Check if we can use the dispval */ if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) && parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF)) { LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal); if (msg) { set_gpgol_draft_info_flags (msg, draft_flags); gpgol_release (msg); } else { log_error ("%s:%s: Failed to get base message.", SRCNAME, __func__); } } else { log_error ("%s:%s: Unexpected parameters.", SRCNAME, __func__); } } if (!is_crypto_mail) { /* Replys to non crypto mails do not interest us anymore. */ TBREAK; } Mail *last_mail = Mail::getLastMail (); if (Mail::isValidPtr (last_mail)) { /* We want to identify here if there was a mail created that should receive the contents of this mail. For this we check for a forward in the same loop as a mail creation. We need to do it this complicated and can't just use get_mail_for_item because the mailitem pointer we get here is a different one then the one with which the mail was loaded. */ char *lastEntryID = get_oom_string (last_mail->item (), "EntryID"); int lastSize = get_oom_int (last_mail->item (), "Size"); std::string lastEntryStr; if (lastEntryID) { lastEntryStr = lastEntryID; xfree (lastEntryID); } if (!lastSize && !lastEntryStr.size ()) { if (!is_reply) { log_debug ("%s:%s: Forward in the same loop as empty " "load Marking %p (item %p) as forwarded.", SRCNAME, __func__, last_mail, last_mail->item ()); last_mail->setIsForwardedCryptoMail (true); } else { log_debug ("%s:%s: Reply in the same loop as empty " "load Marking %p (item %p) as reply.", SRCNAME, __func__, last_mail, last_mail->item ()); } if (m_mail->isBlockHTML ()) { std::string buf; /** TRANSLATORS: Part of a warning dialog that disallows reply and forward with contents */ buf = is_reply ? _("You are replying to an unsigned S/MIME " "email.") : _("You are forwarding an unsigned S/MIME " "email."); buf +="\n\n"; buf += _("In this version of S/MIME an attacker could " "use the missing signature to have you " "decrypt contents from a different, otherwise " "completely unrelated email and place it in the " "quote so they can get hold of it.\n" "This is why we only allow quoting to be done manually."); buf += "\n\n"; buf += _("Please copy the relevant contents and insert " "them into the new email."); gpgol_message_box (get_active_hwnd (), buf.c_str(), _("GpgOL"), MB_OK); do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000); } } // We can now invalidate the last mail Mail::clearLastMail (); } log_oom ("%s:%s: Reply Forward ReplyAll: %p", SRCNAME, __func__, m_mail); if (!opt.reply_crypt) { TBREAK; } int crypto_flags = 0; if (!(crypto_flags = m_mail->getCryptoFlags ())) { TBREAK; } if (parms->cArgs != 2 || parms->rgvarg[1].vt != (VT_DISPATCH) || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF)) { /* This happens in the weird case */ log_debug ("%s:%s: Unexpected args %i %x %x named: %i", SRCNAME, __func__, parms->cArgs, parms->rgvarg[0].vt, parms->rgvarg[1].vt, parms->cNamedArgs); TBREAK; } LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal); if (!msg) { log_debug ("%s:%s: Failed to get base message", SRCNAME, __func__); TBREAK; } set_gpgol_draft_info_flags (msg, crypto_flags); gpgol_release (msg); TBREAK; } case AttachmentRemove: { log_oom ("%s:%s: AttachmentRemove: %p", SRCNAME, __func__, m_mail); if (!m_mail->isCryptoMail () || attachRemoveWarnShown || m_mail->attachmentRemoveWarningDisabled ()) { TRETURN S_OK; } gpgol_message_box (get_active_hwnd (), _("Attachments are part of the crypto message.\nThey " "can't be permanently removed and will be shown again the next " "time this message is opened."), _("Sorry, that's not possible, yet"), MB_OK); attachRemoveWarnShown = true; TRETURN S_OK; } default: log_oom ("%s:%s: Message:%p Unhandled Event: %lx \n", SRCNAME, __func__, m_object, dispid); } TRETURN S_OK; } END_EVENT_SINK(MailItemEvents, IID_MailItemEvents) diff --git a/src/mimedataprovider.cpp b/src/mimedataprovider.cpp index 25e576c..6afcdcd 100644 --- a/src/mimedataprovider.cpp +++ b/src/mimedataprovider.cpp @@ -1,1117 +1,1156 @@ /* mimedataprover.cpp - GpgME dataprovider for mime data * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "common_indep.h" #include "xmalloc.h" #include #include #include #include "mimedataprovider.h" #include "rfc822parse.h" #include "rfc2047parse.h" #include "attachment.h" #include "cpphelp.h" #ifndef HAVE_W32_SYSTEM #define stricmp strcasecmp #endif /* The maximum length of a line we are able to process. RFC822 allows only for 1000 bytes; thus 2000 seems to be a reasonable value. */ #define LINEBUFSIZE 2000 /* How much data is read at once in collect */ #define BUFSIZE 8192 #include /* To keep track of the MIME message structures we use a linked list with each item corresponding to one part. */ struct mimestruct_item_s; typedef struct mimestruct_item_s *mimestruct_item_t; struct mimestruct_item_s { mimestruct_item_t next; unsigned int level; /* Level in the hierarchy of that part. 0 indicates the outer body. */ char *filename; /* Malloced filename or NULL. */ char *cid; /* Malloced content id or NULL. */ char *charset; /* Malloced charset or NULL. */ char content_type[1]; /* String with the content type. */ }; /* The context object we use to track information. */ struct mime_context { rfc822parse_t msg; /* The handle of the RFC822 parser. */ int verify_mode; /* True if we want to verify a signature. */ int nesting_level; /* Current MIME nesting level. */ int in_data; /* We are currently in data (body or attachment). */ int body_seen; /* True if we have seen a part we consider the body of the message. */ std::shared_ptr current_attachment; /* A pointer to the current attachment */ int collect_body; /* True if we are collcting the body */ int collect_html_body; /* True if we are collcting the html body */ int collect_crypto_data; /* True if we are collecting the signed data. */ int collect_signature; /* True if we are collecting a signature. */ int pgp_marker_checked; /* Checked if the first body line is pgp marker*/ int is_encrypted; /* True if we are working on an encrypted mail. */ int start_hashing; /* Flag used to start collecting signed data. */ int hashing_level; /* MIME level where we started hashing. */ int is_qp_encoded; /* Current part is QP encoded. */ int is_base64_encoded; /* Current part is base 64 encoded. */ int is_body; /* The current part belongs to the body. */ protocol_t protocol; /* The detected crypto protocol. */ int part_counter; /* Counts the number of processed parts. */ int any_boundary; /* Indicates whether we have seen any boundary which means that we are actually working on a MIME message and not just on plain rfc822 message. */ /* A linked list describing the structure of the mime message. This list gets build up while parsing the message. */ mimestruct_item_t mimestruct; mimestruct_item_t *mimestruct_tail; mimestruct_item_t mimestruct_cur; int any_attachments_created; /* True if we created a new atatchment. */ b64_state_t base64; /* The state of the Base-64 decoder. */ gpg_error_t parser_error; /* Indicates that we encountered a error from the parser. */ }; typedef struct mime_context *mime_context_t; /* Print the message event EVENT. */ static void debug_message_event (rfc822parse_event_t event) { const char *s; switch (event) { case RFC822PARSE_OPEN: s= "Open"; break; case RFC822PARSE_CLOSE: s= "Close"; break; case RFC822PARSE_CANCEL: s= "Cancel"; break; case RFC822PARSE_T2BODY: s= "T2Body"; break; case RFC822PARSE_FINISH: s= "Finish"; break; case RFC822PARSE_RCVD_SEEN: s= "Rcvd_Seen"; break; case RFC822PARSE_LEVEL_DOWN: s= "Level_Down"; break; case RFC822PARSE_LEVEL_UP: s= "Level_Up"; break; case RFC822PARSE_BOUNDARY: s= "Boundary"; break; case RFC822PARSE_LAST_BOUNDARY: s= "Last_Boundary"; break; case RFC822PARSE_BEGIN_HEADER: s= "Begin_Header"; break; case RFC822PARSE_PREAMBLE: s= "Preamble"; break; case RFC822PARSE_EPILOGUE: s= "Epilogue"; break; default: s= "[unknown event]"; break; } log_data ("%s: rfc822 event %s\n", SRCNAME, s); } /* returns true if the BER encoded data in BUFFER is CMS signed data. LENGTH gives the length of the buffer, for correct detection LENGTH should be at least about 24 bytes. */ #if 0 static int is_cms_signed_data (const char *buffer, size_t length) { TSTART; const char *p = buffer; size_t n = length; tlvinfo_t ti; if (parse_tlv (&p, &n, &ti)) { TRETURN 0; } if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE && ti.is_cons) ) TRETURN 0; if (parse_tlv (&p, &n, &ti)) { TRETURN 0; } if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID && !ti.is_cons && ti.length) || ti.length > n) TRETURN 0; if (ti.length == 9 && !memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9)) { TRETURN 1; } TRETURN 0; } #endif /* Process the transition to body event. This means we have received the empty line indicating the body and should now check the headers to see what to do about this part. This is mostly a C style function because it was based on the old c mimeparser. */ static int t2body (MimeDataProvider *provider, rfc822parse_t msg) { TSTART; rfc822parse_field_t field; mime_context_t ctx = provider->mime_context (); const char *ctmain, *ctsub; const char *s; size_t off; char *p; int is_text = 0; int is_text_attachment = 0; + int is_protected_headers = 0; char *filename = NULL; char *cid = NULL; char *charset = NULL; bool ignore_cid = false; /* Figure out the encoding. */ ctx->is_qp_encoded = 0; ctx->is_base64_encoded = 0; p = rfc822parse_get_field (msg, "Content-Transfer-Encoding", -1, &off); if (p) { if (!stricmp (p+off, "quoted-printable")) ctx->is_qp_encoded = 1; else if (!stricmp (p+off, "base64")) { ctx->is_base64_encoded = 1; b64_init (&ctx->base64); } xfree (p); } /* Get the filename from the header. */ field = rfc822parse_parse_field (msg, "Content-Disposition", -1); if (field) { s = rfc822parse_query_parameter (field, "filename", 0); if (s) filename = rfc2047_parse (s); s = rfc822parse_query_parameter (field, NULL, 1); if (s && strstr (s, "attachment")) { log_debug ("%s:%s: Found Content-Disposition attachment." " Ignoring content-id to avoid hiding.", SRCNAME, __func__); ignore_cid = true; } /* This is a bit of a taste matter how to treat inline attachments. Outlook does not show them inline so we should not put it in the body either as we have no way to show that it was actually an attachment. For something like an inline patch it is better to add it as an attachment instead of just putting it in the body. The handling in the old parser was: if (s && strcmp (s, "inline")) not_inline_text = 1; */ if (ctx->body_seen) { /* Some MUA's like kontact e3.5 send the body as an inline text attachment. So if we have not seen the body yet we treat the first text/plain element as the body and not as an inline attachment. */ is_text_attachment = 1; } rfc822parse_release_field (field); } /* Process the Content-type and all its parameters. */ ctmain = ctsub = NULL; field = rfc822parse_parse_field (msg, "Content-Type", -1); if (field) ctmain = rfc822parse_query_media_type (field, &ctsub); if (!ctmain) { /* Either there is no content type field or it is faulty; in both cases we fall back to text/plain. */ ctmain = "text"; ctsub = "plain"; } log_data ("%s:%s: ctx=%p, ct=`%s/%s'\n", SRCNAME, __func__, ctx, ctmain, ctsub); s = rfc822parse_query_parameter (field, "charset", 0); if (s) charset = xstrdup (s); if (!filename) { /* Check for Content-Type name if Content-Disposition filename was not found */ s = rfc822parse_query_parameter (field, "name", 0); if (s) filename = rfc2047_parse (s); } /* Parse a Content Id header */ if (!ignore_cid) { p = rfc822parse_get_field (msg, "Content-Id", -1, &off); if (p) { cid = xstrdup (p+off); xfree (p); } } /* Update our idea of the entire MIME structure. */ { mimestruct_item_t ms; ms = (mimestruct_item_t) xmalloc (sizeof *ms + strlen (ctmain) + 1 + strlen (ctsub)); ctx->mimestruct_cur = ms; *ctx->mimestruct_tail = ms; ctx->mimestruct_tail = &ms->next; ms->next = NULL; strcpy (stpcpy (stpcpy (ms->content_type, ctmain), "/"), ctsub); ms->level = ctx->nesting_level; ms->filename = filename; ms->cid = cid; filename = NULL; ms->charset = charset; charset = NULL; } if (!strcmp (ctmain, "multipart")) { /* We don't care about the top level multipart layer but wait until it comes to the actual parts which then will get stored as attachments. For now encapsulated signed or encrypted containers are not processed in a special way as they should. Except for the simple verify mode. */ if (!provider->signature() && !strcmp (ctsub, "signed") && (s = rfc822parse_query_parameter (field, "protocol", 0))) { if (!strcmp (s, "application/pgp-signature")) ctx->protocol = PROTOCOL_OPENPGP; else if (!strcmp (s, "application/pkcs7-signature") || !strcmp (s, "application/x-pkcs7-signature")) ctx->protocol = PROTOCOL_SMIME; else ctx->protocol = PROTOCOL_UNKNOWN; /* Need to start the hashing after the next boundary. */ ctx->start_hashing = 1; } else if (!strcmp (ctsub, "encrypted") && (s = rfc822parse_query_parameter (field, "protocol", 0))) { if (!strcmp (s, "application/pgp-encrypted")) ctx->protocol = PROTOCOL_OPENPGP; /* We expect an encrypted mime part. */ ctx->is_encrypted = 1; } } else if (!strcmp (ctmain, "text")) { is_text = !strcmp (ctsub, "html")? 2:1; + is_protected_headers = (!strcmp (ctsub, "rfc822-headers") && + rfc822parse_query_parameter (field, + "protected-headers", -1)); + if (is_protected_headers) + { + log_data ("%s:%s: Found protected headers.", + SRCNAME, __func__); + provider->m_had_protected_headers = true; + } } else if (ctx->nesting_level == 1 && !provider->signature() && !strcmp (ctmain, "application") && ((ctx->protocol == PROTOCOL_OPENPGP && !strcmp (ctsub, "pgp-signature")) || (ctx->protocol == PROTOCOL_SMIME && (!strcmp (ctsub, "pkcs7-signature") || !strcmp (ctsub, "x-pkcs7-signature"))))) { /* This is the second part of a MOSS signature. We only support here full messages thus checking the nesting level is sufficient. We do this only for the first signature (i.e. if sig_data has not been set yet). We also do this only while in verify mode because we don't want to write a full MUA. */ ctx->collect_signature = 1; log_data ("%s:%s: Collecting signature.", SRCNAME, __func__); } else if (ctx->nesting_level == 1 && ctx->is_encrypted && !strcmp (ctmain, "application") && (ctx->protocol == PROTOCOL_OPENPGP && !strcmp (ctsub, "octet-stream"))) { log_data ("%s:%s: Collecting encrypted PGP data.", SRCNAME, __func__); ctx->collect_crypto_data = 1; } else /* Other type. */ { /* Check whether this attachment is an opaque signed S/MIME part. We use a counter to later check that there is only one such part. */ if (!strcmp (ctmain, "application") && (!strcmp (ctsub, "pkcs7-mime") || !strcmp (ctsub, "x-pkcs7-mime"))) { log_data ("%s:%s: Collecting crypted S/MIME data.", SRCNAME, __func__); ctx->collect_crypto_data = 1; } } rfc822parse_release_field (field); /* (Content-type) */ - ctx->in_data = 1; + if (!is_protected_headers) + { + ctx->in_data = 1; + } log_data ("%s:%s: this body: nesting=%d partno=%d is_text=%d" - " charset=\"%s\"\n body_seen=%d is_text_attachment=%d", + " charset=\"%s\"\n body_seen=%d is_text_attachment=%d" + " is_protected_headers=%d", SRCNAME, __func__, ctx->nesting_level, ctx->part_counter, is_text, ctx->mimestruct_cur->charset?ctx->mimestruct_cur->charset:"", - ctx->body_seen, is_text_attachment); + ctx->body_seen, is_text_attachment, is_protected_headers); /* If this is a text part, decide whether we treat it as one of our bodies. */ if ((is_text && !is_text_attachment)) { if (is_text == 2) { ctx->body_seen = 2; ctx->collect_html_body = 1; ctx->collect_body = 0; log_debug ("%s:%s: Collecting HTML body.", SRCNAME, __func__); /* We need this crutch because of one liner html mails which would not be collected by the line collector if they dont have a linefeed at the end. */ provider->set_has_html_body (true); } else { log_debug ("%s:%s: Collecting text body.", SRCNAME, __func__); ctx->body_seen = 1; ctx->collect_body = 1; ctx->collect_html_body = 0; } } else if (!ctx->collect_crypto_data && ctx->nesting_level >= 1) { /* Treat it as an attachment. */ ctx->current_attachment = provider->create_attachment(); ctx->collect_body = 0; ctx->collect_html_body = 0; log_data ("%s:%s: Collecting attachment.", SRCNAME, __func__); } TRETURN 0; } static int message_cb (void *opaque, rfc822parse_event_t event, rfc822parse_t msg) { int retval = 0; MimeDataProvider *provider = static_cast (opaque); mime_context_t ctx = provider->mime_context(); debug_message_event (event); if (event == RFC822PARSE_BEGIN_HEADER || event == RFC822PARSE_T2BODY) { /* We need to check here whether to start collecting signed data because attachments might come without header lines and thus we won't see the BEGIN_HEADER event. */ if (ctx->start_hashing == 1) { ctx->start_hashing = 2; ctx->hashing_level = ctx->nesting_level; ctx->collect_crypto_data = 1; } } switch (event) { case RFC822PARSE_T2BODY: retval = t2body (provider, msg); break; case RFC822PARSE_LEVEL_DOWN: ctx->nesting_level++; break; case RFC822PARSE_LEVEL_UP: if (ctx->nesting_level) ctx->nesting_level--; else { log_error ("%s: ctx=%p, invalid structure: bad nesting level\n", SRCNAME, ctx); ctx->parser_error = gpg_error (GPG_ERR_GENERAL); } break; case RFC822PARSE_BOUNDARY: case RFC822PARSE_LAST_BOUNDARY: ctx->any_boundary = 1; ctx->in_data = 0; ctx->collect_body = 0; if (ctx->start_hashing == 2 && ctx->hashing_level == ctx->nesting_level) { ctx->start_hashing = 3; /* Avoid triggering it again. */ ctx->collect_crypto_data = 0; } break; case RFC822PARSE_BEGIN_HEADER: ctx->part_counter++; break; default: /* Ignore all other events. */ break; } return retval; } MimeDataProvider::MimeDataProvider(bool no_headers) : m_signature(nullptr), m_has_html_body(false), m_collect_everything(no_headers) { TSTART; memdbg_ctor ("MimeDataProvider"); m_mime_ctx = (mime_context_t) xcalloc (1, sizeof *m_mime_ctx); m_mime_ctx->msg = rfc822parse_open (message_cb, this); m_mime_ctx->mimestruct_tail = &m_mime_ctx->mimestruct; TRETURN; } #ifdef HAVE_W32_SYSTEM MimeDataProvider::MimeDataProvider(LPSTREAM stream, bool no_headers): MimeDataProvider(no_headers) { TSTART; if (stream) { stream->AddRef (); memdbg_addRef (stream); } else { log_error ("%s:%s called without stream ", SRCNAME, __func__); TRETURN; } log_data ("%s:%s Collecting data.", SRCNAME, __func__); collect_data (stream); log_data ("%s:%s Data collected.", SRCNAME, __func__); gpgol_release (stream); TRETURN; } #endif MimeDataProvider::MimeDataProvider(FILE *stream, bool no_headers): MimeDataProvider(no_headers) { TSTART; log_data ("%s:%s Collecting data from file.", SRCNAME, __func__); collect_data (stream); log_data ("%s:%s Data collected.", SRCNAME, __func__); TRETURN; } MimeDataProvider::~MimeDataProvider() { TSTART; memdbg_dtor ("MimeDataProvider"); log_debug ("%s:%s", SRCNAME, __func__); while (m_mime_ctx->mimestruct) { mimestruct_item_t tmp = m_mime_ctx->mimestruct->next; xfree (m_mime_ctx->mimestruct->filename); xfree (m_mime_ctx->mimestruct->charset); xfree (m_mime_ctx->mimestruct->cid); xfree (m_mime_ctx->mimestruct); m_mime_ctx->mimestruct = tmp; } rfc822parse_close (m_mime_ctx->msg); m_mime_ctx->current_attachment = NULL; xfree (m_mime_ctx); if (m_signature) { delete m_signature; } TRETURN; } bool MimeDataProvider::isSupported(GpgME::DataProvider::Operation op) const { return op == GpgME::DataProvider::Read || op == GpgME::DataProvider::Seek || op == GpgME::DataProvider::Write || op == GpgME::DataProvider::Release; } ssize_t MimeDataProvider::read(void *buffer, size_t size) { TSTART; log_data ("%s:%s: Reading: " SIZE_T_FORMAT "Bytes", SRCNAME, __func__, size); ssize_t bRead = m_crypto_data.read (buffer, size); if ((opt.enable_debug & DBG_DATA) && bRead) { std::string buf ((char *)buffer, bRead); if (!is_binary (buf)) { log_data ("%s:%s: Data: \n------\n%s\n------", SRCNAME, __func__, buf.c_str()); } else { log_data ("%s:%s: Hex Data: \n------\n%s\n------", SRCNAME, __func__, string_to_hex (buf).c_str ()); } } TRETURN bRead; } /* Split some raw data into lines and handle them accordingly. Returns the amount of bytes not taken from the input buffer. */ size_t MimeDataProvider::collect_input_lines(const char *input, size_t insize) { TSTART; char linebuf[LINEBUFSIZE]; const char *s = input; size_t pos = 0; size_t nleft = insize; size_t not_taken = nleft; size_t len = 0; /* Split the raw data into lines */ for (; nleft; nleft--, s++) { if (pos >= LINEBUFSIZE) { log_error ("%s:%s: rfc822 parser failed: line too long\n", SRCNAME, __func__); GpgME::Error::setSystemError (GPG_ERR_EIO); TRETURN not_taken; } if (*s != '\n') linebuf[pos++] = *s; else { /* Got a complete line. Remove the last CR. */ not_taken -= pos + 1; /* Pos starts at 0 so + 1 for it */ if (pos && linebuf[pos-1] == '\r') { pos--; } log_data ("%s:%s: Parsing line=`%.*s'\n", SRCNAME, __func__, (int)pos, linebuf); /* Check the next state */ if (rfc822parse_insert (m_mime_ctx->msg, (unsigned char*) linebuf, pos)) { log_error ("%s:%s: rfc822 parser failed: %s\n", SRCNAME, __func__, strerror (errno)); TRETURN not_taken; } /* Check if the first line of the body is actually a PGP Inline message. If so treat it as crypto data. */ if (!m_mime_ctx->pgp_marker_checked && m_mime_ctx->collect_body == 2) { m_mime_ctx->pgp_marker_checked = true; if (pos >= 27 && !strncmp ("-----BEGIN PGP MESSAGE-----", linebuf, 27)) { log_debug ("%s:%s: Found PGP Message in body.", SRCNAME, __func__); m_mime_ctx->collect_body = 0; m_mime_ctx->collect_crypto_data = 1; m_mime_ctx->start_hashing = 1; m_collect_everything = true; } } /* If we are currently in a collecting state actually collect that line */ if (m_mime_ctx->collect_crypto_data && m_mime_ctx->start_hashing) { /* Save the signed data. Note that we need to delay the CR/LF because the last line ending belongs to the next boundary. */ if (m_mime_ctx->collect_crypto_data == 2) { m_crypto_data.write ("\r\n", 2); } log_data ("Writing raw crypto data: %.*s", (int)pos, linebuf); m_crypto_data.write (linebuf, pos); m_mime_ctx->collect_crypto_data = 2; } if (m_mime_ctx->in_data && !m_mime_ctx->collect_signature && !m_mime_ctx->collect_crypto_data) { /* We are inside of an attachment part. Write it out. */ if (m_mime_ctx->in_data == 1) /* Skip the first line. */ m_mime_ctx->in_data = 2; int slbrk = 0; if (m_mime_ctx->is_qp_encoded) len = qp_decode (linebuf, pos, &slbrk); else if (m_mime_ctx->is_base64_encoded) len = b64_decode (&m_mime_ctx->base64, linebuf, pos); else len = pos; if (m_mime_ctx->collect_body) { if (m_mime_ctx->collect_body == 2) { m_body += std::string(linebuf, len); if (!m_mime_ctx->is_base64_encoded && !slbrk) { m_body += "\r\n"; } } if (m_body_charset.empty()) { m_body_charset = m_mime_ctx->mimestruct_cur->charset ? m_mime_ctx->mimestruct_cur->charset : ""; } m_mime_ctx->collect_body = 2; } else if (m_mime_ctx->collect_html_body) { if (m_mime_ctx->collect_html_body == 2) { m_html_body += std::string(linebuf, len); if (!m_mime_ctx->is_base64_encoded && !slbrk) { m_html_body += "\r\n"; } } if (m_html_charset.empty()) { m_html_charset = m_mime_ctx->mimestruct_cur->charset ? m_mime_ctx->mimestruct_cur->charset : ""; } m_mime_ctx->collect_html_body = 2; } else if (m_mime_ctx->current_attachment && len) { m_mime_ctx->current_attachment->get_data().write(linebuf, len); if (!m_mime_ctx->is_base64_encoded && !slbrk) { m_mime_ctx->current_attachment->get_data().write("\r\n", 2); } } else { log_data ("%s:%s Collecting ended / failed.", SRCNAME, __func__); } } else if (m_mime_ctx->in_data && m_mime_ctx->collect_signature) { /* We are inside of a signature attachment part. */ if (m_mime_ctx->collect_signature == 1) /* Skip the first line. */ m_mime_ctx->collect_signature = 2; else { int slbrk = 0; if (m_mime_ctx->is_qp_encoded) len = qp_decode (linebuf, pos, &slbrk); else if (m_mime_ctx->is_base64_encoded) len = b64_decode (&m_mime_ctx->base64, linebuf, pos); else len = pos; if (!m_signature) { m_signature = new GpgME::Data(); } if (len) m_signature->write(linebuf, len); if (!m_mime_ctx->is_base64_encoded && !slbrk) m_signature->write("\r\n", 2); } } else if (m_mime_ctx->in_data && !m_mime_ctx->start_hashing) { /* We are inside the data. That should be the actual ciphertext in the given encoding. */ int slbrk = 0; if (m_mime_ctx->is_qp_encoded) len = qp_decode (linebuf, pos, &slbrk); else if (m_mime_ctx->is_base64_encoded) len = b64_decode (&m_mime_ctx->base64, linebuf, pos); else len = pos; log_data ("Writing crypto data: %.*s", (int)pos, linebuf); if (len) m_crypto_data.write(linebuf, len); if (!m_mime_ctx->is_base64_encoded && !slbrk) m_crypto_data.write("\r\n", 2); } /* Continue with next line. */ pos = 0; } } TRETURN not_taken; } #ifdef HAVE_W32_SYSTEM void MimeDataProvider::collect_data(LPSTREAM stream) { TSTART; if (!stream) { TRETURN; } HRESULT hr; char buf[BUFSIZE]; ULONG bRead; bool first_read = true; bool is_pgp_message = false; size_t allRead = 0; while ((hr = stream->Read (buf, BUFSIZE, &bRead)) == S_OK || hr == S_FALSE) { if (!bRead) { log_data ("%s:%s: Input stream at EOF.", SRCNAME, __func__); break; } log_data ("%s:%s: Read %lu bytes.", SRCNAME, __func__, bRead); allRead += bRead; if (first_read) { if (bRead > 12 && strncmp ("MIME-Version", buf, 12) == 0) { /* Fun! In case we have exchange or sent messages created by us we get the mail attachment like it is before the MAPI to MIME conversion. So it has our MIME structure. In that case we have to expect MIME data even if the initial data check suggests that we don't. Checking if the content starts with MIME-Version appears to be a robust way to check if we try to parse MIME data. */ m_collect_everything = false; log_debug ("%s:%s: Found MIME-Version marker." "Expecting headers even if type suggested not to.", SRCNAME, __func__); } else if (bRead > 12 && !strncmp ("Content-Type:", buf, 13)) { /* Similar as above but we messed with the order of the headers for some s/mime mails. So also check for content type. Want some cheese with that hack? */ m_collect_everything = false; log_debug ("%s:%s: Found Content-Type header." "Expecting headers even if type suggested not to.", SRCNAME, __func__); } /* check for the PGP MESSAGE marker to see if we have it. */ if (bRead && m_collect_everything) { std::string tmp (buf, bRead); std::size_t found = tmp.find ("-----BEGIN PGP MESSAGE-----"); if (found != std::string::npos) { log_debug ("%s:%s: found PGP Message marker,", SRCNAME, __func__); is_pgp_message = true; } } } first_read = false; if (m_collect_everything) { /* For S/MIME, Clearsigned, PGP MESSAGES we just pass everything on. Only the Multipart classes need parsing. And the output of course. */ log_data ("%s:%s: Just copying data.", SRCNAME, __func__); m_crypto_data.write ((void*)buf, (size_t) bRead); continue; } m_rawbuf += std::string (buf, bRead); size_t not_taken = collect_input_lines (m_rawbuf.c_str(), m_rawbuf.size()); if (not_taken == m_rawbuf.size()) { log_error ("%s:%s: Collect failed to consume anything.\n" "Buffer too small?", SRCNAME, __func__); break; } log_data ("%s:%s: Consumed: " SIZE_T_FORMAT " bytes", SRCNAME, __func__, m_rawbuf.size() - not_taken); m_rawbuf.erase (0, m_rawbuf.size() - not_taken); } if (is_pgp_message && allRead < (1024 * 100)) { /* Sometimes received PGP Messsages contain extra whitespace / newlines. To also accept such messages we fix up pgp inline messages here. We only do this for messages which are smaller then a hundred KByte for performance. */ log_debug ("%s:%s: Fixing up a possible broken message.", SRCNAME, __func__); /* Copy crypto data to string */ std::string data = m_crypto_data.toString(); m_crypto_data = GpgME::Data(); std::istringstream iss (data); // Now parse it by line. std::string line; while (std::getline (iss, line)) { trim (line); if (line == "-----BEGIN PGP MESSAGE-----") { /* Finish an armor header */ line += "\n\n"; m_crypto_data.write (line.c_str (), line.size ()); continue; } /* Remove empty lines */ if (line.empty()) { continue; } if (line.find (':') != std::string::npos) { log_data ("%s:%s: Removing comment '%s'.", SRCNAME, __func__, line.c_str ()); continue; } line += '\n'; m_crypto_data.write (line.c_str (), line.size ()); } } TRETURN; } #endif void MimeDataProvider::collect_data(FILE *stream) { TSTART; if (!stream) { TRETURN; } char buf[BUFSIZE]; size_t bRead; while ((bRead = fread (buf, 1, BUFSIZE, stream)) > 0) { log_data ("%s:%s: Read " SIZE_T_FORMAT " bytes.", SRCNAME, __func__, bRead); if (m_collect_everything) { /* For S/MIME, Clearsigned, PGP MESSAGES we just pass everything on. Only the Multipart classes need parsing. And the output of course. */ log_data ("%s:%s: Making verbatim copy" SIZE_T_FORMAT " bytes.", SRCNAME, __func__, bRead); m_crypto_data.write ((void*)buf, bRead); continue; } m_rawbuf += std::string (buf, bRead); size_t not_taken = collect_input_lines (m_rawbuf.c_str(), m_rawbuf.size()); if (not_taken == m_rawbuf.size()) { log_error ("%s:%s: Collect failed to consume anything.\n" "Buffer too small?", SRCNAME, __func__); TRETURN; } log_data ("%s:%s: Consumed: " SIZE_T_FORMAT " bytes", SRCNAME, __func__, m_rawbuf.size() - not_taken); m_rawbuf.erase (0, m_rawbuf.size() - not_taken); } TRETURN; } ssize_t MimeDataProvider::write(const void *buffer, size_t bufSize) { TSTART; if (m_collect_everything) { /* Writing with collect everything one means that we are outputprovider. In this case for inline messages we want to collect everything. */ log_data ("%s:%s: Using complete input as body " SIZE_T_FORMAT " bytes.", SRCNAME, __func__, bufSize); m_body += std::string ((const char *) buffer, bufSize); TRETURN bufSize; } m_rawbuf += std::string ((const char*)buffer, bufSize); size_t not_taken = collect_input_lines (m_rawbuf.c_str(), m_rawbuf.size()); if (not_taken == m_rawbuf.size()) { log_error ("%s:%s: Write failed to consume anything.\n" "Buffer too small? or no newlines in text?", SRCNAME, __func__); TRETURN bufSize; } log_data ("%s:%s: Write Consumed: " SIZE_T_FORMAT " bytes", SRCNAME, __func__, m_rawbuf.size() - not_taken); m_rawbuf.erase (0, m_rawbuf.size() - not_taken); TRETURN bufSize; } off_t MimeDataProvider::seek(off_t offset, int whence) { return m_crypto_data.seek (offset, whence); } GpgME::Data * MimeDataProvider::signature() const { TSTART; TRETURN m_signature; } std::shared_ptr MimeDataProvider::create_attachment() { TSTART; log_data ("%s:%s: Creating attachment.", SRCNAME, __func__); auto attach = std::shared_ptr (new Attachment()); attach->set_attach_type (ATTACHTYPE_FROMMOSS); m_mime_ctx->any_attachments_created = 1; /* And now for the real name. We avoid storing the name "smime.p7m" because that one is used at several places in the mapi conversion functions. */ if (m_mime_ctx->mimestruct_cur && m_mime_ctx->mimestruct_cur->filename) { if (!strcmp (m_mime_ctx->mimestruct_cur->filename, "smime.p7m")) { attach->set_display_name ("x-smime.p7m"); } else { log_data ("%s:%s: Attachment filename: %s", SRCNAME, __func__, m_mime_ctx->mimestruct_cur->filename); attach->set_display_name (m_mime_ctx->mimestruct_cur->filename); } } if (m_mime_ctx->mimestruct_cur && m_mime_ctx->mimestruct_cur->cid) { attach->set_content_id (m_mime_ctx->mimestruct_cur->cid); log_data ("%s:%s: content-id: %s", SRCNAME, __func__, m_mime_ctx->mimestruct_cur->cid); } if (m_mime_ctx->mimestruct_cur && m_mime_ctx->mimestruct_cur->content_type) { attach->set_content_type (m_mime_ctx->mimestruct_cur->content_type); log_data ("%s:%s: content-type: %s", SRCNAME, __func__, m_mime_ctx->mimestruct_cur->content_type); } m_attachments.push_back (attach); TRETURN attach; /* TODO handle encoding */ } void MimeDataProvider::finalize () { TSTART; + + if (m_had_protected_headers) + { + const char *subject = rfc822parse_get_field (m_mime_ctx->msg, + "Subject", -1, + nullptr); + if (subject) + { + log_debug ("%s:%s: Found subject %s", SRCNAME, __func__, subject); + if (strlen (subject) <= strlen ("Subject: ")) + { + STRANGEPOINT; + } + else + { + m_internal_subject = subject + strlen ("Subject: "); + } + } + } if (m_rawbuf.size ()) { m_rawbuf += "\r\n"; size_t not_taken = collect_input_lines (m_rawbuf.c_str(), m_rawbuf.size()); m_rawbuf.erase (0, m_rawbuf.size() - not_taken); if (m_rawbuf.size ()) { log_error ("%s:%s: Collect left data in buffer.\n", SRCNAME, __func__); } } TRETURN; } const std::string &MimeDataProvider::get_body () { TSTART; TRETURN m_body; } const std::string &MimeDataProvider::get_html_body () { TSTART; TRETURN m_html_body; } const std::string &MimeDataProvider::get_html_charset() const { TSTART; TRETURN m_html_charset; } const std::string &MimeDataProvider::get_body_charset() const { TSTART; TRETURN m_body_charset; } + +const std::string &MimeDataProvider::get_internal_subject () const +{ + TSTART; + TRETURN m_internal_subject; +} diff --git a/src/mimedataprovider.h b/src/mimedataprovider.h index 40e1c18..17b5549 100644 --- a/src/mimedataprovider.h +++ b/src/mimedataprovider.h @@ -1,158 +1,163 @@ /* mimedataprover.h - GpgME dataprovider for mime data * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef MIMEDATAPROVIDER_H #define MIMEDATAPROVIDER_H #include "config.h" #include #include #include "rfc822parse.h" #ifdef HAVE_W32_SYSTEM #include "mapihelp.h" #endif #include struct mime_context; typedef struct mime_context *mime_context_t; class Attachment; /** This class does simple one level mime parsing to find crypto data. Use the mimedataprovider on a body or attachment stream. It will do the conversion from MIME to PGP / CMS data on the fly. Similarly when writing it will split up the data into a body / html body and attachments. A detached signature will be made available through the signature function. When reading the raw mime data from the underlying stream is "collected" and parsed into crypto data which is then buffered in an internal gpgme data stucture. For historicial reasons this class both provides reading and writing to be able to reuse the same mimeparser code. Similarly using the C-Style parsing code is for historic reason because as this class was created to have a data container unrelated of the Outlook Object model (after creation) the mimeparser code already existed and was stable. */ class MimeDataProvider : public GpgME::DataProvider { public: /* Create an empty dataprovider, useful for writing to. */ MimeDataProvider(bool no_headers = false); #ifdef HAVE_W32_SYSTEM /* Read and parse the stream. Does not hold a reference to the stream but releases it after read. If no_headers is set to true, assume that there are no headers and immediately start collecting crypto data. Eg. When decrypting a MOSS Attachment. */ MimeDataProvider(LPSTREAM stream, bool no_headers = false); #endif /* Test instrumentation. */ MimeDataProvider(FILE *stream, bool no_headers = false); ~MimeDataProvider(); /* Dataprovider interface */ bool isSupported(Operation) const; /** Read some data from the stream. This triggers the conversion code interanally to convert mime data into PGP/CMS Data that GpgME can work with. */ ssize_t read(void *buffer, size_t bufSize); ssize_t write(const void *buffer, size_t bufSize); /* Seek the underlying stream. This discards the internal buffers as the offset is not mapped. Should not really be used but can be used to reset the DataProvider. */ off_t seek(off_t offset, int whence); /* Noop */ void release() {} /* The the data of the signature part. If not null then this is a pointer to the signature data that is valid for the lifetime of this object. */ GpgME::Data *signature() const; /* Add an attachment to the list */ std::shared_ptr create_attachment(); mime_context_t mime_context() {return m_mime_ctx;} /* Checks if there is body data left in the buffer e.g. for inline messages that did not end with a linefeed and adds it to body / returns the body. */ const std::string &get_body(); /* Similar for html body */ const std::string &get_html_body(); const std::vector > get_attachments() const {return m_attachments;} const std::string &get_html_charset() const; const std::string &get_body_charset() const; + const std::string &get_internal_subject() const; void set_has_html_body(bool value) {m_has_html_body = value;} /* Finalize the bodys */ void finalize (); + + bool m_had_protected_headers; private: #ifdef HAVE_W32_SYSTEM /* Collect the data from mapi. */ void collect_data(LPSTREAM stream); #endif /* Collect data from a file. */ void collect_data(FILE *stream); /* Collect a single line. */ size_t collect_input_lines(const char *input, size_t size); /* A detached signature found in the input */ std::string m_sig_data; /* The data to be passed to the crypto operation */ GpgME::Data m_crypto_data; /* The plaintext body. */ std::string m_body; /* The plaintext html body. */ std::string m_html_body; /* A detachted signature found in the mail */ GpgME::Data *m_signature; /* Internal helper to read line based */ std::string m_rawbuf; /* The mime context */ mime_context_t m_mime_ctx; /* List of attachments. */ std::vector > m_attachments; /* Charset of html */ std::string m_html_charset; /* Charset of body */ std::string m_body_charset; /* Do we have html at all */ bool m_has_html_body; /* Collect everything */ bool m_collect_everything; + /* Internal Protected Headers subject */ + std::string m_internal_subject; }; #endif // MIMEDATAPROVIDER_H diff --git a/src/parsecontroller.cpp b/src/parsecontroller.cpp index 0a97fdb..872f7e1 100644 --- a/src/parsecontroller.cpp +++ b/src/parsecontroller.cpp @@ -1,698 +1,709 @@ /* @file parsecontroller.cpp * @brief Parse a mail and decrypt / verify accordingly * * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "parsecontroller.h" #include "attachment.h" #include "mimedataprovider.h" #include "keycache.h" #include #include #include #include #ifdef HAVE_W32_SYSTEM #include "common.h" /* We use UTF-8 internally. */ #undef _ # define _(a) utf8_gettext (a) #else # define _(a) a #endif const char decrypt_template_html[] = { "" "" "" "" "" "" "
" "

%s %s

" "
" "
%s" "
"}; const char decrypt_template[] = {"%s %s\n\n%s"}; using namespace GpgME; static bool expect_no_headers (msgtype_t type) { TSTART; TRETURN type != MSGTYPE_GPGOL_MULTIPART_SIGNED; } static bool expect_no_mime (msgtype_t type) { TSTART; TRETURN type == MSGTYPE_GPGOL_PGP_MESSAGE || type == MSGTYPE_GPGOL_CLEAR_SIGNED; } #ifdef BUILD_TESTS static void get_and_print_key_test (const char *fingerprint, GpgME::Protocol proto) { if (!fingerprint) { STRANGEPOINT; return; } auto ctx = std::unique_ptr (GpgME::Context::createForProtocol (proto)); if (!ctx) { STRANGEPOINT; return; } ctx->setKeyListMode (GpgME::KeyListMode::Local | GpgME::KeyListMode::Signatures | GpgME::KeyListMode::Validate | GpgME::KeyListMode::WithTofu); GpgME::Error err; const auto newKey = ctx->key (fingerprint, err, false); std::stringstream ss; ss << newKey; log_debug ("Key: %s", ss.str().c_str()); return; } #endif #ifdef HAVE_W32_SYSTEM ParseController::ParseController(LPSTREAM instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i" " expect no headers: %i expect no mime: %i", SRCNAME, __func__, instream, type, expect_no_headers (type), expect_no_mime (type)); TRETURN; } #endif ParseController::ParseController(FILE *instream, msgtype_t type): m_inputprovider (new MimeDataProvider(instream, expect_no_headers(type))), m_outputprovider (new MimeDataProvider(expect_no_mime(type))), m_type (type), m_block_html (false) { TSTART; memdbg_ctor ("ParseController"); log_data ("%s:%s: Creating parser for stream: %p of type %i", SRCNAME, __func__, instream, type); TRETURN; } ParseController::~ParseController() { TSTART; log_debug ("%s:%s", SRCNAME, __func__); memdbg_dtor ("ParseController"); delete m_inputprovider; delete m_outputprovider; TRETURN; } static void operation_for_type(msgtype_t type, bool *decrypt, bool *verify) { *decrypt = false; *verify = false; switch (type) { case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED: case MSGTYPE_GPGOL_PGP_MESSAGE: *decrypt = true; break; case MSGTYPE_GPGOL_MULTIPART_SIGNED: case MSGTYPE_GPGOL_CLEAR_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_SIGNED: *verify = true; break; case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED: *decrypt = true; break; default: log_error ("%s:%s: Unknown data type: %i", SRCNAME, __func__, type); } } static bool is_smime (Data &data) { TSTART; data.seek (0, SEEK_SET); auto id = data.type(); data.seek (0, SEEK_SET); TRETURN id == Data::CMSSigned || id == Data::CMSEncrypted; } static std::string format_recipients(GpgME::DecryptionResult result, Protocol protocol) { TSTART; std::string msg; for (const auto recipient: result.recipients()) { auto ctx = Context::createForProtocol(protocol); Error e; if (!ctx) { /* Can't happen */ TRACEPOINT; continue; } const auto key = ctx->key(recipient.keyID(), e, false); delete ctx; if (!key.isNull() && key.numUserIDs() && !e) { msg += std::string("
") + key.userIDs()[0].id() + " (0x" + recipient.keyID() + ")"; continue; } msg += std::string("
") + _("Unknown Key:") + " 0x" + recipient.keyID(); } TRETURN msg; } static std::string format_error(GpgME::DecryptionResult result, Protocol protocol) { TSTART; char *buf; bool no_sec = false; std::string msg; if (result.error ().isCanceled () || result.error ().code () == GPG_ERR_NO_SECKEY) { msg = _("Decryption canceled or timed out."); } if (result.error ().code () == GPG_ERR_DECRYPT_FAILED || result.error ().code () == GPG_ERR_NO_SECKEY) { no_sec = true; for (const auto &recipient: result.recipients ()) { no_sec &= (recipient.status ().code () == GPG_ERR_NO_SECKEY); } } if (no_sec) { msg = _("No secret key found to decrypt the message. " "It is encrypted to the following keys:"); msg += format_recipients (result, protocol); } else { msg = _("Could not decrypt the data: "); if (result.isNull ()) { msg += _("Failed to parse the mail."); } else if (result.isLegacyCipherNoMDC()) { msg += _("Data is not integrity protected. " "Decrypting it could be a security problem. (no MDC)"); } else { msg += result.error().asString(); } } if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, protocol == OpenPGP ? "OpenPGP" : "S/MIME", _("Encrypted message (decryption not possible)"), msg.c_str()) == -1) { log_error ("%s:%s:Failed to Format error.", SRCNAME, __func__); TRETURN "Failed to Format error."; } msg = buf; memdbg_alloc (buf); xfree (buf); TRETURN msg; } void ParseController::setSender(const std::string &sender) { TSTART; m_sender = sender; TRETURN; } static void handle_autocrypt_info (const autocrypt_s &info) { TSTART; if (info.pref.size() && info.pref != "mutual") { log_debug ("%s:%s: Autocrypt preference is %s which is unhandled.", SRCNAME, __func__, info.pref.c_str()); TRETURN; } #ifndef BUILD_TESTS if (!KeyCache::import_pgp_key_data (info.data)) { log_error ("%s:%s: Failed to import", SRCNAME, __func__); } #endif /* No need to free b64decoded as gpgme_data_release handles it */ TRETURN; } static bool is_valid_chksum(const GpgME::Signature &sig) { TSTART; const auto sum = sig.summary(); static unsigned int valid_mask = (unsigned int) ( GpgME::Signature::Valid | GpgME::Signature::Green | GpgME::Signature::KeyRevoked | GpgME::Signature::KeyExpired | GpgME::Signature::SigExpired | GpgME::Signature::CrlMissing | GpgME::Signature::CrlTooOld | GpgME::Signature::TofuConflict ); TRETURN sum & valid_mask; } /* Note on stability: Experiments have shown that we can have a crash if parse Returns at time that is not good for the state of Outlook. This happend in my test instance after a delay of > 1s < 3s with a < 1% chance :-/ So if you have really really bad luck this might still crash although it usually should be either much quicker or much slower (slower e.g. when pinentry is requrired). */ void ParseController::parse() { TSTART; // Wrap the input stream in an attachment / GpgME Data Protocol protocol; bool decrypt, verify; Data input (m_inputprovider); auto inputType = input.type (); if (m_autocrypt_info.exists) { handle_autocrypt_info (m_autocrypt_info); } if (inputType == Data::Type::PGPSigned) { verify = true; decrypt = false; } else { operation_for_type (m_type, &decrypt, &verify); } if ((m_inputprovider->signature() && is_smime (*m_inputprovider->signature())) || is_smime (input)) { protocol = Protocol::CMS; } else { protocol = Protocol::OpenPGP; } auto ctx = std::unique_ptr (Context::createForProtocol (protocol)); if (!ctx) { log_error ("%s:%s:Failed to create context. Installation broken.", SRCNAME, __func__); char *buf; const char *proto = protocol == OpenPGP ? "OpenPGP" : "S/MIME"; if (gpgrt_asprintf (&buf, opt.prefer_html ? decrypt_template_html : decrypt_template, proto, _("Encrypted message (decryption not possible)"), _("Failed to find GnuPG please ensure that GnuPG or " "Gpg4win is properly installed.")) == -1) { log_error ("%s:%s:Failed format error.", SRCNAME, __func__); /* Should never happen */ m_error = std::string("Bad installation"); } memdbg_alloc (buf); m_error = buf; xfree (buf); TRETURN; } /* Maybe a different option for this ? */ if (opt.autoretrieve) { ctx->setFlag("auto-key-retrieve", "1"); } ctx->setArmor(true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } Data output (m_outputprovider); log_debug ("%s:%s:%p decrypt: %i verify: %i with protocol: %s sender: %s type: %i", SRCNAME, __func__, this, decrypt, verify, protocol == OpenPGP ? "OpenPGP" : protocol == CMS ? "CMS" : "Unknown", m_sender.empty() ? "none" : anonstr (m_sender.c_str()), inputType); if (decrypt) { input.seek (0, SEEK_SET); TRACEPOINT; auto combined_result = ctx->decryptAndVerify(input, output); log_debug ("%s:%s:%p decrypt / verify done.", SRCNAME, __func__, this); m_decrypt_result = combined_result.first; m_verify_result = combined_result.second; if ((!m_decrypt_result.error () && m_verify_result.signatures ().empty() && m_outputprovider->signature ()) || is_smime (output) || output.type() == Data::Type::PGPSigned) { TRACEPOINT; /* There is a signature in the output. So we have to verify it now as an extra step. */ input = Data (m_outputprovider); delete m_inputprovider; m_inputprovider = m_outputprovider; m_outputprovider = new MimeDataProvider(); output = Data(m_outputprovider); verify = true; TRACEPOINT; } else { verify = false; } TRACEPOINT; if (m_decrypt_result.error () || m_decrypt_result.isNull () || m_decrypt_result.error ().isCanceled ()) { m_error = format_error (m_decrypt_result, protocol); } } if (verify) { TRACEPOINT; GpgME::Data *sig = m_inputprovider->signature(); input.seek (0, SEEK_SET); if (sig) { sig->seek (0, SEEK_SET); TRACEPOINT; m_verify_result = ctx->verifyDetachedSignature(*sig, input); log_debug ("%s:%s:%p verify done.", SRCNAME, __func__, this); /* Copy the input to output to do a mime parsing. */ char buf[4096]; input.seek (0, SEEK_SET); output.seek (0, SEEK_SET); size_t nread; while ((nread = input.read (buf, 4096)) > 0) { output.write (buf, nread); } } else { TRACEPOINT; m_verify_result = ctx->verifyOpaqueSignature(input, output); TRACEPOINT; const auto sigs = m_verify_result.signatures(); bool allBad = sigs.size(); for (const auto s :sigs) { if (!(s.summary() & Signature::Red)) { allBad = false; break; } } #ifdef HAVE_W32_SYSTEM if (allBad) { log_debug ("%s:%s:%p inline verify error trying native to utf8.", SRCNAME, __func__, this); /* The proper solution would be to take the encoding from the mail / headers. Then convert the wchar body to that encoding. Verify, and convert it after verifcation to UTF-8 which the rest of the code expects. Or native_body from native ACP to InternetCodepage, then verify and convert the output back to utf8 as the rest expects. But as this is clearsigned and we don't really want that. Meh. */ char *utf8 = native_to_utf8 (input.toString().c_str()); if (utf8) { // Try again after conversion. ctx = std::unique_ptr (Context::createForProtocol (protocol)); ctx->setArmor (true); if (!m_sender.empty()) { ctx->setSender(m_sender.c_str()); } input = Data (utf8, strlen (utf8)); xfree (utf8); // Use a fresh output auto provider = new MimeDataProvider (true); // Warning: The dtor of the Data object touches // the provider. So we have to delete it after // the assignment. output = Data (provider); delete m_outputprovider; m_outputprovider = provider; // Try again m_verify_result = ctx->verifyOpaqueSignature(input, output); } } #else (void)allBad; #endif } } log_debug ("%s:%s:%p: decrypt err: %i verify err: %i", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code()); bool has_valid_encrypted_checksum = false; /* Ensure that the Keys for the signatures are available and if it has a valid encrypted checksum. */ for (const auto sig: m_verify_result.signatures()) { TRACEPOINT; has_valid_encrypted_checksum = is_valid_chksum (sig); #ifndef BUILD_TESTS KeyCache::instance ()->update (sig.fingerprint (), protocol); #endif TRACEPOINT; } if (protocol == Protocol::CMS && decrypt && !m_decrypt_result.error() && !has_valid_encrypted_checksum) { log_debug ("%s:%s:%p Encrypted S/MIME without checksum. Block HTML.", SRCNAME, __func__, this); m_block_html = true; } /* Import any application/pgp-keys attachments if the option is set. */ if (opt.autoimport) { for (const auto &attach: get_attachments()) { if (attach->get_content_type () == "application/pgp-keys") { #ifndef BUILD_TESTS KeyCache::import_pgp_key_data (attach->get_data()); #endif } } } if (opt.enable_debug & DBG_DATA) { std::stringstream ss; TRACEPOINT; ss << m_decrypt_result << '\n' << m_verify_result; for (const auto sig: m_verify_result.signatures()) { const auto key = sig.key(); if (key.isNull()) { #ifndef BUILD_TESTS ss << '\n' << "Cached key:\n" << KeyCache::instance()->getByFpr( sig.fingerprint(), false); #else get_and_print_key_test (sig.fingerprint (), protocol); #endif } else { ss << '\n' << key; } } log_debug ("Decrypt / Verify result: %s", ss.str().c_str()); } else { log_debug ("%s:%s:%p Decrypt / verify done errs: %i / %i numsigs: %i.", SRCNAME, __func__, this, m_decrypt_result.error().code(), m_verify_result.error().code(), m_verify_result.numSignatures()); } TRACEPOINT; if (m_outputprovider) { m_outputprovider->finalize (); } TRETURN; } const std::string ParseController::get_html_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body () const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body (); } else { TRETURN std::string(); } } const std::string ParseController::get_body_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_body_charset(); } else { TRETURN std::string(); } } const std::string ParseController::get_html_charset() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_html_charset(); } else { TRETURN std::string(); } } std::vector > ParseController::get_attachments() const { TSTART; if (m_outputprovider) { TRETURN m_outputprovider->get_attachments(); } else { TRETURN std::vector >(); } } + +std::string +ParseController::get_internal_subject() const +{ + TSTART; + if (m_outputprovider) + { + TRETURN m_outputprovider->get_internal_subject (); + } + TRETURN std::string(); +} diff --git a/src/parsecontroller.h b/src/parsecontroller.h index d88ff19..3b7d456 100644 --- a/src/parsecontroller.h +++ b/src/parsecontroller.h @@ -1,117 +1,119 @@ /* @file parsecontroller.h * @brief Controll the parsing and decrypt / verify of a mail * * Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef PARSECONTROLLER_H #define PARSECONTROLLER_H #include #include #include #ifdef HAVE_CONFIG_H #include #endif #include "common_indep.h" #include #include #include class Attachment; class MimeDataProvider; #ifdef HAVE_W32_SYSTEM #include "oomhelp.h" #endif /* A template for decryption errors / status message. */ extern const char decrypt_template[]; extern const char decrypt_template_html[]; class ParseController { public: #ifdef HAVE_W32_SYSTEM /** Construct a new ParseController for the stream instream. instream is expected to point to a mime mail. Adds a reference to the stream and releases it on destruction. */ ParseController(LPSTREAM instream, msgtype_t type); #endif ParseController(FILE *instream, msgtype_t type); ~ParseController(); /** Main entry point. After execution getters will become valid. */ void parse(); /** Get the Body. Call parse first. */ const std::string get_body () const; /** Get the charset of the body. Call parse first. * * That is a bit of a clunky API to make testing * without outlook easier as we use mlang for Charset * conversion which is not available on GNU/Linux. */ const std::string get_body_charset() const; const std::string get_html_charset() const; /** Get an alternative? HTML Body. Call parse first. */ const std::string get_html_body () const; /** Get the decrypted / verified attachments. Call parse first. */ std::vector > get_attachments() const; const GpgME::DecryptionResult decrypt_result() const { return m_decrypt_result; } const GpgME::VerificationResult verify_result() const { return m_verify_result; } const std::string get_formatted_error() const { return m_error; } void setSender(const std::string &sender); bool shouldBlockHtml() const { return m_block_html; } void setAutocryptInfo (const autocrypt_s & info) { m_autocrypt_info = info; } + std::string get_internal_subject() const; + private: /* State variables */ MimeDataProvider *m_inputprovider; MimeDataProvider *m_outputprovider; msgtype_t m_type; std::string m_error; GpgME::DecryptionResult m_decrypt_result; GpgME::VerificationResult m_verify_result; std::string m_sender; bool m_block_html; autocrypt_s m_autocrypt_info; /* Autocrypt info about the mail */ }; #endif /* PARSECONTROLLER_H */