diff --git a/src/common.h b/src/common.h index 583a63a..0ed0d11 100644 --- a/src/common.h +++ b/src/common.h @@ -1,130 +1,131 @@ /* common.h - Common declarations for GpgOL * Copyright (C) 2004 Timo Schulz * Copyright (C) 2005, 2006, 2007, 2008 g10 Code GmbH * 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 * 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 GPGOL_COMMON_H #define GPGOL_COMMON_H #ifdef HAVE_CONFIG_H #include #endif #include #include "common_indep.h" #include /* i18n stuff */ #include "w32-gettext.h" #define _(a) gettext (a) #define N_(a) gettext_noop (a) /* Registry path to store plugin settings */ #define GPGOL_REGPATH "Software\\GNU\\GpgOL" #ifdef __cplusplus extern "C" { #if 0 } #endif #endif extern HINSTANCE glob_hinst; extern UINT this_dll; /*-- common.c --*/ void set_global_hinstance (HINSTANCE hinst); char *get_data_dir (void); char *get_gpg4win_dir (void); int store_extension_value (const char *key, const char *val); int store_extension_subkey_value (const char *subkey, const char *key, const char *val); int load_extension_value (const char *key, char **val); /* Get a temporary filename with and its name */ wchar_t *get_tmp_outfile (wchar_t *name, HANDLE *outHandle); wchar_t *get_pretty_attachment_name (wchar_t *path, protocol_t protocol, int signature); /*-- verify-dialog.c --*/ int verify_dialog_box (gpgme_protocol_t protocol, gpgme_verify_result_t res, const char *filename); /*-- inspectors.cpp --*/ int initialize_inspectors (void); #if __GNUC__ >= 4 # define GPGOL_GCC_A_SENTINEL(a) __attribute__ ((sentinel(a))) #else # define GPGOL_GCC_A_SENTINEL(a) #endif /*-- common.c --*/ void fatal_error (const char *format, ...); char *read_w32_registry_string (const char *root, const char *dir, const char *name); char *percent_escape (const char *str, const char *extra); void fix_linebreaks (char *str, int *len); /* Format a date from gpgme (seconds since epoch) with windows system locale. */ char *format_date_from_gpgme (unsigned long time); /* Get the name of the uiserver */ char *get_uiserver_name (void); int is_elevated (void); /*-- main.c --*/ void read_options (void); int write_options (void); extern int g_ol_version_major; void bring_to_front (HWND wid); int gpgol_message_box (HWND parent, const char *utf8_text, const char *utf8_caption, UINT type); /* Show a bug message with the code. */ void gpgol_bug (HWND parent, int code); void i18n_init (void); #define ERR_CRYPT_RESOLVER_FAILED 1 #define ERR_WANTS_SEND_MIME_BODY 2 #define ERR_WANTS_SEND_INLINE_BODY 3 #define ERR_INLINE_BODY_TO_BODY 4 #define ERR_INLINE_BODY_INV_STATE 5 #define ERR_SEND_FALLBACK_FAILED 6 +#define ERR_GET_BASE_MSG_FAILED 7 #ifdef __cplusplus } #endif #endif /*GPGOL_COMMON_H*/ diff --git a/src/mailitem-events.cpp b/src/mailitem-events.cpp index 26cbbdd..1653565 100644 --- a/src/mailitem-events.cpp +++ b/src/mailitem-events.cpp @@ -1,923 +1,929 @@ /* 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", 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 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 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__); return S_OK; } } bool is_reply = false; switch(dispid) { case Open: { log_oom_extra ("%s:%s: Open : %p", SRCNAME, __func__, m_mail); int draft_flags = 0; if (!opt.encrypt_default && !opt.sign_default) { return S_OK; } LPMESSAGE message = get_oom_base_message (m_object); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); break; } 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); break; } case BeforeRead: { log_oom_extra ("%s:%s: BeforeRead : %p", SRCNAME, __func__, m_mail); if (m_mail->preProcessMessage_m ()) { log_error ("%s:%s: Pre process message failed.", SRCNAME, __func__); } break; } case Read: { log_oom_extra ("%s:%s: Read : %p", SRCNAME, __func__, m_mail); 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)); break; } if (m_mail->setUUID_o ()) { log_debug ("%s:%s: Failed to set uuid.", SRCNAME, __func__); delete m_mail; /* deletes this, too */ return 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); } break; } 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__); break; } const wchar_t *prop_name = parms->rgvarg[0].bstrVal; if (!m_mail->isCryptoMail ()) { if (!opt.autoresolve) { break; } if (m_mail->hasOverrideMimeData()) { /* This is a mail created by us. Ignore propchanges. */ break; } 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); } } break; } 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); return 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); return S_OK; } log_debug ("%s:%s: Message %p looks like send again.", SRCNAME, __func__, m_object); m_mail->setIsSendAgain (true); return 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) { return 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; return S_OK; } case CustomPropertyChange: { log_oom_extra ("%s:%s: CustomPropertyChange : %p", SRCNAME, __func__, m_mail); /* TODO */ break; } 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_extra ("%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); break; } if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF)) { log_debug ("%s:%s: Uncancellable send event.", SRCNAME, __func__); break; } 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; break; } 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; break; } // 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; break; } } } 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; break; } log_debug ("%s:%s: Passing send event for no-mime message %p.", SRCNAME, __func__, m_object); WKSHelper::instance()->allow_notify (1000); break; } 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; break; #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); break; } 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); - // It's important we use the _base_ message here. - mapi_save_changes (message, KEEP_OPEN_READWRITE | FORCE_SAVE); - message->SubmitMessage(0); - gpgol_release (message); - + 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; - - // Close the composer and trigger unloads - CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0, - NULL)); } MAPIFreeBuffer (propval); if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE) { break; } log_debug ("%s:%s: Passing send event for mime-encrypted message %p.", SRCNAME, __func__, m_object); WKSHelper::instance()->allow_notify (1000); break; } else { log_debug ("%s:%s: Message %p cancelling send - " "crypto or second save failed.", SRCNAME, __func__, m_object); *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; } return S_OK; } case Write: { log_oom_extra ("%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__); break; } if (m_mail->isAboutToBeMoved()) { log_debug ("%s:%s: Mail is about to be moved. Passing write for %p", SRCNAME, __func__, m_mail); break; } 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); return 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__); return 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); break; } case AfterWrite: { log_oom_extra ("%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 failes.", SRCNAME, __func__); m_mail->setCryptState (Mail::NoCryptMail); } return S_OK; } if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite) { m_mail->setCryptState (Mail::NeedsUpdateInMAPI); m_mail->updateCryptMAPI_m (); return S_OK; } break; } case Close: { log_oom_extra ("%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__); break; } if (m_mail->getCloseTriggered ()) { /* Our close with discard changes, pass through */ m_mail->setCloseTriggered (false); return S_OK; } *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE; log_oom_extra ("%s:%s: Canceling close event.", SRCNAME, __func__); if (Mail::close(m_mail)) { log_debug ("%s:%s: Close request failed.", SRCNAME, __func__); } } return S_OK; } case Unload: { log_oom_extra ("%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_extra ("%s:%s: deletion done", SRCNAME, __func__); memdbg_dump (); return S_OK; } /* Fallthrough */ case ReplyAll: case Reply: { is_reply = true; } case Forward: { log_oom_extra ("%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. */ break; } 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 caption = _("GpgOL") + std::string (": "); caption += is_reply ? _("Dangerous reply") : _("Dangerous forward"); std::string buf = _("Unsigned S/MIME mails are not integrity " "protected."); buf += "\n\n"; if (is_reply) { buf += _("For security reasons no decrypted contents" " are included in this reply."); } else { buf += _("For security reasons no decrypted contents" " are included in the forwarded mail."); } 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_extra ("%s:%s: Reply Forward ReplyAll: %p", SRCNAME, __func__, m_mail); if (!opt.reply_crypt) { break; } int crypto_flags = 0; if (!(crypto_flags = m_mail->getCryptoFlags ())) { break; } 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); break; } LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal); if (!msg) { log_debug ("%s:%s: Failed to get base message", SRCNAME, __func__); break; } set_gpgol_draft_info_flags (msg, crypto_flags); gpgol_release (msg); break; } case AttachmentRemove: { log_oom_extra ("%s:%s: AttachmentRemove: %p", SRCNAME, __func__, m_mail); if (!m_mail->isCryptoMail () || attachRemoveWarnShown || m_mail->attachmentRemoveWarningDisabled ()) { return 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; return S_OK; } default: log_oom_extra ("%s:%s: Message:%p Unhandled Event: %lx \n", SRCNAME, __func__, m_object, dispid); } return S_OK; } END_EVENT_SINK(MailItemEvents, IID_MailItemEvents) diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp index e9f8a5e..d7ea3c4 100644 --- a/src/ribbon-callbacks.cpp +++ b/src/ribbon-callbacks.cpp @@ -1,797 +1,804 @@ /* ribbon-callbacks.h - Callbacks for the ribbon extension interface * Copyright (C) 2013 Intevation GmbH * 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 . */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "ribbon-callbacks.h" #include "gpgoladdin.h" #include "common.h" #include "mymapi.h" #include "mymapitags.h" #include "common.h" #include "mapihelp.h" #include "mimemaker.h" #include "filetype.h" #include "mail.h" #include "dispcache.h" #include #include #undef _ #define _(a) utf8_gettext (a) using namespace GpgME; /* This is so super stupid. I bet even Microsft developers laugh about the definition of VARIANT_BOOL. And then for COM we have to pass pointers to this stuff. */ static VARIANT_BOOL var_true = VARIANT_TRUE; static VARIANT_BOOL var_false = VARIANT_FALSE; /* Gets the context of a ribbon control. And prints some useful debug output */ HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context) { *context = get_oom_object (ctrl, "get_Context"); if (*context) { char *name = get_object_name (*context); log_debug ("%s:%s: contextObj: %s", SRCNAME, __func__, name); xfree (name); } return context ? S_OK : E_FAIL; } /* getIconDisp Loads a PNG image from the resurce converts it into a Bitmap and Wraps it in an PictureDispatcher that is returned as result. Based on documentation from: http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI */ static LPDISPATCH getIconDisp (int id) { PICTDESC pdesc; LPDISPATCH pPict; HRESULT hr; Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::Bitmap* pbitmap; ULONG_PTR gdiplusToken; HRSRC hResource; DWORD imageSize; const void* pResourceData; HGLOBAL hBuffer; memset (&pdesc, 0, sizeof pdesc); pdesc.cbSizeofstruct = sizeof pdesc; pdesc.picType = PICTYPE_BITMAP; /* Initialize GDI */ gdiplusStartupInput.DebugEventCallback = NULL; gdiplusStartupInput.SuppressBackgroundThread = FALSE; gdiplusStartupInput.SuppressExternalCodecs = FALSE; gdiplusStartupInput.GdiplusVersion = 1; GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL); /* Get the image from the resource file */ hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA); if (!hResource) { log_error ("%s:%s: failed to find image: %i", SRCNAME, __func__, id); return nullptr; } imageSize = SizeofResource (glob_hinst, hResource); if (!imageSize) return nullptr; pResourceData = LockResource (LoadResource(glob_hinst, hResource)); if (!pResourceData) { log_error ("%s:%s: failed to load image: %i", SRCNAME, __func__, id); return nullptr; } hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize); if (hBuffer) { void* pBuffer = GlobalLock (hBuffer); if (pBuffer) { IStream* pStream = NULL; CopyMemory (pBuffer, pResourceData, imageSize); if (CreateStreamOnHGlobal (hBuffer, TRUE, &pStream) == S_OK) { memdbg_addRef (pStream); pbitmap = Gdiplus::Bitmap::FromStream (pStream); gpgol_release (pStream); if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap)) { log_error ("%s:%s: failed to get PNG.", SRCNAME, __func__); } } } GlobalUnlock (pBuffer); } GlobalFree (hBuffer); Gdiplus::GdiplusShutdown (gdiplusToken); /* Wrap the image into an OLE object. */ hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp, TRUE, (void **) &pPict); if (hr != S_OK || !pPict) { log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n", SRCNAME, __func__, hr); return nullptr; } return pPict; } HRESULT getIcon (int id, VARIANT* result) { if (!result) { TRACEPOINT; return S_OK; } auto cache = DispCache::instance (); result->pdispVal = cache->getDisp (id); if (!result->pdispVal) { result->pdispVal = getIconDisp (id); cache->addDisp (id, result->pdispVal); memdbg_addRef (result->pdispVal); } if (result->pdispVal) { result->vt = VT_DISPATCH; result->pdispVal->AddRef(); } return S_OK; } HRESULT mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer) { LPMESSAGE message = NULL; int oldflags, newflags; log_debug ("%s:%s: enter", SRCNAME, __func__); LPDISPATCH context = NULL; if (FAILED(getContext (ctrl, &context))) { TRACEPOINT; return E_FAIL; } LPDISPATCH mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); gpgol_release (context); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); return E_FAIL; } /* Get the uid of this item. */ char *uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); } } Mail *mail = nullptr; if (uid) { mail = Mail::getMailForUUID (uid); xfree (uid); } if (mail) { mail->setCryptoSelectedManually (true); } else { log_debug ("%s:%s: Failed to get mail object.", SRCNAME, __func__); } message = get_oom_base_message (mailitem); gpgol_release (mailitem); if (!message) { log_error ("%s:%s: Failed to get message.", SRCNAME, __func__); return S_OK; } oldflags = get_gpgol_draft_info_flags (message); if (flags == 3 && oldflags != 3) { // If only one sub button is active activate // both now. newflags = 3; } else { newflags = oldflags xor flags; } if (set_gpgol_draft_info_flags (message, newflags)) { log_error ("%s:%s: Failed to set draft flags.", SRCNAME, __func__); } gpgol_release (message); /* We need to invalidate the UI to update the toggle states of the subbuttons and the top button. Yeah, we invalidate a lot *sigh* */ gpgoladdin_invalidate_ui (); if (newflags & 1) { Mail::locateAllCryptoRecipients_o (); } return S_OK; } /* Get the state of encrypt / sign toggle buttons. flag values: 1 get the state of the encrypt button. 2 get the state of the sign button. If is_explorer is set to true we look at the inline response. */ HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result, bool is_explorer) { HRESULT hr; bool value; LPDISPATCH context = NULL, mailitem = NULL; LPMESSAGE message = NULL; result->vt = VT_BOOL | VT_BYREF; result->pboolVal = &var_false; /* First the usual defensive check about our parameters */ if (!ctrl || !result) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return E_FAIL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return E_FAIL; } mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" : "CurrentItem"); if (!mailitem) { log_error ("%s:%s: Failed to get mailitem.", SRCNAME, __func__); goto done; } message = get_oom_base_message (mailitem); if (!message) { log_error ("%s:%s: No message found.", SRCNAME, __func__); goto done; } value = (get_gpgol_draft_info_flags (message) & flags) == flags; result->pboolVal = value ? &var_true: &var_false; done: gpgol_release (context); gpgol_release (mailitem); gpgol_release (message); return S_OK; } static Mail * get_mail_from_control (LPDISPATCH ctrl, bool *none_selected) { HRESULT hr; LPDISPATCH context = NULL, mailitem = NULL; *none_selected = false; if (!ctrl) { log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); return NULL; } hr = getContext (ctrl, &context); if (hr) { log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__, hr); return NULL; } char *name = get_object_name (context); std::string ctx_name; if (name) { ctx_name = name; xfree (name); } if (ctx_name.empty()) { log_error ("%s:%s: Failed to get context name", SRCNAME, __func__); gpgol_release (context); return NULL; } if (!strcmp (ctx_name.c_str(), "_Inspector")) { mailitem = get_oom_object (context, "CurrentItem"); } else if (!strcmp (ctx_name.c_str(), "_Explorer")) { /* Avoid showing wrong crypto state if we don't have a reading pane. In that case the parser will finish for a mail which is gone and the crypto state will not get updated. */ if (!is_preview_pane_visible (context)) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; log_debug ("%s:%s: Preview pane invisible", SRCNAME, __func__); } #if 0 if (g_ol_version_major >= 16) { /* Some Versions of Outlook 2016 crashed when accessing the current view of the Explorer. This was even reproducible with GpgOL disabled and only with Outlook Spy active. If you selected the explorer of an Outlook.com resource and then access the CurrentView and close the CurrentView again in Outlook Spy outlook crashes. See: T3484 The crash no longer occured at least since build 10228. As I'm not sure which Version fixed the crash we don't do a version check and just use the same codepath as for Outlook 2010 and 2013 again. Accessing PreviewPane.WordEditor is not a good solution here as it requires "Microsoft VBA for Office" (See T4056 ). A possible solution for that might be to check if "Mail.GetInspector().WordEditor()" returns NULL. In that case we know that we also won't get a WordEditor in the preview pane. */ LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor"); gpgol_release (prevEdit); if (!prevEdit) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } else { // Preview Pane is not available in older outlooks LPDISPATCH tableView = get_oom_object (context, "CurrentView"); if (!tableView) { // Woops, should not happen. TRACEPOINT; *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } else { int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane"); gpgol_release (tableView); if (!hasReadingPane) { *none_selected = true; gpgol_release (mailitem); mailitem = nullptr; } } } #endif if (!*none_selected) { /* Accessing the selection item can trigger a load event so we only do this here if we think that there might be something visible / selected. To avoid triggering a load if there is no content shown. */ LPDISPATCH selection = get_oom_object (context, "Selection"); if (!selection) { log_error ("%s:%s: Failed to get selection.", SRCNAME, __func__); gpgol_release (context); return NULL; } int count = get_oom_int (selection, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (selection, "Item(1)"); } gpgol_release (selection); if (!mailitem) { *none_selected = true; } } } else if (!strcmp (ctx_name.c_str(), "Selection")) { int count = get_oom_int (context, "Count"); if (count == 1) { // If we call this on a selection with more items // Outlook sends an ItemLoad event for each mail // in that selection. mailitem = get_oom_object (context, "Item(1)"); } if (!mailitem) { *none_selected = true; } } gpgol_release (context); if (!mailitem) { log_debug ("%s:%s: No mailitem. From %s", SRCNAME, __func__, ctx_name.c_str()); return NULL; } char *uid; /* Get the uid of this item. */ uid = get_unique_id (mailitem, 0, nullptr); if (!uid) { LPMESSAGE msg = get_oom_base_message (mailitem); + if (!msg) + { + log_debug ("%s:%s: Failed to get message for %p", + SRCNAME, __func__, mailitem); + gpgol_release (mailitem); + return NULL; + } uid = mapi_get_uid (msg); gpgol_release (msg); if (!uid) { log_debug ("%s:%s: Failed to get uid for %p", SRCNAME, __func__, mailitem); gpgol_release (mailitem); return NULL; } } auto ret = Mail::getMailForUUID (uid); xfree (uid); if (!ret) { log_error ("%s:%s: Failed to find mail %p in map.", SRCNAME, __func__, mailitem); } gpgol_release (mailitem); return ret; } /* Helper to reduce code duplication.*/ #define MY_MAIL_GETTER \ if (!ctrl) \ { \ log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \ return E_FAIL; \ } \ bool none_selected; \ const auto mail = get_mail_from_control (ctrl, &none_selected); \ (void)none_selected; \ if (!mail) \ { \ log_oom ("%s:%s:%i Failed to get mail", \ SRCNAME, __func__, __LINE__); \ } HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (!result) { TRACEPOINT; return S_OK; } result->vt = VT_BOOL | VT_BYREF; result->pboolVal = none_selected ? &var_false : &var_true; TRACEPOINT; return S_OK; } HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (!mail) { log_debug ("%s:%s: No mail.", SRCNAME, __func__); w_result = utf8_to_wchar (_("Insecure")); result->bstrVal = SysAllocString (w_result); xfree (w_result); return S_OK; } w_result = utf8_to_wchar (mail->getCryptoSummary ().c_str ()); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; wchar_t *w_result; if (mail) { w_result = utf8_to_wchar (mail->getCryptoOneLine ().c_str()); } else if (!none_selected) { w_result = utf8_to_wchar (_("Insecure message")); } else { w_result = utf8_to_wchar (_("No message selected")); } result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BSTR; if (none_selected) { result->bstrVal = SysAllocString (L""); TRACEPOINT; return S_OK; } if (!mail || !mail->isCryptoMail ()) { wchar_t *w_result; w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, " "modified and read the message in transit.\n\n" "Click here to learn more.")); result->bstrVal = SysAllocString (w_result); xfree (w_result); TRACEPOINT; return S_OK; } const auto message = mail->getCryptoDetails_o (); wchar_t *w_message = utf8_to_wchar (message.c_str()); result->bstrVal = SysAllocString (w_message); xfree (w_message); TRACEPOINT; return S_OK; } HRESULT launch_cert_details (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail || (!mail->isSigned () && !mail->isEncrypted ())) { ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/infographic", 0, 0, SW_SHOWNORMAL); return S_OK; } if (!mail->isSigned () && mail->isEncrypted ()) { /* Encrypt only, no information but show something. because we want the button to be active. Aheinecke: I don't think we should show to which keys the message is encrypted here. This would confuse users if they see keyids of unknown keys and the information can't be "true" because the sender could have sent the same information to other people or used throw keyids etc. */ char * buf; gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n" "There is no additional information available if it " "was actually sent by '%s' or if someone faked the sender address."), mail->getSender_o ().c_str()); MessageBox (NULL, buf, _("GpgOL"), MB_ICONINFORMATION|MB_OK); xfree (buf); return S_OK; } if (!mail->getSigFpr ()) { std::string buf = _("There was an error verifying the signature.\n" "Full details:\n"); buf += mail->getVerificationResultDump (); gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK); } char *uiserver = get_uiserver_name (); bool showError = false; if (uiserver) { std::string path (uiserver); xfree (uiserver); if (path.find("kleopatra.exe") != std::string::npos) { size_t dpos; if ((dpos = path.find(" --daemon")) != std::string::npos) { path.erase(dpos, strlen(" --daemon")); } auto ctx = Context::createForEngine(SpawnEngine); if (!ctx) { log_error ("%s:%s: No spawn engine.", SRCNAME, __func__); } std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ()); const char *argv[] = {path.c_str(), "--query", mail->getSigFpr (), "--parent-windowid", parentWid.c_str(), NULL }; log_debug ("%s:%s: Starting %s %s %s", SRCNAME, __func__, path.c_str(), argv[1], argv[2]); Data d(Data::null); ctx->spawnAsync(path.c_str(), argv, d, d, d, (GpgME::Context::SpawnFlags) ( GpgME::Context::SpawnAllowSetFg | GpgME::Context::SpawnShowWindow)); } else { showError = true; } } else { showError = true; } if (showError) { MessageBox (NULL, _("Could not find Kleopatra.\n" "Please reinstall Gpg4win with the Kleopatra component enabled."), _("GpgOL"), MB_ICONINFORMATION|MB_OK); } return S_OK; } HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER if (mail) { TRACEPOINT; return getIcon (mail->getCryptoIconID (), result); } TRACEPOINT; return getIcon (IDI_LEVEL_0, result); } HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result) { MY_MAIL_GETTER result->vt = VT_BOOL | VT_BYREF; result->pboolVal = mail && (mail->isSigned () || mail->isEncrypted ()) ? &var_true : &var_false; TRACEPOINT; return S_OK; } HRESULT print_decrypted (LPDISPATCH ctrl) { MY_MAIL_GETTER if (!mail) { log_error ("%s:%s: Failed to get mail.", SRCNAME, __func__); return S_OK; } invoke_oom_method (mail->item(), "PrintOut", NULL); return S_OK; } diff --git a/src/windowmessages.cpp b/src/windowmessages.cpp index ff8a1db..25c5dcd 100644 --- a/src/windowmessages.cpp +++ b/src/windowmessages.cpp @@ -1,534 +1,539 @@ /* @file windowmessages.h * @brief Helper class to work with the windowmessage handler thread. * * 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 "windowmessages.h" #include "common.h" #include "oomhelp.h" #include "mail.h" #include "gpgoladdin.h" #include "wks-helper.h" #include #define RESPONDER_CLASS_NAME "GpgOLResponder" /* Singleton window */ static HWND g_responder_window = NULL; static int invalidation_blocked = 0; LONG_PTR WINAPI gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { // log_debug ("WMG: %x", (unsigned int) message); if (message == WM_USER + 42) { wm_ctx_t *ctx = (wm_ctx_t *) lParam; log_debug ("%s:%s: Recieved user msg: %i", SRCNAME, __func__, ctx->wmsg_type); switch (ctx->wmsg_type) { case (PARSING_DONE): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Parsing done for mail which is gone.", SRCNAME, __func__); break; } mail->parsing_done(); break; } case (RECIPIENT_ADDED): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Recipient add for mail which is gone.", SRCNAME, __func__); break; } mail->locateKeys_o (); break; } case (REVERT_MAIL): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Revert mail for mail which is gone.", SRCNAME, __func__); break; } mail->setNeedsSave (true); /* Some magic here. Accessing any existing inline body cements it. Otherwise updating the body through the revert also changes the body of a inline mail. */ char *inlineBody = get_inline_body (); xfree (inlineBody); // Does the revert. log_debug ("%s:%s: Revert mail. Invoking save.", SRCNAME, __func__); invoke_oom_method (mail->item (), "Save", NULL); log_debug ("%s:%s: Revert mail. Save done. Updating body..", SRCNAME, __func__); mail->updateBody_o (); log_debug ("%s:%s: Revert mail done.", SRCNAME, __func__); break; } case (INVALIDATE_UI): { if (!invalidation_blocked) { log_debug ("%s:%s: Invalidating UI", SRCNAME, __func__); gpgoladdin_invalidate_ui(); log_debug ("%s:%s: Invalidation done", SRCNAME, __func__); } else { log_debug ("%s:%s: Received invalidation msg while blocked." " Ignoring it", SRCNAME, __func__); } break; } case (INVALIDATE_LAST_MAIL): { log_debug ("%s:%s: clearing last mail", SRCNAME, __func__); Mail::clearLastMail (); break; } case (CLOSE): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Close for mail which is gone.", SRCNAME, __func__); break; } mail->refCurrentItem(); Mail::close (mail); log_debug ("%s:%s: Close finished.", SRCNAME, __func__); mail->releaseCurrentItem(); break; } case (CRYPTO_DONE): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Crypto done for mail which is gone.", SRCNAME, __func__); break; } // modify the mail. if (mail->cryptState () == Mail::NeedsUpdateInOOM) { // Save the Mail log_debug ("%s:%s: Crypto done for %p updating oom.", SRCNAME, __func__, mail); mail->updateCryptOOM_o (); } if (mail->cryptState () == Mail::NeedsSecondAfterWrite) { invoke_oom_method (mail->item (), "Save", NULL); log_debug ("%s:%s: Second save done for %p Invoking second send.", SRCNAME, __func__, mail); } // Finaly this should pass. if (invoke_oom_method (mail->item (), "Send", NULL)) { log_error ("%s:%s: Send failed for %p. " "Trying SubmitMessage instead.", SRCNAME, __func__, mail); auto mail_message = get_oom_base_message (mail->item()); + if (!mail_message) + { + gpgol_bug (mail->getWindow (), + ERR_GET_BASE_MSG_FAILED); + break; + } // It's important we use the _base_ message here. - mapi_save_changes (mail_message, - KEEP_OPEN_READWRITE | FORCE_SAVE); + mapi_save_changes (mail_message, FORCE_SAVE); HRESULT hr = mail_message->SubmitMessage(0); gpgol_release (mail_message); if (hr == S_OK) { do_in_ui_thread_async (CLOSE, (LPVOID) mail); } else { log_error ("%s:%s: SubmitMessage Failed hr=0x%lx.", SRCNAME, __func__, hr); gpgol_bug (mail->getWindow (), ERR_SEND_FALLBACK_FAILED); } } else { mail->releaseCurrentItem (); } log_debug ("%s:%s: Send for %p completed.", SRCNAME, __func__, mail); break; } case (BRING_TO_FRONT): { HWND wnd = get_active_hwnd (); if (wnd) { log_debug ("%s:%s: Bringing window %p to front.", SRCNAME, __func__, wnd); bring_to_front (wnd); } else { log_debug ("%s:%s: No active window found for bring to front.", SRCNAME, __func__); } break; } case (WKS_NOTIFY): { WKSHelper::instance ()->notify ((const char *) ctx->data); xfree (ctx->data); break; } case (CLEAR_REPLY_FORWARD): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: Clear reply forward for mail which is gone.", SRCNAME, __func__); break; } mail->wipe_o (true); mail->removeAllAttachments_o (); break; } case (DO_AUTO_SECURE): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.", SRCNAME, __func__); break; } mail->setDoAutosecure_m (true); break; } case (DONT_AUTO_SECURE): { auto mail = (Mail*) ctx->data; if (!Mail::isValidPtr (mail)) { log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.", SRCNAME, __func__); break; } mail->setDoAutosecure_m (false); break; } default: log_debug ("%s:%s: Unknown msg %x", SRCNAME, __func__, ctx->wmsg_type); } return 0; } return DefWindowProc(hWnd, message, wParam, lParam); } HWND create_responder_window () { size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1; char cls_name[cls_name_len]; if (g_responder_window) { return g_responder_window; } /* Create Window wants a mutable string as the first parameter */ snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME); WNDCLASS windowClass; windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS; windowClass.lpfnWndProc = gpgol_window_proc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL); windowClass.hIcon = 0; windowClass.hCursor = 0; windowClass.hbrBackground = 0; windowClass.lpszMenuName = 0; windowClass.lpszClassName = cls_name; RegisterClass(&windowClass); g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0, 0, 0, 0, (HMENU) 0, (HINSTANCE) GetModuleHandle(NULL), 0); return g_responder_window; } static int send_msg_to_ui_thread (wm_ctx_t *ctx) { size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1; char cls_name[cls_name_len]; snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME); HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME); if (!responder) { log_error ("%s:%s: Failed to find responder window.", SRCNAME, __func__); return -1; } SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx); return 0; } int do_in_ui_thread (gpgol_wmsg_type type, void *data) { wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0}; ctx.wmsg_type = type; ctx.data = data; log_debug ("%s:%s: Sending message of type %i", SRCNAME, __func__, type); if (send_msg_to_ui_thread (&ctx)) { return -1; } return ctx.err; } static DWORD WINAPI do_async (LPVOID arg) { wm_ctx_t *ctx = (wm_ctx_t*) arg; log_debug ("%s:%s: Do async with type %i after %i ms", SRCNAME, __func__, ctx ? ctx->wmsg_type : -1, ctx->delay); if (ctx->delay) { Sleep (ctx->delay); } send_msg_to_ui_thread (ctx); xfree (ctx); return 0; } void do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay) { wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t)); ctx->wmsg_type = type; ctx->data = data; ctx->delay = delay; CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL)); } LRESULT CALLBACK gpgol_hook(int code, WPARAM wParam, LPARAM lParam) { /* Once we are in the close events we don't have enough control to revert all our changes so we have to do it with this nice little hack by catching the WM_CLOSE message before it reaches outlook. */ LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam; /* What we do here is that we catch all WM_CLOSE messages that get to Outlook. Then we check if the last open Explorer is the target of the close. In set case we start our shutdown routine before we pass the WM_CLOSE to outlook */ switch (cwp->message) { case WM_CLOSE: { HWND lastChild = NULL; log_debug ("%s:%s: Got WM_CLOSE", SRCNAME, __func__); if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application()) { TRACEPOINT; break; } LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(), "Explorers"); if (!explorers) { log_error ("%s:%s: No explorers object", SRCNAME, __func__); break; } int count = get_oom_int (explorers, "Count"); if (count != 1) { log_debug ("%s:%s: More then one explorer. Not shutting down.", SRCNAME, __func__); gpgol_release (explorers); break; } LPDISPATCH explorer = get_oom_object (explorers, "Item(1)"); gpgol_release (explorers); if (!explorer) { TRACEPOINT; break; } /* Casting to LPOLEWINDOW and calling GetWindow succeeded in Outlook 2016 but always returned the number 1. So we need this hack. */ char *caption = get_oom_string (explorer, "Caption"); gpgol_release (explorer); if (!caption) { log_debug ("%s:%s: No caption.", SRCNAME, __func__); break; } /* rctrl_renwnd32 is the window class of outlook. */ HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32", caption); xfree (caption); lastChild = hwnd; if (hwnd == cwp->hwnd) { log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. " "Shutting down.", SRCNAME, __func__); GpgolAddin::get_instance ()->shutdown(); break; } break; } case WM_SYSCOMMAND: /* This comes to often and when we are closed from the icon we also get WM_CLOSE if (cwp->wParam == SC_CLOSE) { log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.", SRCNAME, __func__); GpgolAddin::get_instance ()->shutdown(); } */ break; default: // log_debug ("WM: %x", (unsigned int) cwp->message); break; } return CallNextHookEx (NULL, code, wParam, lParam); } /* Create the message hook for outlook's windowmessages we are especially interested in WM_QUIT to do cleanups and prevent the "Item has changed" question. */ HHOOK create_message_hook() { return SetWindowsHookEx (WH_CALLWNDPROC, gpgol_hook, NULL, GetCurrentThreadId()); } GPGRT_LOCK_DEFINE (invalidate_lock); static bool invalidation_in_progress; DWORD WINAPI delayed_invalidate_ui (LPVOID minsleep) { if (invalidation_in_progress) { log_debug ("%s:%s: Invalidation canceled as it is in progress.", SRCNAME, __func__); return 0; } TRACEPOINT; invalidation_in_progress = true; gpgrt_lock_lock(&invalidate_lock); int sleep_ms = (intptr_t)minsleep; Sleep (sleep_ms); int i = 0; while (invalidation_blocked) { i++; if (i % 10 == 0) { log_debug ("%s:%s: Waiting for invalidation.", SRCNAME, __func__); } Sleep (100); /* Do we need an abort statement here? */ } do_in_ui_thread (INVALIDATE_UI, nullptr); TRACEPOINT; invalidation_in_progress = false; gpgrt_lock_unlock(&invalidate_lock); return 0; } DWORD WINAPI close_mail (LPVOID mail) { do_in_ui_thread (CLOSE, mail); return 0; } void blockInv() { invalidation_blocked++; log_oom_extra ("%s:%s: Invalidation block count %i", SRCNAME, __func__, invalidation_blocked); } void unblockInv() { invalidation_blocked--; log_oom_extra ("%s:%s: Invalidation block count %i", SRCNAME, __func__, invalidation_blocked); if (invalidation_blocked < 0) { log_error ("%s:%s: Invalidation block mismatch", SRCNAME, __func__); invalidation_blocked = 0; } }