Add Address Book integration
authorAndre Heinecke <aheinecke@intevation.de>
Wed, 5 Sep 2018 14:24:54 +0000 (16:24 +0200)
committerAndre Heinecke <aheinecke@intevation.de>
Wed, 5 Sep 2018 14:24:54 +0000 (16:24 +0200)
* src/Makefile.am: Add new files.
* src/addressbook.cpp, src/addressbook.h: New. Code for
Address Book handling.
* src/gpgoladdin.cpp (GpgolRibbonExtender::GetIDsOfNames),
(GpgolRibbonExtender::Invoke),
(GetCustomUI_MIME): Add Button to configure PGP Key.
* src/mail.cpp (m_locate_in_progress): Make static
locate_in_progress a proper member.
(Mail::locateKeys_o): Trigger address book check.
(Mail::locateAllCryptoRecipients_o): Don't abort
if autoresolve is false.
* src/mail.h: Update accordingly.
* src/mailitem-events.cpp (PropertyChange): Trigger
locate even if autoresolve is off.
* src/ribbon-callbacks.cpp (open_contact_key): New.
* src/ribbon-callbacks.h: Add id and protoype.
* src/windowmessages.cpp (gpgol_window_proc): Handle
config key done.
* src/windowmessages.h (CONFIG_KEY_DONE): New.

--
We do the now usual dance with an external process and
windowmessage callback to configure a PGP Key in the
Address book.
This key or keys override any other key and will
be used regardless of validity or user ids.
This should allow a power user or administrator to centrally
manage keys in a shared address book and enable such
use cases as delegateing one mail address to
a different key.

GnuPG-Bug-Id: T4122

src/Makefile.am
src/addressbook.cpp [new file with mode: 0644]
src/addressbook.h [new file with mode: 0644]
src/gpgoladdin.cpp
src/mail.cpp
src/mail.h
src/mailitem-events.cpp
src/ribbon-callbacks.cpp
src/ribbon-callbacks.h
src/windowmessages.cpp
src/windowmessages.h

index 7906794..9abce23 100644 (file)
@@ -28,6 +28,7 @@ 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 \
     common.h common.cpp \
diff --git a/src/addressbook.cpp b/src/addressbook.cpp
new file mode 100644 (file)
index 0000000..a61c192
--- /dev/null
@@ -0,0 +1,300 @@
+/* addressbook.cpp - Functions for the Addressbook
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "addressbook.h"
+
+#include "oomhelp.h"
+#include "keycache.h"
+#include "mail.h"
+#include "cpphelp.h"
+#include "windowmessages.h"
+
+#include <gpgme++/context.h>
+#include <gpgme++/data.h>
+
+#include <set>
+
+typedef struct
+{
+  std::string name;
+  std::string data;
+  HWND hwnd;
+  shared_disp_t contact;
+} keyadder_args_t;
+
+static DWORD WINAPI
+open_keyadder (LPVOID arg)
+{
+  auto adder_args = std::unique_ptr<keyadder_args_t> ((keyadder_args_t*) arg);
+
+  std::vector<std::string> args;
+
+  // Collect the arguments
+  char *gpg4win_dir = get_gpg4win_dir ();
+  if (!gpg4win_dir)
+    {
+      TRACEPOINT;
+      return -1;
+    }
+  const auto keyadder = std::string (gpg4win_dir) + "\\bin\\gpgolkeyadder.exe";
+  args.push_back (keyadder);
+
+  args.push_back (std::string ("--hwnd"));
+  args.push_back (std::to_string ((int) (intptr_t) adder_args->hwnd));
+
+  args.push_back (std::string ("--username"));
+  args.push_back (adder_args->name);
+
+  auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine);
+  if (!ctx)
+    {
+      // can't happen
+      TRACEPOINT;
+      return -1;
+    }
+
+  GpgME::Data mystdin (adder_args->data.c_str(), adder_args->data.size(),
+                       false);
+  GpgME::Data mystdout, mystderr;
+
+  char **cargs = vector_to_cArray (args);
+  log_debug ("%s:%s: launching keyadder args:", SRCNAME, __func__);
+  for (size_t i = 0; cargs && cargs[i]; i++)
+    {
+      log_debug (SIZE_T_FORMAT ": '%s'", i, cargs[i]);
+    }
+
+  GpgME::Error err = ctx->spawn (cargs[0], const_cast <const char**> (cargs),
+                                 mystdin, mystdout, mystderr,
+                                 (GpgME::Context::SpawnFlags) (
+                                  GpgME::Context::SpawnAllowSetFg |
+                                  GpgME::Context::SpawnShowWindow));
+  release_cArray (cargs);
+  if (err)
+    {
+      log_error ("%s:%s: Err code: %i asString: %s",
+                 SRCNAME, __func__, err.code(), err.asString());
+      return 0;
+    }
+
+  auto newKey = mystdout.toString ();
+
+  rtrim(newKey);
+
+  if (newKey.empty())
+    {
+      log_debug ("%s:%s: keyadder canceled.", SRCNAME, __func__);
+      return 0;
+    }
+  if (newKey == "empty")
+    {
+      log_debug ("%s:%s: keyadder empty.", SRCNAME, __func__);
+      newKey = "";
+    }
+
+  Addressbook::callback_args_t cb_args;
+
+  /* cb args are valid in the same scope as newKey */
+  cb_args.data = newKey.c_str();
+  cb_args.contact = adder_args->contact;
+
+  do_in_ui_thread (CONFIG_KEY_DONE, (void*) &cb_args);
+  return 0;
+}
+
+void
+Addressbook::update_key_o (void *callback_args)
+{
+  if (!callback_args)
+    {
+      TRACEPOINT;
+      return;
+    }
+  callback_args_t *cb_args = static_cast<callback_args_t *> (callback_args);
+  LPDISPATCH contact = cb_args->contact.get();
+
+  LPDISPATCH user_props = get_oom_object (contact, "UserProperties");
+  if (!user_props)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key");
+  if (!pgp_key)
+    {
+      TRACEPOINT;
+      return;
+    }
+  put_oom_string (pgp_key, "Value", cb_args->data);
+
+  log_debug ("%s:%s: PGP key data updated",
+             SRCNAME, __func__);
+
+  gpgol_release (pgp_key);
+  return;
+}
+
+void
+Addressbook::edit_key_o (LPDISPATCH contact)
+{
+  if (!contact)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  LPDISPATCH user_props = get_oom_object (contact, "UserProperties");
+  if (!user_props)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  auto pgp_key = MAKE_SHARED (
+                      find_or_add_text_prop (user_props, "OpenPGP Key"));
+  gpgol_release (user_props);
+
+  if (!pgp_key)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  char *key_data = get_oom_string (pgp_key.get(), "Value");
+  if (!key_data)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  char *name = get_oom_string (contact, "Subject");
+  if (!name)
+    {
+      TRACEPOINT;
+      name = get_oom_string (contact, "Email1Address");
+      if (!name)
+        {
+          name = xstrdup (/* TRANSLATORS: Placeholder for a contact without
+                             a configured name */ _("Unknown contact"));
+        }
+    }
+
+  keyadder_args_t *args = new keyadder_args_t;
+  args->name = name;
+  args->data = key_data;
+  args->hwnd = get_active_hwnd ();
+  contact->AddRef ();
+  memdbg_addRef (contact);
+  args->contact = MAKE_SHARED (contact);
+
+  CloseHandle (CreateThread (NULL, 0, open_keyadder, (LPVOID) args, 0,
+                             NULL));
+  xfree (name);
+  xfree (key_data);
+
+  return;
+}
+
+static std::set <std::string> s_checked_entries;
+/* For each new recipient check the address book to look for a potentially
+   configured key for this recipient and import / register
+   it into the keycache.
+*/
+void
+Addressbook::check_o (Mail *mail)
+{
+  if (!mail)
+    {
+      TRACEPOINT;
+      return;
+    }
+  LPDISPATCH mailitem = mail->item ();
+  if (!mailitem)
+    {
+      TRACEPOINT;
+      return;
+    }
+  auto recipients_obj = MAKE_SHARED (get_oom_object (mailitem, "Recipients"));
+
+  if (!recipients_obj)
+    {
+      TRACEPOINT;
+      return;
+    }
+
+  bool err = false;
+  const auto recipient_entries = get_oom_recipients_with_addrEntry (recipients_obj.get(),
+                                                                    &err);
+  for (const auto pair: recipient_entries)
+    {
+      if (s_checked_entries.find (pair.first) != s_checked_entries.end ())
+        {
+          continue;
+        }
+      s_checked_entries.insert (pair.first);
+
+      if (!pair.second)
+        {
+          TRACEPOINT;
+          continue;
+        }
+
+      auto contact = MAKE_SHARED (get_oom_object (pair.second.get (), "GetContact"));
+      if (!contact)
+        {
+          log_debug ("%s:%s: failed to resolve contact for %s",
+                     SRCNAME, __func__,
+                     (opt.enable_debug & DBG_MIME_PARSER) ?
+                     pair.first.c_str() : "omitted");
+          continue;
+        }
+
+      LPDISPATCH user_props = get_oom_object (contact.get (), "UserProperties");
+      if (!user_props)
+        {
+          TRACEPOINT;
+          continue;
+        }
+
+      LPDISPATCH pgp_key = find_or_add_text_prop (user_props, "OpenPGP Key");
+      gpgol_release (user_props);
+
+      if (!pgp_key)
+        {
+          continue;
+        }
+
+      log_debug ("%s:%s: found configured pgp key for %s",
+                 SRCNAME, __func__,
+                 (opt.enable_debug & DBG_MIME_PARSER) ?
+                 pair.first.c_str() : "omitted");
+
+      char *key_data = get_oom_string (pgp_key, "Value");
+      if (!key_data || !strlen (key_data))
+        {
+          log_debug ("%s:%s: No key data",
+                     SRCNAME, __func__);
+        }
+      KeyCache::instance ()->importFromAddrBook (pair.first, key_data,
+                                                 mail);
+      xfree (key_data);
+      gpgol_release (pgp_key);
+    }
+}
diff --git a/src/addressbook.h b/src/addressbook.h
new file mode 100644 (file)
index 0000000..c175bb8
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef SRC_ADDRESSBOOK_H
+#define SRC_ADDRESSBOOK_H
+/* addressbook.h - Functions for the Addressbook
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include "common.h"
+#include <map>
+#include <string>
+#include <vector>
+#include "oomhelp.h"
+
+class Mail;
+
+namespace Addressbook
+{
+typedef struct
+{
+  shared_disp_t contact;
+  const char *data;
+} callback_args_t;
+
+/* Configure the OpenPGP Key for this contact. */
+void edit_key_o (LPDISPATCH contact);
+
+/* Check the address book for keys to import. */
+void check_o (Mail *mail);
+
+/* Update the key information for a contact. */
+void update_key_o (void *callback_args);
+} // namespace Addressbook
+
+#endif // SRC_ADDRESSBOOK_H
index e331b86..6ab5919 100644 (file)
@@ -714,6 +714,7 @@ GpgolRibbonExtender::GetIDsOfNames (REFIID riid, LPOLESTR *rgszNames,
       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)
@@ -803,6 +804,8 @@ GpgolRibbonExtender::Invoke (DISPID dispid, REFIID riid, LCID lcid,
         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:
@@ -1069,6 +1072,43 @@ GetCustomUI_MIME (BSTR RibbonID, BSTR * RibbonXml)
         optsSTip
         );
     }
+  else if (!wcscmp (RibbonID, L"Microsoft.Outlook.Contact"))
+    {
+      gpgrt_asprintf (&buffer,
+        "<customUI xmlns=\"http://schemas.microsoft.com/office/2009/07/customui\""
+        " onLoad=\"ribbonLoaded\">"
+        " <ribbon>"
+        "   <tabs>"
+        "    <tab idMso=\"TabContact\">"
+        "     <group id=\"gpgol_contact\""
+        "            label=\"%s\">"
+        "       <button id=\"idContactAddkey\""
+        "               getImage=\"btnSignEncryptLarge\""
+        "               size=\"large\""
+        "               label=\"%s\""
+        "               screentip=\"%s\""
+        "               supertip=\"%s\""
+        "               onAction=\"openContactKey\"/>"
+        "       <dialogBoxLauncher>"
+        "         <button id=\"optsBtn_contact\""
+        "                 onAction=\"openOptions\""
+        "                 screentip=\"%s\"/>"
+        "       </dialogBoxLauncher>"
+        "     </group>"
+        "    </tab>"
+        "   </tabs>"
+        " </ribbon>"
+        "</customUI>",
+        _("GpgOL"),
+        _("OpenPGP Key"),
+        _(/* 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."),
+        optsSTip
+        );
+    }
 
   if (buffer)
     {
index 06f58cd..7cd887a 100644 (file)
@@ -38,6 +38,7 @@
 #include "wks-helper.h"
 #include "keycache.h"
 #include "cpphelp.h"
+#include "addressbook.h"
 
 #include <gpgme++/configuration.h>
 #include <gpgme++/tofuinfo.h>
@@ -102,7 +103,8 @@ Mail::Mail (LPDISPATCH mailitem) :
     m_manual_crypto_opts(false),
     m_first_autosecure_check(true),
     m_locate_count(0),
-    m_is_about_to_be_moved(false)
+    m_is_about_to_be_moved(false),
+    m_locate_in_progress(false)
 {
   if (getMailForItem (mailitem))
     {
@@ -2772,9 +2774,7 @@ Mail::getSigFpr () const
 void
 Mail::locateKeys_o ()
 {
-  static bool locate_in_progress;
-
-  if (locate_in_progress)
+  if (m_locate_in_progress)
     {
       /** XXX
         The strangest thing seems to happen here:
@@ -2800,16 +2800,22 @@ Mail::locateKeys_o ()
                  SRCNAME, __func__, this);
       return;
     }
-  locate_in_progress = true;
+  m_locate_in_progress = true;
+
+  Addressbook::check_o (this);
+
+  if (opt.autoresolve)
+    {
+      // First update oom data to have recipients and sender updated.
+      updateOOMData_o ();
+      KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this);
+      KeyCache::instance()->startLocate (getSender_o ().c_str (), this);
+      KeyCache::instance()->startLocate (getCachedRecipients (), this);
+    }
 
-  // First update oom data to have recipients and sender updated.
-  updateOOMData_o ();
-  KeyCache::instance()->startLocateSecret (getSender_o ().c_str (), this);
-  KeyCache::instance()->startLocate (getSender_o ().c_str (), this);
-  KeyCache::instance()->startLocate (getCachedRecipients (), this);
   autosecureCheck ();
 
-  locate_in_progress = false;
+  m_locate_in_progress = false;
 }
 
 bool
@@ -3137,11 +3143,6 @@ Mail::clearLastMail ()
 void
 Mail::locateAllCryptoRecipients_o ()
 {
-  if (!opt.autoresolve)
-    {
-      return;
-    }
-
   gpgrt_lock_lock (&mail_map_lock);
   std::map<LPDISPATCH, Mail *>::iterator it;
   for (it = s_mail_map.begin(); it != s_mail_map.end(); ++it)
index 44c15da..c4f2369 100644 (file)
@@ -394,7 +394,9 @@ public:
   /** Get the recipients. */
   std::vector<std::string> getRecipients_o () const;
 
-  /** Try to locate the keys for all recipients */
+  /** Try to locate the keys for all recipients.
+      This also triggers the Addressbook integration, which we
+      treat as locate jobs. */
   void locateKeys_o ();
 
   /** State variable to check if a close was triggerd by us. */
@@ -646,5 +648,6 @@ private:
   bool m_first_autosecure_check; /* This is the first autoresolve check */
   int m_locate_count; /* The number of key locates pending for this mail. */
   bool m_is_about_to_be_moved;
+  bool m_locate_in_progress; /* Simplified state variable for locate */
 };
 #endif // MAIL_H
index 26cbbdd..2d91cc8 100644 (file)
@@ -228,10 +228,6 @@ EVENT_SINK_INVOKE(MailItemEvents)
           const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
           if (!m_mail->isCryptoMail ())
             {
-              if (!opt.autoresolve)
-                {
-                  break;
-                }
               if (m_mail->hasOverrideMimeData())
                 {
                   /* This is a mail created by us. Ignore propchanges. */
index e9f8a5e..dac67b5 100644 (file)
@@ -43,6 +43,7 @@
 #include "filetype.h"
 #include "mail.h"
 #include "dispcache.h"
+#include "addressbook.h"
 
 #include <gpgme++/context.h>
 #include <gpgme++/data.h>
@@ -795,3 +796,36 @@ HRESULT print_decrypted (LPDISPATCH ctrl)
   invoke_oom_method (mail->item(), "PrintOut", NULL);
   return S_OK;
 }
+
+HRESULT open_contact_key (LPDISPATCH ctrl)
+{
+  if (!ctrl)
+    {
+      log_error ("%s:%s:%i", SRCNAME, __func__, __LINE__);
+      return E_FAIL;
+    }
+  LPDISPATCH inspector = NULL;
+  HRESULT hr = getContext (ctrl, &inspector);
+
+  if (hr)
+    {
+      log_error ("%s:%s:%i : hresult %lx", SRCNAME, __func__, __LINE__,
+                 hr);
+      return S_OK;
+    }
+
+  /* Context is assumed to be the Insepector */
+  LPDISPATCH contact = get_oom_object (inspector, "CurrentItem");
+  gpgol_release (inspector);
+
+  if (!contact)
+    {
+      TRACEPOINT;
+      return S_OK;
+    }
+
+  Addressbook::edit_key_o (contact);
+
+  gpgol_release (contact);
+  return S_OK;
+}
index e4658a1..ce71c1a 100644 (file)
@@ -49,6 +49,7 @@
 #define ID_CMD_SIGN_ENCRYPT_MIME_EX 33
 #define ID_CMD_PRINT_DECRYPTED 34
 #define ID_GET_IS_CRYPTO_MAIL 35
+#define ID_CMD_OPEN_CONTACT_KEY 36
 
 #define ID_BTN_DECRYPT           IDI_DECRYPT_16_PNG
 #define ID_BTN_DECRYPT_LARGE     IDI_DECRYPT_48_PNG
@@ -85,4 +86,6 @@ HRESULT ribbon_loaded (LPDISPATCH ctrl);
 HRESULT get_is_crypto_mail (LPDISPATCH ctrl, VARIANT *result);
 /* Print out the decrypted mail */
 HRESULT print_decrypted (LPDISPATCH ctrl);
+/* Open key configuration for a contact */
+HRESULT open_contact_key (LPDISPATCH ctrl);
 #endif
index e9ec1e5..c99806a 100644 (file)
@@ -26,6 +26,7 @@
 #include "mail.h"
 #include "gpgoladdin.h"
 #include "wks-helper.h"
+#include "addressbook.h"
 
 #include <stdio.h>
 
@@ -228,6 +229,14 @@ gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
               mail->setDoAutosecure_m (false);
               break;
             }
+          case (CONFIG_KEY_DONE):
+            {
+              log_debug ("%s:%s: Key configuration done.",
+                         SRCNAME, __func__);
+
+              Addressbook::update_key_o (ctx->data);
+              break;
+            }
           default:
             log_debug ("%s:%s: Unknown msg %x",
                        SRCNAME, __func__, ctx->wmsg_type);
index 1730a25..599a90c 100644 (file)
@@ -55,6 +55,7 @@ typedef enum _gpgol_wmsg_type
   CLEAR_REPLY_FORWARD,
   DO_AUTO_SECURE,
   DONT_AUTO_SECURE,
+  CONFIG_KEY_DONE,
 } gpgol_wmsg_type;
 
 typedef struct