diff --git a/src/Makefile.am b/src/Makefile.am index 0993bd5..369ce6d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,120 +1,121 @@ # Makefile.am - makefile for GPGol # Copyright (C) 2005 g10 Code GmbH # Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik # Software engineering by Intevation GmbH # Copyright (C) 2018 Intevation GmbH # # This file is free software; as a special exception the author gives # unlimited permission to copy and/or distribute it, with or without # modifications, as long as this notice is preserved. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY, to the extent permitted by law; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. ## Process this file with automake to produce Makefile.in SUBDIRS = icons bin_PROGRAMS = gpgol EXTRA_DIST = \ versioninfo.rc.in mapi32.def Outlook.gpl \ dialogs.rc EXEEXT = .dll AM_CFLAGS = $(GPGME_CFLAGS) -shared AM_CXXFLAGS = $(GPGME_CFLAGS) $(LIBASSUAN_CFLAGS) -shared -std=c++11 AM_CXXFLAGS += $(GPGMEPP_CXXFLAGS) -D_FILE_OFFSET_BITS=64 gpgol_SOURCES = \ addin-options.cpp addin-options.h \ addressbook.cpp addressbook.h \ application-events.cpp \ attachment.h attachment.cpp \ + categorymanager.h categorymanager.cpp \ common.h common.cpp \ common_indep.h common_indep.c \ cpphelp.cpp cpphelp.h \ cryptcontroller.cpp cryptcontroller.h \ debug.h debug.cpp \ dialogs.h \ dispcache.h dispcache.cpp \ eventsink.h \ eventsinks.h \ explorer-events.cpp \ explorers-events.cpp \ filetype.c filetype.h \ folder-events.cpp \ gmime-table-private.h \ gpgoladdin.cpp gpgoladdin.h \ gpgol.def \ gpgol-ids.h \ keycache.cpp keycache.h \ mail.h mail.cpp \ mailitem-events.cpp \ main.c \ mapihelp.cpp mapihelp.h \ memdbg.cpp memdbg.h \ mimedataprovider.cpp mimedataprovider.h \ mimemaker.cpp mimemaker.h \ mlang-charset.cpp mlang-charset.h \ mymapi.h \ mymapitags.h \ olflange.cpp olflange.h \ oomhelp.cpp oomhelp.h \ overlay.cpp overlay.h \ parsecontroller.cpp parsecontroller.h \ parsetlv.h parsetlv.c \ resource.rc \ revert.cpp revert.h \ rfc2047parse.h rfc2047parse.c \ rfc822parse.c rfc822parse.h \ ribbon-callbacks.cpp ribbon-callbacks.h \ w32-gettext.cpp w32-gettext.h \ windowmessages.h windowmessages.cpp \ wks-helper.cpp wks-helper.h \ xmalloc.h #treeview_SOURCES = treeview.c # W32API 3.2 comes with an unusable libmapi32.a. We build our own # version. Note the omission of -k (--kill-at) from the DLLTOOL # command line. We also create our own virtual copies to the _static_ # versions of GPGME and gpg-error, because we want to link to them # statically, and not dynamically (otherwise Outlook would not find # them). gpgol_DEPENDENCIES = libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a if BUILD_W64 DLLTOOLFLAGS64=--as-flags=--64 -m i386:x86-64 endif libmapi32.a: mapi32.def $(DLLTOOL) $(DLLTOOLFLAGS64) --output-lib $@ --def $< libgpg-error.a: ln -s $$($(GPG_ERROR_CONFIG) --prefix)/lib/libgpg-error.a . libgpgme.a: ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgme.a . libassuan.a: ln -s $$($(LIBASSUAN_CONFIG) --prefix)/lib/libassuan.a . libgpgmepp.a: ln -s $$($(GPGME_CONFIG) --prefix)/lib/libgpgmepp.a . clean-local: rm -f libmapi32.a libgpg-error.a libgpgme.a libassuan.a libgpgmepp.a gpgol_LDFLAGS = -static-libgcc -static-libstdc++ gpgol_LDADD = $(srcdir)/gpgol.def \ -L . -lgpgmepp -lgpgme -lassuan -lgpg-error \ -lmapi32 -lshell32 -lgdi32 -lcomdlg32 \ -lole32 -loleaut32 -lws2_32 -ladvapi32 \ -luuid -lgdiplus -lrpcrt4 resource.o: resource.rc versioninfo.rc dialogs.rc dialogs.h .rc.o: $(WINDRES) -I $(srcdir) -I . -I .. `test -f '$<' || echo '$(srcdir)/'`$< $@ diff --git a/src/categorymanager.cpp b/src/categorymanager.cpp new file mode 100644 index 0000000..1716b52 --- /dev/null +++ b/src/categorymanager.cpp @@ -0,0 +1,250 @@ +/* @file categorymanager.cpp + * @brief Handles category management + * + * Copyright (C) 2018 Intevation GmbH + * + * This file is part of GpgOL. + * + * GpgOL is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * GpgOL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + */ + +#include "categorymanager.h" +#include "common.h" +#include "mail.h" +#include "gpgoladdin.h" +#include "oomhelp.h" + +#include + +class CategoryManager::Private +{ +public: + Private() + { + } + + void createCategory (shared_disp_t store, + const std::string &category, int color) + { + TSTART; + LPDISPATCH categories = get_oom_object (store.get(), "Categories"); + if (!categories) + { + STRANGEPOINT; + TRETURN; + } + if (create_category (categories, category.c_str (), color)) + { + log_debug ("%s:%s: Failed to create category %s", + SRCNAME, __func__, anonstr (category.c_str())); + TRETURN; + } + TRETURN; + } + + void registerCategory (const std::string &storeID, + const std::string &category) + { + TSTART; + auto storeIt = mCategoryStoreMap.find (storeID); + if (storeIt == mCategoryStoreMap.end()) + { + /* First category for this store. Create a new + category ref map. */ + std::unordered_map categoryMap; + categoryMap.insert (std::make_pair (category, 1)); + mCategoryStoreMap.insert (std::make_pair (storeID, categoryMap)); + log_debug ("%s:%s: Register category %s in new store %s ref now 1", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str())); + TRETURN; + } + auto categoryIt = storeIt->second.find (category); + if (categoryIt == storeIt->second.end ()) + { + storeIt->second.insert (std::make_pair (category, 1)); + log_debug ("%s:%s: Register category %s in store %s ref now 1", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str())); + } + else + { + categoryIt->second++; + log_debug ("%s:%s: Register category %s in store %s ref now %i", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str()), categoryIt->second); + } + TRETURN; + } + + void unregisterCategory (const std::string &storeID, + const std::string &category) + { + TSTART; + auto storeIt = mCategoryStoreMap.find (storeID); + if (storeIt == mCategoryStoreMap.end ()) + { + log_error ("%s:%s: Unregister called for unregistered store %s", + SRCNAME, __func__, anonstr (storeID.c_str())); + TRETURN; + } + auto categoryIt = storeIt->second.find (category); + if (categoryIt == storeIt->second.end ()) + { + log_debug ("%s:%s: Unregister %s not found for store %s", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str())); + TRETURN; + } + categoryIt->second--; + log_debug ("%s:%s: Unregister category %s in store %s ref now %i", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str()), categoryIt->second); + if (categoryIt->second < 0) + { + log_debug ("%s:%s: Unregister %s negative for store %s", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str())); + TRETURN; + } + if (categoryIt->second == 0) + { + log_debug ("%s:%s: Deleting %s for store %s", + SRCNAME, __func__, anonstr (category.c_str()), + anonstr (storeID.c_str())); + + LPDISPATCH store = get_store_for_id (storeID.c_str()); + if (!store) + { + STRANGEPOINT; + TRETURN; + } + delete_category (store, category.c_str ()); + storeIt->second.erase (categoryIt); + } + TRETURN; + } + + bool categoryExistsInMap (const std::string &storeID, + const std::string &category) + { + const auto it = mCategoryStoreMap.find (storeID); + if (it == mCategoryStoreMap.end ()) + { + return false; + } + return it->second.find (category) != it->second.end(); + } + +private: + /* Map from: store to map of category -> refs. */ + std::unordered_map > mCategoryStoreMap; +}; + +/* static */ +std::shared_ptr +CategoryManager::instance () +{ + return GpgolAddin::get_instance ()->get_category_mngr (); +} + +CategoryManager::CategoryManager(): + d(new Private) +{ +} + +std::string +CategoryManager::addCategoryToMail (Mail *mail, const std::string &category, int color) +{ + TSTART; + std::string ret; + if (!mail || category.empty()) + { + TRETURN ret; + } + + auto store = MAKE_SHARED (get_oom_object (mail->item (), "Parent.Store")); + if (!store) + { + log_error ("%s:%s Failed to obtain store", + SRCNAME, __func__); + TRETURN std::string (); + } + char *storeID = get_oom_string (store.get (), "StoreID"); + if (!storeID) + { + log_error ("%s:%s Failed to obtain storeID", + SRCNAME, __func__); + TRETURN std::string (); + } + ret = storeID; + xfree (storeID); + + if (!d->categoryExistsInMap (ret, category)) + { + d->createCategory (store, category, color); + } + d->registerCategory (ret, category); + + if (add_category (mail->item (), category.c_str())) + { + /* Probably the category already existed there + so it is not super worrysome. */ + log_debug ("%s:%s Failed to add category.", + SRCNAME, __func__); + } + return ret; +} + +void +CategoryManager::removeCategory (Mail *mail, const std::string &category) +{ + TSTART; + if (!mail || category.empty()) + { + STRANGEPOINT; + TRETURN; + } + if (remove_category (mail->item (), category.c_str (), true)) + { + log_debug ("%s:%s Failed to remvoe category.", + SRCNAME, __func__); + } + d->unregisterCategory (mail->storeID (), category.c_str ()); + + TRETURN; +} + +/* static */ +void +CategoryManager::removeAllGpgOLCategories () +{ + TSTART; + delete_all_categories_starting_with ("GpgOL: "); + TRETURN; +} + +/* static */ +const std::string & +CategoryManager::getEncMailCategory () +{ + static std::string decStr; + if (decStr.empty()) + { + decStr = std::string ("GpgOL: ") + + std::string (_("Encrypted Message")); + } + return decStr; +} diff --git a/src/categorymanager.h b/src/categorymanager.h new file mode 100644 index 0000000..0e96485 --- /dev/null +++ b/src/categorymanager.h @@ -0,0 +1,69 @@ +#ifndef CATEGORYMANAGER_H +#define CATEGORYMANAGER_H + +/* @file categorymanager.h + * @brief Handles category management + * + * Copyright (C) 2018 Intevation GmbH + * + * This file is part of GpgOL. + * + * GpgOL is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * GpgOL is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include +#include + +class Mail; +class GpgolAddin; + +/* The category manager is supposed to be only accessed from + the main thread and is not guarded by locks. */ +class CategoryManager +{ + friend class GpgolAddin; + +protected: + /** Internal ctor */ + explicit CategoryManager (); + +public: + /** Get the CategoryManager */ + static std::shared_ptr instance (); + + /** Add a category to a mail. + + @returns the storeID of the mail / category. + */ + std::string addCategoryToMail (Mail *mail, const std::string &category, + int color); + + /** Remove the category @category */ + void removeCategory (Mail * mail, + const std::string &category); + + /** Remove all GpgOL categories from all stores. */ + static void removeAllGpgOLCategories (); + + /** Get the name of the encryption category. */ + static const std::string & getEncMailCategory (); + +private: + class Private; + std::shared_ptr d; +}; + +#endif diff --git a/src/cpphelp.cpp b/src/cpphelp.cpp index 6ed5ad0..30daa9e 100644 --- a/src/cpphelp.cpp +++ b/src/cpphelp.cpp @@ -1,302 +1,316 @@ /* @file cpphelp.h * @brief Common cpp helper stuff * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include "cpphelp.h" #include #include #include #include #include "common.h" #include #include #include #include void release_cArray (char **carray) { if (carray) { for (int idx = 0; carray[idx]; idx++) { xfree (carray[idx]); } xfree (carray); } } void rtrim(std::string &s) { s.erase (std::find_if (s.rbegin(), s.rend(), [] (int ch) { return !std::isspace(ch); }).base(), s.end()); } void ltrim(std::string &s) { s.erase (s.begin(), std::find_if (s.begin(), s.end(), [] (int ch) { return !std::isspace(ch); })); } void trim(std::string &s) { ltrim (s); rtrim (s); } +void +join(const std::vector& v, const char *c, std::string& s) +{ + s.clear(); + for (auto p = v.begin(); p != v.end(); ++p) + { + s += *p; + if (p != v.end() - 1) + { + s += c; + } + } +} + char ** vector_to_cArray(const std::vector &vec) { char ** ret = (char**) xmalloc (sizeof (char*) * (vec.size() + 1)); for (size_t i = 0; i < vec.size(); i++) { ret[i] = xstrdup (vec[i].c_str()); } ret[vec.size()] = NULL; return ret; } std::vector cArray_to_vector(const char **cArray) { std::vector ret; if (!cArray) { return ret; } for (int i = 0; cArray[i]; i++) { ret.push_back (std::string (cArray[i])); } return ret; } 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; } checked = true; GpgME::Error err; const auto components = GpgME::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 ()); 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__); vs_mode = true; return vs_mode; } } vs_mode = false; return vs_mode; } } vs_mode = false; return false; } std::map get_registry_subkeys (const char *path) { HKEY theKey; std::map ret; std::string regPath = GPGOL_REGPATH; regPath += "\\"; regPath += path; if (RegOpenKeyEx (HKEY_CURRENT_USER, regPath.c_str (), 0, KEY_ENUMERATE_SUB_KEYS | KEY_READ, &theKey) != ERROR_SUCCESS) { TRACEPOINT; return ret; } DWORD values = 0, maxValueName = 0, maxValueLen = 0; DWORD err = RegQueryInfoKey (theKey, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &values, &maxValueName, &maxValueLen, nullptr, nullptr); if (err != ERROR_SUCCESS) { TRACEPOINT; RegCloseKey (theKey); return ret; } /* Add space for NULL */ maxValueName++; maxValueLen++; char name[maxValueName + 1]; char value[maxValueLen + 1]; for (int i = 0; i < values; i++) { DWORD nameLen = maxValueName; err = RegEnumValue (theKey, i, name, &nameLen, nullptr, nullptr, nullptr, nullptr); if (err != ERROR_SUCCESS) { TRACEPOINT; continue; } DWORD type; DWORD valueLen = maxValueLen; err = RegQueryValueEx (theKey, name, NULL, &type, (BYTE*)value, &valueLen); if (err != ERROR_SUCCESS) { TRACEPOINT; continue; } if (type != REG_SZ) { TRACEPOINT; continue; } ret.insert (std::make_pair (std::string (name, nameLen), std::string (value, valueLen))); } RegCloseKey (theKey); return ret; } template void internal_split (const std::string &s, char delim, Out result) { std::stringstream ss(s); std::string item; while (std::getline (ss, item, delim)) { *(result++) = item; } } std::vector gpgol_split (const std::string &s, char delim) { std::vector elems; internal_split (s, delim, std::back_inserter (elems)); return elems; } std::string string_to_hex(const std::string& input) { static const char* const lut = "0123456789ABCDEF"; size_t len = input.length(); std::string output; output.reserve (3 * len + (len * 3 / 26)); for (size_t i = 0; i < len; ++i) { const unsigned char c = input[i]; output.push_back (lut[c >> 4]); output.push_back (lut[c & 15]); output.push_back (' '); if (i % 26 == 0) { output.push_back ('\n'); } } return output; } bool is_binary (const std::string &input) { for (int i = 0; i < input.size() - 1; ++i) { const unsigned char c = input[i]; if (c < 32 && c != 0x0d && c != 0x0a) { return true; } } return false; } const char * to_cstr (const GpgME::Protocol &prot) { return prot == GpgME::CMS ? "S/MIME" : prot == GpgME::OpenPGP ? "OpenPGP" : "Unknown Protocol"; } diff --git a/src/cpphelp.h b/src/cpphelp.h index 0b905b1..cd56349 100644 --- a/src/cpphelp.h +++ b/src/cpphelp.h @@ -1,61 +1,64 @@ #ifndef CPPHELP_H #define CPPHELP_H /* @file cpphelp.h * @brief Common cpp helper stuff * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include #include #include #include /* Stuff that should be in common but is c++ so it does not fit in there. */ /* Release a null terminated char* array */ void release_cArray (char **carray); /* Trim whitespace from a string. */ void rtrim (std::string &s); void ltrim (std::string &s); void trim (std::string &s); +/* Join a string vector */ +void join(const std::vector& v, const char *c, std::string& s); + /* Convert a string vector to a null terminated char array */ char **vector_to_cArray (const std::vector &vec); std::vector cArray_to_vector (const char **cArray); /* Check if we are in de_vs mode. */ bool in_de_vs_mode (); /* Get a map of all subkey value pairs in a registry key */ std::map get_registry_subkeys (const char *path); std::vector gpgol_split (const std::string &s, char delim); /* Convert a string to a hex representation */ std::string string_to_hex (const std::string& input); /* Check if a string contains a char < 32 */ bool is_binary (const std::string &input); /* Return a string repr of the GpgME Protocol */ const char *to_cstr (const GpgME::Protocol &prot); #endif // CPPHELP_H diff --git a/src/gpgoladdin.cpp b/src/gpgoladdin.cpp index acc1969..aca9943 100644 --- a/src/gpgoladdin.cpp +++ b/src/gpgoladdin.cpp @@ -1,1253 +1,1265 @@ /* 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 #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; } static 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__); } - /* Set up categories */ - const char *decCategory = _("GpgOL: Encrypted Message"); - const char *verifyCategory = _("GpgOL: Trusted Sender Address"); - ensure_category_exists (m_application, decCategory, 8); - ensure_category_exists (m_application, verifyCategory, 5); + /* 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)); 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"getIsCrypto", ID_GET_IS_CRYPTO_MAIL) ID_MAPPER (L"printDecrypted", ID_CMD_PRINT_DECRYPTED) ID_MAPPER (L"openContactKey", ID_CMD_OPEN_CONTACT_KEY) } 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_debug ("%s:%s: enter with dispid: %x", SRCNAME, __func__, (int)dispid); 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_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_PRINT_DECRYPTED: return print_decrypted (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_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, "" " " " " " " " " " " " " " " " " " " " " " " "