diff --git a/src/mail.cpp b/src/mail.cpp
index 114e65c..7f709eb 100644
--- a/src/mail.cpp
+++ b/src/mail.cpp
@@ -1,2396 +1,2415 @@
 /* @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 <http://www.gnu.org/licenses/>.
  */
 
 #include "config.h"
 #include "dialogs.h"
 #include "common.h"
 #include "mail.h"
 #include "eventsinks.h"
 #include "attachment.h"
 #include "mapihelp.h"
 #include "mimemaker.h"
 #include "message.h"
 #include "revert.h"
 #include "gpgoladdin.h"
 #include "mymapitags.h"
 #include "parsecontroller.h"
 #include "gpgolstr.h"
 #include "windowmessages.h"
 #include "mlang-charset.h"
 
 #include <gpgme++/configuration.h>
 #include <gpgme++/tofuinfo.h>
 #include <gpgme++/verificationresult.h>
 #include <gpgme++/decryptionresult.h>
 #include <gpgme++/key.h>
 #include <gpgme++/context.h>
 #include <gpgme++/keylistresult.h>
 #include <gpg-error.h>
 
 #include <map>
 #include <set>
 #include <vector>
 #include <memory>
 
 
 #undef _
 #define _(a) utf8_gettext (a)
 
 using namespace GpgME;
 
 static std::map<LPDISPATCH, Mail*> g_mail_map;
 static std::map<std::string, Mail*> g_uid_map;
 static std::set<std::string> uids_searched;
 
 static bool
 in_de_vs_mode()
 {
   /* We cache the values only once. A change requires restart.
      This is because checking this is very expensive as gpgconf
      spawns each process to query the settings. */
   static bool checked;
   static bool vs_mode;
 
   if (checked)
     {
       return vs_mode;
     }
   Error err;
   const auto components = Configuration::Component::load (err);
   log_debug ("%s:%s: Checking for de-vs mode.",
              SRCNAME, __func__);
   if (err)
     {
       log_error ("%s:%s: Failed to get gpgconf components: %s",
                  SRCNAME, __func__, err.asString ());
       checked = true;
       vs_mode = false;
       return vs_mode;
     }
   for (const auto &component: components)
     {
       if (component.name () && !strcmp (component.name (), "gpg"))
         {
           for (const auto &option: component.options ())
             {
               if (option.name () && !strcmp (option.name (), "compliance") &&
                   option.currentValue ().stringValue () &&
                   !stricmp (option.currentValue ().stringValue (), "de-vs"))
                 {
                   log_debug ("%s:%s: Detected de-vs mode",
                              SRCNAME, __func__);
                   checked = true;
                   vs_mode = true;
                   return vs_mode;
                 }
             }
           checked = true;
           vs_mode = false;
           return vs_mode;
         }
     }
   checked = true;
   vs_mode = false;
   return false;
 }
 
 #define COPYBUFSIZE (8 * 1024)
 
 Mail::Mail (LPDISPATCH mailitem) :
     m_mailitem(mailitem),
     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_cached_recipients(nullptr),
     m_type(MSGTYPE_UNKNOWN),
     m_do_inline(false),
     m_is_gsuite(false)
 {
   if (get_mail_for_item (mailitem))
     {
       log_error ("Mail object for item: %p already exists. Bug.",
                  mailitem);
       return;
     }
 
   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);
       return;
     }
   g_mail_map.insert (std::pair<LPDISPATCH, Mail *> (mailitem, this));
 }
 
 GPGRT_LOCK_DEFINE(dtor_lock);
 
 Mail::~Mail()
 {
   /* 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. */
   gpgrt_lock_lock (&dtor_lock);
   std::map<LPDISPATCH, Mail *>::iterator it;
 
   detach_MailItemEvents_sink (m_event_sink);
   gpgol_release(m_event_sink);
 
   it = g_mail_map.find(m_mailitem);
   if (it != g_mail_map.end())
     {
       g_mail_map.erase (it);
     }
 
   if (!m_uuid.empty())
     {
       auto it2 = g_uid_map.find(m_uuid);
       if (it2 != g_uid_map.end())
         {
           g_uid_map.erase (it2);
         }
     }
 
   gpgol_release(m_mailitem);
   if (!m_uuid.empty())
     {
       log_oom_extra ("%s:%s: destroyed: %p uuid: %s",
                      SRCNAME, __func__, this, m_uuid.c_str());
     }
   else
     {
       log_oom_extra ("%s:%s: non crypto mail: %p destroyed",
                      SRCNAME, __func__, this);
     }
   xfree (m_cached_html_body);
   xfree (m_cached_plain_body);
+  for (int i = 0; m_cached_recipients && m_cached_recipients[i]; ++i)
+      xfree (m_cached_recipients[i]);
+  xfree (m_cached_recipients);
   gpgrt_lock_unlock (&dtor_lock);
 }
 
 Mail *
 Mail::get_mail_for_item (LPDISPATCH mailitem)
 {
   if (!mailitem)
     {
       return NULL;
     }
   std::map<LPDISPATCH, Mail *>::iterator it;
   it = g_mail_map.find(mailitem);
   if (it == g_mail_map.end())
     {
       return NULL;
     }
   return it->second;
 }
 
 Mail *
 Mail::get_mail_for_uuid (const char *uuid)
 {
   if (!uuid)
     {
       return NULL;
     }
   auto it = g_uid_map.find(std::string(uuid));
   if (it == g_uid_map.end())
     {
       return NULL;
     }
   return it->second;
 }
 
 bool
 Mail::is_valid_ptr (const Mail *mail)
 {
   auto it = g_mail_map.begin();
   while (it != g_mail_map.end())
     {
       if (it->second == mail)
         return true;
       ++it;
     }
   return false;
 }
 
 int
 Mail::pre_process_message ()
 {
   LPMESSAGE message = get_oom_base_message (m_mailitem);
   if (!message)
     {
       log_error ("%s:%s: Failed to get base message.",
                  SRCNAME, __func__);
       return 0;
     }
   log_oom_extra ("%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)
     {
       gpgol_release (message);
       return 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);
   return 0;
 }
 
 static LPDISPATCH
 get_attachment (LPDISPATCH mailitem, int pos)
 {
   LPDISPATCH attachment;
   LPDISPATCH attachments = get_oom_object (mailitem, "Attachments");
   if (!attachments)
     {
       log_debug ("%s:%s: Failed to get attachments.",
                  SRCNAME, __func__);
       return 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);
       return 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);
 
   return attachment;
 }
 
 /** Helper to check that all attachments are hidden, to be
   called before crypto. */
 int
 Mail::check_attachments () const
 {
   LPDISPATCH attachments = get_oom_object (m_mailitem, "Attachments");
   if (!attachments)
     {
       log_debug ("%s:%s: Failed to get attachments.",
                  SRCNAME, __func__);
       return 1;
     }
   int count = get_oom_int (attachments, "Count");
   if (!count)
     {
       gpgol_release (attachments);
       return 0;
     }
 
   std::string message;
 
   if (is_encrypted () && is_signed ())
     {
       message += _("Not all attachments were encrypted or signed.\n"
                    "The unsigned / unencrypted attachments are:\n\n");
     }
   else if (is_signed ())
     {
       message += _("Not all attachments were signed.\n"
                    "The unsigned attachments are:\n\n");
     }
   else if (is_encrypted ())
     {
       message += _("Not all attachments were encrypted.\n"
                    "The unencrypted attachments are:\n\n");
     }
   else
     {
       gpgol_release (attachments);
       return 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);
     }
   return 0;
 }
 
 /** Get the cipherstream of the mailitem. */
 static LPSTREAM
 get_attachment_stream (LPDISPATCH mailitem, int pos)
 {
   if (!pos)
     {
       log_debug ("%s:%s: Called with zero pos.",
                  SRCNAME, __func__);
       return NULL;
     }
   LPDISPATCH attachment = get_attachment (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__);
           return NULL;
         }
       hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0,
                                   (LPUNKNOWN*)&stream);
       if (hr)
         {
           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
                      SRCNAME, __func__, hr);
           return NULL;
         }
       return 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);
       return NULL;
     }
   if (FAILED (mapi_attachment->OpenProperty (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);
     }
   return 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<Attachment> attach)
 {
   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;
       return 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<unsigned long> (size);
   rgsabound[0].lLbound = 0;
 
   /* Create an OLE SAFEARRAY */
   var.parray = SafeArrayCreate (VT_UI1, 1, rgsabound);
   if (var.parray == NULL)
     {
       TRACEPOINT;
       VariantClear(&var);
       return 1;
     }
 
   void *buffer = NULL;
   /* Get a safe pointer to the array */
   if (SafeArrayAccessData(var.parray, &buffer) != S_OK)
     {
       TRACEPOINT;
       VariantClear(&var);
       return 1;
     }
 
   /* Copy data to it */
   size_t nread = attach->get_data ().read (buffer, static_cast<size_t> (size));
 
   if (nread != static_cast<size_t> (size))
     {
       TRACEPOINT;
       VariantClear(&var);
       return 1;
     }
 
   /*/ Unlock the variant data */
   if (SafeArrayUnaccessData(var.parray) != S_OK)
     {
       TRACEPOINT;
       VariantClear(&var);
       return 1;
     }
 
   if (set_pa_variant (target, PR_ATTACH_DATA_BIN_DASL, &var))
     {
       TRACEPOINT;
       VariantClear(&var);
       return 1;
     }
 
   VariantClear(&var);
   return 0;
 }
 #endif
 
 static int
 copy_attachment_to_file (std::shared_ptr<Attachment> att, HANDLE hFile)
 {
   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__);
           return 1;
         }
       if (nread != nwritten)
         {
           log_error ("%s:%s: Write truncated.",
                      SRCNAME, __func__);
           return 1;
         }
     }
   return 0;
 }
 
 /** Sets some meta data on the last attachment added. The meta
   data is taken from the attachment object. */
 static int
 fixup_last_attachment (LPDISPATCH mail, std::shared_ptr<Attachment> attachment)
 {
   /* Currently we only set content id */
   if (attachment->get_content_id ().empty())
     {
       log_debug ("%s:%s: Content id not found.",
                  SRCNAME, __func__);
       return 0;
     }
 
   LPDISPATCH attach = get_attachment (mail, -1);
   if (!attach)
     {
       log_error ("%s:%s: No attachment.",
                  SRCNAME, __func__);
       return 1;
     }
   int ret = put_pa_string (attach,
                            PR_ATTACH_CONTENT_ID_DASL,
                            attachment->get_content_id ().c_str());
   gpgol_release (attach);
   return ret;
 }
 
 /** Helper to update the attachments of a mail object in oom.
   does not modify the underlying mapi structure. */
 static int
 add_attachments(LPDISPATCH mail,
                 std::vector<std::shared_ptr<Attachment> > attachments)
 {
   int err = 0;
   for (auto att: attachments)
     {
       if (att->get_display_name().empty())
         {
           log_error ("%s:%s: Ignoring attachment without display name.",
                      SRCNAME, __func__);
           continue;
         }
       wchar_t* wchar_name = utf8_to_wchar (att->get_display_name().c_str());
       HANDLE hFile;
       wchar_t* wchar_file = get_tmp_outfile (wchar_name,
                                              &hFile);
       if (copy_attachment_to_file (att, hFile))
         {
           log_error ("%s:%s: Failed to copy attachment %s to temp file",
                      SRCNAME, __func__, att->get_display_name().c_str());
           err = 1;
         }
       if (add_oom_attachment (mail, wchar_file, wchar_name))
         {
           log_error ("%s:%s: Failed to add attachment: %s",
                      SRCNAME, __func__, att->get_display_name().c_str());
           err = 1;
         }
       if (!DeleteFileW (wchar_file))
         {
           log_error ("%s:%s: Failed to delete tmp attachment for: %s",
                      SRCNAME, __func__, att->get_display_name().c_str());
           err = 1;
         }
       xfree (wchar_file);
       xfree (wchar_name);
 
       err = fixup_last_attachment (mail, att);
     }
   return err;
 }
 
 GPGRT_LOCK_DEFINE(parser_lock);
 
 static DWORD WINAPI
 do_parsing (LPVOID arg)
 {
   gpgrt_lock_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::is_valid_ptr (mail))
     {
       log_debug ("%s:%s: canceling parsing for: %p already deleted",
                  SRCNAME, __func__, arg);
       gpgrt_lock_unlock (&dtor_lock);
       return 0;
     }
   /* This takes a shared ptr of parser. So the parser is
      still valid when the mail is deleted. */
   auto parser = mail->parser();
   gpgrt_lock_unlock (&dtor_lock);
 
   gpgrt_lock_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::is_valid_ptr (mail))
     {
       log_debug ("%s:%s: cancel for: %p already deleted",
                  SRCNAME, __func__, arg);
       gpgrt_lock_unlock (&parser_lock);
       return 0;
     }
 
   if (!parser)
     {
       log_error ("%s:%s: no parser found for mail: %p",
                  SRCNAME, __func__, arg);
       gpgrt_lock_unlock (&parser_lock);
       return -1;
     }
   parser->parse();
   do_in_ui_thread (PARSING_DONE, arg);
   gpgrt_lock_unlock (&parser_lock);
   return 0;
 }
 
 bool
 Mail::is_crypto_mail() const
 {
   if (m_type == MSGTYPE_UNKNOWN || m_type == MSGTYPE_GPGOL ||
       m_type == MSGTYPE_SMIME)
     {
       /* Not a message for us. */
       return false;
     }
   return true;
 }
 
 int
 Mail::decrypt_verify()
 {
   if (!is_crypto_mail())
     {
       log_debug ("%s:%s: Decrypt Verify for non crypto mail: %p.",
                  SRCNAME, __func__, m_mailitem);
       return 0;
     }
   if (m_needs_wipe)
     {
       log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
                  SRCNAME, __func__, m_mailitem);
       return 1;
     }
   set_uuid ();
   m_processed = true;
   /* Insert placeholder */
   char *placeholder_buf;
   if (gpgrt_asprintf (&placeholder_buf, opt.prefer_html ? decrypt_template_html :
                       decrypt_template,
                       is_smime() ? "S/MIME" : "OpenPGP",
                       _("Encrypted message"),
                       _("Please wait while the message is being decrypted / verified...")) == -1)
     {
       log_error ("%s:%s: Failed to format placeholder.",
                  SRCNAME, __func__);
       return 1;
     }
 
   if (opt.prefer_html)
     {
       m_orig_body = get_oom_string (m_mailitem, "HTMLBody");
       if (put_oom_string (m_mailitem, "HTMLBody", placeholder_buf))
         {
           log_error ("%s:%s: Failed to modify html body of item.",
                      SRCNAME, __func__);
         }
     }
   else
     {
       m_orig_body = get_oom_string (m_mailitem, "Body");
       if (put_oom_string (m_mailitem, "Body", placeholder_buf))
         {
           log_error ("%s:%s: Failed to modify body of item.",
                      SRCNAME, __func__);
         }
     }
   xfree (placeholder_buf);
 
   /* Do the actual parsing */
   auto cipherstream = get_attachment_stream (m_mailitem, m_moss_position);
 
   if (!cipherstream)
     {
       log_debug ("%s:%s: Failed to get cipherstream.",
                  SRCNAME, __func__);
       return 1;
     }
 
   m_parser = std::shared_ptr <ParseController> (new ParseController (cipherstream, m_type));
   m_parser->setSender(GpgME::UserID::addrSpecFromString(get_sender().c_str()));
   log_mime_parser ("%s:%s: Parser for \"%s\" is %p",
                    SRCNAME, __func__, get_subject ().c_str(), m_parser.get());
   gpgol_release (cipherstream);
 
   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);
 
   return 0;
 }
 
 void find_and_replace(std::string& source, const std::string &find,
                       const std::string &replace)
 {
   for(std::string::size_type i = 0; (i = source.find(find, i)) != std::string::npos;)
     {
       source.replace(i, find.length(), replace);
       i += replace.length();
     }
 }
 
 void
 Mail::update_body()
 {
   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
         {
           if (put_oom_string (m_mailitem, "Body",
                               error.c_str ()))
             {
               log_error ("%s:%s: Failed to modify html body of item.",
                          SRCNAME, __func__);
             }
         }
       return;
     }
   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__);
             }
         }
       return;
     }
   // No need to carry body anymore
   m_orig_body = std::string();
   auto html = m_parser->get_html_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())
     {
       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__);
         }
       return;
     }
   auto body = m_parser->get_body ();
   find_and_replace (body, "\r\r\n", "\r\n");
   char *converted = ansi_charset_to_utf8 (m_parser->get_body_charset().c_str(),
                                           body.c_str(), body.size());
   int ret = put_oom_string (m_mailitem, "Body", converted ? converted : "");
   xfree (converted);
   if (ret)
     {
       log_error ("%s:%s: Failed to modify body of item.",
                  SRCNAME, __func__);
     }
   return;
 }
 
 void
 Mail::parsing_done()
 {
   TRACEPOINT;
   log_oom_extra ("Mail %p Parsing done for parser: %p",
                  this, 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);
       return;
     }
   /* Store the results. */
   m_decrypt_result = m_parser->decrypt_result ();
   m_verify_result = m_parser->verify_result ();
 
   m_crypto_flags = 0;
   if (!m_decrypt_result.isNull())
     {
       m_crypto_flags |= 1;
     }
   if (m_verify_result.numSignatures())
     {
       m_crypto_flags |= 2;
     }
 
   update_sigstate ();
   m_needs_wipe = true;
 
   TRACEPOINT;
   /* Set categories according to the result. */
   update_categories();
 
   TRACEPOINT;
   /* Update the body */
   update_body();
   TRACEPOINT;
 
   /* Check that there are no unsigned / unencrypted messages. */
   check_attachments ();
 
   /* Update attachments */
   if (add_attachments (m_mailitem, m_parser->get_attachments()))
     {
       log_error ("%s:%s: Failed to update attachments.",
                  SRCNAME, __func__);
     }
 
   /* Invalidate UI to set the correct sig status. */
   m_parser = nullptr;
   gpgoladdin_invalidate_ui ();
   TRACEPOINT;
   return;
 }
 
 int
 Mail::encrypt_sign ()
 {
   int err = -1,
       flags = 0;
   protocol_t proto = opt.enable_smime ? PROTOCOL_UNKNOWN : PROTOCOL_OPENPGP;
   if (!needs_crypto())
     {
       return 0;
     }
   LPMESSAGE message = get_oom_base_message (m_mailitem);
   if (!message)
     {
       log_error ("%s:%s: Failed to get base message.",
                  SRCNAME, __func__);
       return err;
     }
   flags = get_gpgol_draft_info_flags (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. */
 
       wchar_t *w_title = utf8_to_wchar (_(
                               "GpgOL: Oops, G Suite Sync account detected"));
       if (n_att_usable)
         {
           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);
           return -1;
         }
       if (flags == 2)
         {
           wchar_t *msg = utf8_to_wchar (
                        _("G Suite Sync breaks outgoing signed mails.\n"
                         "Ensuring mail integrity (signing) 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);
           return -1;
         }
       if (flags == 3)
         {
           wchar_t *msg = utf8_to_wchar (
                         _("G Suite Sync breaks outgoing signed mails.\n"
                           "Ensuring mail integrity (signing) with G Suite Sync "
                           "is not supported.\n\n"
                           "See: https://dev.gnupg.org/T3545 for details.\n\n"
                           "Do you want to only encrypt the message?"));
           if(MessageBoxW (window,
                           msg,
                           w_title,
                           MB_ICONINFORMATION|MB_YESNO) != IDYES)
             {
               xfree (msg);
               xfree (w_title);
               return -1;
             }
           else
             {
               flags = 1;
             }
           xfree (msg);
         }
       xfree (w_title);
     }
 
   m_do_inline = m_is_gsuite ? true : opt.inline_pgp;
 
   EnableWindow (window, FALSE);
   if (flags == 3)
     {
       log_debug ("%s:%s: Sign / Encrypting message",
                  SRCNAME, __func__);
       err = message_sign_encrypt (message, proto,
                                   NULL, get_sender ().c_str (), this);
     }
   else if (flags == 2)
     {
       err = message_sign (message, proto,
                           NULL, get_sender ().c_str (), this);
     }
   else if (flags == 1)
     {
       err = message_encrypt (message, proto,
                              NULL, get_sender ().c_str (), this);
     }
   else
     {
       log_debug ("%s:%s: Unknown flags for crypto: %i",
                  SRCNAME, __func__, flags);
     }
   log_debug ("%s:%s: Status: %i",
              SRCNAME, __func__, err);
   EnableWindow (window, TRUE);
   gpgol_release (message);
   m_crypt_successful = !err;
   return err;
 }
 
 int
 Mail::needs_crypto ()
 {
   LPMESSAGE message = get_oom_message (m_mailitem);
   bool ret;
   if (!message)
     {
       log_error ("%s:%s: Failed to get message.",
                  SRCNAME, __func__);
       return false;
     }
   ret = get_gpgol_draft_info_flags (message);
   gpgol_release(message);
   return ret;
 }
 
 int
 Mail::wipe (bool force)
 {
   if (!m_needs_wipe && !force)
     {
       return 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);
           return -1;
         }
       return -1;
     }
   m_needs_wipe = false;
   return 0;
 }
 
 int
 Mail::update_oom_data ()
 {
   LPDISPATCH sender = NULL;
   log_debug ("%s:%s", SRCNAME, __func__);
 
-  /* 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)
+  if (!is_crypto_mail())
     {
-      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");
+      /* 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");
+
+      for (int i = 0; m_cached_recipients && m_cached_recipients[i]; ++i)
+          xfree (m_cached_recipients[i]);
+      xfree (m_cached_recipients);
+      m_cached_recipients = get_recipients ();
     }
-  xfree (m_cached_plain_body);
-  m_cached_plain_body = get_oom_string (m_mailitem, "Body");
   /* 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 (is_crypto_mail ())
     {
       /* 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. */
       char *type = get_oom_string (m_mailitem, "SenderEmailType");
       if (type && !strcmp ("SMTP", type))
         {
           char *senderMail = get_oom_string (m_mailitem, "SenderEmailAddress");
           if (senderMail)
             {
               log_debug ("%s:%s Sender found", SRCNAME, __func__);
               m_sender = senderMail;
               xfree (senderMail);
               xfree (type);
               return 0;
             }
         }
       xfree (type);
     }
   sender = get_oom_object (m_mailitem, "SendUsingAccount");
   if (sender)
     {
       char *buf = get_oom_string (sender, "SmtpAddress");
       char *dispName = get_oom_string (sender, "DisplayName");
       gpgol_release (sender);
 
       /* Check for G Suite account */
       if (dispName && !strcmp ("G Suite", dispName))
         {
           m_is_gsuite = true;
         }
       xfree (dispName);
       if (buf && strlen (buf))
         {
           log_debug ("%s:%s Sender fallback 1", SRCNAME, __func__);
           m_sender = buf;
           xfree (buf);
           return 0;
         }
       xfree (buf);
     }
   /* Fallback to Sender object */
   sender = get_oom_object (m_mailitem, "Sender");
   if (sender)
     {
       char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
       gpgol_release (sender);
       if (buf && strlen (buf))
         {
           log_debug ("%s:%s Sender fallback 2", SRCNAME, __func__);
           m_sender = buf;
           xfree (buf);
           return 0;
         }
       xfree (buf);
     }
   /* We don't have s sender object or SendUsingAccount,
      well, in that case fall back to the current user. */
   sender = get_oom_object (m_mailitem, "Session.CurrentUser");
   if (sender)
     {
       char *buf = get_pa_string (sender, PR_SMTP_ADDRESS_DASL);
       gpgol_release (sender);
       if (buf && strlen (buf))
         {
           log_debug ("%s:%s Sender fallback 3", SRCNAME, __func__);
           m_sender = buf;
           xfree (buf);
           return 0;
         }
       xfree (buf);
     }
 
   log_debug ("%s:%s: All fallbacks failed.",
              SRCNAME, __func__);
   return -1;
 }
 
 std::string
 Mail::get_sender ()
 {
   if (m_sender.empty())
     update_oom_data();
   return m_sender;
 }
 
 int
 Mail::close_all_mails ()
 {
   int err = 0;
   std::map<LPDISPATCH, Mail *>::iterator it;
   TRACEPOINT;
   std::map<LPDISPATCH, Mail *> mail_map_copy = g_mail_map;
   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 (!is_valid_ptr (it->second))
         {
           log_debug ("%s:%s: Already deleted mail for %p",
                    SRCNAME, __func__, it->first);
           continue;
         }
 
       if (!it->second->is_crypto_mail())
         {
           continue;
         }
       if (close_inspector (it->second) || close (it->second))
         {
           log_error ("Failed to close mail: %p ", it->first);
           /* Should not happen */
           if (is_valid_ptr (it->second) && it->second->revert())
             {
               err++;
             }
         }
     }
   return err;
 }
 int
 Mail::revert_all_mails ()
 {
   int err = 0;
   std::map<LPDISPATCH, Mail *>::iterator it;
   for (it = g_mail_map.begin(); it != g_mail_map.end(); ++it)
     {
       if (it->second->revert ())
         {
           log_error ("Failed to revert mail: %p ", it->first);
           err++;
           continue;
         }
 
       it->second->set_needs_save (true);
       if (!invoke_oom_method (it->first, "Save", NULL))
         {
           log_error ("Failed to save reverted mail: %p ", it->second);
           err++;
           continue;
         }
     }
   return err;
 }
 
 int
 Mail::wipe_all_mails ()
 {
   int err = 0;
   std::map<LPDISPATCH, Mail *>::iterator it;
   for (it = g_mail_map.begin(); it != g_mail_map.end(); ++it)
     {
       if (it->second->wipe ())
         {
           log_error ("Failed to wipe mail: %p ", it->first);
           err++;
         }
     }
   return err;
 }
 
 int
 Mail::revert ()
 {
   int err = 0;
   if (!m_processed)
     {
       return 0;
     }
 
   err = gpgol_mailitem_revert (m_mailitem);
   if (err == -1)
     {
       log_error ("%s:%s: Message revert failed falling back to wipe.",
                  SRCNAME, __func__);
       return wipe ();
     }
   /* We need to reprocess the mail next time around. */
   m_processed = false;
   m_needs_wipe = false;
   return 0;
 }
 
 bool
 Mail::is_smime ()
 {
   msgtype_t msgtype;
   LPMESSAGE message;
 
   if (m_is_smime_checked)
     {
       return m_is_smime;
     }
 
   message = get_oom_message (m_mailitem);
 
   if (!message)
     {
       log_error ("%s:%s: No message?",
                  SRCNAME, __func__);
       return 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 ("Protocol in multipart signed mail.");
         }
       xfree (proto);
       xfree (ct);
     }
   gpgol_release (message);
   m_is_smime_checked  = true;
   return m_is_smime;
 }
 
 static std::string
 get_string (LPDISPATCH item, const char *str)
 {
   char *buf = get_oom_string (item, str);
   if (!buf)
     return std::string();
   std::string ret = buf;
   xfree (buf);
   return ret;
 }
 
 std::string
 Mail::get_subject() const
 {
   return get_string (m_mailitem, "Subject");
 }
 
 std::string
 Mail::get_body() const
 {
   return get_string (m_mailitem, "Body");
 }
 
 std::string
 Mail::get_html_body() const
 {
   return get_string (m_mailitem, "HTMLBody");
 }
 
 char **
 Mail::get_recipients() const
 {
   LPDISPATCH recipients = get_oom_object (m_mailitem, "Recipients");
   if (!recipients)
     {
       TRACEPOINT;
       return nullptr;
     }
   auto ret = get_oom_recipients (recipients);
   gpgol_release (recipients);
   return ret;
 }
 
 int
 Mail::close_inspector (Mail *mail)
 {
   LPDISPATCH inspector = get_oom_object (mail->item(), "GetInspector");
   HRESULT hr;
   DISPID dispid;
   if (!inspector)
     {
       log_debug ("%s:%s: No inspector.",
                  SRCNAME, __func__);
       return -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);
           return -1;
         }
     }
   gpgol_release (inspector);
   return 0;
 }
 
 /* static */
 int
 Mail::close (Mail *mail)
 {
   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_extra ("%s:%s: Invoking close for: %p",
                  SRCNAME, __func__, mail->item());
   mail->set_close_triggered (true);
   int rc = invoke_oom_method_with_parms (mail->item(), "Close",
                                        NULL, &dispparams);
 
   log_oom_extra ("%s:%s: Returned from close",
                  SRCNAME, __func__);
   return rc;
 }
 
 void
 Mail::set_close_triggered (bool value)
 {
   m_close_triggered = value;
 }
 
 bool
 Mail::get_close_triggered () const
 {
   return m_close_triggered;
 }
 
 static const UserID
 get_uid_for_sender (const Key &k, const char *sender)
 {
   UserID ret;
 
   if (!sender)
     {
       return ret;
     }
 
   if (!k.numUserIDs())
     {
       log_debug ("%s:%s: Key without uids",
                  SRCNAME, __func__);
       return ret;
     }
 
   for (const auto uid: k.userIDs())
     {
       if (!uid.email())
         {
           log_error ("%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__, uid.email(), sender);
           continue;
         }
       if (normalized_sender == normalized_uid)
         {
           ret = uid;
         }
     }
   return ret;
 }
 
 void
 Mail::update_sigstate ()
 {
   std::string sender = get_sender();
 
   if (sender.empty())
     {
       log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
       return;
     }
 
   if (m_verify_result.isNull())
     {
       log_debug ("%s:%s: No verify result.",
                  SRCNAME, __func__);
       return;
     }
 
   if (m_verify_result.error())
     {
       log_debug ("%s:%s: verify error.",
                  SRCNAME, __func__);
       return;
     }
 
   for (const auto sig: m_verify_result.signatures())
     {
       m_is_signed = true;
       m_uid = get_uid_for_sender (sig.key(), sender.c_str());
       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;
         }
       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",
                  SRCNAME, __func__, m_uid.validity());
       m_sig = sig;
       m_is_valid = true;
       return;
     }
 
   log_debug ("%s:%s: No signature with enough trust. Using first",
              SRCNAME, __func__);
   m_sig = m_verify_result.signature(0);
   return;
 }
 
 bool
 Mail::is_valid_sig ()
 {
    return m_is_valid;
 }
 
 void
 Mail::remove_categories ()
 {
   const char *decCategory = _("GpgOL: Encrypted Message");
   const char *verifyCategory = _("GpgOL: Trusted Sender Address");
   remove_category (m_mailitem, decCategory);
   remove_category (m_mailitem, verifyCategory);
 }
 
 /* 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 ()
 {
   HWND wnd = get_active_hwnd ();
   static std::vector<HWND> 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. */
       return;
   }
 
   if (!wnd)
     {
       TRACEPOINT;
       return;
     }
   RECT oldpos;
   if (!GetWindowRect (wnd, &oldpos))
     {
       TRACEPOINT;
       return;
     }
 
   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;
       return;
     }
 
   if (!SetWindowPos (wnd, nullptr,
                      (int)oldpos.left,
                      (int)oldpos.top,
                      (int)oldpos.right - oldpos.left,
                      (int)oldpos.bottom - oldpos.top, 0))
     {
       TRACEPOINT;
       return;
     }
   resized_windows.push_back(wnd);
 }
 
 void
 Mail::update_categories ()
 {
   const char *decCategory = _("GpgOL: Encrypted Message");
   const char *verifyCategory = _("GpgOL: Trusted Sender Address");
   if (is_valid_sig())
     {
       add_category (m_mailitem, verifyCategory);
     }
   else
     {
       remove_category (m_mailitem, verifyCategory);
     }
 
   if (!m_decrypt_result.isNull())
     {
       add_category (m_mailitem, decCategory);
     }
   else
     {
       /* As a small safeguard against fakes we remove our
          categories */
       remove_category (m_mailitem, decCategory);
     }
 
   resize_active_window();
 
   return;
 }
 
 bool
 Mail::is_signed() const
 {
   return m_verify_result.numSignatures() > 0;
 }
 
 bool
 Mail::is_encrypted() const
 {
   return !m_decrypt_result.isNull();
 }
 
 int
 Mail::set_uuid()
 {
   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);
       return -1;
     }
   if (m_uuid.empty())
     {
       m_uuid = uuid;
       Mail *other = get_mail_for_uuid (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;
         }
       g_uid_map.insert (std::pair<std::string, Mail *> (m_uuid, this));
       log_debug ("%s:%s: uuid for %p is now %s",
                  SRCNAME, __func__, this,
                  m_uuid.c_str());
     }
   xfree (uuid);
   return 0;
 }
 
 /* Returns 2 if the userid is ultimately trusted.
 
    Returns 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)
 {
   if (uid.isNull())
     {
       return 0;
     }
   if (uid.validity () == UserID::Validity::Ultimate)
     {
       return 2;
     }
   if (uid.validity () == UserID::Validity::Full)
     {
       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: ParseController::get_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__, signer_uid_str, sig_uid_str,
                                  secKeyID);
                       return 1;
                     }
                 }
             }
         }
     }
   return 0;
 }
 
 std::string
 Mail::get_crypto_summary ()
 {
   const int level = get_signature_level ();
 
   bool enc = is_encrypted ();
   if (level == 4 && enc)
     {
       return _("Security Level 4");
     }
   if (level == 4)
     {
       return _("Trust Level 4");
     }
   if (level == 3 && enc)
     {
       return _("Security Level 3");
     }
   if (level == 3)
     {
       return _("Trust Level 3");
     }
   if (level == 2 && enc)
     {
       return _("Security Level 2");
     }
   if (level == 2)
     {
       return _("Trust Level 2");
     }
   if (enc)
     {
       return _("Encrypted");
     }
   if (is_signed ())
     {
       /* 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. */
       return _("Insecure");
     }
   return _("Insecure");
 }
 
 std::string
 Mail::get_crypto_one_line()
 {
   bool sig = is_signed ();
   bool enc = is_encrypted ();
   if (sig || enc)
     {
       if (sig && enc)
         {
           return _("Signed and encrypted message");
         }
       else if (sig)
         {
           return _("Signed message");
         }
       else if (enc)
         {
           return _("Encrypted message");
         }
     }
   return _("Insecure message");
 }
 
 std::string
 Mail::get_crypto_details()
 {
   std::string message;
 
   /* No signature with keys but error */
   if (!is_encrypted() && !is_signed () && 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();
       return message;
     }
   /* No crypo, what are we doing here? */
   if (!is_encrypted () && !is_signed ())
     {
       return _("You cannot be sure who sent, "
                "modified and read the message in transit.");
     }
   /* Handle encrypt only */
   if (is_encrypted() && !is_signed ())
     {
       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.");
       return message;
     }
 
   bool keyFound = true;
   bool isOpenPGP = m_sig.key().isNull() ? !is_smime() :
                    m_sig.key().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 && m_sig.key().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__);
           return 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"),
                       m_sig.key().issuerName());
       message = buf;
       xfree (buf);
     }
   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 ());
       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);
           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 = is_encrypted () ? _("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");
         }
       else if (m_sig.summary() & Signature::Summary::SysError ||
                m_verify_result.numSignatures() < 1)
         {
           message += _("There was an error verifying the signature.\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\"."),
                           get_sender().c_str());
           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 (is_signed ())
          {
            if (m_sig.isDeVs ())
              {
                message += _("The signature is VS-NfD-compliant.");
              }
            else
              {
                message += _("The signature is not VS-NfD-compliant.");
              }
            message += "\n";
          }
        if (is_encrypted ())
          {
            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.");
     }
   return message;
 }
 
 int
 Mail::get_signature_level () const
 {
   if (!m_is_signed)
     {
       return 0;
     }
 
   if (m_uid.isNull ())
     {
       /* No m_uid matches our sender. */
       return 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()))
     {
       return 4;
     }
   if (m_is_valid && m_uid.validity () == UserID::Validity::Full &&
       (!in_de_vs_mode () || m_sig.isDeVs()))
     {
       return 3;
     }
   if (m_is_valid)
     {
       return 2;
     }
   if (m_sig.validity() == Signature::Validity::Marginal)
     {
       return 1;
     }
   if (m_sig.summary() & Signature::Summary::TofuConflict ||
       m_uid.tofuInfo().validity() == TofuInfo::Conflict)
     {
       return 0;
     }
   return 0;
 }
 
 int
 Mail::get_crypto_icon_id () const
 {
   int level = get_signature_level ();
   int offset = is_encrypted () ? ENCRYPT_ICON_OFFSET : 0;
   return IDI_LEVEL_0 + level + offset;
 }
 
 const char*
 Mail::get_sig_fpr() const
 {
   if (!m_is_signed || m_sig.isNull())
     {
       return nullptr;
     }
   return m_sig.fingerprint();
 }
 
 
 static DWORD WINAPI
 do_locate (LPVOID arg)
 {
   char *recipient = (char*) arg;
   log_debug ("%s:%s searching key for recipient: \"%s\"",
              SRCNAME, __func__, recipient);
   Context *ctx = Context::createForProtocol (OpenPGP);
 
   if (!ctx)
     {
       TRACEPOINT;
       return 0;
     }
 
   ctx->setKeyListMode (GpgME::Extern | GpgME::Local);
   ctx->startKeyListing (recipient, false);
 
   std::vector<Key> keys;
   Error err;
   do {
       keys.push_back (ctx->nextKey(err));
     } while (!err);
   keys.pop_back ();
   ctx->endKeyListing ();
   delete ctx;
 
   if (keys.size ())
     {
       log_debug ("%s:%s found key for recipient: \"%s\"",
                  SRCNAME, __func__, recipient);
     }
   xfree (recipient);
   // do_in_ui_thread (UNKNOWN, NULL);
   return 0;
 }
 
 GPGRT_LOCK_DEFINE(uids_searched_lock);
 
 /** Try to locate the keys for all recipients */
 void Mail::locate_keys()
 {
   gpgrt_lock_lock (&uids_searched_lock);
   char ** recipients = get_recipients ();
 
   if (!recipients)
     {
       TRACEPOINT;
       return;
     }
   for (int i = 0; recipients[i]; i++)
     {
       std::string recp = recipients[i];
       if (uids_searched.find (recp) == uids_searched.end ())
         {
           uids_searched.insert (recp);
           HANDLE thread = CreateThread (NULL, 0, do_locate,
                                         (LPVOID) strdup(recipients[i]), 0,
                                         NULL);
           CloseHandle (thread);
         }
       xfree (recipients[i]);
     }
   xfree (recipients);
   gpgrt_lock_unlock (&uids_searched_lock);
 }
 
 bool
 Mail::is_html_alternative () const
 {
   return m_is_html_alternative;
 }
 
 char *
 Mail::take_cached_html_body ()
 {
   char *ret = m_cached_html_body;
   m_cached_html_body = nullptr;
   return ret;
 }
 
 char *
 Mail::take_cached_plain_body ()
 {
   char *ret = m_cached_plain_body;
   m_cached_plain_body = nullptr;
   return ret;
 }
 
 int
 Mail::get_crypto_flags () const
 {
   return m_crypto_flags;
 }
 
 void
 Mail::set_needs_encrypt (bool value)
 {
   m_needs_encrypt = value;
 }
 
 bool
 Mail::needs_encrypt() const
 {
   return m_needs_encrypt;
 }
+
+char **
+Mail::take_cached_recipients()
+{
+  char **ret = m_cached_recipients;
+  m_cached_recipients = nullptr;
+  return ret;
+}
diff --git a/src/mail.h b/src/mail.h
index 28b4bb5..48141b3 100644
--- a/src/mail.h
+++ b/src/mail.h
@@ -1,372 +1,378 @@
 /* @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 <http://www.gnu.org/licenses/>.
  */
 #ifndef MAIL_H
 #define MAIL_H
 
 #include "oomhelp.h"
 #include "mapihelp.h"
 #include "gpgme++/verificationresult.h"
 #include "gpgme++/decryptionresult.h"
 #include "gpgme++/key.h"
 
 #include <string>
 #include <future>
 
 class ParseController;
 
 /** @brief Data wrapper around a mailitem.
  *
  * This class is intended to bundle all that we know about
  * a Mail. Due to the restrictions in Outlook we sometimes may
  * need additional information that is not available at the time
  * like the sender address of an exchange account in the afterWrite
  * event.
  *
  * This class bundles such information and also provides a way to
  * access the event handler of a mail.
  */
 class Mail
 {
 public:
   /** @brief Construct a mail object for the item.
     *
     * This also installs the event sink for this item.
     *
     * The mail object takes ownership of the mailitem
     * reference. Do not Release it! */
   Mail (LPDISPATCH mailitem);
 
   ~Mail ();
 
   /** @brief looks for existing Mail objects for the OOM mailitem.
 
     @returns A reference to an existing mailitem or NULL in case none
     could be found.
   */
   static Mail* get_mail_for_item (LPDISPATCH mailitem);
 
   /** @brief looks for existing Mail objects in the uuid map.
     Only objects for which set_uid has been called can be found
     in the uid map. Get the Unique ID of a mailitem thorugh get_unique_id
 
     @returns A reference to an existing mailitem or NULL in case none
     could be found.
   */
   static Mail* get_mail_for_uuid (const char *uuid);
 
   /** @brief looks for existing Mail objects.
 
     @returns A reference to an existing mailitem or NULL in case none
     could be found. Can be used to check if a mail object was destroyed.
   */
   static bool is_valid_ptr (const Mail *mail);
 
   /** @brief wipe the plaintext from all known Mail objects.
     *
     * This is intended as a "cleanup" call to be done on unload
     * to avoid leaking plaintext in case we are deactivated while
     * some mails still have their plaintext inserted.
     *
     * @returns the number of errors that occured.
     */
   static int wipe_all_mails ();
 
   /** @brief revert all known Mail objects.
     *
     * Similar to wipe but works on MAPI to revert our attachment
     * dance and restore an original MIME mail.
     *
     * @returns the number of errors that occured.
     */
   static int revert_all_mails ();
 
   /** @brief close all known Mail objects.
     *
     * Close our mail with discard changes set to true.
     * This discards the plaintext / attachments. Afterwards
     * it calls save if neccessary to sync back the collected
     * property changes.
     *
     * This is the nicest of our three "Clean plaintext"
     * functions. Will fallback to revert if closing fails.
     * Closed mails are deleted.
     *
     * @returns the number of errors that occured.
     */
   static int close_all_mails ();
 
   /** @brief Reference to the mailitem. Do not Release! */
   LPDISPATCH item () { return m_mailitem; }
 
   /** @brief Pre process the message. Ususally to be called from BeforeRead.
    *
    * This function assumes that the base message interface can be accessed
    * and calles the MAPI Message handling which changes the message class
    * to enable our own handling.
    *
    * @returns 0 on success.
    */
   int pre_process_message ();
 
   /** @brief Decrypt / Verify the mail.
    *
    * Sets the needs_wipe and was_encrypted variable.
    *
    * @returns 0 on success. */
   int decrypt_verify ();
 
   /** @brief do crypto operations as selected by the user.
    *
    * Initiates the crypto operations according to the gpgol
    * draft info flags.
    *
    * @returns 0 on success. */
   int encrypt_sign ();
 
   /** @brief Necessary crypto operations were completed successfully. */
   bool crypto_successful () { return !needs_crypto() || m_crypt_successful; }
 
   /** @brief Message should be encrypted and or signed.
     0: No
     1: Encrypt
     2: Sign
     3: Encrypt & Sign
   */
   int needs_crypto ();
 
   /** @brief wipe the plaintext from the message and encrypt attachments.
    *
    * @returns 0 on success; */
   int wipe (bool force = false);
 
   /** @brief revert the message to the original mail before our changes.
    *
    * @returns 0 on success; */
   int revert ();
 
   /** @brief update some data collected from the oom
    *
    * This updates cached values from the OOM that are not available
    * in MAPI events like after Write.
    *
    * For Exchange 2013 at least we don't have any other way to get the
    * senders SMTP address then through the object model. So we have to
    * store the sender address for later events that do not allow us to
    * access the OOM but enable us to work with the underlying MAPI structure.
    *
    * It also updated the is_html_alternative value.
    *
    * @returns 0 on success */
   int update_oom_data ();
 
   /** @brief get sender SMTP address (UTF-8 encoded).
    *
    * If the sender address has not been set through update_sender this
    * calls update_sender before returning the sender.
    *
    * @returns A reference to the utf8 sender address. Or NULL. */
   std::string get_sender ();
 
   /** @brief get the subject string (UTF-8 encoded).
     *
     * @returns the subject or an empty string. */
   std::string get_subject () const;
 
   /** @brief Is this a crypto mail handled by gpgol.
   *
   * Calling this is only valid after a message has been processed.
   *
   * @returns true if the mail was either signed or encrypted and we processed
   * it.
   */
   bool is_crypto_mail () const;
 
   /** @brief This mail needs to be actually written.
   *
   * @returns true if the next write event should not be canceled.
   */
   bool needs_save () { return m_needs_save; }
 
   /** @brief set the needs save state.
   */
   void set_needs_save (bool val) { m_needs_save = val; }
 
   /** @brief is this mail an S/MIME mail.
     *
     * @returns true for smime messages.
     */
   bool is_smime ();
 
   /** @brief closes the inspector for a mail
     *
     * @returns true on success.
   */
   static int close_inspector (Mail *mail);
 
   /** @brief get the associated parser.
     only valid while the actual parsing happens. */
   std::shared_ptr<ParseController> parser () { return m_parser; }
 
   /** To be called from outside once the paser was done.
    In Qt this would be a slot that is called once it is finished
    we hack around that a bit by calling it from our windowmessages
    handler.
   */
   void parsing_done ();
 
   /** Returns true if the mail was verified and has at least one
     signature. Regardless of the validity of the mail */
   bool is_signed () const;
 
   /** Returns true if the mail is encrypted to at least one
     recipient. Regardless if it could be decrypted. */
   bool is_encrypted () const;
 
   /** Are we "green" */
   bool is_valid_sig ();
 
   /** Get UID gets UniqueID property of this mail. Returns
     an empty string if the uid was not set with set uid.*/
   const std::string & get_uuid () const { return m_uuid; }
 
   /** Returns 0 on success if the mail has a uid alrady or sets
     the uid. Setting only succeeds if the OOM is currently
     accessible. Returns -1 on error. */
   int set_uuid ();
 
   /** Returns a localized string describing in one or two
     words the crypto status of this mail. */
   std::string get_crypto_summary ();
 
   /** Returns a localized string describing the detailed
     crypto state of this mail. */
   std::string get_crypto_details ();
 
   /** Returns a localized string describing a one line
     summary of the crypto state. */
   std::string get_crypto_one_line ();
 
   /** Get the icon id of the appropiate icon for this mail */
   int get_crypto_icon_id () const;
 
   /** Get the fingerprint of an associated signature or null
       if it is not signed. */
   const char *get_sig_fpr() const;
 
   /** Remove all categories of this mail */
   void remove_categories ();
 
   /** Get the body of the mail */
   std::string get_body () const;
 
   /** Get the html of the mail */
   std::string get_html_body () const;
 
   /** Get the recipients recipients is a null
       terminated array of strings. Needs to be freed
       by the caller. */
   char ** get_recipients () const;
 
   /** Call close with discard changes to discard
       plaintext. returns the value of the oom close
       call. This may have delete the mail if the close
       triggers an unload.
   */
   static int close (Mail *mail);
 
   /** Try to locate the keys for all recipients */
   void locate_keys();
 
   /** State variable to check if a close was triggerd by us. */
   void set_close_triggered (bool value);
   bool get_close_triggered () const;
 
   /** Check if the mail should be sent as html alternative mail.
     Only valid if update_oom_data was called before. */
   bool is_html_alternative () const;
 
   /** Get the html body. It is updated in update_oom_data.
       Caller takes ownership of the string and has to free it.
   */
   char *take_cached_html_body ();
 
   /** Get the plain body. It is updated in update_oom_data.
       Caller takes ownership of the string and has to free it.
   */
   char *take_cached_plain_body ();
 
+  /** Get the cached recipients. It is updated in update_oom_data.
+      Caller takes ownership of the list and has to free it.
+  */
+  char **take_cached_recipients ();
+
   /** Returns 1 if the mail was encrypted, 2 if signed, 3 if both.
       Only valid after decrypt_verify.
   */
   int get_crypto_flags () const;
 
   /** Returns true if the mail should be encrypted in the
       after write event. */
   bool needs_encrypt () const;
   void set_needs_encrypt (bool val);
 
   /** Gets the level of the signature. See:
     https://wiki.gnupg.org/EasyGpg2016/AutomatedEncryption for
     a definition of the levels. */
   int get_signature_level () const;
 
   /** Check if all attachments are hidden and show a warning
     message appropiate to the crypto state if necessary. */
   int check_attachments () const;
 
   /** Check if the mail should be encrypted "inline" */
   bool should_inline_crypt () const {return m_do_inline;}
 private:
   void update_categories ();
   void update_body ();
   void update_sigstate ();
 
   LPDISPATCH m_mailitem;
   LPDISPATCH m_event_sink;
   bool m_processed,    /* The message has been porcessed by us.  */
        m_needs_wipe,   /* We have added plaintext to the mesage. */
        m_needs_save,   /* A property was changed but not by us. */
        m_crypt_successful, /* We successfuly performed crypto on the item. */
        m_is_smime, /* This is an smime mail. */
        m_is_smime_checked, /* it was checked if this is an smime mail */
        m_is_signed, /* Mail is signed */
        m_is_valid, /* Mail is valid signed. */
        m_close_triggered, /* We have programtically triggered a close */
        m_is_html_alternative, /* Body Format is not plain text */
        m_needs_encrypt; /* Send was triggered we want to encrypt. */
   int m_moss_position; /* The number of the original message attachment. */
   int m_crypto_flags;
   std::string m_sender;
   char *m_cached_html_body; /* Cached html body. */
   char *m_cached_plain_body; /* Cached plain body. */
+  char **m_cached_recipients;
   msgtype_t m_type; /* Our messagetype as set in mapi */
   std::shared_ptr <ParseController> m_parser;
   GpgME::VerificationResult m_verify_result;
   GpgME::DecryptionResult m_decrypt_result;
   GpgME::Signature m_sig;
   GpgME::UserID m_uid;
   std::string m_uuid;
   std::string m_orig_body;
   bool m_do_inline;
   bool m_is_gsuite; /* Are we on a gsuite account */
 };
 #endif // MAIL_H
diff --git a/src/message.cpp b/src/message.cpp
index 08a38de..8e63c0d 100644
--- a/src/message.cpp
+++ b/src/message.cpp
@@ -1,1171 +1,1182 @@
 /* message.cpp - Functions for message handling
  * Copyright (C) 2006, 2007, 2008 g10 Code 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 <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 #include <windows.h>
 
 #include "mymapi.h"
 #include "mymapitags.h"
 #include "myexchext.h"
 #include "common.h"
 #include "mapihelp.h"
 #include "mimeparser.h"
 #include "mimemaker.h"
 #include "display.h"
 #include "message.h"
 #include "gpgolstr.h"
+#include "mail.h"
 
 
 /* Wrapper around UlRelease with error checking. */
 static void 
 ul_release (LPVOID punk, const char *func, int lnr)
 {
   ULONG res;
   
   if (!punk)
     return;
   res = UlRelease (punk);
   if (opt.enable_debug & DBG_MEMORY)
     log_debug ("%s:%s:%d: UlRelease(%p) had %lu references\n", 
                SRCNAME, func, lnr, punk, res);
 }
 
 
 /* A helper function used by OnRead and OnOpen to dispatch the
    message.  If FORCE is true, the force flag is passed to the
    verification or decryption.  Returns:
      0 = Message has not been processed by us.
      1 = Message has been processed and was not encrypted.
      2 = Message has been processed by us and was possibly encrypted.
 */
 int
 message_incoming_handler (LPMESSAGE message, HWND hwnd, bool force)
 {
   int retval = 0;
   msgtype_t msgtype;
   int pass = 0;
 
  retry:
   pass++;
   msgtype = mapi_get_message_type (message);
   switch (msgtype)
     {
     case MSGTYPE_UNKNOWN: 
       /* If this message has never passed our change message class
          code it won't have an unknown msgtype _and_ no sig status
          flag.  Thus we look at the message class now and change it if
          required.  It won't get displayed correctly right away but a
          latter decrypt command or when viewed a second time all has
          been set.  Note that we should have similar code for some
          message classes in GpgolUserEvents:OnSelectionChange; but
          there are a couple of problems.  */
       if (pass == 1 && !force && !mapi_has_sig_status (message)
           && !opt.disable_gpgol)
         {
           log_debug ("%s:%s: message class not yet checked - doing now\n",
                      SRCNAME, __func__);
           if (mapi_change_message_class (message, 0, NULL))
             goto retry;
         }
       break;
     case MSGTYPE_SMIME:
       if (pass == 1 && !force && opt.enable_smime && !opt.disable_gpgol)
         {
           log_debug ("%s:%s: message class not checked with smime enabled "
                      "- doing now\n", SRCNAME, __func__);
           if (mapi_change_message_class (message, 0, NULL))
             goto retry;
         }
       break;
     case MSGTYPE_GPGOL:
       log_debug ("%s:%s: ignoring unknown message of original SMIME class\n",
                  SRCNAME, __func__);
       break;
     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
       log_debug ("%s:%s: processing multipart signed message\n", 
                  SRCNAME, __func__);
       retval = 1;
       message_verify (message, msgtype, force, hwnd);
       break;
     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
       log_debug ("%s:%s: processing multipart encrypted message\n",
                  SRCNAME, __func__);
       retval = 2;
       message_decrypt (message, msgtype, force, hwnd);
       break;
     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
       log_debug ("%s:%s: processing opaque signed message\n", 
                  SRCNAME, __func__);
       retval = 1;
       message_verify (message, msgtype, force, hwnd);
       break;
     case MSGTYPE_GPGOL_CLEAR_SIGNED:
       log_debug ("%s:%s: processing clear signed pgp message\n", 
                  SRCNAME, __func__);
       retval = 1;
       message_verify (message, msgtype, force, hwnd);
       break;
     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
       log_debug ("%s:%s: processing opaque encrypted message\n",
                  SRCNAME, __func__);
       retval = 2;
       message_decrypt (message, msgtype, force, hwnd);
       break;
     case MSGTYPE_GPGOL_PGP_MESSAGE:
       log_debug ("%s:%s: processing pgp message\n", SRCNAME, __func__);
       retval = 2;
       message_decrypt (message, msgtype, force, hwnd);
       break;
     }
 
   return retval;
 }
 
 
 /* Common Code used by OnReadComplete and OnOpenComplete to display a
    modified message.   Returns true if the message was encrypted.  */
 bool
 message_display_handler (LPMESSAGE message, LPDISPATCH inspector, HWND hwnd)
 {
   int err;
   int ishtml, wasprotected = false;
   char *body;
 
   if (mapi_get_message_type (message) == MSGTYPE_GPGOL_CLEAR_SIGNED)
     {
       /* We used to display the clearsigned data in the processed
          form, that is without the PGP lines and without the dash
          escaping.  However, this poses the problem that the user does
          not notice that he is viewing a mail which was signed using a
          deprecated method and - far worse - it might update the
          PR_BODY and thus all signature information will get lost.  Of
          course we could save the body away first like we do it with
          encrypted mails, but that is too much overhead and GpgOL will
          always be required to show such a message, which contrdicts
          the very reason of clearsigned messages.  */
       log_debug ("%s:%s: skipping display update for ClearSigned\n",
                  SRCNAME, __func__);
     }
   else
     {
       err = mapi_get_gpgol_body_attachment (message, &body, NULL, 
                                             &ishtml, &wasprotected);
       if (!err && body)
         {
           update_display (hwnd, inspector, wasprotected, ishtml, body);
         }
       else
         {
           update_display (hwnd, NULL, 0, 0, 
                           _("[Crypto operation failed - "
                             "can't show the body of the message]"));
         }
       xfree (body);
     }
 
   return !!wasprotected;
 }
 
 
 
 /* Helper for message_wipe_body_cruft.  */
 static void
 do_wipe_body (LPMESSAGE message)
 {
   if (!mapi_delete_body_props (message, KEEP_OPEN_READWRITE))
     log_debug ("%s:%s: body cruft removed", SRCNAME, __func__); 
 }
 
 
 /* If the current message is an encrypted one remove the body
    properties which might have come up due to OL internal
    syncronization and a failing olDiscard feature.  */
 void
 message_wipe_body_cruft (LPEXCHEXTCALLBACK eecb)
 {
   
   HRESULT hr;
   LPMESSAGE message = NULL;
   LPMDB mdb = NULL;
       
   log_debug ("%s:%s: enter", SRCNAME, __func__);
   hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
   if (SUCCEEDED (hr))
     {
       switch (mapi_get_message_type (message))
         {
         case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
         case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
           {
             if (mapi_has_last_decrypted (message))
               do_wipe_body (message);
             else
               log_debug_w32 (hr, "%s:%s: "
                              "error getting message decryption status", 
                              SRCNAME, __func__);
           }
           break;
 
         case MSGTYPE_GPGOL_PGP_MESSAGE:
           {
             /* In general we can't delete the body of a message if it
                is an inline PGP encrypted message because the body
                holds the ciphertext.  However, while decrypting, we
                take a copy of the body and work on that in future; if
                this has been done we can delete the body.  */
             mapi_attach_item_t *table;
             int found = 0;
             int tblidx;
 
             table = mapi_create_attach_table (message, 0);
             if (table)
               {
                 for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
                   if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
                       && table[tblidx].filename 
                       && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
                     {
                       found = 1;
                       break;
                     }
               }
             mapi_release_attach_table (table);
             if (found)
               do_wipe_body (message);
           }
           break;
 
         default: 
           break;
         }
       
       ul_release (message, __func__, __LINE__);
       ul_release (mdb, __func__, __LINE__);
     }
 }
 
 
 
 /* Display some information about MESSAGE.  */
 void
 message_show_info (LPMESSAGE message, HWND hwnd)
 {
   char *msgcls = mapi_get_message_class (message);
   char *sigstat = mapi_get_sig_status (message);
   char *mimeinfo = mapi_get_mime_info (message);
   size_t buflen;
   char *buffer;
 
   buflen = strlen (msgcls) + strlen (sigstat) + strlen (mimeinfo) + 200;
   buffer = (char*)xmalloc (buflen+1);
   snprintf (buffer, buflen, 
             _("Signature status: %s\n"
               "Message class ..: %s\n"
               "MIME structure .:\n"
               "%s"), 
             sigstat,
             msgcls,
             mimeinfo);
   
   MessageBox (hwnd, buffer, _("GpgOL - Message Information"),
               MB_ICONINFORMATION|MB_OK);
   xfree (buffer);
   xfree (mimeinfo);
   xfree (sigstat);
   xfree (msgcls);
 }
 
 
 static void
 show_message (HWND hwnd, const char *text)
 {
   MessageBox (hwnd, text, _("GpgOL"), MB_ICONINFORMATION|MB_OK);
 }
 
 
 
 /* Convert the clear signed message from INPUT into a PGP/MIME signed
    message and return it in a new allocated buffer.  OUTPUTLEN
    received the valid length of that buffer; the buffer is guaranteed
    to be Nul terminated.  Note: Because we need to insert an empty
    line to indicate the end of MIME headers, the signature won't
    verify unless we tell the signature verification routine to skip
    this first line.  */
 static char *
 pgp_mime_from_clearsigned (LPSTREAM input, size_t *outputlen)
 {
   HRESULT hr;
   STATSTG statinfo;
   ULONG nread;
   char *body = NULL;
   char *p, *p0, *dest, *mark;
   char boundary[BOUNDARYSIZE+1];
   char top_header[200 + 2*BOUNDARYSIZE]; 
   char sig_header[100 + BOUNDARYSIZE]; 
   char end_header[10 + BOUNDARYSIZE];
   size_t reserved_space;
   int state;
 
   *outputlen = 0;
 
   /* Note that our parser does not make use of the micalg parameter.  */
   generate_boundary (boundary);
   snprintf (top_header, sizeof top_header,
             "MIME-Version: 1.0\r\n"
             "Content-Type: multipart/signed; boundary=\"%s\";\r\n"
             "              protocol=\"application/pgp-signature\"\r\n"
             "\r\n"
             "--%s\r\n\r\n", boundary, boundary);
   snprintf (sig_header, sizeof sig_header,
             "--%s\r\n"
             "Content-Type: application/pgp-signature\r\n"
             "\r\n", boundary);
   snprintf (end_header, sizeof end_header,
             "\r\n"
             "--%s--\r\n", boundary);
   reserved_space = (strlen (top_header) + strlen (sig_header) 
                     + strlen (end_header)+ 100);
 
   hr = input->Stat (&statinfo, STATFLAG_NONAME);
   if (hr)
     {
       log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
       return NULL;
     }
       
   body = (char*)xmalloc (reserved_space
                          + (size_t)statinfo.cbSize.QuadPart + 2);
   hr = input->Read (body+reserved_space,
                     (size_t)statinfo.cbSize.QuadPart, &nread);
   if (hr)
     {
       log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
       xfree (body);
       return NULL;
     }
   body[reserved_space + nread] = 0;
   body[reserved_space + nread+1] = 0;  /* Just in case this is
                                           accidently an wchar_t. */
   if (nread != statinfo.cbSize.QuadPart)
     {
       log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
       xfree (body);
       return NULL;
     }
 
   /* We do in place conversion. */
   state = 0;
   dest = NULL;
   for (p=body+reserved_space; p && *p; p = (p=strchr (p+1, '\n'))? (p+1):NULL)
     {
       if (!state && !strncmp (p, "-----BEGIN PGP SIGNED MESSAGE-----", 34)
           && trailing_ws_p (p+34) )
         {
           dest = stpcpy (body, top_header);
           state = 1;
         }
       else if (state == 1)
         {
           /* Wait for an empty line.  */
           if (trailing_ws_p (p))
             state = 2;
         }
       else if (state == 2 && strncmp (p, "-----", 5))
         {
           /* Copy signed data. */
           p0 = p;
           if (*p == '-' && p[1] == ' ')
             p +=2;  /* Remove escaping.  */
           mark = NULL;
           while (*p && *p != '\n')
             {
               if (*p == ' ' || *p == '\t' || *p == '\r')
                 {
                   if (!mark)
                     mark = dest;
                 }
               else
                 mark = NULL;
               *dest++ = *p++;
             }
           if (mark)
             dest = mark;
           if (*p == '\n')
             {
               if (p > p0 && p[-1] == '\r')
                 *dest++ = '\r';
               *dest++ = '\n';
             }
           if (p > p0)
             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
         }
       else if (state == 2)
         {
           /* Armor line encountered.  */
           p0 = p;
           if (strncmp (p, "-----BEGIN PGP SIGNATURE-----", 29)
               || !trailing_ws_p (p+29) )
             log_debug ("%s:%s: invalid clear signed message\n", 
                        SRCNAME, __func__);
           state = 3;
           dest = stpcpy (dest, sig_header);
         
           while (*p && *p != '\n')
             *dest++ = *p++;
           if (*p == '\n')
             {
               if (p[-1] == '\r')
                 *dest++ = '\r';
               *dest++ = '\n';
             }
           if (p > p0)
             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
         }
       else if (state == 3 && strncmp (p, "-----", 5))
         {
           /* Copy signature.  */
           p0 = p;
           while (*p && *p != '\n')
             *dest++ = *p++;
           if (*p == '\n')
             {
               if (p[-1] == '\r')
                 *dest++ = '\r';
               *dest++ = '\n';
             }
           if (p > p0)
             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
         }
       else if (state == 3)
         {
           /* Armor line encountered.  */
           p0 = p;
           if (strncmp (p, "-----END PGP SIGNATURE-----", 27)
               || !trailing_ws_p (p+27) )
             log_debug ("%s:%s: invalid clear signed message (no end)\n", 
                        SRCNAME, __func__);
           while (*p && *p != '\n')
             *dest++ = *p++;
           if (*p == '\n')
             {
               if (p[-1] == '\r')
                 *dest++ = '\r';
               *dest++ = '\n';
             }
           dest = stpcpy (dest, end_header);
           if (p > p0)
             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
           break; /* Ah well, we can stop here.  */
         }
     }
   if (!dest)
     {
       xfree (body);
       return NULL;
     }
   *dest = 0;
   *outputlen = strlen (body);
 
   return body;
 }
 
 
 /* Verify MESSAGE and update the attachments as required.  MSGTYPE
    should be the type of the message so that the fucntion can decide
    what to do.  With FORCE set the verification is done regardlessless
    of a cached signature result. */
 int
 message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
 {
   mapi_attach_item_t *table = NULL;
   LPSTREAM opaquestream = NULL;
   int moss_idx = -1;
   int i;
   char *inbuf = NULL;
   size_t inbuflen = 0;
   protocol_t protocol = PROTOCOL_UNKNOWN;
   int err;
   int mimehack = 0;
 
   switch (msgtype)
     {
     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
     case MSGTYPE_GPGOL_CLEAR_SIGNED:
       break;
     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
     case MSGTYPE_GPGOL_PGP_MESSAGE:
       log_debug ("%s:%s: message of type %d not expected",
                  SRCNAME, __func__, msgtype);
       if (force)
         show_message (hwnd, _("Signature verification of an encrypted message "
                               "is not possible."));
       return -1; /* Should not be called for such a message.  */
     case MSGTYPE_GPGOL:
     case MSGTYPE_SMIME:
     case MSGTYPE_UNKNOWN:
       log_debug ("%s:%s: message of type %d ignored", 
                  SRCNAME, __func__, msgtype);
       if (!force)
         ;
       else if (msgtype == MSGTYPE_GPGOL)
         show_message (hwnd, _("Signature verification of this "
                               "message class is not possible."));
       else if (msgtype == MSGTYPE_SMIME)
         show_message (hwnd, _("Signature verification of this "
                               "S/MIME message is not possible.  Please check "
                               "that S/MIME processing has been enabled."));
       else
         show_message (hwnd, _("This message has no signature."));
       return 0; /* Nothing to do.  */
     }
   
   /* If a verification is forced, we set the cached signature status
      first to "?" to mark that no verification has yet happened.  If a
      verification status has been set and the body attachment is
      available we don't do a verification again.  The need to check
      for the body attachment is to avoid problems if that attachment
      has accidently be deleted. */
   if (force)
     mapi_set_sig_status (message, "?");
   else if (mapi_test_sig_status (message) 
            && !mapi_get_gpgol_body_attachment (message, NULL,NULL,NULL,NULL))
     return 0; /* Already checked that message.  */
 
   if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED)
     {
       /* PGP's clear signed messages are special: All is contained in
          the body and thus there is no requirement for an
          attachment.  */
       LPSTREAM rawstream;
       
       rawstream = mapi_get_body_as_stream (message);
       if (!rawstream)
         return -1;
       
       inbuf = pgp_mime_from_clearsigned (rawstream, &inbuflen);
       gpgol_release (rawstream);
       if (!inbuf)
         return -1;
       protocol = PROTOCOL_OPENPGP;
       mimehack = 1; /* Required for our made up PGP/MIME.  */
     }
   else if (msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED)
     {
       /* S/MIME opaque signed message: The data is expected to be in
          an attachment.  */
       table = mapi_create_attach_table (message, 0);
       if (!table)
         return -1; /* No attachment - this should not happen.  */
 
       for (i=0; !table[i].end_of_table; i++)
         if (table[i].content_type               
             && (!strcmp (table[i].content_type, "application/pkcs7-mime")
                 || !strcmp (table[i].content_type,
                             "application/x-pkcs7-mime"))
             && table[i].filename
             && !strcmp (table[i].filename, "smime.p7m"))
           break;
       if (table[i].end_of_table)
         {
           log_debug ("%s:%s: attachment for opaque signed S/MIME not found",
                      SRCNAME, __func__);
           mapi_release_attach_table (table);
           return -1;
         }
 
       opaquestream = mapi_get_attach_as_stream (message, table+i, NULL);
       if (!opaquestream)
         {
           mapi_release_attach_table (table);
           return -1; /* Problem getting the attachment.  */
         }
       protocol = PROTOCOL_SMIME;
     }
   else
     {
       /* PGP/MIME or S/MIME stuff.  */
       table = mapi_create_attach_table (message, 0);
       if (!table)
         return -1; /* No attachment - this should not happen.  */
 
       for (i=0; !table[i].end_of_table; i++)
         if (table[i].attach_type == ATTACHTYPE_MOSS)
           {
             moss_idx = i;
             break;
           }
       if (moss_idx == -1 && !table[0].end_of_table && table[1].end_of_table)
         {
           /* No MOSS flag found in the table but there is only one
              attachment.  Due to the message type we know that this is
              the original MOSS message.  We mark this attachment as
              hidden, so that it won't get displayed.  We further mark
              it as our original MOSS attachment so that after parsing
              we have a mean to find it again (see above).  */ 
           moss_idx = 0;
           mapi_mark_moss_attach (message, table+0);
         }
       
       if (moss_idx == -1)
         {
           mapi_release_attach_table (table);
           return -1; /* No original attachment - this should not happen.  */
         }
 
       inbuf = mapi_get_attach (message, 0, table+0, &inbuflen);
       if (!inbuf)
         {
           mapi_release_attach_table (table);
           return -1; /* Problem getting the attachment.  */
         }
     }
 
   if (opaquestream)
     err = mime_verify_opaque (protocol, opaquestream,
                               NULL, 0, message, hwnd, 0, 0);
   else
     err = mime_verify (protocol, inbuf, inbuflen, message, hwnd, 0, mimehack);
   log_debug ("mime_verify%s returned %d", opaquestream? "_opaque":"", err);
   if (err && opt.enable_debug)
     {
       char buf[200];
       
       snprintf (buf, sizeof buf, "Verify failed (%s)", gpg_strerror (err));
       MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
     }
   if (opaquestream)
     gpgol_release (opaquestream);
   xfree (inbuf);
                     
   if (err)
     {
       char buf[200];
       snprintf (buf, sizeof buf, "- %s", gpg_strerror (err));
       mapi_set_sig_status (message, gpg_strerror (err));
     }
   else
     mapi_set_sig_status (message, "! Good signature");
 
   mapi_save_changes (message, KEEP_OPEN_READWRITE);
 
   mapi_release_attach_table (table);
   return 0;
 }
 
 
 /* Decrypt MESSAGE, check signature and update the attachments as
    required.  MSGTYPE should be the type of the message so that the
    function can decide what to do.  With FORCE set the decryption is
    done regardless whether it has already been done.  */
 int
 message_decrypt (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
 {
   mapi_attach_item_t *table = NULL;
   int part1_idx, part2_idx;
   int tblidx;
   int retval = -1;
   LPSTREAM cipherstream;
   gpg_error_t err, sig_err;
   int is_opaque = 0;
   protocol_t protocol;
   LPATTACH saved_attach = NULL;
   int need_saved_attach = 0;
   int need_rfc822_parser = 0;
   int is_simple_pgp = 0;
   
 
   switch (msgtype)
     {
     case MSGTYPE_UNKNOWN:
     case MSGTYPE_SMIME:
     case MSGTYPE_GPGOL:
     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
     case MSGTYPE_GPGOL_CLEAR_SIGNED:
       if (force)
         show_message (hwnd, _("This message is not encrypted.")); 
       return -1; /* Should not have been called for this.  */
     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
       break;
     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
       is_opaque = 1;
       break;
     case MSGTYPE_GPGOL_PGP_MESSAGE:
       break;
     }
   
   if (!force && mapi_test_last_decrypted (message))
     return 0; /* Already decrypted this message once during this
                  session.  No need to do it again. */
 
   if (msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
     {
       /* PGP messages are special: All is contained in the body and
          thus there would be no requirement for an attachment.
          However, due to problems with Outlook overwriting the body of
          the message after decryption, we need to save the body away
          before decrypting it.  We then always look for that original
          body attachment or create one if it does not exist.  */
       part1_idx = -1;
       table = mapi_create_attach_table (message, 0);
       if (!table)
         ;
       else
         {
           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
             if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
                 && table[tblidx].filename 
                 && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
               {
                 part1_idx = tblidx;
                 break;
               }
         }
       if (part1_idx == -1)
         {
           mapi_release_attach_table (table);
           if (mapi_body_to_attachment (message))
             table = NULL;
           else
             table = mapi_create_attach_table (message, 0);
           if (table)
             {
               for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
                 if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
                     && table[tblidx].filename 
                     && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
                   {
                     part1_idx = tblidx;
                     break;
                   }
             }
         }
       if (!table || part1_idx == -1)
         {
           log_debug ("%s:%s: problem copying the PGP inline encrypted message",
                      SRCNAME, __func__);
           goto leave;
         }
       cipherstream = mapi_get_attach_as_stream (message, table+part1_idx,
                                                 NULL);
       if (!cipherstream)
         goto leave; /* Problem getting the attachment.  */
       protocol = PROTOCOL_OPENPGP;
       need_rfc822_parser = 1;
       is_simple_pgp = 1;
       
     }
   else
     {
       /* PGP/MIME or S/MIME stuff.  */
       table = mapi_create_attach_table (message, 0);
       if (!table)
         goto leave; /* No attachment - this should not happen.  */
 
       if (is_opaque)
         {
           /* S/MIME opaque encrypted message: We expect one
              attachment.  As we don't know wether we are called the
              first time, we first try to find this attachment by
              looking at all attachments.  Only if this fails we
              identify it by its order.  */
           part2_idx = -1;
           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
             if (table[tblidx].attach_type == ATTACHTYPE_MOSSTEMPL)
               {
                 /* This attachment has been generated by us in the
                    course of sending a new message.  The content will
                    be multipart/signed because we used this to trick
                    out OL.  We stop here and use this part for further
                    processing.  */
                 part2_idx = tblidx;
                 need_rfc822_parser = 1;
                 break;
               }
             else if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
               {
                 if (part2_idx == -1 && table[tblidx].content_type 
                     && (!strcmp (table[tblidx].content_type,
                                  "application/pkcs7-mime")
                         || !strcmp (table[tblidx].content_type,
                                     "application/x-pkcs7-mime")))
                   part2_idx = tblidx;
               }
           if (part2_idx == -1 && tblidx >= 1)
             {
               /* We have attachments but none are marked.  Thus we
                  assume that this is the first time we see this
                  message and we will set the mark now if we see
                  appropriate content types. */
               if (table[0].content_type               
                   && (!strcmp (table[0].content_type, "application/pkcs7-mime")
                       || !strcmp (table[0].content_type,
                                   "application/x-pkcs7-mime")))
                 part2_idx = 0;
               if (part2_idx != -1)
                 mapi_mark_moss_attach (message, table+part2_idx);
             }
           if (part2_idx == -1)
             {
               log_debug ("%s:%s: this is not an S/MIME encrypted message",
                          SRCNAME, __func__);
               goto leave;
             }
           protocol = PROTOCOL_SMIME;
         }
       else 
         {
           /* Multipart/encrypted message: We expect 2 attachments.
              The first one with the version number and the second one
              with the ciphertext.  As we don't know wether we are
              called the first time, we first try to find these
              attachments by looking at all attachments.  Only if this
              fails we identify them by their order (i.e. the first 2
              attachments) and mark them as part1 and part2.  */
           part1_idx = part2_idx = -1;
           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
             if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
               {
                 if (part1_idx == -1 && table[tblidx].content_type 
                     && !strcmp (table[tblidx].content_type,
                                 "application/pgp-encrypted"))
                   part1_idx = tblidx;
                 else if (part2_idx == -1 && table[tblidx].content_type 
                          && !strcmp (table[tblidx].content_type,
                                      "application/octet-stream"))
                   part2_idx = tblidx;
               }
           if (part1_idx == -1 && part2_idx == -1 && tblidx >= 2)
             {
               /* At least 2 attachments but none are marked.  Thus we
                  assume that this is the first time we see this
                  message and we will set the mark now if we see
                  appropriate content types. */
               if (table[0].content_type               
                   && !strcmp (table[0].content_type,
                               "application/pgp-encrypted"))
                 part1_idx = 0;
               if (table[1].content_type             
                   && !strcmp (table[1].content_type, 
                               "application/octet-stream"))
                 part2_idx = 1;
               if (part1_idx != -1 && part2_idx != -1)
                 {
                   mapi_mark_moss_attach (message, table+part1_idx);
                   mapi_mark_moss_attach (message, table+part2_idx);
                 }
             }
 
 
           if (part1_idx == -1 || (part2_idx == -1
               && !table[0].end_of_table && table[1].end_of_table
               && table[0].attach_type == ATTACHTYPE_MOSS
               && table[0].filename 
               && !strcmp (table[0].filename, MIMEATTACHFILENAME)))
             {
               /* This is likely a PGP/MIME created by us.  Due to the
                  way we created that message, the MAPI derived content
                  type is wrong and there is only one attachment
                  (gpgolXXX.dat).  We simply assume that it is PGP/MIME
                  encrypted and pass it on to the mime parser.  We also
                  keep the attachment open so that we can later set it
                  to hidden if not yet done.  I can't remember whether
                  it is possible to set the hidden attribute when
                  creating the message - probably not.  Thus we take
                  care of it here. */
               log_debug ("%s:%s: "
                          "assuming self-created PGP/MIME encrypted message",
                          SRCNAME, __func__);
               part2_idx = 0;
               need_saved_attach = 1;
             }
           else if (part1_idx == -1 || part2_idx == -1)
             {
               log_debug ("%s:%s: this is not a PGP/MIME encrypted message",
                          SRCNAME, __func__);
               goto leave;
             }
           protocol = PROTOCOL_OPENPGP;
         }
       
       cipherstream = mapi_get_attach_as_stream (message, table+part2_idx,
                                                 need_saved_attach? 
                                                 &saved_attach : NULL );
       if (!cipherstream)
         goto leave; /* Problem getting the attachment.  */
     }
 
   sig_err = gpg_error (GPG_ERR_NO_DATA);
   err = mime_decrypt (protocol, cipherstream, message, 
                       need_rfc822_parser, is_simple_pgp, hwnd, 0, &sig_err);
   log_debug ("mime_decrypt returned %d (%s)", err, gpg_strerror (err));
   if (err && opt.enable_debug)
     {
       char buf[200];
       
       switch (gpg_err_code (err))
         {
         case GPG_ERR_NO_DATA:
           /* The UI server already displayed a message.  */
           break;
         default:
           snprintf (buf, sizeof buf,
                     _("Decryption failed\n(%s)"), gpg_strerror (err));
           MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
           break;
         }
     }
   else if (!err)
     {
       if (saved_attach)
         mapi_set_attach_hidden (saved_attach);
 
       if (gpg_err_code (sig_err) != GPG_ERR_NO_DATA)
         {
           /* Note: Saving the result of the signature in a property
              will reveal that there is a signature inside the
              encrypted message - however it does reveal only a
              common assumption and thus it is acceptable to do
              this.  */
           if (sig_err)
             {
               char buf[200];
               snprintf (buf, sizeof buf, "- %s", gpg_strerror (sig_err));
               mapi_set_sig_status (message, gpg_strerror (sig_err));
             }
           else
             mapi_set_sig_status (message, "! Good signature");
           mapi_save_changes (message, KEEP_OPEN_READWRITE);
         }
       
     }
   gpgol_release (cipherstream);
   retval = 0;
 
 
  leave:
   if (saved_attach)
     gpgol_release (saved_attach);
   mapi_release_attach_table (table);
   return retval;
 }
 
 
 
 
 /* Return an array of strings with the recipients of the message. On
    success a malloced array is returned containing allocated strings
    for each recipient.  The end of the array is marked by NULL.
    Caller is responsible for releasing the array.  On failure NULL is
    returned.  */
 static char **
 get_recipients (LPMESSAGE message)
 {
   static SizedSPropTagArray (2L, PropRecipientNum) = {2L, {PR_SMTP_ADDRESS,
                                                            PR_EMAIL_ADDRESS}};
   HRESULT hr;
   LPMAPITABLE lpRecipientTable = NULL;
   LPSRowSet lpRecipientRows = NULL;
   unsigned int rowidx;
   SPropValue_s val;
   char **rset;
   int rsetidx;
 
 
   if (!message)
     return NULL;
 
   hr = message->GetRecipientTable (0, &lpRecipientTable);
   if (FAILED (hr)) 
     {
       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed", SRCNAME, __func__);
       return NULL;
     }
 
   hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
                        NULL, NULL, 0L, &lpRecipientRows);
   if (FAILED (hr)) 
     {
       log_debug_w32 (-1, "%s:%s: HrQueryAllRows failed", SRCNAME, __func__);
       if (lpRecipientTable)
         gpgol_release (lpRecipientTable);
       return NULL;
     }
 
   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
 
   for (rowidx=0, rsetidx=0; rowidx < lpRecipientRows->cRows; rowidx++)
     {
       bool found_one = false;
       for (int colidx = 0; colidx < lpRecipientRows->aRow[rowidx].cValues;
            colidx++)
         {
           val = lpRecipientRows->aRow[rowidx].lpProps[colidx];
 
           switch (PROP_TYPE (val.ulPropTag))
             {
             case PT_UNICODE:
               if ((rset[rsetidx] = wchar_to_utf8 (val.Value.lpszW)))
                 {
                   rsetidx++;
                   found_one = true;
                 }
               else
                 log_debug ("%s:%s: error converting recipient to utf8\n",
                            SRCNAME, __func__);
               break;
 
             case PT_STRING8: /* Assume ASCII. */
               rset[rsetidx++] = xstrdup (val.Value.lpszA);
               found_one = true;
               break;
 
+            case PT_ERROR:
+              log_debug ("%s:%s: proptag=0x%08lx is error",
+                         SRCNAME, __func__, val.ulPropTag);
             default:
               log_debug ("%s:%s: proptag=0x%08lx not supported\n",
                          SRCNAME, __func__, val.ulPropTag);
               break;
             }
           if (found_one)
             {
               break;
             }
 
         }
     }
 
   if (lpRecipientTable)
     gpgol_release (lpRecipientTable);
   if (lpRecipientRows)
     FreeProws(lpRecipientRows);	
   
   log_debug ("%s:%s: got %d recipients:\n", SRCNAME, __func__, rsetidx);
   for (rsetidx=0; rset[rsetidx]; rsetidx++)
     log_debug ("%s:%s: \t`%s'\n", SRCNAME, __func__, rset[rsetidx]);
 
   return rset;
 }
 
 
 static void
 release_recipient_array (char **recipients)
 {
   int idx;
 
   if (recipients)
     {
       for (idx=0; recipients[idx]; idx++)
         xfree (recipients[idx]);
       xfree (recipients);
     }
 }
 
 
 
 static int
 sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd, int signflag,
               const char *sender, Mail* mail)
 {
   gpg_error_t err;
-  char **recipients;
+  char **recipients = nullptr;
 
-  recipients = get_recipients (message);
+  if (mail)
+    {
+      recipients = mail->take_cached_recipients ();
+    }
+  if (!recipients)
+    {
+      recipients = get_recipients (message);
+    }
   if (!recipients || !recipients[0])
     {
       MessageBox (hwnd, _("No recipients to encrypt to are given"),
                   "GpgOL", MB_ICONERROR|MB_OK);
 
       err = gpg_error (GPG_ERR_GENERAL);
     }
   else
     {
       if (signflag)
         err = mime_sign_encrypt (message, hwnd, protocol, recipients,
                                  sender, mail);
       else
         err = mime_encrypt (message, hwnd, protocol, recipients,
                             sender, mail);
       if (gpg_err_code (err) == GPG_ERR_NO_DATA)
         {
           MessageBox (hwnd, _("Encrypting or signing an empty message "
                               "is not possible."),
                       "GpgOL", MB_ICONERROR|MB_OK);
         }
       else if (err && err != -1)
         {
           char buf[200];
           snprintf (buf, sizeof buf,
                     _("Encryption failed (%s)"), gpg_strerror (err));
           MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
         }
     }
   release_recipient_array (recipients);
   return err;
 }
 
 
 /* Sign the MESSAGE.  */
 int 
 message_sign (LPMESSAGE message, protocol_t protocol, HWND hwnd,
               const char *sender, Mail *mail)
 {
   gpg_error_t err;
 
   err = mime_sign (message, hwnd, protocol, sender, mail);
   if (gpg_err_code (err) == GPG_ERR_NO_DATA)
     {
       MessageBox (hwnd, _("Encrypting or signing an empty message "
                           "is not possible."),
                   "GpgOL", MB_ICONERROR|MB_OK);
     }
   else if (err && opt.enable_debug)
     {
       char buf[200];
       
       snprintf (buf, sizeof buf,
                 _("Signing failed (%s)"), gpg_strerror (err));
       MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
     }
   return err;
 }
 
 
 
 /* Encrypt the MESSAGE.  */
 int 
 message_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd,
                  const char *sender, Mail *mail)
 {
   return sign_encrypt (message, protocol, hwnd, 0, sender, mail);
 }
 
 
 /* Sign+Encrypt the MESSAGE.  */
 int 
 message_sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd,
                       const char *sender, Mail *mail)
 {
   return sign_encrypt (message, protocol, hwnd, 1, sender, mail);
 }
 
 
 
diff --git a/src/oomhelp.cpp b/src/oomhelp.cpp
index 2141c9c..45013b4 100644
--- a/src/oomhelp.cpp
+++ b/src/oomhelp.cpp
@@ -1,1709 +1,1771 @@
 /* oomhelp.cpp - Helper functions for the Outlook Object Model
  * Copyright (C) 2009 g10 Code GmbH
  * 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 <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
 #include <windows.h>
 #include <olectl.h>
 #include <string>
 #include <rpc.h>
 
 #include "myexchext.h"
 #include "common.h"
 
 #include "oomhelp.h"
 #include "gpgoladdin.h"
 
 
 /* Return a malloced string with the utf-8 encoded name of the object
    or NULL if not available.  */
 char *
 get_object_name (LPUNKNOWN obj)
 {
   HRESULT hr;
   LPDISPATCH disp = NULL;
   LPTYPEINFO tinfo = NULL;
   BSTR bstrname;
   char *name = NULL;
 
   if (!obj)
     goto leave;
 
   obj->QueryInterface (IID_IDispatch, (void **)&disp);
   if (!disp)
     goto leave;
 
   disp->GetTypeInfo (0, 0, &tinfo);
   if (!tinfo)
     {
       log_debug ("%s:%s: no typeinfo found for object\n", 
                  SRCNAME, __func__);
       goto leave;
     }
 
   bstrname = NULL;
   hr = tinfo->GetDocumentation (MEMBERID_NIL, &bstrname, 0, 0, 0);
   if (hr || !bstrname)
     log_debug ("%s:%s: GetDocumentation failed: hr=%#lx\n", 
                SRCNAME, __func__, hr);
   if (bstrname)
     {
       name = wchar_to_utf8 (bstrname);
       SysFreeString (bstrname);
     }
 
  leave:
   if (tinfo)
     gpgol_release (tinfo);
   if (disp)
     gpgol_release (disp);
 
   return name;
 }
 
 
 /* Lookup the dispid of object PDISP for member NAME.  Returns
    DISPID_UNKNOWN on error.  */
 DISPID
 lookup_oom_dispid (LPDISPATCH pDisp, const char *name)
 {
   HRESULT hr;
   DISPID dispid;
   wchar_t *wname;
 
   if (!pDisp || !name)
     return DISPID_UNKNOWN; /* Error: Invalid arg.  */
 
   wname = utf8_to_wchar (name);
   if (!wname)
     return DISPID_UNKNOWN;/* Error:  Out of memory.  */
 
   hr = pDisp->GetIDsOfNames (IID_NULL, &wname, 1, 
                              LOCALE_SYSTEM_DEFAULT, &dispid);
   xfree (wname);
   if (hr != S_OK || dispid == DISPID_UNKNOWN)
     log_debug ("%s:%s: error looking up dispid(%s)=%d: hr=0x%x\n",
                SRCNAME, __func__, name, (int)dispid, (unsigned int)hr);
   if (hr != S_OK)
     dispid = DISPID_UNKNOWN;
 
   return dispid;
 }
 
 static void
 init_excepinfo (EXCEPINFO *err)
 {
   if (!err)
     {
       return;
     }
   err->wCode = 0;
   err->wReserved = 0;
   err->bstrSource = nullptr;
   err->bstrDescription = nullptr;
   err->bstrHelpFile = nullptr;
   err->dwHelpContext = 0;
   err->pvReserved = nullptr;
   err->pfnDeferredFillIn = nullptr;
   err->scode = 0;
 }
 
 void
 dump_excepinfo (EXCEPINFO err)
 {
   log_debug ("%s:%s: Exception: \n"
              "              wCode: 0x%x\n"
              "              wReserved: 0x%x\n"
              "              source: %S\n"
              "              desc: %S\n"
              "              help: %S\n"
              "              helpCtx: 0x%x\n"
              "              deferredFill: %p\n"
              "              scode: 0x%x\n",
              SRCNAME, __func__, (unsigned int) err.wCode,
              (unsigned int) err.wReserved,
              err.bstrSource ? err.bstrSource : L"null",
              err.bstrDescription ? err.bstrDescription : L"null",
              err.bstrHelpFile ? err.bstrDescription : L"null",
              (unsigned int) err.dwHelpContext,
              err.pfnDeferredFillIn,
              (unsigned int) err.scode);
 }
 
 /* Return the OOM object's IDispatch interface described by FULLNAME.
    Returns NULL if not found.  PSTART is the object where the search
    starts.  FULLNAME is a dot delimited sequence of object names.  If
    an object name has a "(foo)" suffix this passes it as a parameter
    to the invoke function (i.e. using (DISPATCH|PROPERTYGET)).  Object
    names including the optional suffix are truncated at 127 byte.  */
 LPDISPATCH
 get_oom_object (LPDISPATCH pStart, const char *fullname)
 {
   HRESULT hr;
   LPDISPATCH pObj = pStart;
   LPDISPATCH pDisp = NULL;
 
   log_oom ("%s:%s: looking for %p->`%s'",
            SRCNAME, __func__, pStart, fullname);
 
   while (pObj)
     {
       DISPPARAMS dispparams;
       VARIANT aVariant[4];
       VARIANT vtResult;
       wchar_t *wname;
       char name[128];
       int n_parms = 0;
       BSTR parmstr = NULL;
       INT  parmint = 0;
       DISPID dispid;
       char *p, *pend;
       int dispmethod;
       unsigned int argErr = 0;
       EXCEPINFO execpinfo;
 
       init_excepinfo (&execpinfo);
 
       if (pDisp)
         {
           gpgol_release (pDisp);
           pDisp = NULL;
         }
       if (pObj->QueryInterface (IID_IDispatch, (LPVOID*)&pDisp) != S_OK)
         {
           log_error ("%s:%s Object does not support IDispatch",
                      SRCNAME, __func__);
           gpgol_release (pObj);
           return NULL;
         }
       /* Confirmed through testing that the retval needs a release */
       if (pObj != pStart)
         gpgol_release (pObj);
       pObj = NULL;
       if (!pDisp)
         return NULL;  /* The object has no IDispatch interface.  */
       if (!*fullname)
         {
           log_oom ("%s:%s:         got %p",SRCNAME, __func__, pDisp);
           return pDisp; /* Ready.  */
         }
       
       /* Break out the next name part.  */
       {
         const char *dot;
         size_t n;
         
         dot = strchr (fullname, '.');
         if (dot == fullname)
           {
             gpgol_release (pDisp);
             return NULL;  /* Empty name part: error.  */
           }
         else if (dot)
           n = dot - fullname;
         else
           n = strlen (fullname);
         
         if (n >= sizeof name)
           n = sizeof name - 1;
         strncpy (name, fullname, n);
         name[n] = 0;
         
         if (dot)
           fullname = dot + 1;
         else
           fullname += strlen (fullname);
       }
       
       if (!strncmp (name, "get_", 4) && name[4])
         {
           dispmethod = DISPATCH_PROPERTYGET;
           memmove (name, name+4, strlen (name+4)+1);
         }
       else if ((p = strchr (name, '(')))
         {
           *p++ = 0;
           pend = strchr (p, ')');
           if (pend)
             *pend = 0;
 
           if (*p == ',' && p[1] != ',')
             {
               /* We assume this is "foo(,30007)".  I.e. the frst arg
                  is not given and the second one is an integer.  */
               parmint = (int)strtol (p+1, NULL, 10);
               n_parms = 4;
             }
           else
             {
               wname = utf8_to_wchar (p);
               if (wname)
                 {
                   parmstr = SysAllocString (wname);
                   xfree (wname);
                 }
               if (!parmstr)
                 {
                   gpgol_release (pDisp);
                   return NULL; /* Error:  Out of memory.  */
                 }
               n_parms = 1;
             }
           dispmethod = DISPATCH_METHOD|DISPATCH_PROPERTYGET;
         }
       else
         dispmethod = DISPATCH_METHOD;
 
       /* Lookup the dispid.  */
       dispid = lookup_oom_dispid (pDisp, name);
       if (dispid == DISPID_UNKNOWN)
         {
           if (parmstr)
             SysFreeString (parmstr);
           gpgol_release (pDisp);
           return NULL;  /* Name not found.  */
         }
 
       /* Invoke the method.  */
       dispparams.rgvarg = aVariant;
       dispparams.cArgs = 0;
       if (n_parms)
         {
           if (n_parms == 4)
             {
               dispparams.rgvarg[0].vt = VT_ERROR; 
               dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; 
               dispparams.rgvarg[1].vt = VT_ERROR; 
               dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; 
               dispparams.rgvarg[2].vt = VT_INT; 
               dispparams.rgvarg[2].intVal = parmint; 
               dispparams.rgvarg[3].vt = VT_ERROR; 
               dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
               dispparams.cArgs = n_parms;
             }
           else if (n_parms == 1 && parmstr)
             {
               dispparams.rgvarg[0].vt = VT_BSTR;
               dispparams.rgvarg[0].bstrVal = parmstr;
               dispparams.cArgs++;
             }
         }
       dispparams.cNamedArgs = 0;
       VariantInit (&vtResult);
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           dispmethod, &dispparams,
                           &vtResult, &execpinfo, &argErr);
       if (parmstr)
         SysFreeString (parmstr);
       if (hr != S_OK || vtResult.vt != VT_DISPATCH)
         {
-          log_debug ("%s:%s:       error: '%s' p=%p vt=%d hr=0x%x argErr=0x%x",
+          log_debug ("%s:%s: failure: '%s' p=%p vt=%d hr=0x%x argErr=0x%x",
                      SRCNAME, __func__,
                      name, vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
                      (unsigned int)argErr);
           dump_excepinfo (execpinfo);
           VariantClear (&vtResult);
           gpgol_release (pDisp);
           return NULL;  /* Invoke failed.  */
         }
 
       pObj = vtResult.pdispVal;
     }
-  log_debug ("%s:%s:       error: no object", SRCNAME, __func__);
+  log_debug ("%s:%s: no object", SRCNAME, __func__);
   return NULL;
 }
 
 
 /* Helper for put_oom_icon.  */
 static int
 put_picture_or_mask (LPDISPATCH pDisp, int resource, int size, int is_mask)
 {
   HRESULT hr;
   PICTDESC pdesc;
   LPDISPATCH pPict;
   DISPID dispid_put = DISPID_PROPERTYPUT;
   UINT fuload;
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[2];
 
   /* When loading the mask we need to set the monochrome flag.  We
      better create a DIB section to avoid possible rendering
      problems.  */
   fuload = LR_CREATEDIBSECTION | LR_SHARED;
   if (is_mask)
     fuload |= LR_MONOCHROME;
   
   memset (&pdesc, 0, sizeof pdesc);
   pdesc.cbSizeofstruct = sizeof pdesc;
   pdesc.picType = PICTYPE_BITMAP;
   pdesc.bmp.hbitmap = (HBITMAP) LoadImage (glob_hinst,
                                            MAKEINTRESOURCE (resource),
                                            IMAGE_BITMAP, size, size, fuload);
   if (!pdesc.bmp.hbitmap)
     {
       log_error_w32 (-1, "%s:%s: LoadImage(%d) failed\n", 
                      SRCNAME, __func__, resource);
       return -1;
     }
 
   /* 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 -1;
     }
         
   /* Store to the Picture or Mask property of the CommandBarButton.  */
   dispid = lookup_oom_dispid (pDisp, is_mask? "Mask":"Picture");
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_DISPATCH;
   dispparams.rgvarg[0].pdispVal = pPict;
   dispparams.cArgs = 1;
   dispparams.rgdispidNamedArgs = &dispid_put;
   dispparams.cNamedArgs = 1;
   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                       DISPATCH_PROPERTYPUT, &dispparams,
                       NULL, NULL, NULL);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: Putting icon failed: %#lx", SRCNAME, __func__, hr);
       return -1;
     }
   return 0;
 }
 
 
 /* Update the icon of PDISP using the bitmap with RESOURCE ID.  The
    function adds the system pixel size to the resource id to compute
    the actual icon size.  The resource id of the mask is the N+1.  */
 int
 put_oom_icon (LPDISPATCH pDisp, int resource_id, int size)
 {
   int rc;
 
   /* This code is only relevant for Outlook < 2010.
     Ideally it should grab the system pixel size and use an
     icon of the appropiate size (e.g. 32 or 64px)
   */
 
   rc = put_picture_or_mask (pDisp, resource_id, size, 0);
   if (!rc)
     rc = put_picture_or_mask (pDisp, resource_id + 1, size, 1);
 
   return rc;
 }
 
 
 /* Set the boolean property NAME to VALUE.  */
 int
 put_oom_bool (LPDISPATCH pDisp, const char *name, int value)
 {
   HRESULT hr;
   DISPID dispid_put = DISPID_PROPERTYPUT;
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[1];
 
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid == DISPID_UNKNOWN)
     return -1;
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_BOOL;
   dispparams.rgvarg[0].boolVal = value? VARIANT_TRUE:VARIANT_FALSE;
   dispparams.cArgs = 1;
   dispparams.rgdispidNamedArgs = &dispid_put;
   dispparams.cNamedArgs = 1;
   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                       DISPATCH_PROPERTYPUT, &dispparams,
                       NULL, NULL, NULL);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
                  SRCNAME, __func__, name, hr);
       return -1;
     }
   return 0;
 }
 
 
 /* Set the property NAME to VALUE.  */
 int
 put_oom_int (LPDISPATCH pDisp, const char *name, int value)
 {
   HRESULT hr;
   DISPID dispid_put = DISPID_PROPERTYPUT;
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[1];
 
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid == DISPID_UNKNOWN)
     return -1;
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_INT;
   dispparams.rgvarg[0].intVal = value;
   dispparams.cArgs = 1;
   dispparams.rgdispidNamedArgs = &dispid_put;
   dispparams.cNamedArgs = 1;
   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                       DISPATCH_PROPERTYPUT, &dispparams,
                       NULL, NULL, NULL);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
                  SRCNAME, __func__, name, hr);
       return -1;
     }
   return 0;
 }
 
 
 /* Set the property NAME to STRING.  */
 int
 put_oom_string (LPDISPATCH pDisp, const char *name, const char *string)
 {
   HRESULT hr;
   DISPID dispid_put = DISPID_PROPERTYPUT;
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[1];
   BSTR bstring;
   EXCEPINFO execpinfo;
 
   init_excepinfo (&execpinfo);
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid == DISPID_UNKNOWN)
     return -1;
 
   {
     wchar_t *tmp = utf8_to_wchar (string);
     bstring = tmp? SysAllocString (tmp):NULL;
     xfree (tmp);
     if (!bstring)
       {
         log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
         return -1;
       }
   }
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_BSTR;
   dispparams.rgvarg[0].bstrVal = bstring;
   dispparams.cArgs = 1;
   dispparams.rgdispidNamedArgs = &dispid_put;
   dispparams.cNamedArgs = 1;
   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                       DISPATCH_PROPERTYPUT, &dispparams,
                       NULL, &execpinfo, NULL);
   SysFreeString (bstring);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: Putting '%s' failed: %#lx", 
                  SRCNAME, __func__, name, hr);
       dump_excepinfo (execpinfo);
       return -1;
     }
   return 0;
 }
 
 
 /* Get the boolean property NAME of the object PDISP.  Returns False if
    not found or if it is not a boolean property.  */
 int
 get_oom_bool (LPDISPATCH pDisp, const char *name)
 {
   HRESULT hr;      
   int result = 0;
   DISPID dispid;
   
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid != DISPID_UNKNOWN)
     {
       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
       VARIANT rVariant;
 
       VariantInit (&rVariant);
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           DISPATCH_PROPERTYGET, &dispparams,
                           &rVariant, NULL, NULL);
       if (hr != S_OK)
         log_debug ("%s:%s: Property '%s' not found: %#lx",
                    SRCNAME, __func__, name, hr);
       else if (rVariant.vt != VT_BOOL)
         log_debug ("%s:%s: Property `%s' is not a boolean (vt=%d)",
                    SRCNAME, __func__, name, rVariant.vt);
       else
         result = !!rVariant.boolVal;
       VariantClear (&rVariant);
     }
 
   return result;
 }
 
 
 /* Get the integer property NAME of the object PDISP.  Returns 0 if
    not found or if it is not an integer property.  */
 int
 get_oom_int (LPDISPATCH pDisp, const char *name)
 {
   HRESULT hr;      
   int result = 0;
   DISPID dispid;
   
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid != DISPID_UNKNOWN)
     {
       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
       VARIANT rVariant;
 
       VariantInit (&rVariant);
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           DISPATCH_PROPERTYGET, &dispparams,
                           &rVariant, NULL, NULL);
       if (hr != S_OK)
         log_debug ("%s:%s: Property '%s' not found: %#lx",
                    SRCNAME, __func__, name, hr);
       else if (rVariant.vt != VT_INT && rVariant.vt != VT_I4)
         log_debug ("%s:%s: Property `%s' is not an integer (vt=%d)",
                    SRCNAME, __func__, name, rVariant.vt);
       else
         result = rVariant.intVal;
       VariantClear (&rVariant);
     }
 
   return result;
 }
 
 
 /* Get the string property NAME of the object PDISP.  Returns NULL if
    not found or if it is not a string property.  */
 char *
 get_oom_string (LPDISPATCH pDisp, const char *name)
 {
   HRESULT hr;      
   char *result = NULL;
   DISPID dispid;
   
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid != DISPID_UNKNOWN)
     {
       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
       VARIANT rVariant;
 
       VariantInit (&rVariant);
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           DISPATCH_PROPERTYGET, &dispparams,
                           &rVariant, NULL, NULL);
       if (hr != S_OK)
         log_debug ("%s:%s: Property '%s' not found: %#lx",
                    SRCNAME, __func__, name, hr);
       else if (rVariant.vt != VT_BSTR)
         log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
                    SRCNAME, __func__, name, rVariant.vt);
       else if (rVariant.bstrVal)
         result = wchar_to_utf8 (rVariant.bstrVal);
       VariantClear (&rVariant);
     }
 
   return result;
 }
 
 
 /* Get the object property NAME of the object PDISP.  Returns NULL if
    not found or if it is not an object perty.  */
 LPUNKNOWN
 get_oom_iunknown (LPDISPATCH pDisp, const char *name)
 {
   HRESULT hr;      
   DISPID dispid;
   
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid != DISPID_UNKNOWN)
     {
       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
       VARIANT rVariant;
 
       VariantInit (&rVariant);
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           DISPATCH_PROPERTYGET, &dispparams,
                           &rVariant, NULL, NULL);
       if (hr != S_OK)
         log_debug ("%s:%s: Property '%s' not found: %#lx",
                    SRCNAME, __func__, name, hr);
       else if (rVariant.vt != VT_UNKNOWN)
         log_debug ("%s:%s: Property `%s' is not of class IUnknown (vt=%d)",
                    SRCNAME, __func__, name, rVariant.vt);
       else
         return rVariant.punkVal;
 
       VariantClear (&rVariant);
     }
 
   return NULL;
 }
 
 
 /* Return the control object described by the tag property with value
    TAG. The object POBJ must support the FindControl method.  Returns
    NULL if not found.  */
 LPDISPATCH
 get_oom_control_bytag (LPDISPATCH pDisp, const char *tag)
 {
   HRESULT hr;      
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[4];
   VARIANT rVariant;
   BSTR bstring;
   LPDISPATCH result = NULL;
 
   dispid = lookup_oom_dispid (pDisp, "FindControl");
   if (dispid == DISPID_UNKNOWN)
     {
       log_debug ("%s:%s: Object %p has no FindControl method",
                  SRCNAME, __func__, pDisp);
       return NULL;
     }
 
   {
     wchar_t *tmp = utf8_to_wchar (tag);
     bstring = tmp? SysAllocString (tmp):NULL;
     xfree (tmp);
     if (!bstring)
       {
         log_error_w32 (-1, "%s:%s: SysAllocString failed", SRCNAME, __func__);
         return NULL;
       }
   }
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_ERROR; /* Visible */
   dispparams.rgvarg[0].scode = DISP_E_PARAMNOTFOUND; 
   dispparams.rgvarg[1].vt = VT_BSTR;  /* Tag */
   dispparams.rgvarg[1].bstrVal = bstring;
   dispparams.rgvarg[2].vt = VT_ERROR; /* Id */
   dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND;
   dispparams.rgvarg[3].vt = VT_ERROR;/* Type */
   dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
   dispparams.cArgs = 4;
   dispparams.cNamedArgs = 0;
   VariantInit (&rVariant);
   hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                       DISPATCH_METHOD, &dispparams,
                       &rVariant, NULL, NULL);
   SysFreeString (bstring);
   if (hr == S_OK && rVariant.vt == VT_DISPATCH && rVariant.pdispVal)
     {
       rVariant.pdispVal->QueryInterface (IID_IDispatch, (LPVOID*)&result);
       gpgol_release (rVariant.pdispVal);
       if (!result)
         log_debug ("%s:%s: Object with tag `%s' has no dispatch intf.",
                    SRCNAME, __func__, tag);
     }
   else
     {
       log_debug ("%s:%s: No object with tag `%s' found: vt=%d hr=%#lx",
                  SRCNAME, __func__, tag, rVariant.vt, hr);
       VariantClear (&rVariant);
     }
 
   return result;
 }
 
 
 /* Add a new button to an object which supports the add method.
    Returns the new object or NULL on error.  */
 LPDISPATCH
 add_oom_button (LPDISPATCH pObj)
 {
   HRESULT hr;      
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[5];
   VARIANT rVariant;
 
   dispid = lookup_oom_dispid (pObj, "Add");
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_BOOL;  /* Temporary */
   dispparams.rgvarg[0].boolVal = VARIANT_TRUE;
   dispparams.rgvarg[1].vt = VT_ERROR;  /* Before */
   dispparams.rgvarg[1].scode = DISP_E_PARAMNOTFOUND; 
   dispparams.rgvarg[2].vt = VT_ERROR;  /* Parameter */
   dispparams.rgvarg[2].scode = DISP_E_PARAMNOTFOUND; 
   dispparams.rgvarg[3].vt = VT_ERROR;  /* Id */
   dispparams.rgvarg[3].scode = DISP_E_PARAMNOTFOUND; 
   dispparams.rgvarg[4].vt = VT_INT;    /* Type */
   dispparams.rgvarg[4].intVal = MSOCONTROLBUTTON;
   dispparams.cArgs = 5;
   dispparams.cNamedArgs = 0;
   VariantInit (&rVariant);
   hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                      DISPATCH_METHOD, &dispparams,
                      &rVariant, NULL, NULL);
   if (hr != S_OK || rVariant.vt != VT_DISPATCH || !rVariant.pdispVal)
     {
       log_error ("%s:%s: Adding Control failed: %#lx - vt=%d",
                  SRCNAME, __func__, hr, rVariant.vt);
       VariantClear (&rVariant);
       return NULL;
     }
   return rVariant.pdispVal;
 }
 
 
 /* Add a new button to an object which supports the add method.
    Returns the new object or NULL on error.  */
 void
 del_oom_button (LPDISPATCH pObj)
 {
   HRESULT hr;      
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT aVariant[5];
 
   dispid = lookup_oom_dispid (pObj, "Delete");
 
   dispparams.rgvarg = aVariant;
   dispparams.rgvarg[0].vt = VT_BOOL;  /* Temporary */
   dispparams.rgvarg[0].boolVal = VARIANT_FALSE;
   dispparams.cArgs = 1;
   dispparams.cNamedArgs = 0;
   hr = pObj->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                      DISPATCH_METHOD, &dispparams,
                      NULL, NULL, NULL);
   if (hr != S_OK)
     log_error ("%s:%s: Deleting Control failed: %#lx",
                SRCNAME, __func__, hr);
 }
 
 /* Gets the current contexts HWND. Returns NULL on error */
 HWND
 get_oom_context_window (LPDISPATCH context)
 {
   LPOLEWINDOW actExplorer;
   HWND ret = NULL;
   actExplorer = (LPOLEWINDOW) get_oom_object(context,
                                              "Application.ActiveExplorer");
   if (actExplorer)
     actExplorer->GetWindow (&ret);
   else
     {
       log_debug ("%s:%s: Could not find active window",
                  SRCNAME, __func__);
     }
   gpgol_release (actExplorer);
   return ret;
 }
 
 int
 put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value)
 {
   LPDISPATCH propertyAccessor;
   VARIANT cVariant[2];
   VARIANT rVariant;
   DISPID dispid;
   DISPPARAMS dispparams;
   HRESULT hr;
   EXCEPINFO execpinfo;
   BSTR b_property;
   wchar_t *w_property;
   unsigned int argErr = 0;
 
   init_excepinfo (&execpinfo);
 
   log_oom ("%s:%s: Looking up property: %s;",
              SRCNAME, __func__, dasl_id);
 
   propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
   if (!propertyAccessor)
     {
       log_error ("%s:%s: Failed to look up property accessor.",
                  SRCNAME, __func__);
       return -1;
     }
 
   dispid = lookup_oom_dispid (propertyAccessor, "SetProperty");
 
   if (dispid == DISPID_UNKNOWN)
   {
     log_error ("%s:%s: could not find SetProperty DISPID",
                SRCNAME, __func__);
     return -1;
   }
 
   /* Prepare the parameter */
   w_property = utf8_to_wchar (dasl_id);
   b_property = SysAllocString (w_property);
   xfree (w_property);
 
   /* Variant 0 carries the data. */
   VariantInit (&cVariant[0]);
   if (VariantCopy (&cVariant[0], value))
     {
       log_error ("%s:%s: Falied to copy value.",
                  SRCNAME, __func__);
       return -1;
     }
 
   /* Variant 1 is the DASL as found out by experiments. */
   VariantInit (&cVariant[1]);
   cVariant[1].vt = VT_BSTR;
   cVariant[1].bstrVal = b_property;
   dispparams.rgvarg = cVariant;
   dispparams.cArgs = 2;
   dispparams.cNamedArgs = 0;
   VariantInit (&rVariant);
 
   hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                                  DISPATCH_METHOD, &dispparams,
                                  &rVariant, &execpinfo, &argErr);
   VariantClear (&cVariant[0]);
   VariantClear (&cVariant[1]);
   gpgol_release (propertyAccessor);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: error: invoking SetProperty p=%p vt=%d"
                  " hr=0x%x argErr=0x%x",
                  SRCNAME, __func__,
                  rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
                  (unsigned int)argErr);
       VariantClear (&rVariant);
       dump_excepinfo (execpinfo);
       return -1;
     }
   VariantClear (&rVariant);
   return 0;
 }
 
 int
 put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value)
 {
   wchar_t *w_value = utf8_to_wchar (value);
   BSTR b_value = SysAllocString(w_value);
   VARIANT var;
   VariantInit (&var);
   var.vt = VT_BSTR;
   var.bstrVal = b_value;
   int ret = put_pa_variant (pDisp, dasl_id, &var);
   VariantClear (&var);
   return ret;
 }
 
 int
 put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value)
 {
   VARIANT var;
   VariantInit (&var);
   var.vt = VT_INT;
   var.intVal = value;
   int ret = put_pa_variant (pDisp, dasl_id, &var);
   VariantClear (&var);
   return ret;
 }
 
 /* Get a MAPI property through OOM using the PropertyAccessor
  * interface and the DASL Uid. Returns -1 on error.
  * Variant has to be cleared with VariantClear.
  * rVariant must be a pointer to a Variant.
  */
 int get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant)
 {
   LPDISPATCH propertyAccessor;
   VARIANT cVariant[1];
   DISPID dispid;
   DISPPARAMS dispparams;
   HRESULT hr;
   EXCEPINFO execpinfo;
   BSTR b_property;
   wchar_t *w_property;
   unsigned int argErr = 0;
 
   init_excepinfo (&execpinfo);
   log_oom ("%s:%s: Looking up property: %s;",
              SRCNAME, __func__, dasl_id);
 
   propertyAccessor = get_oom_object (pDisp, "PropertyAccessor");
   if (!propertyAccessor)
     {
       log_error ("%s:%s: Failed to look up property accessor.",
                  SRCNAME, __func__);
       return -1;
     }
 
   dispid = lookup_oom_dispid (propertyAccessor, "GetProperty");
 
   if (dispid == DISPID_UNKNOWN)
   {
     log_error ("%s:%s: could not find GetProperty DISPID",
                SRCNAME, __func__);
     return -1;
   }
 
   /* Prepare the parameter */
   w_property = utf8_to_wchar (dasl_id);
   b_property = SysAllocString (w_property);
   xfree (w_property);
 
   cVariant[0].vt = VT_BSTR;
   cVariant[0].bstrVal = b_property;
   dispparams.rgvarg = cVariant;
   dispparams.cArgs = 1;
   dispparams.cNamedArgs = 0;
   VariantInit (rVariant);
 
   hr = propertyAccessor->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                                  DISPATCH_METHOD, &dispparams,
                                  rVariant, &execpinfo, &argErr);
   SysFreeString (b_property);
   gpgol_release (propertyAccessor);
   if (hr != S_OK && strcmp (GPGOL_UID_DASL, dasl_id))
     {
       /* It often happens that mails don't have a uid by us e.g. if
          they are not crypto mails or just dont have one. This is
          not an error. */
       log_debug ("%s:%s: error: invoking GetProperty p=%p vt=%d"
                  " hr=0x%x argErr=0x%x",
                  SRCNAME, __func__,
                  rVariant->pdispVal, rVariant->vt, (unsigned int)hr,
                  (unsigned int)argErr);
       dump_excepinfo (execpinfo);
       VariantClear (rVariant);
       return -1;
     }
   return 0;
 }
 
 /* Get a property string by using the PropertyAccessor of pDisp
  * returns NULL on error or a newly allocated result. */
 char *
 get_pa_string (LPDISPATCH pDisp, const char *property)
 {
   VARIANT rVariant;
   char *result = NULL;
 
   if (get_pa_variant (pDisp, property, &rVariant))
     {
       return NULL;
     }
 
   if (rVariant.vt == VT_BSTR && rVariant.bstrVal)
     {
       result = wchar_to_utf8 (rVariant.bstrVal);
     }
   else if (rVariant.vt & VT_ARRAY && !(rVariant.vt & VT_BYREF))
     {
       LONG uBound, lBound;
       VARTYPE vt;
       char *data;
       SafeArrayGetVartype(rVariant.parray, &vt);
 
       if (SafeArrayGetUBound (rVariant.parray, 1, &uBound) != S_OK ||
           SafeArrayGetLBound (rVariant.parray, 1, &lBound) != S_OK ||
           vt != VT_UI1)
         {
           log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
           VariantClear (&rVariant);
           return NULL;
         }
 
       result = (char *)xmalloc (uBound - lBound + 1);
       data = (char *) rVariant.parray->pvData;
       memcpy (result, data + lBound, uBound - lBound);
       result[uBound - lBound] = '\0';
     }
   else
     {
       log_debug ("%s:%s: Property `%s' is not a string (vt=%d)",
                  SRCNAME, __func__, property, rVariant.vt);
     }
 
   VariantClear (&rVariant);
 
   return result;
 }
 
 int
 get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt)
 {
   VARIANT rVariant;
 
   if (get_pa_variant (pDisp, property, &rVariant))
     {
       return -1;
     }
 
   if (rVariant.vt != VT_I4)
     {
       log_debug ("%s:%s: Property `%s' is not a int (vt=%d)",
                  SRCNAME, __func__, property, rVariant.vt);
       return -1;
     }
 
   *rInt = rVariant.lVal;
 
   VariantClear (&rVariant);
   return 0;
 }
 
+/* Helper for additional fallbacks in recipient lookup */
+static char *
+get_recipient_addr_fallbacks (LPDISPATCH recipient)
+{
+  LPDISPATCH addr_entry = get_oom_object (recipient, "AddressEntry");
+
+  if (!addr_entry)
+    {
+      log_debug ("%s:%s: Failed to find AddressEntry",
+                 SRCNAME, __func__);
+      return nullptr;
+    }
+
+  /* Maybe check for type here? We are pretty sure that we are exchange */
+
+  /* According to MSDN Message Boards the PR_EMS_AB_PROXY_ADDRESSES_DASL
+     is more avilable then the SMTP Address. */
+  char *ret = get_pa_string (addr_entry, PR_EMS_AB_PROXY_ADDRESSES_DASL);
+  if (ret)
+    {
+      log_debug ("%s:%s: Found recipient through AB_PROXY: %s",
+                 SRCNAME, __func__, ret);
+
+      char *smtpbegin = strstr(ret, "SMTP:");
+      if (smtpbegin == ret)
+        {
+          ret += 5;
+        }
+      gpgol_release (addr_entry);
+      return ret;
+    }
+  else
+    {
+      log_debug ("%s:%s: Failed AB_PROXY lookup.",
+                 SRCNAME, __func__);
+    }
+
+  LPDISPATCH ex_user = get_oom_object (addr_entry, "GetExchangeUser");
+  gpgol_release (addr_entry);
+  if (!ex_user)
+    {
+      log_debug ("%s:%s: Failed to find ExchangeUser",
+                 SRCNAME, __func__);
+      return nullptr;
+    }
+
+  ret = get_oom_string (ex_user, "PrimarySmtpAddress");
+  gpgol_release (ex_user);
+  if (ret)
+    {
+      log_debug ("%s:%s: Found recipient through exchange user primary smtp address: %s",
+                 SRCNAME, __func__, ret);
+      return ret;
+    }
+
+  return nullptr;
+}
+
 /* Gets a malloced NULL terminated array of recipent strings from
    an OOM recipients Object. */
 char **
 get_oom_recipients (LPDISPATCH recipients)
 {
   int recipientsCnt = get_oom_int (recipients, "Count");
   char **recipientAddrs = NULL;
   int i;
 
   if (!recipientsCnt)
     {
       return NULL;
     }
 
   /* Get the recipients */
   recipientAddrs = (char**) xmalloc((recipientsCnt + 1) * sizeof(char*));
   recipientAddrs[recipientsCnt] = NULL;
   for (i = 1; i <= recipientsCnt; i++)
     {
       char buf[16];
       LPDISPATCH recipient;
       snprintf (buf, sizeof (buf), "Item(%i)", i);
       recipient = get_oom_object (recipients, buf);
       if (!recipient)
         {
           /* Should be impossible */
           recipientAddrs[i-1] = NULL;
           log_error ("%s:%s: could not find Item %i;",
                      SRCNAME, __func__, i);
           break;
         }
-      else
+      char *resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
+      if (resolved)
         {
-          char *address,
-               *resolved;
-          address = get_oom_string (recipient, "Address");
-          resolved = get_pa_string (recipient, PR_SMTP_ADDRESS_DASL);
-          if (resolved)
-            {
-              xfree (address);
-              recipientAddrs[i-1] = resolved;
-              continue;
-            }
-          log_debug ("%s:%s: Failed to look up SMTP Address;",
-                     SRCNAME, __func__);
-          recipientAddrs[i-1] = address;
+          recipientAddrs[i-1] = resolved;
+          gpgol_release (recipient);
+          continue;
         }
+      /* No PR_SMTP_ADDRESS first fallback */
+      resolved = get_recipient_addr_fallbacks (recipient);
+      gpgol_release (recipient);
+      if (resolved)
+        {
+          recipientAddrs[i-1] = resolved;
+          continue;
+        }
+
+      char *address = get_oom_string (recipient, "Address");
+      log_debug ("%s:%s: Failed to look up Address probably EX addr is returned",
+                 SRCNAME, __func__);
+      recipientAddrs[i-1] = address;
     }
   return recipientAddrs;
 }
 
 /* Add an attachment to the outlook dispatcher disp
    that has an Attachment property.
    inFile is the path to the attachment. Name is the
    name that should be used in outlook. */
 int
 add_oom_attachment (LPDISPATCH disp, const wchar_t* inFileW,
                     const wchar_t* displayName)
 {
   LPDISPATCH attachments = get_oom_object (disp, "Attachments");
 
   DISPID dispid;
   DISPPARAMS dispparams;
   VARIANT vtResult;
   VARIANT aVariant[4];
   HRESULT hr;
   BSTR inFileB = nullptr,
        dispNameB = nullptr;
   unsigned int argErr = 0;
   EXCEPINFO execpinfo;
 
   init_excepinfo (&execpinfo);
   dispid = lookup_oom_dispid (attachments, "Add");
 
   if (dispid == DISPID_UNKNOWN)
   {
     log_error ("%s:%s: could not find attachment dispatcher",
                SRCNAME, __func__);
     return -1;
   }
 
   if (inFileW)
     {
       inFileB = SysAllocString (inFileW);
     }
   if (displayName)
     {
       dispNameB = SysAllocString (displayName);
     }
 
   dispparams.rgvarg = aVariant;
 
   /* Contrary to the documentation the Source is the last
      parameter and not the first. Additionally DisplayName
      is documented but gets ignored by Outlook since Outlook
      2003 */
   dispparams.rgvarg[0].vt = VT_BSTR; /* DisplayName */
   dispparams.rgvarg[0].bstrVal = dispNameB;
   dispparams.rgvarg[1].vt = VT_INT;  /* Position */
   dispparams.rgvarg[1].intVal = 1;
   dispparams.rgvarg[2].vt = VT_INT;  /* Type */
   dispparams.rgvarg[2].intVal = 1;
   dispparams.rgvarg[3].vt = VT_BSTR; /* Source */
   dispparams.rgvarg[3].bstrVal = inFileB;
   dispparams.cArgs = 4;
   dispparams.cNamedArgs = 0;
   VariantInit (&vtResult);
   hr = attachments->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                             DISPATCH_METHOD, &dispparams,
                             &vtResult, &execpinfo, &argErr);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: error: invoking Add p=%p vt=%d hr=0x%x argErr=0x%x",
                  SRCNAME, __func__,
                  vtResult.pdispVal, vtResult.vt, (unsigned int)hr,
                  (unsigned int)argErr);
       dump_excepinfo (execpinfo);
     }
 
   if (inFileB)
     SysFreeString (inFileB);
   if (dispNameB)
     SysFreeString (dispNameB);
   VariantClear (&vtResult);
   gpgol_release (attachments);
 
   return hr == S_OK ? 0 : -1;
 }
 
 LPDISPATCH
 get_object_by_id (LPDISPATCH pDisp, REFIID id)
 {
   LPDISPATCH disp = NULL;
 
   if (!pDisp)
     return NULL;
 
   if (pDisp->QueryInterface (id, (void **)&disp) != S_OK)
     return NULL;
   return disp;
 }
 
 LPDISPATCH
 get_strong_reference (LPDISPATCH mail)
 {
   VARIANT var;
   VariantInit (&var);
   DISPPARAMS args;
   VARIANT argvars[2];
   VariantInit (&argvars[0]);
   VariantInit (&argvars[1]);
   argvars[1].vt = VT_DISPATCH;
   argvars[1].pdispVal = mail;
   argvars[0].vt = VT_INT;
   argvars[0].intVal = 1;
   args.cArgs = 2;
   args.cNamedArgs = 0;
   args.rgvarg = argvars;
   LPDISPATCH ret = NULL;
   if (!invoke_oom_method_with_parms (
       GpgolAddin::get_instance()->get_application(),
       "GetObjectReference", &var, &args))
     {
       ret = var.pdispVal;
       log_oom ("%s:%s: Got strong ref %p for %p",
                SRCNAME, __func__, ret, mail);
     }
   else
     {
       log_error ("%s:%s: Failed to get strong ref.",
                  SRCNAME, __func__);
     }
   VariantClear (&var);
   return ret;
 }
 
 LPMESSAGE
 get_oom_message (LPDISPATCH mailitem)
 {
   LPUNKNOWN mapi_obj = get_oom_iunknown (mailitem, "MapiObject");
   if (!mapi_obj)
     {
       log_error ("%s:%s: Failed to obtain MAPI Message.",
                  SRCNAME, __func__);
       return NULL;
     }
   return (LPMESSAGE) mapi_obj;
 }
 
 static LPMESSAGE
 get_oom_base_message_from_mapi (LPDISPATCH mapi_message)
 {
   HRESULT hr;
   LPDISPATCH secureItem = NULL;
   LPMESSAGE message = NULL;
   LPMAPISECUREMESSAGE secureMessage = NULL;
 
   secureItem = get_object_by_id (mapi_message,
                                  IID_IMAPISecureMessage);
   if (!secureItem)
     {
       log_error ("%s:%s: Failed to obtain SecureItem.",
                  SRCNAME, __func__);
       return NULL;
     }
 
   secureMessage = (LPMAPISECUREMESSAGE) secureItem;
 
   /* The call to GetBaseMessage is pretty much a jump
      in the dark. So it would not be surprising to get
      crashes here in the future. */
   log_oom_extra("%s:%s: About to call GetBaseMessage.",
                 SRCNAME, __func__);
   hr = secureMessage->GetBaseMessage (&message);
   gpgol_release (secureMessage);
   if (hr != S_OK)
     {
       log_error_w32 (hr, "Failed to GetBaseMessage.");
       return NULL;
     }
 
   return message;
 }
 
 LPMESSAGE
 get_oom_base_message (LPDISPATCH mailitem)
 {
   LPMESSAGE mapi_message = get_oom_message (mailitem);
   LPMESSAGE ret = NULL;
   if (!mapi_message)
     {
       log_error ("%s:%s: Failed to obtain mapi_message.",
                  SRCNAME, __func__);
       return NULL;
     }
   ret = get_oom_base_message_from_mapi ((LPDISPATCH)mapi_message);
   gpgol_release (mapi_message);
   return ret;
 }
 
 int
 invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
                               VARIANT *rVariant, DISPPARAMS *params)
 {
   HRESULT hr;
   DISPID dispid;
 
   dispid = lookup_oom_dispid (pDisp, name);
   if (dispid != DISPID_UNKNOWN)
     {
       EXCEPINFO execpinfo;
       init_excepinfo (&execpinfo);
       DISPPARAMS dispparams = {NULL, NULL, 0, 0};
 
       hr = pDisp->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                           DISPATCH_METHOD, params ? params : &dispparams,
                           rVariant, &execpinfo, NULL);
       if (hr != S_OK)
         {
           log_debug ("%s:%s: Method '%s' invokation failed: %#lx",
                      SRCNAME, __func__, name, hr);
           dump_excepinfo (execpinfo);
           return -1;
         }
     }
 
   return 0;
 }
 
 int
 invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant)
 {
   return invoke_oom_method_with_parms (pDisp, name, rVariant, NULL);
 }
 
 LPMAPISESSION
 get_oom_mapi_session ()
 {
   LPDISPATCH application = GpgolAddin::get_instance ()->get_application ();
   LPDISPATCH oom_session = NULL;
   LPMAPISESSION session = NULL;
   LPUNKNOWN mapiobj = NULL;
   HRESULT hr;
 
   if (!application)
     {
       log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
       return NULL;
     }
 
   oom_session = get_oom_object (application, "Session");
   if (!oom_session)
     {
       log_error ("%s:%s: session object not found", SRCNAME, __func__);
       return NULL;
     }
   mapiobj = get_oom_iunknown (oom_session, "MAPIOBJECT");
   gpgol_release (oom_session);
 
   if (!mapiobj)
     {
       log_error ("%s:%s: error getting Session.MAPIOBJECT", SRCNAME, __func__);
       return NULL;
     }
   session = NULL;
   hr = mapiobj->QueryInterface (IID_IMAPISession, (void**)&session);
   gpgol_release (mapiobj);
   if (hr != S_OK || !session)
     {
       log_error ("%s:%s: error getting IMAPISession: hr=%#lx",
                  SRCNAME, __func__, hr);
       return NULL;
     }
   return session;
 }
 
 static int
 create_category (LPDISPATCH categories, const char *category, int color)
 {
   VARIANT cVariant[3];
   VARIANT rVariant;
   DISPID dispid;
   DISPPARAMS dispparams;
   HRESULT hr;
   EXCEPINFO execpinfo;
   BSTR b_name;
   wchar_t *w_name;
   unsigned int argErr = 0;
 
   init_excepinfo (&execpinfo);
 
   if (!categories || !category)
     {
       TRACEPOINT;
       return 1;
     }
 
   dispid = lookup_oom_dispid (categories, "Add");
   if (dispid == DISPID_UNKNOWN)
   {
     log_error ("%s:%s: could not find Add DISPID",
                SRCNAME, __func__);
     return -1;
   }
 
   /* Do the string dance */
   w_name = utf8_to_wchar (category);
   b_name = SysAllocString (w_name);
   xfree (w_name);
 
   /* Variants are in reverse order
      ShortcutKey -> 0 / Int
      Color -> 1 / Int
      Name -> 2 / Bstr */
   VariantInit (&cVariant[2]);
   cVariant[2].vt = VT_BSTR;
   cVariant[2].bstrVal = b_name;
 
   VariantInit (&cVariant[1]);
   cVariant[1].vt = VT_INT;
   cVariant[1].intVal = color;
 
   VariantInit (&cVariant[0]);
   cVariant[0].vt = VT_INT;
   cVariant[0].intVal = 0;
 
   dispparams.cArgs = 3;
   dispparams.cNamedArgs = 0;
   dispparams.rgvarg = cVariant;
 
   hr = categories->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
                            DISPATCH_METHOD, &dispparams,
                            &rVariant, &execpinfo, &argErr);
   SysFreeString (b_name);
   VariantClear (&cVariant[0]);
   VariantClear (&cVariant[1]);
   VariantClear (&cVariant[2]);
   if (hr != S_OK)
     {
       log_debug ("%s:%s: error: invoking Add p=%p vt=%d"
                  " hr=0x%x argErr=0x%x",
                  SRCNAME, __func__,
                  rVariant.pdispVal, rVariant.vt, (unsigned int)hr,
                  (unsigned int)argErr);
       dump_excepinfo (execpinfo);
       VariantClear (&rVariant);
       return -1;
     }
   VariantClear (&rVariant);
   log_debug ("%s:%s: Created category '%s'",
              SRCNAME, __func__, category);
   return 0;
 }
 
 void
 ensure_category_exists (LPDISPATCH application, const char *category, int color)
 {
   if (!application || !category)
     {
       TRACEPOINT;
       return;
     }
 
   log_debug ("Ensure category exists called for %s, %i", category, color);
 
   LPDISPATCH stores = get_oom_object (application, "Session.Stores");
   if (!stores)
     {
       log_error ("%s:%s: No stores found.",
                  SRCNAME, __func__);
       return;
     }
   auto store_count = get_oom_int (stores, "Count");
 
   for (int n = 1; n <= store_count; n++)
     {
       const auto store_str = std::string("Item(") + std::to_string(n) + ")";
       LPDISPATCH store = get_oom_object (stores, store_str.c_str());
 
       if (!store)
         {
           TRACEPOINT;
           continue;
         }
 
       LPDISPATCH categories = get_oom_object (store, "Categories");
       gpgol_release (store);
       if (!categories)
         {
           categories = get_oom_object (application, "Session.Categories");
           if (!categories)
             {
               TRACEPOINT;
               continue;
             }
         }
 
       auto count = get_oom_int (categories, "Count");
       bool found = false;
       for (int i = 1; i <= count && !found; i++)
         {
           const auto item_str = std::string("Item(") + std::to_string(i) + ")";
           LPDISPATCH category_obj = get_oom_object (categories, item_str.c_str());
           if (!category_obj)
             {
               TRACEPOINT;
               break;
             }
           char *name = get_oom_string (category_obj, "Name");
           if (name && !strcmp (category, name))
             {
               log_debug ("%s:%s: Found category '%s'",
                          SRCNAME, __func__, name);
               found = true;
             }
           /* We don't check the color here as the user may change that. */
           gpgol_release (category_obj);
           xfree (name);
         }
 
       if (!found)
         {
           if (create_category (categories, category, color))
             {
               log_debug ("%s:%s: Found category '%s'",
                          SRCNAME, __func__, category);
             }
         }
       /* Otherwise we have to create the category */
       gpgol_release (categories);
     }
   gpgol_release (stores);
 }
 
 int
 add_category (LPDISPATCH mail, const char *category)
 {
   char *tmp = get_oom_string (mail, "Categories");
   if (!tmp)
     {
       TRACEPOINT;
       return 1;
     }
 
   if (strstr (tmp, category))
     {
       log_debug ("%s:%s: category '%s' already added.",
                  SRCNAME, __func__, category);
       return 0;
     }
 
   std::string newstr (tmp);
   xfree (tmp);
   if (!newstr.empty ())
     {
       newstr += ", ";
     }
   newstr += category;
 
   return put_oom_string (mail, "Categories", newstr.c_str ());
 }
 
 int
 remove_category (LPDISPATCH mail, const char *category)
 {
   char *tmp = get_oom_string (mail, "Categories");
   if (!tmp)
     {
       TRACEPOINT;
       return 1;
     }
   std::string newstr (tmp);
   xfree (tmp);
   std::string cat (category);
 
   size_t pos1 = newstr.find (cat);
   size_t pos2 = newstr.find (std::string(", ") + cat);
   if (pos1 == std::string::npos && pos2 == std::string::npos)
     {
       log_debug ("%s:%s: category '%s' not found.",
                  SRCNAME, __func__, category);
       return 0;
     }
 
   size_t len = cat.size();
   if (pos2)
     {
       len += 2;
     }
   newstr.erase (pos2 != std::string::npos ? pos2 : pos1, len);
   log_debug ("%s:%s: removing category '%s'",
              SRCNAME, __func__, category);
 
   return put_oom_string (mail, "Categories", newstr.c_str ());
 }
 
 static char *
 generate_uid ()
 {
   UUID uuid;
   UuidCreate (&uuid);
 
   unsigned char *str;
   UuidToStringA (&uuid, &str);
 
   char *ret = strdup ((char*)str);
   RpcStringFreeA (&str);
 
   return ret;
 }
 
 char *
 get_unique_id (LPDISPATCH mail, int create, const char *uuid)
 {
   if (!mail)
     {
       return NULL;
     }
 
   /* Get the User Properties. */
   if (!create)
     {
       char *uid = get_pa_string (mail, GPGOL_UID_DASL);
       if (!uid)
         {
           log_debug ("%s:%s: No uuid found in oom for '%p'",
                      SRCNAME, __func__, mail);
           return NULL;
         }
       else
         {
           log_debug ("%s:%s: Found uid '%s' for '%p'",
                      SRCNAME, __func__, uid, mail);
           return uid;
         }
     }
   char *newuid;
   if (!uuid)
     {
       newuid = generate_uid ();
     }
   else
     {
       newuid = strdup (uuid);
     }
   int ret = put_pa_string (mail, GPGOL_UID_DASL, newuid);
 
   if (ret)
     {
       log_debug ("%s:%s: failed to set uid '%s' for '%p'",
                  SRCNAME, __func__, newuid, mail);
       xfree (newuid);
       return NULL;
     }
 
 
   log_debug ("%s:%s: '%p' has now the uid: '%s' ",
              SRCNAME, __func__, mail, newuid);
   return newuid;
 }
 
 HWND
 get_active_hwnd ()
 {
   LPDISPATCH app = GpgolAddin::get_instance ()->get_application ();
 
   if (!app)
     {
       TRACEPOINT;
       return nullptr;
     }
 
   LPDISPATCH activeWindow = get_oom_object (app, "ActiveWindow");
   if (!activeWindow)
     {
       TRACEPOINT;
       return nullptr;
     }
 
   /* Both explorer and inspector have this. */
   char *caption = get_oom_string (activeWindow, "Caption");
   gpgol_release (activeWindow);
   if (!caption)
     {
       TRACEPOINT;
       return nullptr;
     }
   /* Might not be completly true for multiple explorers
      on the same folder but good enugh. */
   HWND hwnd = FindWindowExA(NULL, NULL, "rctrl_renwnd32",
                             caption);
   xfree (caption);
 
   return hwnd;
 }
diff --git a/src/oomhelp.h b/src/oomhelp.h
index e719b79..1008860 100644
--- a/src/oomhelp.h
+++ b/src/oomhelp.h
@@ -1,321 +1,326 @@
 /* oomhelp.h - Defs for helper functions for the Outlook Object Model
  * Copyright (C) 2009 g10 Code GmbH
  * 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 <http://www.gnu.org/licenses/>.
  */
 
 #ifndef OOMHELP_H
 #define OOMHELP_H
 
 #include <unknwn.h>
 #include "mymapi.h"
 #include "myexchext.h"
 
 #define MSOCONTROLBUTTON    1
 #define MSOCONTROLEDIT      2
 #define MSOCONTROLDROPDOWN  3
 #define MSOCONTROLCOMBOBOX  4
 #define MSOCONTROLPOPUP    10
 
 enum 
   {
     msoButtonAutomatic = 0,
     msoButtonIcon = 1,
     msoButtonCaption = 2,
     msoButtonIconAndCaption = 3,
     msoButtonIconAndWrapCaption = 7,
     msoButtonIconAndCaptionBelow = 11,
     msoButtonWrapCaption = 14,
     msoButtonIconAndWrapCaptionBelow = 15 
   };
 
 enum
   {
     msoButtonDown = -1,
     msoButtonUp = 0,
     msoButtonMixed = 2
   };
 
 
 DEFINE_GUID(GUID_NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
 
 DEFINE_GUID(IID_IConnectionPoint, 
             0xb196b286, 0xbab4, 0x101a,
             0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07);
 DEFINE_GUID(IID_IConnectionPointContainer, 
             0xb196b284, 0xbab4, 0x101a,
             0xb6, 0x9c, 0x00, 0xaa, 0x00, 0x34, 0x1d, 0x07);
 DEFINE_GUID(IID_IPictureDisp,
             0x7bf80981, 0xbf32, 0x101a,
             0x8b, 0xbb, 0x00, 0xaa, 0x00, 0x30, 0x0c, 0xab);
 DEFINE_GUID(IID_ApplicationEvents, 0x0006304E, 0x0000, 0x0000,
             0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
 DEFINE_GUID(IID_ExplorerEvents, 0x0006300F, 0x0000, 0x0000,
             0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
 DEFINE_GUID(IID_ExplorersEvents, 0x00063078, 0x0000, 0x0000,
             0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
 DEFINE_GUID(IID_MailItemEvents, 0x0006302B, 0x0000, 0x0000,
             0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
 DEFINE_GUID(IID_MailItem, 0x00063034, 0x0000, 0x0000,
             0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
 DEFINE_GUID(IID_IMAPISecureMessage, 0x253cc320, 0xeab6, 0x11d0,
             0x82, 0x22, 0, 0x60, 0x97, 0x93, 0x87, 0xea);
 
 DEFINE_OLEGUID(IID_IUnknown,                  0x00000000, 0, 0);
 DEFINE_OLEGUID(IID_IDispatch,                 0x00020400, 0, 0);
 DEFINE_OLEGUID(IID_IOleWindow,                0x00000114, 0, 0);
 
 #ifndef PR_SMTP_ADDRESS_DASL
 #define PR_SMTP_ADDRESS_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x39FE001E"
 #endif
 
+#ifndef PR_EMS_AB_PROXY_ADDRESSES_DASL
+#define PR_EMS_AB_PROXY_ADDRESSES_DASL \
+  "http://schemas.microsoft.com/mapi/proptag/0x800F101E"
+#endif
+
 #ifndef PR_ATTACHMENT_HIDDEN_DASL
 #define PR_ATTACHMENT_HIDDEN_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
 #endif
 
 #define PR_MESSAGE_CLASS_W_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x001A001F"
 #define GPGOL_ATTACHTYPE_DASL \
   "http://schemas.microsoft.com/mapi/string/" \
   "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL Attach Type/0x00000003"
 #define GPGOL_UID_DASL \
   "http://schemas.microsoft.com/mapi/string/" \
   "{31805AB8-3E92-11DC-879C-00061B031004}/GpgOL UID/0x0000001F"
 #define PR_ATTACH_DATA_BIN_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x37010102"
 #define PR_BODY_W_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x1000001F"
 #define PR_ATTACHMENT_HIDDEN_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x7FFE000B"
 #define PR_ATTACH_MIME_TAG_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x370E001F"
 #define PR_ATTACH_CONTENT_ID_DASL \
   "http://schemas.microsoft.com/mapi/proptag/0x3712001F"
 #ifdef __cplusplus
 extern "C" {
 #if 0
 }
 #endif
 #endif
 
 /* Return the malloced name of an COM+ object.  */
 char *get_object_name (LPUNKNOWN obj);
 
 /* Helper to lookup a dispid.  */
 DISPID lookup_oom_dispid (LPDISPATCH pDisp, const char *name);
 
 /* Return the OOM object's IDispatch interface described by FULLNAME.  */
 LPDISPATCH get_oom_object (LPDISPATCH pStart, const char *fullname);
 
 /* Set the Icon of a CommandBarControl.  */
 int put_oom_icon (LPDISPATCH pDisp, int rsource_id, int size);
 
 /* Set the boolean property NAME to VALUE.  */
 int put_oom_bool (LPDISPATCH pDisp, const char *name, int value);
 
 /* Set the property NAME to VALUE.  */
 int put_oom_int (LPDISPATCH pDisp, const char *name, int value);
 
 /* Set the property NAME to STRING.  */
 int put_oom_string (LPDISPATCH pDisp, const char *name, const char *string);
 
 /* Get the boolean property NAME of the object PDISP.  */
 int get_oom_bool (LPDISPATCH pDisp, const char *name);
 
 /* Get the integer property NAME of the object PDISP.  */
 int get_oom_int (LPDISPATCH pDisp, const char *name);
 
 /* Get the string property NAME of the object PDISP.  */
 char *get_oom_string (LPDISPATCH pDisp, const char *name);
 
 /* Get an IUnknown object from property NAME of PDISP.  */
 LPUNKNOWN get_oom_iunknown (LPDISPATCH pDisp, const char *name);
 
 /* Return the control object with tag property value TAG.  */
 LPDISPATCH get_oom_control_bytag (LPDISPATCH pObj, const char *tag);
 
 /* Add a new button to an object which supports the add method.
    Returns the new object or NULL on error.  */
 LPDISPATCH add_oom_button (LPDISPATCH pObj);
 
 /* Delete a button.  */
 void del_oom_button (LPDISPATCH button);
 
 /* Get the HWND of the active window in the current context */
 HWND get_oom_context_window (LPDISPATCH context);
 
 /* Get the address of the recipients as string list */
 char ** get_oom_recipients (LPDISPATCH recipients);
 
 /* Add an attachment to a dispatcher */
 int
 add_oom_attachment (LPDISPATCH disp, const wchar_t* inFile,
                     const wchar_t *displayName);
 
 /* Look up a string with the propertyAccessor interface */
 char *
 get_pa_string (LPDISPATCH pDisp, const char *property);
 
 /* Look up a long with the propertyAccessor interface.
  returns -1 on error.*/
 int
 get_pa_int (LPDISPATCH pDisp, const char *property, int *rInt);
 
 /* Set a variant with the propertyAccessor interface.
 
    This is tested to work at least vor BSTR variants. Trying
    to set PR_ATTACH_DATA_BIN_DASL with this failed with
    hresults 0x80020005 type mismatch or 0x80020008 vad
    variable type for:
    VT_ARRAY | VT_UI1 | VT_BYREF
    VT_SAFEARRAY | VT_UI1 | VT_BYREF
    VT_BSTR | VT_BYREF
    VT_BSTR
    VT_ARRAY | VT_UI1
    VT_SAFEARRAY | VT_UI1
 
    No idea whats wrong there. Needs more experiments. The
    Type is only documented as "Binary". Outlookspy also
    fails with the same error when trying to modify the
    property.
 */
 int
 put_pa_string (LPDISPATCH pDisp, const char *dasl_id, const char *value);
 
 int
 put_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *value);
 
 int
 put_pa_int (LPDISPATCH pDisp, const char *dasl_id, int value);
 
 /* Look up a variant with the propertyAccessor interface */
 int
 get_pa_variant (LPDISPATCH pDisp, const char *dasl_id, VARIANT *rVariant);
 
 /* Look up a LONG with the propertyAccessor interface */
 LONG
 get_pa_long (LPDISPATCH pDisp, const char *dasl_id);
 
 /* Queries the interface of the dispatcher for the id
    id. Returns NULL on error. The returned Object
    must be released.
    Mainly useful to check if an object is what
    it appears to be. */
 LPDISPATCH
 get_object_by_id (LPDISPATCH pDisp, REFIID id);
 
 /* Obtain the MAPI Message corresponding to the
    Mailitem. Returns NULL on error.
 
    The returned Message needs to be released by the
    caller */
 LPMESSAGE
 get_oom_message (LPDISPATCH mailitem);
 
 /* Obtain the Base MAPI Message of a MailItem.
    The parameter should be a pointer to a MailItem.
    returns NULL on error.
 
    The returned Message needs to be released by the
    caller.
 */
 LPMESSAGE
 get_oom_base_message (LPDISPATCH mailitem);
 
 /* Get a strong reference for a mail object by calling
    Application.GetObjectReference with type strong. The
    documentation is unclear what this acutally does.
    This function is left over from experiments about
    strong references. Maybe there is a use for them.
    The reference we use in the Mail object is documented
    as a Weak reference. But changing that does not appear
    to make a difference.
 */
 LPDISPATCH
 get_strong_reference (LPDISPATCH mail);
 
 /* Invoke a method of an outlook object.
    returns true on success false otherwise.
 
    rVariant should either point to a propery initialized
    variant (initinalized wiht VariantInit) to hold
    the return value or a pointer to NULL.
    */
 int
 invoke_oom_method (LPDISPATCH pDisp, const char *name, VARIANT *rVariant);
 
 /* Invoke a method of an outlook object.
    returns true on success false otherwise.
 
    rVariant should either point to a propery initialized
    variant (initinalized wiht VariantInit) to hold
    the return value or a pointer to NULL.
 
    parms can optionally be used to provide a DISPPARAMS structure
    with parameters for the function.
    */
 int
 invoke_oom_method_with_parms (LPDISPATCH pDisp, const char *name,
                               VARIANT *rVariant, DISPPARAMS *params);
 
 /* Try to obtain the mapisession through the Application.
   returns NULL on error.*/
 LPMAPISESSION
 get_oom_mapi_session (void);
 
 /* Ensure a category of the name name exists in
   the session for the Mail mail.
 
   Creates the category with the specified color if required.
 
   returns 0 on success. */
 void
 ensure_category_exists (LPDISPATCH mail, const char *category, int color);
 
 /* Add a category to a mail if it is not already added. */
 int
 add_category (LPDISPATCH mail, const char *category);
 
 /* Remove a category from a mail if it was added. */
 int
 remove_category (LPDISPATCH mail, const char *category);
 
 /* Get a unique identifier for a mail object. The
    uuid is a custom property. If create is set
    a new uuid will be added if none exists and the
    value of that uuid returned.
 
    The optinal uuid value can be set to be used
    as uuid instead of a generated one.
 
    Return value has to be freed by the caller.
    */
 char *
 get_unique_id (LPDISPATCH mail, int create, const char* uuid);
 
 
 /* Uses the Application->ActiveWindow to determine the hwnd
    through FindWindow and the caption. Does not use IOleWindow
    because that was unreliable somhow. */
 HWND get_active_hwnd (void);
 #ifdef __cplusplus
 }
 #endif
 #endif /*OOMHELP_H*/