Add GPGME based async decryption
authorAndre Heinecke <aheinecke@intevation.de>
Mon, 22 Aug 2016 13:40:37 +0000 (15:40 +0200)
committerAndre Heinecke <aheinecke@intevation.de>
Mon, 22 Aug 2016 13:40:37 +0000 (15:40 +0200)
* src/Makefile.am (gpgol_SOURCES): Add Mimedataprovider.
* src/mail.cpp (Mail::m_moss_position): Store moss pos.
(Mail::is_mail_valid): Helper to check for orphaned mail pointer.
(Mail::pre_process_message): Store moss position.
(get_cipherstream): Use stored moss position.
(use_body): Removed. Always use MOSS.
(do_parsing): Helper to run in different thread.
(Mail::decrypt_verify): Insert placehoder, create parser,
start parser thread.
(Mail::parsing_done): Helper to insert plaintext in UI Thread.
(Mail::revert_all_mails): Fix typo.
(Mail::wipe_all_mails): Init err.
(Mail::close_inspector): Helper to close current inspector
with discard changes.
* src/mail.h: Update accordingly.
* src/mailparser.cpp (MailParser::MailParser): Use MimeDataProvider.
(operation_for_type): Helper to figure out type of work and protocol.
(MailParser::parse): Enable a fist version of decrypt.
* src/mailparser.h: Update accordingly.
* src/main.c (DllMain): Set w32-inst-dir for GpgME.
* src/mimedataprovider.cpp: New. Copy stream into GpgME Data.
* src/mimedataprovider.h: New.
* src/util.h (log_mime_parser): New helper for logging.
* src/windowmessages.cpp (gpgol_window_proc): Handle parsing
done signal.
* src/windowmessages.h (PARSING_DONE): New.
* src/attachment.cpp: Implement DataProvider interface.
* src/attachment.h: Update accordingly.

--
The idea is that DataProvider classes will take and recieve
Data from GpgOL, copy it into internal buffers (to avoid working
with the OOM from a background thread) and then provide the
data as needed to GpgOL. On Read they shall parse the MIME e.g.
into a signature part, and on Write they shall deliver useful
objects (e.g. attachments) for Outlook to work with.

The Close / Wipe changes are a bit unrelated as they were created
for experiments to avoid "save your changes" message boxes.

13 files changed:
src/Makefile.am
src/attachment.cpp
src/attachment.h
src/mail.cpp
src/mail.h
src/mailparser.cpp
src/mailparser.h
src/main.c
src/mimedataprovider.cpp [new file with mode: 0644]
src/mimedataprovider.h [new file with mode: 0644]
src/util.h
src/windowmessages.cpp
src/windowmessages.h

index 65e4f7b..fd91691 100644 (file)
@@ -88,7 +88,8 @@ gpgol_SOURCES = \
        gmime-table-private.h \
        exechelp.c exechelp.h \
        addin-options.cpp addin-options.h \
-       mailparser.cpp mailparser.h
+       mailparser.cpp mailparser.h \
+       mimedataprovider.cpp mimedataprovider.h
 
 
 #treeview_SOURCES = treeview.c
index a9ad736..b052d00 100644 (file)
@@ -25,7 +25,9 @@
 #include "mapihelp.h"
 #include "gpgolstr.h"
 
+#include <climits>
 #include <gpg-error.h>
+#include <gpgme++/error.h>
 
 Attachment::Attachment()
 {
@@ -42,6 +44,15 @@ Attachment::Attachment()
     }
 }
 
+Attachment::Attachment(LPSTREAM stream)
+{
+  if (stream)
+    {
+      stream->AddRef ();
+    }
+  m_stream = stream;
+}
+
 Attachment::~Attachment()
 {
   log_debug ("%s:%s", SRCNAME, __func__);
@@ -60,12 +71,6 @@ Attachment::get_display_name() const
   return m_utf8DisplayName;
 }
 
-std::string
-Attachment::get_tmp_file_name() const
-{
-  return m_utf8FileName;
-}
-
 void
 Attachment::set_display_name(const char *name)
 {
@@ -78,26 +83,129 @@ Attachment::set_attach_type(attachtype_t type)
   m_type = type;
 }
 
-void
-Attachment::set_hidden(bool value)
+bool
+Attachment::isSupported(GpgME::DataProvider::Operation op) const
 {
-  m_hidden = value;
+  return op == GpgME::DataProvider::Read ||
+         op == GpgME::DataProvider::Write ||
+         op == GpgME::DataProvider::Seek ||
+         op == GpgME::DataProvider::Release;
+}
+
+ssize_t
+Attachment::read(void *buffer, size_t bufSize)
+{
+  if (!bufSize)
+    {
+      return 0;
+    }
+  if (!buffer || bufSize >= ULONG_MAX)
+    {
+      log_error ("%s:%s: Read invalid",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EINVAL);
+      return -1;
+    }
+  if (!m_stream)
+    {
+      log_error ("%s:%s: Read on null stream.",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      return -1;
+    }
+
+  ULONG cb = static_cast<size_t> (bufSize);
+  ULONG bRead = 0;
+  HRESULT hr = m_stream->Read (buffer, cb, &bRead);
+  if (hr != S_OK && hr != S_FALSE)
+    {
+      log_error ("%s:%s: Read failed",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      return -1;
+    }
+  return static_cast<size_t>(bRead);
 }
 
-int
-Attachment::write(const char *data, size_t size)
+ssize_t
+Attachment::write(const void *data, size_t size)
 {
-  if (!data || !size)
+  if (!size)
     {
       return 0;
     }
-  if (!m_stream && m_stream->Write (data, size, NULL) != S_OK)
+  if (!data || size >= ULONG_MAX)
+    {
+      GpgME::Error::setSystemError (GPG_ERR_EINVAL);
+      return -1;
+    }
+  if (!m_stream)
     {
-      return 1;
+      log_error ("%s:%s: Write on NULL stream. ",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      return -1;
+    }
+  ULONG written = 0;
+  if (m_stream->Write (data, static_cast<ULONG>(size), &written) != S_OK)
+    {
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      log_error ("%s:%s: Write failed.",
+                 SRCNAME, __func__);
+      return -1;
     }
   if (m_stream->Commit (0) != S_OK)
     {
-      return 1;
+      log_error ("%s:%s: Commit failed. ",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      return -1;
+    }
+  return static_cast<ssize_t> (written);
+}
+
+off_t Attachment::seek(off_t offset, int whence)
+{
+  DWORD dwOrigin;
+  switch (whence)
+    {
+      case SEEK_SET:
+          dwOrigin = STREAM_SEEK_SET;
+          break;
+      case SEEK_CUR:
+          dwOrigin = STREAM_SEEK_CUR;
+          break;
+      case SEEK_END:
+          dwOrigin = STREAM_SEEK_END;
+          break;
+      default:
+          GpgME::Error::setSystemError (GPG_ERR_EINVAL);
+          return (off_t) - 1;
+   }
+  if (!m_stream)
+    {
+      log_error ("%s:%s: Seek on null stream.",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EIO);
+      return (off_t) - 1;
+    }
+  LARGE_INTEGER move = {0, 0};
+  move.QuadPart = offset;
+  ULARGE_INTEGER result;
+  HRESULT hr = m_stream->Seek (move, dwOrigin, &result);
+
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: Seek failed. ",
+                 SRCNAME, __func__);
+      GpgME::Error::setSystemError (GPG_ERR_EINVAL);
+      return (off_t) - 1;
     }
-  return 0;
+  return result.QuadPart;
+}
+
+void Attachment::release()
+{
+  /* No op. */
+  log_debug ("%s:%s", SRCNAME, __func__);
 }
index 68821ff..ae1fb49 100644 (file)
@@ -1,6 +1,6 @@
-/* attachment.h - Functions for attachment handling
+/* attachment.h - Wrapper class for attachments
  *    Copyright (C) 2005, 2007 g10 Code GmbH
- *    Copyright (C) 2015 Intevation GmbH
+ *    Copyright (C) 2015, 2016 Intevation GmbH
  *
  * This file is part of GpgOL.
  *
 #include "mapihelp.h"
 #include <string>
 
+#include <gpgme++/interfaces/dataprovider.h>
+
 /** Helper class for attachment actions. */
-class Attachment
+class Attachment : public GpgME::DataProvider
 {
 public:
   /** Creates and opens a new temporary stream. */
   Attachment();
 
+  /** Creates the attachment wrapper for an existing stream. */
+  Attachment(LPSTREAM stream);
+
   /** Deletes the attachment and the underlying temporary file. */
   ~Attachment();
 
   /** Get an assoicated ISteam ptr or NULL. */
   LPSTREAM get_stream();
 
-  /** Writes data to the attachment stream.
-   * Calling this method automatically commits the stream.
-   *
-   * Returns 0 on success. */
-  int write(const char *data, size_t size);
-
   /** Set the display name */
   void set_display_name(const char *name);
   std::string get_display_name() const;
 
-  std::string get_tmp_file_name() const;
-
   void set_attach_type(attachtype_t type);
 
-  void set_hidden(bool value);
+  /* Dataprovider interface */
+  bool isSupported(Operation) const;
+  ssize_t read(void *buffer, size_t bufSize);
+  ssize_t write(const void *buffer, size_t bufSize);
+  off_t seek(off_t offset, int whence);
+  void release();
+
 private:
   LPSTREAM m_stream;
-  std::string m_utf8FileName;
   std::string m_utf8DisplayName;
   attachtype_t m_type;
-  bool m_hidden;
 };
 
 #endif // ATTACHMENT_H
index 4c16852..6043ed1 100644 (file)
@@ -31,6 +31,7 @@
 #include "mymapitags.h"
 #include "mailparser.h"
 #include "gpgolstr.h"
+#include "windowmessages.h"
 
 #include <map>
 #include <vector>
@@ -87,6 +88,7 @@ Mail::Mail (LPDISPATCH mailitem) :
     m_crypt_successful(false),
     m_is_smime(false),
     m_is_smime_checked(false),
+    m_moss_position(0),
     m_sender(NULL),
     m_type(MSGTYPE_UNKNOWN)
 {
@@ -142,6 +144,19 @@ Mail::get_mail_for_item (LPDISPATCH mailitem)
   return it->second;
 }
 
+bool
+Mail::is_mail_valid (const Mail *mail)
+{
+  auto it = g_mail_map.begin();
+  while (it != g_mail_map.end())
+    {
+      if (it->second == mail)
+        return true;
+      ++it;
+    }
+  return false;
+}
+
 int
 Mail::pre_process_message ()
 {
@@ -168,7 +183,8 @@ Mail::pre_process_message ()
 
   /* Create moss attachments here so that they are properly
      hidden when the item is read into the model. */
-  if (mapi_mark_or_create_moss_attach (message, m_type))
+  m_moss_position = mapi_mark_or_create_moss_attach (message, m_type);
+  if (!m_moss_position)
     {
       log_error ("%s:%s: Failed to find moss attachment.",
                  SRCNAME, __func__);
@@ -181,7 +197,7 @@ Mail::pre_process_message ()
 
 /** Get the cipherstream of the mailitem. */
 static LPSTREAM
-get_cipherstream (LPDISPATCH mailitem)
+get_cipherstream (LPDISPATCH mailitem, int pos)
 {
   LPDISPATCH attachments = get_oom_object (mailitem, "Attachments");
   LPDISPATCH attachment = NULL;
@@ -203,13 +219,9 @@ get_cipherstream (LPDISPATCH mailitem)
       gpgol_release (attachments);
       return NULL;
     }
-  if (count > 1)
-    {
-      log_debug ("%s:%s: More then one attachment count: %i. Continuing anway.",
-                 SRCNAME, __func__, count);
-    }
   /* We assume the crypto attachment is the second item. */
-  attachment = get_oom_object (attachments, "Item(2)");
+  const auto item_str = std::string("Item(") + std::to_string(pos) + ")";
+  attachment = get_oom_object (attachments, item_str.c_str());
   gpgol_release (attachments);
   attachments = NULL;
 
@@ -231,21 +243,6 @@ get_cipherstream (LPDISPATCH mailitem)
   return stream;
 }
 
-/** Helper to check if the body should be used for decrypt verify
-  or if the mime Attachment should be used. */
-static bool
-use_body(msgtype_t type)
-{
-  switch (type)
-    {
-      case MSGTYPE_GPGOL_PGP_MESSAGE:
-      case MSGTYPE_GPGOL_CLEAR_SIGNED:
-        return true;
-      default:
-        return false;
-    }
-}
-
 /** Helper to update the attachments of a mail object in oom.
   does not modify the underlying mapi structure. */
 static bool
@@ -270,6 +267,19 @@ add_attachments(LPDISPATCH mail,
   return false;
 }
 
+static DWORD WINAPI
+do_parsing (LPVOID arg)
+{
+  log_debug ("%s:%s: starting parsing for: %p",
+             SRCNAME, __func__, arg);
+
+  Mail *mail = (Mail *)arg;
+  auto parser = mail->parser();
+  parser->parse();
+  do_in_ui_thread (PARSING_DONE, arg);
+  return 0;
+}
+
 int
 Mail::decrypt_verify()
 {
@@ -282,75 +292,75 @@ Mail::decrypt_verify()
     {
       log_error ("%s:%s: Decrypt verify called for msg that needs wipe: %p",
                  SRCNAME, __func__, m_mailitem);
-      return 0;
+      return 1;
     }
 
   m_processed = true;
-  /* Do the actual parsing */
-  std::unique_ptr<MailParser> parser;
-  if (!use_body (m_type))
-    {
-      auto cipherstream = get_cipherstream (m_mailitem);
-
-      if (!cipherstream)
-        {
-          /* TODO Error message? */
-          log_debug ("%s:%s: Failed to get cipherstream.",
-                     SRCNAME, __func__);
-          return 1;
-        }
-
-      parser = std::unique_ptr<MailParser>(new MailParser (cipherstream, m_type));
-      gpgol_release (cipherstream);
-    }
-  else
+  /* Inser placeholder */
+  if (put_oom_string (m_mailitem, "HTMLBody", WAIT_TEMPLATE))
     {
-      parser = std::unique_ptr<MailParser>();
+      log_error ("%s:%s: Failed to modify html body of item.",
+                 SRCNAME, __func__);
+      return 1;
     }
 
-  const std::string err = parser->parse();
-  if (!err.empty())
+  /* Do the actual parsing */
+  auto cipherstream = get_cipherstream (m_mailitem, m_moss_position);
+
+  if (!cipherstream)
     {
-      /* TODO Show error message. */
-      log_error ("%s:%s: Failed to parse message: %s",
-                 SRCNAME, __func__, err.c_str());
+      /* TODO Error message? */
+      log_debug ("%s:%s: Failed to get cipherstream.",
+                 SRCNAME, __func__);
       return 1;
     }
 
+  m_parser = new MailParser (cipherstream, m_type);
+  gpgol_release (cipherstream);
+
+  CreateThread (NULL, 0, do_parsing, (LPVOID) this, 0,
+                NULL);
+  return 0;
+}
+
+void Mail::parsing_done()
+{
   m_needs_wipe = true;
   /* Update the body */
-  const auto html = parser->get_utf8_html_body();
+  const auto html = m_parser->get_utf8_html_body();
   if (!html->empty())
     {
       if (put_oom_string (m_mailitem, "HTMLBody", html->c_str()))
         {
           log_error ("%s:%s: Failed to modify html body of item.",
                      SRCNAME, __func__);
-          return 1;
+          return;
         }
     }
   else
     {
-      const auto body = parser->get_utf8_text_body();
+      const auto body = m_parser->get_utf8_text_body();
       if (put_oom_string (m_mailitem, "Body", body->c_str()))
         {
           log_error ("%s:%s: Failed to modify body of item.",
                      SRCNAME, __func__);
-          return 1;
+          return;
         }
     }
 
   /* Update attachments */
-  if (add_attachments (m_mailitem, parser->get_attachments()))
+  if (add_attachments (m_mailitem, m_parser->get_attachments()))
     {
       log_error ("%s:%s: Failed to update attachments.",
                  SRCNAME, __func__);
-      return 1;
+      return;
     }
 
   /* Invalidate UI to set the correct sig status. */
+  delete m_parser;
+  m_parser = nullptr;
   gpgoladdin_invalidate_ui ();
-  return 0;
+  return;
 }
 
 int
@@ -493,7 +503,7 @@ Mail::revert_all_mails ()
     {
       if (it->second->revert ())
         {
-          log_error ("Failed to wipe mail: %p ", it->first);
+          log_error ("Failed to revert mail: %p ", it->first);
           err++;
         }
     }
@@ -519,14 +529,13 @@ Mail::wipe_all_mails ()
 int
 Mail::revert ()
 {
-  int err;
+  int err = 0;
   if (!m_processed)
     {
       return 0;
     }
 
   err = gpgol_mailitem_revert (m_mailitem);
-
   if (err == -1)
     {
       log_error ("%s:%s: Message revert failed falling back to wipe.",
@@ -590,3 +599,41 @@ Mail::get_subject()
 {
   return std::string(get_oom_string (m_mailitem, "Subject"));
 }
+
+int
+Mail::close_inspector ()
+{
+  LPDISPATCH inspector = get_oom_object (m_mailitem, "GetInspector");
+  HRESULT hr;
+  DISPID dispid;
+  if (!inspector)
+    {
+      log_debug ("%s:%s: No inspector.",
+                 SRCNAME, __func__);
+      return -1;
+    }
+
+  dispid = lookup_oom_dispid (inspector, "Close");
+  if (dispid != DISPID_UNKNOWN)
+    {
+      VARIANT aVariant[1];
+      DISPPARAMS dispparams;
+
+      dispparams.rgvarg = aVariant;
+      dispparams.rgvarg[0].vt = VT_INT;
+      dispparams.rgvarg[0].intVal = 1;
+      dispparams.cArgs = 1;
+      dispparams.cNamedArgs = 0;
+
+      hr = inspector->Invoke (dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
+                              DISPATCH_METHOD, &dispparams,
+                              NULL, NULL, NULL);
+      if (hr != S_OK)
+        {
+          log_debug ("%s:%s: Failed to close inspector: %#lx",
+                     SRCNAME, __func__, hr);
+          return -1;
+        }
+    }
+  return 0;
+}
index 1bbc789..0dc96d4 100644 (file)
@@ -25,6 +25,9 @@
 #include "mapihelp.h"
 
 #include <string>
+#include <future>
+
+class MailParser;
 
 /** @brief Data wrapper around a mailitem.
  *
@@ -57,6 +60,13 @@ public:
   */
   static Mail* get_mail_for_item (LPDISPATCH mailitem);
 
+  /** @brief looks for existing Mail objects.
+
+    @returns A reference to an existing mailitem or NULL in case none
+    could be found. Can be used to check if a mail object was destroyed.
+  */
+  static bool is_mail_valid (const Mail *mail);
+
   /** @brief wipe the plaintext from all known Mail objects.
     *
     * This is intended as a "cleanup" call to be done on unload
@@ -168,6 +178,22 @@ public:
     */
   bool is_smime ();
 
+  /** @brief closes the inspector for this mail
+    *
+    * @returns true on success.
+  */
+  int close_inspector ();
+
+  /** @brief get the associated parser.
+    only valid while the actual parsing happens. */
+  MailParser *parser () { return m_parser; }
+
+  /** To be called from outside once the paser was done.
+   In Qt this would be a slot that is called once it is finished
+   we hack around that a bit by calling it from our windowmessages
+   handler.
+  */
+  void parsing_done ();
 private:
   LPDISPATCH m_mailitem;
   LPDISPATCH m_event_sink;
@@ -177,7 +203,9 @@ private:
        m_crypt_successful, /* We successfuly performed crypto on the item. */
        m_is_smime, /* This is an smime mail. */
        m_is_smime_checked; /* it was checked if this is an smime mail */
+  int m_moss_position; /* The number of the original message attachment. */
   char *m_sender;
   msgtype_t m_type; /* Our messagetype as set in mapi */
+  MailParser *m_parser;
 };
 #endif // MAIL_H
index 500a383..c73de1f 100644 (file)
 
 #include "mailparser.h"
 #include "attachment.h"
+#include "mimedataprovider.h"
+
+#include <gpgme++/context.h>
+#include <gpgme++/decryptionresult.h>
+
+#include <sstream>
+
+using namespace GpgME;
 
 MailParser::MailParser(LPSTREAM instream, msgtype_t type):
     m_body (std::shared_ptr<std::string>(new std::string())),
     m_htmlbody (std::shared_ptr<std::string>(new std::string())),
-    m_stream (instream),
+    m_input (Data(new MimeDataProvider(instream))),
     m_type (type),
-    m_error (false),
-    m_in_data (false),
-    signed_data (nullptr),
-    sig_data (nullptr)
+    m_error (false)
 {
-  log_debug ("%s:%s: Creating parser for stream: %p",
-             SRCNAME, __func__, instream);
-  instream->AddRef();
+  log_mime_parser ("%s:%s: Creating parser for stream: %p",
+                   SRCNAME, __func__, instream);
 }
 
 MailParser::~MailParser()
 {
-  gpgol_release(m_stream);
+  log_debug ("%s:%s", SRCNAME, __func__);
+}
+
+static void
+operation_for_type(msgtype_t type, bool *decrypt,
+                   bool *verify, Protocol *protocol)
+{
+  *decrypt = false;
+  *verify = false;
+  *protocol = Protocol::UnknownProtocol;
+  switch (type)
+    {
+      case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
+      case MSGTYPE_GPGOL_PGP_MESSAGE:
+        *decrypt = true;
+        *protocol = Protocol::OpenPGP;
+        break;
+      case MSGTYPE_GPGOL_MULTIPART_SIGNED:
+      case MSGTYPE_GPGOL_CLEAR_SIGNED:
+        *verify = true;
+        *protocol = Protocol::OpenPGP;
+        break;
+      case MSGTYPE_GPGOL_OPAQUE_SIGNED:
+        *protocol = Protocol::CMS;
+        *verify = true;
+        break;
+      case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
+        *protocol = Protocol::CMS;
+        *decrypt = true;
+        break;
+      default:
+        log_error ("%s:%s: Unknown data type: %i",
+                   SRCNAME, __func__, type);
+    }
 }
 
 std::string
 MailParser::parse()
 {
-  m_body = std::shared_ptr<std::string>(new std::string("Hello world"));
+  // Wrap the input stream in an attachment / GpgME Data
+  Protocol protocol;
+  bool decrypt, verify;
+  operation_for_type (m_type, &decrypt, &verify, &protocol);
+  auto ctx = Context::createForProtocol (protocol);
+  ctx->setArmor(true);
+
+  if (decrypt)
+    {
+      Data output;
+      log_debug ("%s:%s: Decrypting with protocol: %s",
+                 SRCNAME, __func__,
+                 protocol == OpenPGP ? "OpenPGP" :
+                 protocol == CMS ? "CMS" : "Unknown");
+      auto combined_result = ctx->decryptAndVerify(m_input, output);
+      m_decrypt_result = combined_result.first;
+      m_verify_result = combined_result.second;
+      if (m_decrypt_result.error())
+        {
+          MessageBox (NULL, "Decryption failed.", "Failed", MB_OK);
+        }
+      char buf[2048];
+      size_t bRead;
+      output.seek (0, SEEK_SET);
+      while ((bRead = output.read (buf, 2048)) > 0)
+        {
+          (*m_body).append(buf, bRead);
+        }
+      log_debug ("Body is: %s", m_body->c_str());
+    }
+
+  if (opt.enable_debug)
+    {
+       std::stringstream ss;
+       ss << m_decrypt_result << '\n' << m_verify_result;
+       log_debug ("Decrypt / Verify result: %s", ss.str().c_str());
+    }
   Attachment *att = new Attachment ();
   att->write ("Hello attachment", strlen ("Hello attachment"));
   att->set_display_name ("The Attachment.txt");
@@ -55,17 +128,20 @@ MailParser::parse()
   return std::string();
 }
 
-std::shared_ptr<std::string> MailParser::get_utf8_html_body()
+std::shared_ptr<std::string>
+MailParser::get_utf8_html_body()
 {
   return m_htmlbody;
 }
 
-std::shared_ptr<std::string> MailParser::get_utf8_text_body()
+std::shared_ptr<std::string>
+MailParser::get_utf8_text_body()
 {
   return m_body;
 }
 
-std::vector<std::shared_ptr<Attachment> > MailParser::get_attachments()
+std::vector<std::shared_ptr<Attachment> >
+MailParser::get_attachments()
 {
   return m_attachments;
 }
index 1e03139..f92c313 100644 (file)
 
 #include "oomhelp.h"
 #include "mapihelp.h"
-#include "gpgme.h"
 
 #include <string>
 #include <vector>
 #include <memory>
 
+#include <gpgme++/decryptionresult.h>
+#include <gpgme++/verificationresult.h>
+#include <gpgme++/data.h>
+
 class Attachment;
 
 class MailParser
@@ -65,17 +68,11 @@ private:
   std::shared_ptr<std::string> m_htmlbody;
 
   /* State variables */
-  LPSTREAM m_stream;
+  GpgME::Data m_input;
   msgtype_t m_type;
   bool m_error;
-  bool m_in_data;
-  gpgme_data_t signed_data;/* NULL or the data object used to collect
-                              the signed data. It would be better to
-                              just hash it but there is no support in
-                              gpgme for this yet. */
-  gpgme_data_t sig_data;  /* NULL or data object to collect the
-                             signature attachment which should be a
-                             signature then.  */
+  GpgME::DecryptionResult m_decrypt_result;
+  GpgME::VerificationResult m_verify_result;
 };
 
 #endif /* MAILPARSER_H */
index 12321f3..fdff49c 100644 (file)
@@ -162,6 +162,13 @@ DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved)
 
       gpg_err_init ();
 
+      /* Set the installation directory for GpgME so that
+         it can find tools like gpgme-w32-spawn correctly. */
+      char *instdir;
+      gpgrt_asprintf (&instdir, "%s\\bin", get_gpg4win_dir ());
+      gpgme_set_global_flag ("w32-inst-dir", instdir);
+      xfree (instdir);
+
       /* The next call initializes subsystems of gpgme and should be
          done as early as possible.  The actual return value (the
          version string) is not used here.  It may be called at any
diff --git a/src/mimedataprovider.cpp b/src/mimedataprovider.cpp
new file mode 100644 (file)
index 0000000..d6628f3
--- /dev/null
@@ -0,0 +1,213 @@
+/* mimedataprover.cpp - GpgME dataprovider for mime data
+ *    Copyright (C) 2016 Intevation GmbH
+ *
+ * This file is part of GpgOL.
+ *
+ * GpgOL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GpgOL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "config.h"
+#include "common.h"
+
+#include "mimedataprovider.h"
+
+/* The maximum length of a line we are able to process.  RFC822 allows
+   only for 1000 bytes; thus 2000 seems to be a reasonable value. */
+#define LINEBUFSIZE 2000
+
+/* How much data is read from the underlying stream in a collect
+   call. */
+#define BUFSIZE 8192
+
+#include <gpgme++/error.h>
+
+static int
+message_cb (void *opaque, rfc822parse_event_t event,
+            rfc822parse_t msg)
+{
+  (void) opaque;
+  (void) event;
+  (void) msg;
+  return 0;
+}
+
+MimeDataProvider::MimeDataProvider(LPSTREAM stream):
+  m_collect(true),
+  m_parser(rfc822parse_open (message_cb, this)),
+  m_current_encoding(None)
+{
+  if (stream)
+    {
+      stream->AddRef ();
+    }
+  else
+    {
+      log_error ("%s:%s called without stream ", SRCNAME, __func__);
+      return;
+    }
+  b64_init (&m_base64_context);
+  log_mime_parser ("%s:%s Collecting data.", SRCNAME, __func__);
+  collect_data (stream);
+  log_mime_parser ("%s:%s Data collected.", SRCNAME, __func__);
+  gpgol_release (stream);
+}
+
+MimeDataProvider::~MimeDataProvider()
+{
+  log_debug ("%s:%s", SRCNAME, __func__);
+}
+
+bool
+MimeDataProvider::isSupported(GpgME::DataProvider::Operation op) const
+{
+  return op == GpgME::DataProvider::Read ||
+         op == GpgME::DataProvider::Seek ||
+         op == GpgME::DataProvider::Release;
+}
+
+ssize_t
+MimeDataProvider::read(void *buffer, size_t size)
+{
+  log_mime_parser ("%s:%s: Reading: " SIZE_T_FORMAT "Bytes",
+                 SRCNAME, __func__, size);
+  ssize_t bRead = m_data.read (buffer, size);
+  if (opt.enable_debug & DBG_MIME_PARSER)
+    {
+      std::string buf ((char *)buffer, bRead);
+      log_mime_parser ("%s:%s: Data: \"%s\"",
+                     SRCNAME, __func__, buf.c_str());
+    }
+  return bRead;
+}
+
+void
+MimeDataProvider::decode_and_collect(char *line, size_t pos)
+{
+  /* We are inside the data.  That should be the actual
+     ciphertext in the given encoding. Add it to our internal
+     cache. */
+  int slbrk = 0;
+  size_t len;
+
+  if (m_current_encoding == Quoted)
+    len = qp_decode (line, pos, &slbrk);
+  else if (m_current_encoding == Base64)
+    len = b64_decode (&m_base64_context, line, pos);
+  else
+    len = pos;
+  m_data.write (line, len);
+  if (m_current_encoding != Encoding::Base64 && !slbrk)
+    {
+      m_data.write ("\r\n", 2);
+    }
+  return;
+}
+
+/* Split some raw data into lines and handle them accordingly.
+   returns the amount of bytes not taken from the input buffer.
+*/
+size_t
+MimeDataProvider::collect_input_lines(const char *input, size_t insize)
+{
+  char linebuf[LINEBUFSIZE];
+  const char *s = input;
+  size_t pos = 0;
+  size_t nleft = insize;
+  size_t not_taken = nleft;
+
+  /* Split the raw data into lines */
+  for (; nleft; nleft--, s++)
+    {
+      if (pos >= LINEBUFSIZE)
+        {
+          log_error ("%s:%s: rfc822 parser failed: line too long\n",
+                     SRCNAME, __func__);
+          GpgME::Error::setSystemError (GPG_ERR_EIO);
+          return not_taken;
+        }
+      if (*s != '\n')
+        linebuf[pos++] = *s;
+      else
+        {
+          /* Got a complete line.  Remove the last CR.  */
+          not_taken -= pos + 1; /* Pos starts at 0 so + 1 for it */
+          if (pos && linebuf[pos-1] == '\r')
+            {
+              pos--;
+            }
+
+          if (rfc822parse_insert (m_parser,
+                                  (unsigned char*) linebuf,
+                                  pos))
+            {
+              log_error ("%s:%s: rfc822 parser failed: %s\n",
+                         SRCNAME, __func__, strerror (errno));
+              return not_taken;
+            }
+          /* If we are currently in a collecting state actually
+             collect that line */
+          if (m_collect)
+            {
+              decode_and_collect (linebuf, pos);
+            }
+          /* Continue with next line. */
+          pos = 0;
+        }
+    }
+  return not_taken;
+}
+
+void
+MimeDataProvider::collect_data(LPSTREAM stream)
+{
+  if (!stream)
+    {
+      return;
+    }
+  HRESULT hr;
+  char buf[BUFSIZE];
+  ULONG bRead;
+  while ((hr = stream->Read (buf, BUFSIZE, &bRead)) == S_OK ||
+         hr == S_FALSE)
+    {
+      if (!bRead)
+        {
+          log_mime_parser ("%s:%s: Input stream at EOF.",
+                           SRCNAME, __func__);
+          return;
+        }
+      log_mime_parser ("%s:%s: Read %lu bytes.",
+                       SRCNAME, __func__, bRead);
+
+      m_rawbuf += std::string (buf, bRead);
+      size_t not_taken = collect_input_lines (m_rawbuf.c_str(),
+                                              m_rawbuf.size());
+
+      if (not_taken == m_rawbuf.size())
+        {
+          log_error ("%s:%s: Collect failed to consume anything.\n"
+                     "Buffer too small?",
+                     SRCNAME, __func__);
+          return;
+        }
+      log_mime_parser ("%s:%s: Consumed: " SIZE_T_FORMAT " bytes",
+                       SRCNAME, __func__, m_rawbuf.size() - not_taken);
+      m_rawbuf.erase (0, m_rawbuf.size() - not_taken);
+    }
+}
+
+off_t
+MimeDataProvider::seek(off_t offset, int whence)
+{
+  return m_data.seek (offset, whence);
+}
diff --git a/src/mimedataprovider.h b/src/mimedataprovider.h
new file mode 100644 (file)
index 0000000..8b9d8ee
--- /dev/null
@@ -0,0 +1,83 @@
+/* mimedataprover.h - GpgME dataprovider for mime data
+ *    Copyright (C) 2016 Intevation GmbH
+ *
+ * This file is part of GpgOL.
+ *
+ * GpgOL is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GpgOL is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef MIMEDATAPROVIDER_H
+#define MIMEDATAPROVIDER_H
+
+#include <gpgme++/interfaces/dataprovider.h>
+#include <gpgme++/data.h>
+#include "oomhelp.h"
+#include "mapihelp.h"
+#include "rfc822parse.h"
+
+#include <string>
+
+/** This class does simple one level mime parsing to find crypto
+  data.
+
+  Use the mimedataprovider on a body or attachment stream. It
+  will do the conversion from MIME to PGP / CMS data on the fly.
+
+  The raw mime data from the underlying stream is "collected" and
+  parsed into Crypto data which is then buffered in "databuf".
+*/
+class MimeDataProvider : public GpgME::DataProvider
+{
+public:
+  /* Read and parse the stream. Does not hold a reference
+     to the stream but releases it after read. */
+  MimeDataProvider(LPSTREAM stream);
+  ~MimeDataProvider();
+
+  /* Dataprovider interface */
+  bool isSupported(Operation) const;
+
+  /** Read some data from the stream. This triggers
+    the conversion code interanally to convert mime
+    data into PGP/CMS Data that GpgME can work with. */
+  ssize_t read(void *buffer, size_t bufSize);
+  ssize_t write(const void *buffer, size_t bufSize) {
+      (void)buffer; (void)bufSize; return -1;
+  }
+  /* Seek the underlying stream. This discards the internal
+     buffers as the offset is not mapped. Should not really
+     be used but can be used to reset the DataProvider. */
+  off_t seek(off_t offset, int whence);
+  /* Noop */
+  void release() {}
+
+  /* The the data of the signature part. */
+  const GpgME::Data &get_signature_data();
+private:
+  /* Collect the crypto data from mime. */
+  void collect_data(LPSTREAM stream);
+  /* Collect a single line. */
+  size_t collect_input_lines(const char *input, size_t size);
+  /* Move actual data into the databuffer. */
+  void decode_and_collect(char *line, size_t pos);
+  enum Encoding {None, Base64, Quoted};
+  std::string m_sig_data;
+  GpgME::Data m_data;
+  GpgME::Data m_signature;
+  std::string m_rawbuf;
+  bool m_collect;
+  rfc822parse_t m_parser;
+  Encoding m_current_encoding;
+  b64_state_t m_base64_context;
+};
+#endif // MIMEDATAPROVIDER_H
index b20f0bc..0b78332 100644 (file)
@@ -96,6 +96,8 @@ void log_window_hierarchy (HWND window, const char *fmt,
 
 #define log_oom if (opt.enable_debug & DBG_OOM) log_debug
 #define log_oom_extra if (opt.enable_debug & DBG_OOM_EXTRA) log_debug
+#define log_mime_parser if (opt.enable_debug & DBG_MIME_PARSER) log_debug
+
 #define gpgol_release(X) \
 { \
   if (X && opt.enable_debug & DBG_OOM_EXTRA) \
index e0c95cd..d850a8c 100644 (file)
@@ -1,7 +1,28 @@
+/* @file windowmessages.h
+ * @brief Helper class to work with the windowmessage handler thread.
+ *
+ *    Copyright (C) 2015, 2016 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 "windowmessages.h"
 
 #include "util.h"
 #include "oomhelp.h"
+#include "mail.h"
 
 #include <stdio.h>
 
@@ -39,10 +60,21 @@ gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
               ctx->err = request_send_mail ((LPDISPATCH) ctx->data);
               break;
             }
+          case (PARSING_DONE):
+            {
+              auto mail = (Mail*) ctx->data;
+              if (!Mail::is_mail_valid (mail))
+                {
+                  log_debug ("%s:%s: Parsing done for mail which is gone.",
+                             SRCNAME, __func__);
+                }
+              mail->parsing_done();
+            }
+          break;
           default:
             log_debug ("Unknown msg");
         }
-        return 0;
+        return DefWindowProc(hWnd, message, wParam, lParam);
     }
 
   return DefWindowProc(hWnd, message, wParam, lParam);
index c9355d5..ec66f7b 100644 (file)
 typedef enum _gpgol_wmsg_type
 {
   UNKNOWN = 0,
-  REQUEST_SEND_MAIL = 1 /* Request to send a mail.
-                           Data should be LPMAILITEM */
+  REQUEST_SEND_MAIL = 1, /* Request to send a mail.
+                            Data should be LPMAILITEM */
+  PARSING_DONE = 2 /* A mail was parsed. Data should be a pointer
+                      to the mail object. */
 } gpgol_wmsg_type;
 
 typedef struct