diff --git a/src/explorer-events.cpp b/src/explorer-events.cpp
index 6139460..3389bf6 100644
--- a/src/explorer-events.cpp
+++ b/src/explorer-events.cpp
@@ -1,231 +1,239 @@
/* explorer-events.cpp - Event handling for the application.
* Copyright (C) 2016 by Bundesamt für Sicherheit in der Informationstechnik
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
/* The event handler classes defined in this file follow the
general pattern that they implment the IDispatch interface
through the eventsink macros and handle event invocations
in their invoke methods.
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include "eventsink.h"
#include "ocidl.h"
#include "common.h"
#include "oomhelp.h"
#include "mail.h"
#include "gpgoladdin.h"
#include "windowmessages.h"
/* Explorer Events */
BEGIN_EVENT_SINK(ExplorerEvents, IDispatch)
EVENT_SINK_DEFAULT_CTOR(ExplorerEvents)
EVENT_SINK_DEFAULT_DTOR(ExplorerEvents)
typedef enum
{
Activate = 0xF001,
AttachmentSelectionChange = 0xFC79,
BeforeFolderSwitch = 0xF003,
BeforeItemCopy = 0xFA0E,
BeforeItemCut = 0xFA0F,
BeforeItemPaste = 0xFA10,
BeforeMaximize = 0xFA11,
BeforeMinimize = 0xFA12,
BeforeMove = 0xFA13,
BeforeSize = 0xFA14,
BeforeViewSwitch = 0xF005,
Close = 0xF008,
Deactivate = 0xF006,
DisplayModeChange = 0xFC98,
FolderSwitch = 0xF002,
InlineResponse = 0xFC92,
InlineResponseClose = 0xFC96,
SelectionChange = 0xF007,
ViewSwitch = 0xF004
} ExplorerEvent;
/*
We need to avoid UI invalidations as much as possible as invalidations
can trigger reloads of mails and at a bad time can crash us.
So we only invalidate the UI after we have handled the read event of
a mail and again after decrypt / verify.
The problem is that we also need to update the UI when mails are
unselected so we don't show "Secure" if nothing is selected.
On a switch from one Mail to another we see two selection changes.
One for the unselect the other for the select immediately after
each other.
When we just have an unselect we see only one selection change.
So after we detect the unselect we switch the state in our
explorerMap to unselect seen and start a WatchDog thread.
That thread sleeps for 500ms and then checks if the state
was switched to select seen in the meantime. If
not it triggers the UI Invalidation in the GUI thread.
*/
#include
typedef enum
{
WatchDogActive = 0x01,
UnselectSeen = 0x02,
SelectSeen = 0x04,
} SelectionState;
std::map s_explorerMap;
gpgrt_lock_t explorer_map_lock = GPGRT_LOCK_INITIALIZER;
static bool
hasSelection (LPDISPATCH explorer)
{
LPDISPATCH selection = get_oom_object (explorer, "Selection");
if (!selection)
{
TRACEPOINT;
return false;
}
int count = get_oom_int (selection, "Count");
gpgol_release (selection);
if (count)
{
return true;
}
return false;
}
static DWORD WINAPI
start_watchdog (LPVOID arg)
{
LPDISPATCH explorer = (LPDISPATCH) arg;
Sleep (500);
gpgol_lock (&explorer_map_lock);
auto it = s_explorerMap.find (explorer);
if (it == s_explorerMap.end ())
{
log_error ("%s:%s: Watchdog for unknwon explorer %p",
SRCNAME, __func__, explorer);
gpgol_unlock (&explorer_map_lock);
return 0;
}
if ((it->second & SelectSeen))
{
log_oom ("%s:%s: Cancel watchdog as we have seen a select %p",
SRCNAME, __func__, explorer);
it->second = SelectSeen;
}
else if ((it->second & UnselectSeen))
{
log_debug ("%s:%s: Deteced unselect invalidating UI.",
SRCNAME, __func__);
it->second = UnselectSeen;
gpgol_unlock (&explorer_map_lock);
do_in_ui_thread (INVALIDATE_UI, nullptr);
return 0;
}
gpgol_unlock (&explorer_map_lock);
return 0;
}
static void
changeSeen (LPDISPATCH explorer)
{
+ auto view = get_oom_object_s (explorer, "CurrentView");
+ if (view && get_object_name_s (view.get ()) == "_PeopleView")
+ {
+ log_oom ("Selection change in people view. Invalidating.");
+ gpgoladdin_invalidate_ui ();
+ return;
+ }
+
gpgol_lock (&explorer_map_lock);
auto it = s_explorerMap.find (explorer);
if (it == s_explorerMap.end ())
{
it = s_explorerMap.insert (std::make_pair (explorer, 0)).first;
}
auto state = it->second;
bool has_selection = hasSelection (explorer);
if (has_selection)
{
it->second = (state & WatchDogActive) + SelectSeen;
log_oom ("%s:%s: Seen select for %p",
SRCNAME, __func__, explorer);
}
else
{
if ((it->second & WatchDogActive))
{
log_oom ("%s:%s: Seen unselect for %p but watchdog exists.",
SRCNAME, __func__, explorer);
}
else
{
CloseHandle (CreateThread (NULL, 0, start_watchdog, (LPVOID) explorer,
0, NULL));
}
it->second = UnselectSeen + WatchDogActive;
}
gpgol_unlock (&explorer_map_lock);
}
EVENT_SINK_INVOKE(ExplorerEvents)
{
USE_INVOKE_ARGS
switch(dispid)
{
case SelectionChange:
{
log_oom ("%s:%s: Selection change in explorer: %p",
SRCNAME, __func__, this);
changeSeen (m_object);
break;
}
case Close:
{
log_oom ("%s:%s: Deleting event handler: %p",
SRCNAME, __func__, this);
GpgolAddin::get_instance ()->unregisterExplorerSink (this);
gpgol_lock (&explorer_map_lock);
s_explorerMap.erase (m_object);
gpgol_unlock (&explorer_map_lock);
delete this;
return S_OK;
}
default:
break;
#if 0
log_oom ("%s:%s: Unhandled Event: %lx \n",
SRCNAME, __func__, dispid);
#endif
}
return S_OK;
}
END_EVENT_SINK(ExplorerEvents, IID_ExplorerEvents)
diff --git a/src/gpgoladdin.cpp b/src/gpgoladdin.cpp
index 0489d3f..01bf9f4 100644
--- a/src/gpgoladdin.cpp
+++ b/src/gpgoladdin.cpp
@@ -1,1325 +1,1329 @@
/* gpgoladdin.cpp - Connect GpgOL to Outlook as an addin
* Copyright (C) 2013 Intevation GmbH
* 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include "common.h"
#include "gpgoladdin.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "mapihelp.h"
#include "oomhelp.h"
#include "olflange.h"
#include "gpgol-ids.h"
#include "ribbon-callbacks.h"
#include "eventsinks.h"
#include "eventsink.h"
#include "windowmessages.h"
#include "mail.h"
#include "addin-options.h"
#include "cpphelp.h"
#include "dispcache.h"
#include "categorymanager.h"
#include "keycache.h"
#include
#include
#define ICON_SIZE_LARGE 32
#define ICON_SIZE_NORMAL 16
/* We use UTF-8 internally. */
#undef _
#define _(a) utf8_gettext (a)
ULONG addinLocks = 0;
bool can_unload = false;
static GpgolAddin * addin_instance = NULL;
/* This is the main entry point for the addin
Outlook uses this function to query for an Object implementing
the IClassFactory interface.
*/
STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID* ppvObj)
{
if (!ppvObj)
return E_POINTER;
*ppvObj = NULL;
if (rclsid != CLSID_GPGOL)
return CLASS_E_CLASSNOTAVAILABLE;
/* Let the factory give the requested interface. */
GpgolAddinFactory* factory = new GpgolAddinFactory();
if (!factory)
return E_OUTOFMEMORY;
HRESULT hr = factory->QueryInterface (riid, ppvObj);
if(FAILED(hr))
{
*ppvObj = NULL;
delete factory;
}
return hr;
}
STDAPI DllCanUnloadNow()
{
/* This is called regularly to check if memory can be freed
by unloading the dll. The following unload will not call
any addin methods like disconnect etc. It will just
unload the Library. Any callbacks will become invalid.
So we _only_ say it's ok to unload if we were disconnected.
For the epic story behind the next line see GnuPG-Bug-Id 1837 */
TRACEPOINT;
return can_unload ? S_OK : S_FALSE;
}
/* Class factory */
STDMETHODIMP GpgolAddinFactory::QueryInterface (REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if ((IID_IUnknown == riid) || (IID_IClassFactory == riid))
*ppvObj = static_cast(this);
else
{
hr = E_NOINTERFACE;
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
/* Should not happen */
log_debug ("GpgolAddinFactory queried for unknown interface: %S \n", sRiid);
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
/* This actually creates the instance of our COM object */
STDMETHODIMP GpgolAddinFactory::CreateInstance (LPUNKNOWN punk, REFIID riid,
LPVOID* ppvObj)
{
(void)punk;
*ppvObj = NULL;
GpgolAddin* obj = GpgolAddin::get_instance();
if (NULL == obj)
return E_OUTOFMEMORY;
HRESULT hr = obj->QueryInterface (riid, ppvObj);
if (FAILED(hr))
{
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
fprintf(stderr, "failed to create instance for: %S", sRiid);
}
return hr;
}
GpgolAddinFactory::~GpgolAddinFactory()
{
log_debug ("%s:%s: Object deleted\n", SRCNAME, __func__);
}
/* GpgolAddin definition */
/* Constructor of GpgolAddin
Initializes members and creates the interface objects for the new
context. Does the DLL initialization if it has not been done
before.
The ref count is set by the factory after creation.
*/
GpgolAddin::GpgolAddin (void) : m_lRef(0),
m_application(nullptr),
m_addin(nullptr),
m_applicationEventSink(nullptr),
m_explorersEventSink(nullptr),
m_disabled(false),
m_shutdown(false),
m_hook(nullptr),
m_dispcache(new DispCache)
{
read_options ();
/* RibbonExtender is it's own object to avoid the pitfalls of
multiple inheritance
*/
m_ribbonExtender = new GpgolRibbonExtender();
}
GpgolAddin::~GpgolAddin (void)
{
if (m_disabled)
{
return;
}
addin_instance = NULL;
log_debug ("%s:%s: Object deleted\n", SRCNAME, __func__);
}
STDMETHODIMP
GpgolAddin::QueryInterface (REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if (m_disabled)
return E_NOINTERFACE;
if ((riid == IID_IUnknown) || (riid == IID_IDTExtensibility2) ||
(riid == IID_IDispatch))
{
*ppvObj = (LPUNKNOWN) this;
}
else if (riid == IID_IRibbonExtensibility)
{
return m_ribbonExtender->QueryInterface (riid, ppvObj);
}
else
{
hr = E_NOINTERFACE;
#if 0
LPOLESTR sRiid = NULL;
StringFromIID(riid, &sRiid);
log_debug ("%s:%s: queried for unimplmented interface: %S",
SRCNAME, __func__, sRiid);
#endif
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
static void
addGpgOLToReg (const std::string &path)
{
HKEY h;
int err = RegOpenKeyEx (HKEY_CURRENT_USER, path.c_str(), 0,
KEY_ALL_ACCESS, &h);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: no DoNotDisableAddinList entry '%s' creating it",
SRCNAME, __func__, path.c_str ());
err = RegCreateKeyEx (HKEY_CURRENT_USER, path.c_str (), 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
&h, NULL);
}
if (err != ERROR_SUCCESS)
{
log_error ("%s:%s: failed to create key.",
SRCNAME, __func__);
return;
}
DWORD type;
err = RegQueryValueEx (h, GPGOL_PROGID, NULL, &type, NULL, NULL);
if (err == ERROR_SUCCESS)
{
log_debug ("%s:%s: Found gpgol reg key. Leaving it unchanged.",
SRCNAME, __func__);
RegCloseKey (h);
return;
}
// No key exists. Create one.
DWORD dwTemp = 1;
err = RegSetValueEx (h, GPGOL_PROGID, 0, REG_DWORD, (BYTE*)&dwTemp, 4);
RegCloseKey (h);
if (err != ERROR_SUCCESS)
{
log_error ("%s:%s: failed to set registry value.",
SRCNAME, __func__);
}
else
{
log_debug ("%s:%s: added gpgol to %s",
SRCNAME, __func__, path.c_str ());
}
}
/* This is a bit evil as we basically disable outlooks resiliency
for us. But users are still able to manually disable the addon
or change the donotdisable setting to zero and we won't change
it.
It has been much requested by users that we do this automatically.
*/
static void
setupDoNotDisable ()
{
std::string path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Resiliency\\DoNotDisableAddinList";
addGpgOLToReg (path);
path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Resiliency\\AddinList";
addGpgOLToReg (path);
}
STDMETHODIMP
GpgolAddin::OnConnection (LPDISPATCH Application, ext_ConnectMode ConnectMode,
LPDISPATCH AddInInst, SAFEARRAY ** custom)
{
(void)custom;
char* version;
log_debug ("%s:%s: this is GpgOL %s\n",
SRCNAME, __func__, PACKAGE_VERSION);
m_shutdown = false;
can_unload = false;
m_application = Application;
m_application->AddRef();
memdbg_addRef (m_application);
m_addin = AddInInst;
version = get_oom_string (Application, "Version");
log_debug ("%s:%s: using GPGME %s\n",
SRCNAME, __func__, gpgme_check_version (NULL));
log_debug ("%s:%s: in Outlook %s\n",
SRCNAME, __func__, version);
g_ol_version_major = atoi (version);
if (!version || !strlen (version) ||
(strncmp (version, "14", 2) &&
strncmp (version, "15", 2) &&
strncmp (version, "16", 2)))
{
m_disabled = true;
log_debug ("%s:%s: Disabled addin for unsupported version.",
SRCNAME, __func__);
xfree (version);
return S_OK;
}
xfree (version);
setupDoNotDisable ();
if (ConnectMode != ext_cm_Startup)
{
OnStartupComplete (custom);
}
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnDisconnection (ext_DisconnectMode RemoveMode,
SAFEARRAY** custom)
{
(void)custom;
(void)RemoveMode;
log_debug ("%s:%s: cleaning up GpgolAddin object;",
SRCNAME, __func__);
/* Doing the wipe in the dtor is too late. Outlook
does not allow us any OOM calls then and only returns
"Unexpected error" in that case. Weird. */
shutdown ();
can_unload = true;
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnAddInsUpdate (SAFEARRAY** custom)
{
(void)custom;
return S_OK;
}
void
check_html_preferred()
{
/* Check if HTML Mail should be enabled. */
HKEY h;
std::string path = "Software\\Microsoft\\Office\\";
path += std::to_string (g_ol_version_major);
path += ".0\\Outlook\\Options\\Mail";
opt.prefer_html = 1;
int err = RegOpenKeyEx (HKEY_CURRENT_USER, path.c_str() , 0, KEY_READ, &h);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: no mail options under %s",
SRCNAME, __func__, path.c_str());
return;
}
else
{
DWORD type;
err = RegQueryValueEx (h, "ReadAsPlain", NULL, &type, NULL, NULL);
if (err != ERROR_SUCCESS || type != REG_DWORD)
{
log_debug ("%s:%s: No type or key for ReadAsPlain",
SRCNAME, __func__);
return;
}
else
{
DWORD data;
DWORD size = sizeof (DWORD);
err = RegQueryValueEx (h, "ReadAsPlain", NULL, NULL, (LPBYTE)&data,
&size);
if (err != ERROR_SUCCESS)
{
log_debug ("%s:%s: Failed to find out ReadAsPlain",
SRCNAME, __func__);
return;
}
opt.prefer_html = data ? 0 : 1;
return;
}
}
}
static LPDISPATCH
install_explorer_sinks (LPDISPATCH application)
{
LPDISPATCH explorers = get_oom_object (application, "Explorers");
if (!explorers)
{
log_error ("%s:%s: No explorers object",
SRCNAME, __func__);
return nullptr;
}
int count = get_oom_int (explorers, "Count");
for (int i = 1; i <= count; i++)
{
std::string item = "Item(";
item += std::to_string (i) + ")";
LPDISPATCH explorer = get_oom_object (explorers, item.c_str());
if (!explorer)
{
log_error ("%s:%s: failed to get explorer %i",
SRCNAME, __func__, i);
continue;
}
/* Explorers delete themself in the close event of the explorer. */
LPDISPATCH sink = install_ExplorerEvents_sink (explorer);
if (!sink)
{
log_error ("%s:%s: failed to create eventsink for explorer %i",
SRCNAME, __func__, i);
}
else
{
log_oom ("%s:%s: created sink %p for explorer %i",
SRCNAME, __func__, sink, i);
GpgolAddin::get_instance ()->registerExplorerSink (sink);
}
gpgol_release (explorer);
}
/* Now install the event sink to handle new explorers */
LPDISPATCH ret = install_ExplorersEvents_sink (explorers);
gpgol_release (explorers);
return ret;
}
static DWORD WINAPI
init_gpgme_config (LPVOID)
{
/* This is a check we need to do anyway. GpgME++ caches
the configuration once it is accessed for the first time
so this call also initializes GpgME++ */
bool de_vs_mode = in_de_vs_mode ();
log_debug ("%s:%s: init_gpgme_config de_vs_mode %i",
SRCNAME, __func__, de_vs_mode);
return 0;
}
STDMETHODIMP
GpgolAddin::OnStartupComplete (SAFEARRAY** custom)
{
(void)custom;
TRACEPOINT;
i18n_init ();
if (!create_responder_window())
{
log_error ("%s:%s: Failed to create the responder window;",
SRCNAME, __func__);
}
if (!m_application)
{
/* Should not happen as OnConnection should be called before */
log_error ("%s:%s: no application set;",
SRCNAME, __func__);
return E_NOINTERFACE;
}
if (!(m_hook = create_message_hook ()))
{
log_error ("%s:%s: Failed to create messagehook. ",
SRCNAME, __func__);
}
/* Clean GpgOL prefixed categories.
They might be left over from a crash or something unexpected
error. We want to avoid pollution with the signed by categories.
*/
CategoryManager::removeAllGpgOLCategories ();
install_forms ();
m_applicationEventSink = install_ApplicationEvents_sink (m_application);
m_explorersEventSink = install_explorer_sinks (m_application);
check_html_preferred ();
CloseHandle (CreateThread (NULL, 0, init_gpgme_config, nullptr, 0,
NULL));
KeyCache::instance ()->populate ();
return S_OK;
}
STDMETHODIMP
GpgolAddin::OnBeginShutdown (SAFEARRAY * * custom)
{
(void)custom;
TRACEPOINT;
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetTypeInfoCount (UINT *r_count)
{
*r_count = 0;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetTypeInfo (UINT iTypeInfo, LCID lcid,
LPTYPEINFO *r_typeinfo)
{
(void)iTypeInfo;
(void)lcid;
(void)r_typeinfo;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolAddin::GetIDsOfNames (REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
(void)riid;
(void)rgszNames;
(void)cNames;
(void)lcid;
(void)rgDispId;
TRACEPOINT; /* Should not happen */
return E_NOINTERFACE;
}
STDMETHODIMP
GpgolAddin::Invoke (DISPID dispid, REFIID riid, LCID lcid,
WORD flags, DISPPARAMS *parms, VARIANT *result,
EXCEPINFO *exepinfo, UINT *argerr)
{
USE_INVOKE_ARGS
TRACEPOINT; /* Should not happen */
return DISP_E_MEMBERNOTFOUND;
}
/* Definition of GpgolRibbonExtender */
GpgolRibbonExtender::GpgolRibbonExtender (void) : m_lRef(0)
{
}
GpgolRibbonExtender::~GpgolRibbonExtender (void)
{
log_debug ("%s:%s: cleaning up GpgolRibbonExtender object;",
SRCNAME, __func__);
memdbg_dump ();
}
STDMETHODIMP
GpgolRibbonExtender::QueryInterface(REFIID riid, LPVOID* ppvObj)
{
HRESULT hr = S_OK;
*ppvObj = NULL;
if ((riid == IID_IUnknown) || (riid == IID_IRibbonExtensibility) ||
(riid == IID_IDispatch))
{
*ppvObj = (LPUNKNOWN) this;
}
else
{
LPOLESTR sRiid = NULL;
StringFromIID (riid, &sRiid);
log_debug ("%s:%s: queried for unknown interface: %S",
SRCNAME, __func__, sRiid);
}
if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();
return hr;
}
STDMETHODIMP
GpgolRibbonExtender::GetTypeInfoCount (UINT *r_count)
{
*r_count = 0;
TRACEPOINT; /* Should not happen */
return S_OK;
}
STDMETHODIMP
GpgolRibbonExtender::GetTypeInfo (UINT iTypeInfo, LCID lcid,
LPTYPEINFO *r_typeinfo)
{
(void)iTypeInfo;
(void)lcid;
(void)r_typeinfo;
TRACEPOINT; /* Should not happen */
return S_OK;
}
/* Good documentation of what this function is supposed to do can
be found at: http://msdn.microsoft.com/en-us/library/cc237568.aspx
There is also a very good blog explaining how Ribbon Extensibility
is supposed to work.
http://blogs.msdn.com/b/andreww/archive/2007/03/09/
why-is-it-so-hard-to-shim-iribbonextensibility.aspx
*/
#define ID_MAPPER(name,id) \
if (!wcscmp (rgszNames[i], name)) \
{ \
found = true; \
rgDispId[i] = id; \
break; \
} \
STDMETHODIMP
GpgolRibbonExtender::GetIDsOfNames (REFIID riid, LPOLESTR *rgszNames,
UINT cNames, LCID lcid,
DISPID *rgDispId)
{
(void)riid;
(void)lcid;
bool found = false;
if (!rgszNames || !cNames || !rgDispId)
{
return E_POINTER;
}
for (unsigned int i = 0; i < cNames; i++)
{
log_debug ("%s:%s: GetIDsOfNames for: %S",
SRCNAME, __func__, rgszNames[i]);
/* How this is supposed to work with cNames > 1 is unknown,
but we can just say that we won't support callbacks with
different parameters and just match the name (the first element)
and we give it one of our own dispIds's that are later handled in
the invoke part */
ID_MAPPER (L"btnDecrypt", ID_BTN_DECRYPT)
ID_MAPPER (L"btnDecryptLarge", ID_BTN_DECRYPT_LARGE)
ID_MAPPER (L"btnEncrypt", ID_BTN_ENCRYPT)
ID_MAPPER (L"btnEncryptLarge", ID_BTN_ENCRYPT_LARGE)
ID_MAPPER (L"btnEncryptSmall", IDI_ENCRYPT_20_PNG)
ID_MAPPER (L"btnSignSmall", IDI_SIGN_20_PNG)
ID_MAPPER (L"btnSignEncryptLarge", IDI_SIGN_ENCRYPT_40_PNG)
ID_MAPPER (L"btnEncryptFileLarge", ID_BTN_ENCSIGN_LARGE)
ID_MAPPER (L"btnSignLarge", ID_BTN_SIGN_LARGE)
ID_MAPPER (L"btnVerifyLarge", ID_BTN_VERIFY_LARGE)
ID_MAPPER (L"btnSigstateLarge", ID_BTN_SIGSTATE_LARGE)
/* MIME support: */
ID_MAPPER (L"encryptMime", ID_CMD_MIME_ENCRYPT)
ID_MAPPER (L"encryptMimeEx", ID_CMD_MIME_ENCRYPT_EX)
ID_MAPPER (L"signMime", ID_CMD_MIME_SIGN)
ID_MAPPER (L"signMimeEx", ID_CMD_MIME_SIGN_EX)
ID_MAPPER (L"encryptSignMime", ID_CMD_SIGN_ENCRYPT_MIME)
ID_MAPPER (L"encryptSignMimeEx", ID_CMD_SIGN_ENCRYPT_MIME_EX)
ID_MAPPER (L"getEncryptPressed", ID_GET_ENCRYPT_PRESSED)
ID_MAPPER (L"getEncryptPressedEx", ID_GET_ENCRYPT_PRESSED_EX)
ID_MAPPER (L"getSignPressed", ID_GET_SIGN_PRESSED)
ID_MAPPER (L"getSignPressedEx", ID_GET_SIGN_PRESSED_EX)
ID_MAPPER (L"getSignEncryptPressed", ID_GET_SIGN_ENCRYPT_PRESSED)
ID_MAPPER (L"getSignEncryptPressedEx", ID_GET_SIGN_ENCRYPT_PRESSED_EX)
ID_MAPPER (L"ribbonLoaded", ID_ON_LOAD)
ID_MAPPER (L"openOptions", ID_CMD_OPEN_OPTIONS)
ID_MAPPER (L"getSigLabel", ID_GET_SIG_LABEL)
ID_MAPPER (L"getSigSTip", ID_GET_SIG_STIP)
ID_MAPPER (L"getSigTip", ID_GET_SIG_TTIP)
ID_MAPPER (L"launchDetails", ID_LAUNCH_CERT_DETAILS)
ID_MAPPER (L"getIsDetailsEnabled", ID_GET_IS_DETAILS_ENABLED)
+ ID_MAPPER (L"getIsAddrBookEnabled", ID_GET_IS_ADDR_BOOK_ENABLED)
ID_MAPPER (L"getIsCrypto", ID_GET_IS_CRYPTO_MAIL)
ID_MAPPER (L"openContactKey", ID_CMD_OPEN_CONTACT_KEY)
ID_MAPPER (L"overrideFileClose", ID_CMD_FILE_CLOSE)
ID_MAPPER (L"overrideFileSaveAs", ID_CMD_FILE_SAVE_AS)
ID_MAPPER (L"overrideFileSaveAsWindowed", ID_CMD_FILE_SAVE_AS_IN_WINDOW)
ID_MAPPER (L"decryptPermanently", ID_CMD_DECRYPT_PERMANENTLY)
}
if (cNames > 1)
{
log_debug ("More then one name provided. Should not happen");
}
return found ? S_OK : E_NOINTERFACE;
}
STDMETHODIMP
GpgolRibbonExtender::Invoke (DISPID dispid, REFIID riid, LCID lcid,
WORD flags, DISPPARAMS *parms, VARIANT *result,
EXCEPINFO *exepinfo, UINT *argerr)
{
USE_INVOKE_ARGS
log_oom ("%s:%s: enter with dispid: %x",
SRCNAME, __func__, (int)dispid);
/*
log_oom ("%s:%s: Parms: %s",
SRCNAME, __func__, format_dispparams (parms).c_str ());
*/
if (!(flags & DISPATCH_METHOD))
{
log_debug ("%s:%s: not called in method mode. Bailing out.",
SRCNAME, __func__);
return DISP_E_MEMBERNOTFOUND;
}
switch (dispid)
{
case ID_CMD_SIGN_ENCRYPT_MIME:
return mark_mime_action (parms->rgvarg[1].pdispVal,
OP_SIGN|OP_ENCRYPT, false);
case ID_CMD_SIGN_ENCRYPT_MIME_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal,
OP_SIGN|OP_ENCRYPT, true);
case ID_CMD_MIME_ENCRYPT:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_ENCRYPT,
false);
case ID_CMD_MIME_SIGN:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_SIGN,
false);
case ID_GET_ENCRYPT_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_ENCRYPT,
result, false);
case ID_GET_SIGN_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN,
result, false);
case ID_GET_SIGN_ENCRYPT_PRESSED:
return get_crypt_pressed (parms->rgvarg[0].pdispVal,
OP_SIGN | OP_ENCRYPT,
result, false);
case ID_CMD_MIME_SIGN_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_SIGN, true);
case ID_CMD_MIME_ENCRYPT_EX:
return mark_mime_action (parms->rgvarg[1].pdispVal, OP_ENCRYPT, true);
case ID_GET_ENCRYPT_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_ENCRYPT,
result, true);
case ID_GET_SIGN_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN,
result, true);
case ID_GET_SIGN_ENCRYPT_PRESSED_EX:
return get_crypt_pressed (parms->rgvarg[0].pdispVal, OP_SIGN | OP_ENCRYPT,
result, true);
case ID_GET_SIG_STIP:
return get_sig_stip (parms->rgvarg[0].pdispVal, result);
case ID_GET_SIG_TTIP:
return get_sig_ttip (parms->rgvarg[0].pdispVal, result);
case ID_GET_SIG_LABEL:
return get_sig_label (parms->rgvarg[0].pdispVal, result);
case ID_LAUNCH_CERT_DETAILS:
return launch_cert_details (parms->rgvarg[0].pdispVal);
case ID_GET_IS_DETAILS_ENABLED:
return get_is_details_enabled (parms->rgvarg[0].pdispVal, result);
+ case ID_GET_IS_ADDR_BOOK_ENABLED:
+ return get_is_addr_book_enabled (parms->rgvarg[0].pdispVal, result);
case ID_ON_LOAD:
{
GpgolAddin::get_instance ()->addRibbon (parms->rgvarg[0].pdispVal);
return S_OK;
}
case ID_CMD_OPEN_OPTIONS:
{
options_dialog_box (NULL);
return S_OK;
}
case ID_CMD_DECRYPT_PERMANENTLY:
return decrypt_permanently (parms->rgvarg[0].pdispVal);
case ID_GET_IS_CRYPTO_MAIL:
return get_is_crypto_mail (parms->rgvarg[0].pdispVal, result);
case ID_CMD_OPEN_CONTACT_KEY:
return open_contact_key (parms->rgvarg[0].pdispVal);
case ID_CMD_FILE_CLOSE :
return override_file_close ();
case ID_CMD_FILE_SAVE_AS:
return override_file_save_as (parms);
case ID_CMD_FILE_SAVE_AS_IN_WINDOW:
return override_file_save_as_in_window (parms);
case ID_BTN_ENCRYPT:
case ID_BTN_DECRYPT:
case ID_BTN_DECRYPT_LARGE:
case ID_BTN_ENCRYPT_LARGE:
case ID_BTN_ENCSIGN_LARGE:
case ID_BTN_SIGN_LARGE:
case ID_BTN_VERIFY_LARGE:
case IDI_SIGN_ENCRYPT_40_PNG:
case IDI_ENCRYPT_20_PNG:
case IDI_SIGN_20_PNG:
return getIcon (dispid, result);
case ID_BTN_SIGSTATE_LARGE:
return get_crypto_icon (parms->rgvarg[0].pdispVal, result);
}
log_debug ("%s:%s: leave", SRCNAME, __func__);
return DISP_E_MEMBERNOTFOUND;
}
/* Returns the XML markup for the various RibbonID's
The custom ui syntax is documented at:
http://msdn.microsoft.com/en-us/library/dd926139%28v=office.12%29.aspx
The outlook specific elements are documented at:
http://msdn.microsoft.com/en-us/library/office/ee692172%28v=office.14%29.aspx
*/
static STDMETHODIMP
GetCustomUI_MIME (BSTR RibbonID, BSTR * RibbonXml)
{
char * buffer = NULL;
/* const char *certManagerTTip =
_("Start the Certificate Management Software");
const char *certManagerSTip =
_("Open GPA or Kleopatra to manage your certificates. "
"You can use this you to generate your "
"own certificates. ");*/
const char *encryptTTip =
_("Encrypt the message");
const char *encryptSTip =
_("Encrypts the message and all attachments before sending");
const char *signTTip =
_("Sign the message");
const char *signSTip =
_("Sign the message and all attachments before sending");
const char *secureTTip =
_("Sign and encrypt the message");
const char *secureSTip =
_("Encrypting and cryptographically signing a message means that the "
"recipients can be sure that no one modified the message and only the "
"recipients can read it");
const char *optsSTip =
_("Open the settings dialog for GpgOL");
log_debug ("%s:%s: GetCustomUI_MIME for id: %ls", SRCNAME, __func__, RibbonID);
if (!RibbonXml || !RibbonID)
return E_POINTER;
if (!wcscmp (RibbonID, L"Microsoft.Outlook.Mail.Compose"))
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" ", _("GpgOL"),
_("Secure"), secureTTip, secureSTip,
_("Sign"), signTTip, signSTip,
_("Encrypt"), encryptTTip, encryptSTip,
optsSTip
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Mail.Read"))
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
/* " getEnabled=\"getIsCrypto\"/>" */
" "
" "
" "
" "
" "
" "
" "
" ",
_("GpgOL"),
optsSTip
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Explorer") && g_ol_version_major > 14)
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" ",
_("GpgOL"),
optsSTip,
_("GpgOL"),
_("Settings"),
_(/* TRANSLATORS: Tooltip caption */
"Configure GpgOL keys and settings for this contact."),
_(/* TRANSLATORS: Tooltip content */
"Configure contact specific keys and settings to override "
"the default behavior for this contact."),
_("GpgOL Settings"),
optsSTip,
_("GpgOL"),
_("Secure"), secureTTip, secureSTip,
_("Sign"), signTTip, signSTip,
_("Encrypt"), encryptTTip, encryptSTip,
optsSTip,
_("Permanently &decrypt")
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Explorer"))
{
// No TabComposeTools in Outlook 2010
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" ",
_("GpgOL"),
optsSTip
);
}
else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Contact"))
{
gpgrt_asprintf (&buffer,
""
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" "
" ",
_("GpgOL"),
_("Settings"),
_(/* TRANSLATORS: Tooltip caption */
"Configure GpgOL keys and settings for this contact."),
_(/* TRANSLATORS: Tooltip content */
"Configure contact specific keys and settings to override "
"the default behavior for this contact."),
_("GpgOL Settings"),
optsSTip
);
}
//_(/* TRANSLATORS: Tooltip caption */
// "Configure the OpenPGP key for this contact."),
// _(/* TRANSLATORS: Tooltip content */
// "The configured key or keys will be used for this contact even "
// "if they are not certified."),
if (buffer)
{
memdbg_alloc (buffer);
wchar_t *wbuf = utf8_to_wchar (buffer);
xfree (buffer);
*RibbonXml = SysAllocString (wbuf);
xfree (wbuf);
}
else
*RibbonXml = NULL;
return S_OK;
}
STDMETHODIMP
GpgolRibbonExtender::GetCustomUI (BSTR RibbonID, BSTR * RibbonXml)
{
return GetCustomUI_MIME (RibbonID, RibbonXml);
}
/* RibbonUi elements are created on demand but they are reused
in different inspectors. So far and from all documentation
I could find RibbonUi elments are never
deleted. When they are created the onLoad callback is called
to register them.
The callbacks registered in the XML description are only
executed on Load. So to have different information depending
on the available mails we have to invalidate the UI ourself.
This means that the callbacks will be reevaluated and the UI
Updated. Sadly we don't know which ribbon_ui needs updates
so we have to invalidate everything.
*/
void
gpgoladdin_invalidate_ui ()
{
GpgolAddin::get_instance ()->invalidateRibbons();
}
GpgolAddin *
GpgolAddin::get_instance ()
{
if (!addin_instance)
{
addin_instance = new GpgolAddin ();
}
if (addin_instance->isShutdown ())
{
log_warn ("%s:%s: Get instance after shutdown",
SRCNAME, __func__);
}
return addin_instance;
}
void
GpgolAddin::invalidateRibbons()
{
/* This can only be done in the main thread so no
need for locking or copying here. */
for (const auto it: m_ribbon_uis)
{
log_debug ("%s:%s: Invalidating ribbon: %p",
SRCNAME, __func__, it);
invoke_oom_method (it, "Invalidate", NULL);
}
log_debug ("%s:%s: Invalidation done.",
SRCNAME, __func__);
}
void
GpgolAddin::addRibbon (LPDISPATCH disp)
{
m_ribbon_uis.push_back (disp);
}
void
GpgolAddin::shutdown ()
{
TSTART;
if (m_shutdown)
{
log_debug ("%s:%s: Already shutdown",
SRCNAME, __func__);
TRETURN;
}
m_shutdown = true;
/* Disabling message hook */
UnhookWindowsHookEx (m_hook);
log_debug ("%s:%s: Releasing Application Event Sink;",
SRCNAME, __func__);
detach_ApplicationEvents_sink (m_applicationEventSink);
gpgol_release (m_applicationEventSink);
log_debug ("%s:%s: Releasing Explorers Event Sink;",
SRCNAME, __func__);
detach_ExplorersEvents_sink (m_explorersEventSink);
gpgol_release (m_explorersEventSink);
log_debug ("%s:%s: Releasing Explorer Event Sinks;",
SRCNAME, __func__);
for (auto sink: m_explorerEventSinks)
{
detach_ExplorerEvents_sink (sink);
gpgol_release (sink);
}
write_options ();
if (Mail::closeAllMails_o ())
{
MessageBox (NULL,
"Failed to remove plaintext from at least one message.\n\n"
"Until GpgOL is activated again it is possible that the "
"plaintext of messages decrypted in this Session is saved "
"or transfered back to your mailserver.",
_("GpgOL"),
MB_ICONINFORMATION|MB_OK);
}
gpgol_release (m_application);
m_application = nullptr;
TRETURN;
}
void
GpgolAddin::registerExplorerSink (LPDISPATCH sink)
{
m_explorerEventSinks.push_back (sink);
}
void
GpgolAddin::unregisterExplorerSink (LPDISPATCH sink)
{
for (int i = 0; i < m_explorerEventSinks.size(); ++i)
{
if (m_explorerEventSinks[i] == sink)
{
m_explorerEventSinks.erase(m_explorerEventSinks.begin() + i);
return;
}
}
log_error ("%s:%s: Unregister %p which was not registered.",
SRCNAME, __func__, sink);
}
std::shared_ptr
GpgolAddin::get_category_mngr ()
{
if (!m_category_mngr)
{
m_category_mngr = std::shared_ptr (
new CategoryManager ());
}
return m_category_mngr;
}
diff --git a/src/ribbon-callbacks.cpp b/src/ribbon-callbacks.cpp
index 7b7d560..7fee036 100644
--- a/src/ribbon-callbacks.cpp
+++ b/src/ribbon-callbacks.cpp
@@ -1,978 +1,1016 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include "ribbon-callbacks.h"
#include "gpgoladdin.h"
#include "common.h"
#include "mymapi.h"
#include "mymapitags.h"
#include "common.h"
#include "mapihelp.h"
#include "mimemaker.h"
#include "filetype.h"
#include "mail.h"
#include "dispcache.h"
#include "addressbook.h"
#include "windowmessages.h"
#include
#include
#undef _
#define _(a) utf8_gettext (a)
using namespace GpgME;
/* This is so super stupid. I bet even Microsft developers laugh
about the definition of VARIANT_BOOL. And then for COM we
have to pass pointers to this stuff. */
static VARIANT_BOOL var_true = VARIANT_TRUE;
static VARIANT_BOOL var_false = VARIANT_FALSE;
/* Gets the context of a ribbon control. And prints some
useful debug output */
HRESULT getContext (LPDISPATCH ctrl, LPDISPATCH *context)
{
*context = get_oom_object (ctrl, "get_Context");
if (*context)
{
char *name = get_object_name (*context);
log_debug ("%s:%s: contextObj: %s",
SRCNAME, __func__, name);
xfree (name);
}
return context ? S_OK : E_FAIL;
}
/* getIconDisp
Loads a PNG image from the resurce converts it into a Bitmap
and Wraps it in an PictureDispatcher that is returned as result.
Based on documentation from:
http://www.codeproject.com/Articles/3537/Loading-JPG-PNG-resources-using-GDI
*/
static LPDISPATCH
getIconDisp (int id)
{
PICTDESC pdesc;
LPDISPATCH pPict;
HRESULT hr;
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::Bitmap* pbitmap;
ULONG_PTR gdiplusToken;
HRSRC hResource;
DWORD imageSize;
const void* pResourceData;
HGLOBAL hBuffer;
memset (&pdesc, 0, sizeof pdesc);
pdesc.cbSizeofstruct = sizeof pdesc;
pdesc.picType = PICTYPE_BITMAP;
/* Initialize GDI */
gdiplusStartupInput.DebugEventCallback = NULL;
gdiplusStartupInput.SuppressBackgroundThread = FALSE;
gdiplusStartupInput.SuppressExternalCodecs = FALSE;
gdiplusStartupInput.GdiplusVersion = 1;
GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, NULL);
/* Get the image from the resource file */
hResource = FindResource (glob_hinst, MAKEINTRESOURCE(id), RT_RCDATA);
if (!hResource)
{
log_error ("%s:%s: failed to find image: %i",
SRCNAME, __func__, id);
return nullptr;
}
imageSize = SizeofResource (glob_hinst, hResource);
if (!imageSize)
return nullptr;
pResourceData = LockResource (LoadResource(glob_hinst, hResource));
if (!pResourceData)
{
log_error ("%s:%s: failed to load image: %i",
SRCNAME, __func__, id);
return nullptr;
}
hBuffer = GlobalAlloc (GMEM_MOVEABLE, imageSize);
if (hBuffer)
{
void* pBuffer = GlobalLock (hBuffer);
if (pBuffer)
{
IStream* pStream = NULL;
CopyMemory (pBuffer, pResourceData, imageSize);
if (CreateStreamOnHGlobal (hBuffer, TRUE, &pStream) == S_OK)
{
memdbg_addRef (pStream);
pbitmap = Gdiplus::Bitmap::FromStream (pStream);
gpgol_release (pStream);
if (!pbitmap || pbitmap->GetHBITMAP (0, &pdesc.bmp.hbitmap))
{
log_error ("%s:%s: failed to get PNG.",
SRCNAME, __func__);
}
}
}
GlobalUnlock (pBuffer);
}
GlobalFree (hBuffer);
Gdiplus::GdiplusShutdown (gdiplusToken);
/* Wrap the image into an OLE object. */
hr = OleCreatePictureIndirect (&pdesc, IID_IPictureDisp,
TRUE, (void **) &pPict);
if (hr != S_OK || !pPict)
{
log_error ("%s:%s: OleCreatePictureIndirect failed: hr=%#lx\n",
SRCNAME, __func__, hr);
return nullptr;
}
return pPict;
}
HRESULT
getIcon (int id, VARIANT* result)
{
if (!result)
{
TRACEPOINT;
return S_OK;
}
auto cache = DispCache::instance ();
result->pdispVal = cache->getDisp (id);
if (!result->pdispVal)
{
result->pdispVal = getIconDisp (id);
cache->addDisp (id, result->pdispVal);
memdbg_addRef (result->pdispVal);
}
if (result->pdispVal)
{
result->vt = VT_DISPATCH;
result->pdispVal->AddRef();
}
return S_OK;
}
HRESULT
mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer)
{
LPMESSAGE message = NULL;
int oldflags,
newflags;
log_debug ("%s:%s: enter", SRCNAME, __func__);
LPDISPATCH context = NULL;
if (FAILED(getContext (ctrl, &context)))
{
TRACEPOINT;
return E_FAIL;
}
LPDISPATCH mailitem = get_oom_object (context,
is_explorer ? "ActiveInlineResponse" :
"CurrentItem");
gpgol_release (context);
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
return E_FAIL;
}
/* Get the uid of this item. */
char *uid = get_unique_id (mailitem, 0, nullptr);
if (!uid)
{
LPMESSAGE msg = get_oom_base_message (mailitem);
uid = mapi_get_uid (msg);
gpgol_release (msg);
if (!uid)
{
log_debug ("%s:%s: Failed to get uid for %p",
SRCNAME, __func__, mailitem);
}
}
Mail *mail = nullptr;
if (uid)
{
mail = Mail::getMailForUUID (uid);
xfree (uid);
}
if (mail)
{
mail->setCryptoSelectedManually (true);
}
else
{
log_debug ("%s:%s: Failed to get mail object.",
SRCNAME, __func__);
}
message = get_oom_base_message (mailitem);
gpgol_release (mailitem);
if (!message)
{
log_error ("%s:%s: Failed to get message.",
SRCNAME, __func__);
return S_OK;
}
oldflags = get_gpgol_draft_info_flags (message);
if (flags == 3 && oldflags != 3)
{
// If only one sub button is active activate
// both now.
newflags = 3;
}
else
{
newflags = oldflags xor flags;
}
if (set_gpgol_draft_info_flags (message, newflags))
{
log_error ("%s:%s: Failed to set draft flags.",
SRCNAME, __func__);
}
gpgol_release (message);
/* We need to invalidate the UI to update the toggle
states of the subbuttons and the top button. Yeah,
we invalidate a lot *sigh* */
gpgoladdin_invalidate_ui ();
if (newflags & 1)
{
Mail::locateAllCryptoRecipients_o ();
}
return S_OK;
}
/* Get the state of encrypt / sign toggle buttons.
flag values: 1 get the state of the encrypt button.
2 get the state of the sign button.
If is_explorer is set to true we look at the inline response.
*/
HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result,
bool is_explorer)
{
HRESULT hr;
bool value;
LPDISPATCH context = NULL,
mailitem = NULL;
LPMESSAGE message = NULL;
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = &var_false;
/* First the usual defensive check about our parameters */
if (!ctrl || !result)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return E_FAIL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return E_FAIL;
}
mailitem = get_oom_object (context, is_explorer ? "ActiveInlineResponse" :
"CurrentItem");
if (!mailitem)
{
log_error ("%s:%s: Failed to get mailitem.",
SRCNAME, __func__);
goto done;
}
message = get_oom_base_message (mailitem);
if (!message)
{
log_error ("%s:%s: No message found.",
SRCNAME, __func__);
goto done;
}
value = (get_gpgol_draft_info_flags (message) & flags) == flags;
result->pboolVal = value ? &var_true: &var_false;
done:
gpgol_release (context);
gpgol_release (mailitem);
gpgol_release (message);
return S_OK;
}
static Mail *
get_mail_from_control (LPDISPATCH ctrl, bool *none_selected)
{
HRESULT hr;
LPDISPATCH context = NULL,
mailitem = NULL;
*none_selected = false;
if (!ctrl)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return NULL;
}
hr = getContext (ctrl, &context);
if (hr)
{
log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
hr);
return NULL;
}
char *name = get_object_name (context);
std::string ctx_name;
if (name)
{
ctx_name = name;
xfree (name);
}
if (ctx_name.empty())
{
log_error ("%s:%s: Failed to get context name",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
if (!strcmp (ctx_name.c_str(), "_Inspector"))
{
mailitem = get_oom_object (context, "CurrentItem");
}
else if (!strcmp (ctx_name.c_str(), "_Explorer"))
{
/* Avoid showing wrong crypto state if we don't have a reading
pane. In that case the parser will finish for a mail which is gone
and the crypto state will not get updated. */
if (!is_preview_pane_visible (context))
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
log_debug ("%s:%s: Preview pane invisible", SRCNAME, __func__);
}
#if 0
if (g_ol_version_major >= 16)
{
/* Some Versions of Outlook 2016 crashed when accessing the current view
of the Explorer. This was even reproducible with
GpgOL disabled and only with Outlook Spy active. If you selected
the explorer of an Outlook.com resource and then access
the CurrentView and close the CurrentView again in Outlook Spy
outlook crashes. See: T3484
The crash no longer occured at least since build 10228. As
I'm not sure which Version fixed the crash we don't do a version
check and just use the same codepath as for Outlook 2010 and
2013 again.
Accessing PreviewPane.WordEditor is not a good solution here as
it requires "Microsoft VBA for Office" (See T4056 ).
A possible solution for that might be to check if
"Mail.GetInspector().WordEditor()" returns NULL. In that case we
know that we also won't get a WordEditor in the preview pane. */
LPDISPATCH prevEdit = get_oom_object (context, "PreviewPane.WordEditor");
gpgol_release (prevEdit);
if (!prevEdit)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
else
{
// Preview Pane is not available in older outlooks
LPDISPATCH tableView = get_oom_object (context, "CurrentView");
if (!tableView)
{
// Woops, should not happen.
TRACEPOINT;
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
else
{
int hasReadingPane = get_oom_bool (tableView, "ShowReadingPane");
gpgol_release (tableView);
if (!hasReadingPane)
{
*none_selected = true;
gpgol_release (mailitem);
mailitem = nullptr;
}
}
}
#endif
if (!*none_selected)
{
/* Accessing the selection item can trigger a load event
so we only do this here if we think that there might be
something visible / selected. To avoid triggering a load
if there is no content shown. */
LPDISPATCH selection = get_oom_object (context, "Selection");
if (!selection)
{
log_error ("%s:%s: Failed to get selection.",
SRCNAME, __func__);
gpgol_release (context);
return NULL;
}
int count = get_oom_int (selection, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (selection, "Item(1)");
}
gpgol_release (selection);
if (!mailitem)
{
*none_selected = true;
}
}
}
else if (!strcmp (ctx_name.c_str(), "Selection"))
{
int count = get_oom_int (context, "Count");
if (count == 1)
{
// If we call this on a selection with more items
// Outlook sends an ItemLoad event for each mail
// in that selection.
mailitem = get_oom_object (context, "Item(1)");
}
if (!mailitem)
{
*none_selected = true;
}
}
gpgol_release (context);
if (!mailitem)
{
log_debug ("%s:%s: No mailitem. From %s",
SRCNAME, __func__, ctx_name.c_str());
return NULL;
}
char *uid;
/* Get the uid of this item. */
uid = get_unique_id (mailitem, 0, nullptr);
if (!uid)
{
LPMESSAGE msg = get_oom_base_message (mailitem);
if (!msg)
{
log_debug ("%s:%s: Failed to get message for %p",
SRCNAME, __func__, mailitem);
gpgol_release (mailitem);
return NULL;
}
uid = mapi_get_uid (msg);
gpgol_release (msg);
if (!uid)
{
log_debug ("%s:%s: Failed to get uid for %p",
SRCNAME, __func__, mailitem);
gpgol_release (mailitem);
return NULL;
}
}
auto ret = Mail::getMailForUUID (uid);
xfree (uid);
if (!ret)
{
log_error ("%s:%s: Failed to find mail %p in map.",
SRCNAME, __func__, mailitem);
}
gpgol_release (mailitem);
return ret;
}
/* Helper to reduce code duplication.*/
#define MY_MAIL_GETTER \
if (!ctrl) \
{ \
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__); \
return E_FAIL; \
} \
bool none_selected; \
const auto mail = get_mail_from_control (ctrl, &none_selected); \
(void)none_selected; \
if (!mail) \
{ \
log_oom ("%s:%s:%i Failed to get mail", \
SRCNAME, __func__, __LINE__); \
}
HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (!result)
{
TRACEPOINT;
return S_OK;
}
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = none_selected ? &var_false : &var_true;
TRACEPOINT;
return S_OK;
}
+HRESULT get_is_addr_book_enabled (LPDISPATCH ctrl, VARIANT *result)
+{
+ log_dbg ("Check for address book enable state.");
+ if (!ctrl)
+ {
+ log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
+ return E_FAIL;
+ }
+ result->vt = VT_BOOL | VT_BYREF;
+ result->pboolVal = &var_false;
+ LPDISPATCH context = nullptr;
+ HRESULT hr = getContext (ctrl, &context);
+
+ if (hr || !context)
+ {
+ log_error ("%s:%s:%i :Failed to get context hresult %lx",
+ SRCNAME, __func__, __LINE__, hr);
+ return S_OK;
+ }
+ auto selection = get_oom_object (context, "Selection");
+ gpgol_release (context);
+ if (!selection)
+ {
+ log_error ("%s:%s: Failed to get selection.",
+ SRCNAME, __func__);
+ return S_OK;
+ }
+ int count = get_oom_int (selection, "Count");
+ gpgol_release (selection);
+ if (count == 1)
+ {
+ result->pboolVal = &var_true;
+ log_dbg ("Selection count is one, return true.");
+ return S_OK;
+ }
+ return S_OK;
+}
+
HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (!mail)
{
log_debug ("%s:%s: No mail.",
SRCNAME, __func__);
w_result = utf8_to_wchar (_("Insecure"));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
return S_OK;
}
w_result = utf8_to_wchar (mail->getCryptoSummary ().c_str ());
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
TRACEPOINT;
return S_OK;
}
HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
wchar_t *w_result;
if (mail)
{
w_result = utf8_to_wchar (mail->getCryptoOneLine ().c_str());
}
else if (!none_selected)
{
w_result = utf8_to_wchar (_("Insecure message"));
}
else
{
w_result = utf8_to_wchar (_("No message selected"));
}
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
TRACEPOINT;
return S_OK;
}
HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BSTR;
if (none_selected)
{
result->bstrVal = SysAllocString (L"");
TRACEPOINT;
return S_OK;
}
if (!mail || !mail->isCryptoMail ())
{
wchar_t *w_result;
w_result = utf8_to_wchar (utf8_gettext ("You cannot be sure who sent, "
"modified and read the message in transit.\n\n"
"Click here to learn more."));
result->bstrVal = SysAllocString (w_result);
xfree (w_result);
TRACEPOINT;
return S_OK;
}
const auto message = mail->getCryptoDetails_o ();
wchar_t *w_message = utf8_to_wchar (message.c_str());
result->bstrVal = SysAllocString (w_message);
xfree (w_message);
TRACEPOINT;
return S_OK;
}
HRESULT launch_cert_details (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail || (!mail->isSigned () && !mail->isEncrypted ()))
{
ShellExecuteA(NULL, NULL, "https://emailselfdefense.fsf.org/en/infographic.html",
0, 0, SW_SHOWNORMAL);
return S_OK;
}
if (!mail->isSigned () && mail->isEncrypted ())
{
/* Encrypt only, no information but show something. because
we want the button to be active.
Aheinecke: I don't think we should show to which keys the message
is encrypted here. This would confuse users if they see keyids
of unknown keys and the information can't be "true" because the
sender could have sent the same information to other people or
used throw keyids etc.
*/
char * buf;
gpgrt_asprintf (&buf, _("The message was not cryptographically signed.\n"
"There is no additional information available if it "
"was actually sent by '%s' or if someone faked the sender address."), mail->getSender_o ().c_str());
wchar_t *w_msg = utf8_to_wchar (buf);
wchar_t *w_title = utf8_to_wchar (_("GpgOL"));
MessageBoxW (NULL, w_msg, w_title,
MB_ICONINFORMATION|MB_OK);
xfree (buf);
xfree (w_msg);
xfree (w_title);
return S_OK;
}
if (!mail->getSigFpr ())
{
std::string buf = _("There was an error verifying the signature.\n"
"Full details:\n");
buf += mail->getVerificationResultDump ();
gpgol_message_box (get_active_hwnd(), buf.c_str(), _("GpgOL"), MB_OK);
}
char *uiserver = get_uiserver_name ();
bool showError = false;
if (uiserver)
{
std::string path (uiserver);
xfree (uiserver);
if (path.find("kleopatra.exe") != std::string::npos)
{
size_t dpos;
if ((dpos = path.find(" --daemon")) != std::string::npos)
{
path.erase(dpos, strlen(" --daemon"));
}
auto ctx = Context::createForEngine(SpawnEngine);
if (!ctx)
{
log_error ("%s:%s: No spawn engine.",
SRCNAME, __func__);
}
std::string parentWid = std::to_string ((int) (intptr_t) get_active_hwnd ());
const char *argv[] = {path.c_str(),
"--query",
mail->getSigFpr (),
"--parent-windowid",
parentWid.c_str(),
NULL };
log_debug ("%s:%s: Starting %s %s %s",
SRCNAME, __func__, path.c_str(), argv[1], argv[2]);
Data d(Data::null);
ctx->spawnAsync(path.c_str(), argv, d, d,
d, (GpgME::Context::SpawnFlags) (
GpgME::Context::SpawnAllowSetFg |
GpgME::Context::SpawnShowWindow));
}
else
{
showError = true;
}
}
else
{
showError = true;
}
if (showError)
{
wchar_t *w_title = utf8_to_wchar (_("GpgOL"));
wchar_t *w_msg = utf8_to_wchar (_("Could not find Kleopatra.\n"
"Please reinstall Gpg4win with the Kleopatra component enabled."));
MessageBoxW (NULL, w_msg, w_title,
MB_ICONINFORMATION|MB_OK);
xfree (w_title);
xfree (w_msg);
}
return S_OK;
}
HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
if (mail)
{
TRACEPOINT;
return getIcon (mail->getCryptoIconID (), result);
}
TRACEPOINT;
return getIcon (IDI_LEVEL_0, result);
}
HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result)
{
MY_MAIL_GETTER
result->vt = VT_BOOL | VT_BYREF;
result->pboolVal = mail && (mail->isSigned () || mail->isEncrypted ()) ?
&var_true : &var_false;
TRACEPOINT;
return S_OK;
}
HRESULT decrypt_permanently (LPDISPATCH ctrl)
{
MY_MAIL_GETTER
if (!mail)
{
log_error ("%s:%s: Failed to get mail.",
SRCNAME, __func__);
return S_OK;
}
mail->decryptPermanently_o ();
return S_OK;
}
HRESULT open_contact_key (LPDISPATCH ctrl)
{
if (!ctrl)
{
log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
return E_FAIL;
}
LPDISPATCH context_disp = nullptr;
HRESULT hr = getContext (ctrl, &context_disp);
auto context = MAKE_SHARED (context_disp);
if (hr || !context)
{
log_error ("%s:%s:%i :Failed to get context hresult %lx",
SRCNAME, __func__, __LINE__, hr);
return S_OK;
}
char *name = get_object_name (context.get ());
std::string ctx_name;
if (name)
{
ctx_name = name;
xfree (name);
}
if (ctx_name.empty())
{
log_error ("%s:%s: Failed to get context name",
SRCNAME, __func__);
return S_OK;
}
shared_disp_t contact = nullptr;
if (!strcmp (ctx_name.c_str(), "_Inspector"))
{
contact = get_oom_object_s (context, "CurrentItem");
}
else if (!strcmp (ctx_name.c_str(), "_Explorer"))
{
auto selection = get_oom_object_s (context, "Selection");
if (!selection)
{
log_error ("%s:%s: Failed to get selection.",
SRCNAME, __func__);
return S_OK;
}
int count = get_oom_int (selection, "Count");
if (!count)
{
log_err ("Nothing selected, button should have been inactive.");
return S_OK;
}
else if (count != 1)
{
log_err ("More then one selected. Button should have been inactive.");
return S_OK;
}
contact = get_oom_object_s (selection, "Item(1)");
}
if (!contact)
{
TRACEPOINT;
return S_OK;
}
Addressbook::edit_key_o (contact.get ());
return S_OK;
}
HRESULT override_file_close ()
{
TSTART;
auto inst = GpgolAddin::get_instance ();
/* We need to get it first as shutdown releases the reference */
auto app = inst->get_application ();
app->AddRef ();
inst->shutdown();
log_debug ("%s:%s: Shutdown complete. Quitting.",
SRCNAME, __func__);
invoke_oom_method (app, "Quit", nullptr);
TRETURN S_OK;
}
bool g_ignore_next_load = false;
HRESULT override_file_save_as (DISPPARAMS *parms)
{
TSTART;
if (!parms)
{
STRANGEPOINT;
TRETURN S_OK;
}
/* Check if we are in a dedicated window which we must treat differently. */
LPDISPATCH context = nullptr;
if (parms->cArgs == 2 &&
getContext (parms->rgvarg[1].pdispVal, &context) == S_OK)
{
char *name = get_object_name (context);
std::string ctx_name;
if (name)
{
ctx_name = name;
xfree (name);
}
if (ctx_name == "_Inspector")
{
gpgol_release (context);
TRETURN override_file_save_as_in_window (parms);
}
}
gpgol_release (context);
/* Do not cancel the event so that the underlying File Save As works. */
parms->rgvarg[0].pvarVal->boolVal = VARIANT_FALSE;
/* File->SaveAs triggers an ItemLoad event immediately after this
callback. To avoid attaching our decrypt / save prevention code
on that mail we set this global variable so that application-events
knows that it should ignore the next item load. That way we do
not interfere and it can just save what is in MAPI (the encrypted
mail). */
g_ignore_next_load = true;
TRETURN S_OK;
}
HRESULT override_file_save_as_in_window (DISPPARAMS *parms)
{
TSTART;
if (!parms)
{
STRANGEPOINT;
TRETURN S_OK;
}
/* Do not cancel the event so that the underlying File Save As works. */
parms->rgvarg[0].pvarVal->boolVal = VARIANT_FALSE;
LPDISPATCH ctrl = parms->rgvarg[1].pdispVal;
MY_MAIL_GETTER
if (!mail)
{
log_debug ("%s:%s: No mail.",
SRCNAME, __func__);
TRETURN S_OK;
}
/* Close the mail async to allow the save as. */
do_in_ui_thread_async (CLOSE, mail);
TRETURN S_OK;
}
diff --git a/src/ribbon-callbacks.h b/src/ribbon-callbacks.h
index 7c08b94..7fcc4c0 100644
--- a/src/ribbon-callbacks.h
+++ b/src/ribbon-callbacks.h
@@ -1,101 +1,104 @@
/* ribbon-callbacks.h - Callbacks for the ribbon extension interface
* Copyright (C) 2013 Intevation GmbH
* Software engineering by Intevation GmbH
*
* This file is part of GpgOL.
*
* GpgOL is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* GpgOL is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see .
*/
#ifndef RIBBON_CALLBACKS_H
#define RIBBON_CALLBACKS_H
#include "gpgoladdin.h"
/* For the Icon IDS */
#include "dialogs.h"
/* Id's of our callbacks */
#define ID_CMD_MIME_SIGN 13
#define ID_CMD_MIME_ENCRYPT 14
#define ID_GET_SIGN_PRESSED 15
#define ID_GET_ENCRYPT_PRESSED 16
#define ID_ON_LOAD 17
#define ID_CMD_OPEN_OPTIONS 18
#define ID_GET_IS_DETAILS_ENABLED 19
#define ID_CMD_MIME_SIGN_EX 21
#define ID_CMD_MIME_ENCRYPT_EX 22
#define ID_GET_SIGN_PRESSED_EX 23
#define ID_GET_ENCRYPT_PRESSED_EX 24
#define ID_GET_SIG_STIP 25
#define ID_GET_SIG_TTIP 26
#define ID_GET_SIG_LABEL 27
#define ID_LAUNCH_CERT_DETAILS 28
#define ID_BTN_SIGSTATE_LARGE 29
#define ID_GET_SIGN_ENCRYPT_PRESSED 30
#define ID_GET_SIGN_ENCRYPT_PRESSED_EX 31
#define ID_CMD_SIGN_ENCRYPT_MIME 32
#define ID_CMD_SIGN_ENCRYPT_MIME_EX 33
#define ID_GET_IS_CRYPTO_MAIL 35
#define ID_CMD_OPEN_CONTACT_KEY 36
#define ID_CMD_FILE_CLOSE 37
#define ID_CMD_DECRYPT_PERMANENTLY 38
#define ID_CMD_FILE_SAVE_AS 39
#define ID_CMD_FILE_SAVE_AS_IN_WINDOW 40
+#define ID_GET_IS_ADDR_BOOK_ENABLED 41
#define ID_BTN_DECRYPT IDI_DECRYPT_16_PNG
#define ID_BTN_DECRYPT_LARGE IDI_DECRYPT_48_PNG
#define ID_BTN_ENCRYPT IDI_ENCRYPT_16_PNG
#define ID_BTN_ENCRYPT_LARGE IDI_ENCRYPT_48_PNG
#define ID_BTN_ENCSIGN_LARGE IDI_ENCSIGN_FILE_48_PNG
#define ID_BTN_SIGN_LARGE IDI_SIGN_48_PNG
#define ID_BTN_VERIFY_LARGE IDI_VERIFY_48_PNG
#define OP_ENCRYPT 1 /* Encrypt the data */
#define OP_SIGN 2 /* Sign the data */
HRESULT getIcon (int id, VARIANT* result);
/* Get the toggle state of a crypt button. Flag value 1: encrypt, 2: sign */
HRESULT get_crypt_pressed (LPDISPATCH ctrl, int flags, VARIANT *result, bool is_explorer);
/* Mark the mail to be mime encrypted on send. Flags as above */
HRESULT mark_mime_action (LPDISPATCH ctrl, int flags, bool is_explorer);
/* Check the if the gpgol button should be enabled */
HRESULT get_is_details_enabled (LPDISPATCH ctrl, VARIANT *result);
+/* Check the if the gpgol address book button should be enabled */
+HRESULT get_is_addr_book_enabled (LPDISPATCH ctrl, VARIANT *result);
/* Get the label for the signature. Returns BSTR */
HRESULT get_sig_label (LPDISPATCH ctrl, VARIANT *result);
/* Get the tooltip for the signature. Returns BSTR */
HRESULT get_sig_ttip (LPDISPATCH ctrl, VARIANT *result);
/* Get the supertip for the signature. Returns BSTR */
HRESULT get_sig_stip (LPDISPATCH ctrl, VARIANT *result);
/* Show a certificate details dialog. Returns nothing. */
HRESULT launch_cert_details (LPDISPATCH ctrl);
/* Callback to get the sigstate icon. */
HRESULT get_crypto_icon (LPDISPATCH ctrl, VARIANT *result);
/* Callback to get our own control reference */
HRESULT ribbon_loaded (LPDISPATCH ctrl);
/* Is the currently selected mail a crypto mail ? */
HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result);
/* Open key configuration for a contact */
HRESULT open_contact_key (LPDISPATCH ctrl);
/* An explorer is closed by File->Close */
HRESULT override_file_close ();
/* Decrypt permanently */
HRESULT decrypt_permanently (LPDISPATCH ctrl);
/* SaveAs from the file menu */
HRESULT override_file_save_as (DISPPARAMS *parms);
/* Like above but for mails opened in their own window as they
behave differently. */
HRESULT override_file_save_as_in_window (DISPPARAMS *parms);
#endif