Added translation framework and translated a few strings.
[gpgol.git] / src / gpgmsg.cpp
index 36ae8a1..b4edf31 100644 (file)
@@ -1,14 +1,14 @@
 /* gpgmsg.cpp - Implementation ofthe GpgMsg class
  *     Copyright (C) 2005 g10 Code GmbH
  *
- * This file is part of OutlGPG.
+ * This file is part of GPGol.
  * 
- * OutlGPG is free software; you can redistribute it and/or
+ * 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 of the License, or (at your option) any later version.
  * 
- * OutlGPG is distributed in the hope that it will be useful,
+ * 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.
 #include "mymapi.h"
 #include "mymapitags.h"
 
-
+#include "intern.h"
 #include "gpgmsg.hh"
 #include "util.h"
 #include "msgcache.h"
+#include "pgpmime.h"
 #include "engine.h"
+#include "display.h"
+
+static const char oid_mimetag[] =
+  {0x2A, 0x86, 0x48, 0x86, 0xf7, 0x14, 0x03, 0x0a, 0x04};
 
-/* The string used as the standard XXXXX of decrypted attachments. */
-#define ATT_FILE_PREFIX ".pgpenc"
 
 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
-                                       __FILE__, __func__, __LINE__); \
+                                       SRCNAME, __func__, __LINE__); \
                         } while (0)
 
+/* Constants to describe the PGP armor types. */
+typedef enum 
+  {
+    ARMOR_NONE = 0,
+    ARMOR_MESSAGE,
+    ARMOR_SIGNATURE,
+    ARMOR_SIGNED,
+    ARMOR_FILE,     
+    ARMOR_PUBKEY,
+    ARMOR_SECKEY
+  }
+armor_t;
+
+
+struct attach_info
+{
+  int end_of_table;  /* True if this is the last plus one entry of the
+                        table. */
+  int invalid;       /* Invalid table entry - usally ignored. */
+   
+  int is_encrypted;  /* This is an encrypted attchment. */
+  int is_signed;     /* This is a signed attachment. */
+  unsigned int sig_pos; /* For signed attachments the index of the
+                           attachment with the detached signature. */
+  
+  int method;        /* MAPI attachmend method. */
+  char *filename;    /* Malloced filename of this attachment or NULL. */
+  char *content_type;/* Malloced string with the mime attrib or NULL.
+                        Parameters are stripped off thus a compare
+                        against "type/subtype" is sufficient. */
+  const char *content_type_parms; /* If not NULL the parameters of the
+                                     content_type. */
+  armor_t armor_type;   /* 0 or the type of the PGP armor. */
+};
+typedef struct attach_info *attach_info_t;
+
+
+static int get_attach_method (LPATTACH obj);
+static bool set_x_header (LPMESSAGE msg, const char *name, const char *val);
+
 
 
 /*
-   The implementation class of MapiGPGME.  
+   The implementation class of GpgMsg.  
  */
 class GpgMsgImpl : public GpgMsg
 {
@@ -54,13 +97,16 @@ public:
   GpgMsgImpl () 
   {
     message = NULL;
+    exchange_cb = NULL;
     body = NULL;
     body_plain = NULL;
-    body_cipher = NULL;
-    body_signed = NULL;
-    body_cipher_is_html = false;
+    is_pgpmime = false;
+    has_attestation = false;
+    silent = false;
 
-    attach.table = NULL;
+    attestation = NULL;
+
+    attach.att_table = NULL;
     attach.rows = NULL;
   }
 
@@ -70,13 +116,14 @@ public:
       message->Release ();
     xfree (body);
     xfree (body_plain);
-    xfree (body_cipher);
-    xfree (body_signed);
 
-    if (attach.table)
+    if (attestation)
+      gpgme_data_release (attestation);
+
+    if (attach.att_table)
       {
-        attach.table->Release ();
-        attach.table = NULL;
+        attach.att_table->Release ();
+        attach.att_table = NULL;
       }
     if (attach.rows)
       {
@@ -104,45 +151,78 @@ public:
       }
     if (msg)
       {
-      log_debug ("%s:%s:%d: here\n", __FILE__, __func__, __LINE__);
         msg->AddRef ();
-      log_debug ("%s:%s:%d: here\n", __FILE__, __func__, __LINE__);
         message = msg;
       }
   }
+
+  /* Set the callback for Exchange. */
+  void setExchangeCallback (void *cb)
+  {
+    exchange_cb = cb;
+  }
   
+  void setSilent (bool value)
+  {
+    silent = value;
+  }
+
   openpgp_t getMessageType (void);
   bool hasAttachments (void);
-  const char *getOrigText (void);
+  const char *getOrigText (bool want_html);
   const char *GpgMsgImpl::getDisplayText (void);
   const char *getPlainText (void);
-  void setPlainText (char *string);
-  void setCipherText (char *string, bool html);
-  void setSignedText (char *string);
-  void saveChanges (bool permanent);
-  bool matchesString (const char *string);
+
+  int decrypt (HWND hwnd);
+  int sign (HWND hwnd);
+  int encrypt (HWND hwnd, bool want_html)
+  {
+    return encrypt_and_sign (hwnd, want_html, false);
+  }
+  int signEncrypt (HWND hwnd, bool want_html)
+  {
+    return encrypt_and_sign (hwnd, want_html, true);
+  }
+  int attachPublicKey (const char *keyid);
+
   char **getRecipients (void);
   unsigned int getAttachments (void);
-  void decryptAttachment (HWND hwnd, int pos, bool save_plaintext);
-  void signAttachment (HWND hwnd, int pos, int ttl);
+  void verifyAttachment (HWND hwnd, attach_info_t table,
+                         unsigned int pos_data,
+                         unsigned int pos_sig);
+  void decryptAttachment (HWND hwnd, int pos, bool save_plaintext, int ttl,
+                          const char *filename);
+  void signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl);
+  int encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
+                         gpgme_key_t sign_key, int ttl);
 
 
 private:
   LPMESSAGE message;  /* Pointer to the message. */
+  void *exchange_cb;  /* Call back used with the display function. */
   char *body;         /* utf-8 encoded body string or NULL. */
   char *body_plain;   /* Plaintext version of BODY or NULL. */
-  char *body_cipher;  /* Enciphered version of BODY or NULL. */
-  char *body_signed;  /* Signed version of BODY or NULL. */
-  bool body_cipher_is_html; /* Indicating that BODY_CIPHER holds HTML. */
+  bool is_pgpmime;    /* True if the message is a PGP/MIME encrypted one. */
+  bool has_attestation;/* True if we found an attestation attachment. */
+  bool silent;        /* Don't pop up message boxes.  Currently this
+                         is only used with decryption.  */
+
+  /* If not NULL, collect attestation information here. */
+  gpgme_data_t attestation;
+  
 
   /* This structure collects the information about attachments. */
   struct 
   {
-    LPMAPITABLE table;/* The loaded attachment table or NULL. */
-    LPSRowSet   rows; /* The retrieved set of rows from the table. */
+    LPMAPITABLE att_table;/* The loaded attachment table or NULL. */
+    LPSRowSet   rows;     /* The retrieved set of rows from the table. */
   } attach;
   
-  void loadBody (void);
+  void loadBody (bool want_html);
+  bool isPgpmimeVersionPart (int pos);
+  void writeAttestation (void);
+  attach_info_t gatherAttachmentInfo (void);
+  int encrypt_and_sign (HWND hwnd, bool want_html, bool sign);
 };
 
 
@@ -159,116 +239,326 @@ CreateGpgMsg (LPMESSAGE msg)
 }
 
 
+/* Release an array of GPGME keys. */
+static void 
+free_key_array (gpgme_key_t *keys)
+{
+  if (keys)
+    {
+      for (int i = 0; keys[i]; i++) 
+       gpgme_key_release (keys[i]);
+      xfree (keys);
+    }
+}
+
+/* Release an array of strings. */
+static void
+free_string_array (char **strings)
+{
+  if (strings)
+    {
+      for (int i=0; strings[i]; i++) 
+       xfree (strings[i]);     
+      xfree (strings);
+    }
+}
+
+/* Release a table with attachments infos. */
+static void
+release_attach_info (attach_info_t table)
+{
+  int i;
+
+  if (!table)
+    return;
+  for (i=0; !table[i].end_of_table; i++)
+    {
+      xfree (table[i].filename);
+      xfree (table[i].content_type);
+    }
+  xfree (table);
+}
+
+
+/* Return the number of strings in the array STRINGS. */
+static size_t
+count_strings (char **strings)
+{
+  size_t i;
+  
+  for (i=0; strings[i]; i++)
+    ;
+  return i;
+}
+
+static size_t
+count_keys (gpgme_key_t *keys)
+{
+  size_t i;
+  
+  for (i=0; keys[i]; i++)
+    ;
+  return i;
+}
+
+
+/* Return a string suitable for displaying in a message box.  The
+   function takes FORMAT and replaces the string "@LIST@" with the
+   names of the attachmets. Depending on the set bits in WHAT only
+   certain attachments are inserted. 
+
+   Defined bits in MODE are:
+      0 = Any attachment
+      1 = signed attachments
+      2 = encrypted attachments
+
+   Caller must free the returned value.  Routine is guaranteed to
+   return a string.
+*/
+static char *
+text_from_attach_info (attach_info_t table, const char *format,
+                       unsigned int what)
+{
+  int pos;
+  size_t length;
+  char *buffer, *p;
+  const char *marker;
+
+  marker = strstr (format, "@LIST@");
+  if (!marker)
+    return xstrdup (format);
+
+#define CONDITION  (table[pos].filename \
+                    && ( (what&1) \
+                         || ((what & 2) && table[pos].is_signed) \
+                         || ((what & 4) && table[pos].is_encrypted)))
+
+  for (length=0, pos=0; !table[pos].end_of_table; pos++)
+    if (CONDITION)
+      length += 2 + strlen (table[pos].filename) + 1;
+
+  length += strlen (format);
+  buffer = p = (char*)xmalloc (length+1);
+
+  strncpy (p, format, marker - format);
+  p += marker - format;
+
+  for (pos=0; !table[pos].end_of_table; pos++)
+    if (CONDITION)
+      {
+        if (table[pos].is_signed)
+          p = stpcpy (p, "S ");
+        else if (table[pos].is_encrypted)
+          p = stpcpy (p, "E ");
+        else
+          p = stpcpy (p, "* ");
+        p = stpcpy (p, table[pos].filename);
+        p = stpcpy (p, "\n");
+      }
+  strcpy (p, marker+6);
+#undef CONDITION
+
+  return buffer;
+}
+
+
+\f
 /* Load the body and make it available as an UTF8 string in the
    instance variable BODY.  */
 void
-GpgMsgImpl::loadBody (void)
+GpgMsgImpl::loadBody (bool want_html)
 {
   HRESULT hr;
   LPSPropValue lpspvFEID = NULL;
   LPSTREAM stream;
-  SPropValue prop;
+//   SPropValue prop;
   STATSTG statInfo;
   ULONG nread;
 
   if (body || !message)
     return;
-  
-#if 1
-  hr = message->OpenProperty (PR_BODY, &IID_IStream,
-                              0, 0, (LPUNKNOWN*)&stream);
-  if ( hr != S_OK )
-    {
-      log_debug_w32 (hr, "%s:%s: OpenProperty failed", __FILE__, __func__);
-      return;
-    }
 
-  hr = stream->Stat (&statInfo, STATFLAG_NONAME);
-  if ( hr != S_OK )
-    {
-      log_debug_w32 (hr, "%s:%s: Stat failed", __FILE__, __func__);
-      stream->Release ();
-      return;
-    }
-  
-  /* FIXME: We might want to read only the first 1k to decide whetehr
-     this is actually an OpenPGP message and only then continue
-     reading.  This requires some changes in this module. */
-  body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
-  hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
-  if ( hr != S_OK )
-    {
-      log_debug_w32 (hr, "%s:%s: Read failed", __FILE__, __func__);
-      xfree (body);
-      body = NULL;
-      stream->Release ();
-      return;
+  hr = HrGetOneProp ((LPMAPIPROP)message,
+                     want_html? PR_BODY_HTML : PR_BODY, &lpspvFEID);
+  if (SUCCEEDED (hr))
+    { /* Message is small enough to be retrieved this way. */
+      switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
+        {
+        case PT_UNICODE:
+          body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
+          if (!body)
+            log_debug ("%s: error converting to utf8\n", __func__);
+          break;
+          
+        case PT_STRING8:
+          body = xstrdup (lpspvFEID->Value.lpszA);
+          break;
+          
+        default:
+          log_debug ("%s: proptag=0x%08lx not supported\n",
+                     __func__, lpspvFEID->ulPropTag);
+          break;
+        }
+      MAPIFreeBuffer (lpspvFEID);
     }
-  body[nread] = 0;
-  body[nread+1] = 0;
-  if (nread != statInfo.cbSize.QuadPart)
+  else /* Message is large; Use a stream to read it. */
     {
-      log_debug ("%s:%s: not enough bytes returned\n", __FILE__, __func__);
-      xfree (body);
-      body = NULL;
+      hr = message->OpenProperty (want_html? PR_BODY_HTML : PR_BODY,
+                                  &IID_IStream, 0, 0, (LPUNKNOWN*)&stream);
+      if ( hr != S_OK )
+        {
+          log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
+                     SRCNAME, __func__, hr);
+          if (want_html)
+            {
+              log_debug ("%s:%s: trying to read it from the OOM\n",
+                         SRCNAME, __func__);
+              body = get_outlook_property (exchange_cb, "HTMLBody");
+              if (body)
+                goto ready;
+            }
+          
+          return;
+        }
+      
+      hr = stream->Stat (&statInfo, STATFLAG_NONAME);
+      if ( hr != S_OK )
+        {
+          log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
+          stream->Release ();
+          return;
+        }
+      
+      /* Fixme: We might want to read only the first 1k to decide
+         whether this is actually an OpenPGP message and only then
+         continue reading.  This requires some changes in this
+         module. */
+      body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
+      hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
+      if ( hr != S_OK )
+        {
+          log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
+          xfree (body);
+          body = NULL;
+          stream->Release ();
+          return;
+        }
+      body[nread] = 0;
+      body[nread+1] = 0;
+      if (nread != statInfo.cbSize.QuadPart)
+        {
+          log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
+          xfree (body);
+          body = NULL;
+          stream->Release ();
+          return;
+        }
       stream->Release ();
-      return;
-    }
-  stream->Release ();
-
-  /* Fixme: We need to optimize this. */
-  {
-    char *tmp;
-    tmp = wchar_to_utf8 ((wchar_t*)body);
-    if (!tmp)
-      log_debug ("%s: error converting to utf8\n", __func__);
-    else
+      
+      /* FIXME: We need to optimize this. */
       {
-        xfree (body);
-        body = tmp;
+        char *tmp;
+        tmp = wchar_to_utf8 ((wchar_t*)body);
+        if (!tmp)
+          log_debug ("%s: error converting to utf8\n", __func__);
+        else
+          {
+            xfree (body);
+            body = tmp;
+          }
       }
-  }
+    }
+
+ ready:
+  if (body)
+    log_debug ("%s:%s: loaded body `%s' at %p\n",
+               SRCNAME, __func__, body, body);
+  
+
+//   prop.ulPropTag = PR_ACCESS;
+//   prop.Value.l = MAPI_ACCESS_MODIFY;
+//   hr = HrSetOneProp (message, &prop);
+//   if (FAILED (hr))
+//     log_debug ("%s:%s: updating message access to 0x%08lx failed: hr=%#lx",
+//                    SRCNAME, __func__, prop.Value.l, hr);
+}
+
 
-#else /* Old method. */
-  hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
+/* Return the subject of the message or NULL if it does not
+   exists.  Caller must free. */
+#if 0
+static char *
+get_subject (LPMESSAGE obj)
+{
+  HRESULT hr;
+  LPSPropValue propval = NULL;
+  char *name;
+
+  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_SUBJECT, &propval);
   if (FAILED (hr))
     {
-      log_debug ("%s: HrGetOneProp failed\n", __func__);
-      return;
+      log_error ("%s:%s: error getting the subject: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      return NULL; 
     }
-    
-  switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
+  switch ( PROP_TYPE (propval->ulPropTag) )
     {
     case PT_UNICODE:
-      body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
-      if (!body)
-        log_debug ("%s: error converting to utf8\n", __func__);
+      name = wchar_to_utf8 (propval->Value.lpszW);
+      if (!name)
+        log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
       break;
       
     case PT_STRING8:
-      body = xstrdup (lpspvFEID->Value.lpszA);
+      name = xstrdup (propval->Value.lpszA);
       break;
       
     default:
-      log_debug ("%s: proptag=0x%08lx not supported\n",
-                 __func__, lpspvFEID->ulPropTag);
+      log_debug ("%s:%s: proptag=%#lx not supported\n",
+                 SRCNAME, __func__, propval->ulPropTag);
+      name = NULL;
       break;
     }
-  MAPIFreeBuffer (lpspvFEID);
-#endif  
+  MAPIFreeBuffer (propval);
+  return name;
+}
+#endif
 
-  if (body)
-    log_debug ("%s:%s: loaded body `%s' at %p\n",
-               __FILE__, __func__, body, body);
+/* Set the subject of the message OBJ to STRING. Returns 0 on
+   success. */
+#if 0
+static int
+set_subject (LPMESSAGE obj, const char *string)
+{
+  HRESULT hr;
+  SPropValue prop;
+  const char *s;
   
-
-//   prop.ulPropTag = PR_ACCESS;
-//   prop.Value.l = MAPI_ACCESS_MODIFY;
-//   hr = HrSetOneProp (message, &prop);
-//   if (FAILED (hr))
-//     log_debug_w32 (-1,"%s:%s: updating access to 0x%08lx failed",
-//                    __FILE__, __func__, prop.Value.l);
+  /* Decide whether we ned to use the Unicode version. */
+  for (s=string; *s && !(*s & 0x80); s++)
+    ;
+  if (*s)
+    {
+      prop.ulPropTag = PR_SUBJECT_W;
+      prop.Value.lpszW = utf8_to_wchar (string);
+      hr = HrSetOneProp (obj, &prop);
+      xfree (prop.Value.lpszW);
+    }
+  else /* Only plain ASCII. */
+    {
+      prop.ulPropTag = PR_SUBJECT_A;
+      prop.Value.lpszA = (CHAR*)string;
+      hr = HrSetOneProp (obj, &prop);
+    }
+  if (hr != S_OK)
+    {
+      log_debug ("%s:%s: HrSetOneProp failed: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+  return 0;
 }
+#endif
 
 
 /* Return the type of a message. */
@@ -277,7 +567,7 @@ GpgMsgImpl::getMessageType (void)
 {
   const char *s;
   
-  loadBody ();
+  loadBody (false);
   
   if (!body || !(s = strstr (body, "BEGIN PGP ")))
     return OPENPGP_NONE;
@@ -301,9 +591,9 @@ GpgMsgImpl::getMessageType (void)
 /* Return the body text as received or composed.  This is guaranteed
    to never return NULL.  */
 const char *
-GpgMsgImpl::getOrigText ()
+GpgMsgImpl::getOrigText (bool want_html)
 {
-  loadBody ();
+  loadBody (want_html);
   
   return body? body : "";
 }
@@ -314,7 +604,7 @@ GpgMsgImpl::getOrigText ()
 const char *
 GpgMsgImpl::getDisplayText (void)
 {
-  loadBody ();
+  loadBody (false);
 
   if (body_plain)
     return body_plain;
@@ -326,114 +616,6 @@ GpgMsgImpl::getDisplayText (void)
 
 
 
-/* Save STRING as the plaintext version of the message.  WARNING:
-   ownership of STRING is transferred to this object. */
-void
-GpgMsgImpl::setPlainText (char *string)
-{
-  xfree (body_plain);
-  body_plain = string;
-  msgcache_put (body_plain, 0, message);
-}
-
-/* Save STRING as the ciphertext version of the message.  WARNING:
-   ownership of STRING is transferred to this object. HTML indicates
-   whether the ciphertext was originally HTML. */
-void
-GpgMsgImpl::setCipherText (char *string, bool html)
-{
-  xfree (body_cipher);
-  body_cipher = string;
-  body_cipher_is_html = html;
-}
-
-/* Save STRING as the signed version of the message.  WARNING:
-   ownership of STRING is transferred to this object. */
-void
-GpgMsgImpl::setSignedText (char *string)
-{
-  xfree (body_signed);
-  body_signed = string;
-}
-
-/* Save the changes made to the message.  With PERMANENT set to true
-   they are really stored, when not set they are only saved
-   temporary. */
-void
-GpgMsgImpl::saveChanges (bool permanent)
-{
-  SPropValue sProp; 
-  HRESULT hr;
-  int rc = TRUE;
-
-  if (!body_plain)
-    return; /* Nothing to save. */
-
-  if (!permanent)
-    return;
-  
-  /* Make sure that the Plaintext and the Richtext are in sync. */
-//   if (message)
-//     {
-//       BOOL changed;
-
-//       sProp.ulPropTag = PR_BODY_A;
-//       sProp.Value.lpszA = "";
-//       hr = HrSetOneProp(message, &sProp);
-//       changed = false;
-//       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
-//       sProp.Value.lpszA = body_plain;
-//       hr = HrSetOneProp(message, &sProp);
-//       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
-//     }
-
-  sProp.ulPropTag = PR_BODY_W;
-  sProp.Value.lpszW = utf8_to_wchar (body_plain);
-  if (!sProp.Value.lpszW)
-    {
-      log_debug_w32 (-1, "%s:%s: error converting from utf8\n",
-                     __FILE__, __func__);
-      return;
-    }
-  hr = HrSetOneProp (message, &sProp);
-  xfree (sProp.Value.lpszW);
-  if (hr < 0)
-    log_debug_w32 (-1, "%s:%s: HrSetOneProp failed", __FILE__, __func__);
-  else
-    {
-      log_debug ("%s:%s: PR_BODY set to `%s'\n",
-                 __FILE__, __func__, body_plain);
-      {
-        GpgMsg *xmsg = CreateGpgMsg (message);
-        log_debug ("%s:%s:    cross check `%s'\n",
-                   __FILE__, __func__, xmsg->getOrigText ());
-        delete xmsg;
-      }
-      if (permanent && message)
-        {
-          hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
-          if (hr < 0)
-            log_debug_w32 (-1, "%s:%s: SaveChanges failed",
-                           __FILE__, __func__);
-        }
-    }
-
-  log_debug ("%s:%s: leave\n", __FILE__, __func__);
-}
-
-
-/* Returns true if STRING matches the actual message. */ 
-bool
-GpgMsgImpl::matchesString (const char *string)
-{
-  /* FIXME:  This is a too simple implementation. */
-  if (string && strstr (string, "BEGIN PGP ") )
-    return true;
-  return false;
-}
-
-
-
 /* Return an array of strings with the recipients of the message. On
    success a malloced array is returned containing allocated strings
    for each recipient.  The end of the array is marked by NULL.
@@ -447,7 +629,6 @@ GpgMsgImpl::getRecipients ()
   LPMAPITABLE lpRecipientTable = NULL;
   LPSRowSet lpRecipientRows = NULL;
   char **rset;
-  const char *s;
   int i, j;
 
   if (!message)
@@ -457,7 +638,7 @@ GpgMsgImpl::getRecipients ()
   if (FAILED (hr)) 
     {
       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed",
-                     __FILE__, __func__);
+                     SRCNAME, __func__);
       return NULL;
     }
 
@@ -465,7 +646,7 @@ GpgMsgImpl::getRecipients ()
                        NULL, NULL, 0L, &lpRecipientRows);
   if (FAILED (hr)) 
     {
-      log_debug_w32 (-1, "%s:%s: GHrQueryAllRows failed", __FILE__, __func__);
+      log_debug_w32 (-1, "%s:%s: GHrQueryAllRows failed", SRCNAME, __func__);
       if (lpRecipientTable)
         lpRecipientTable->Release();
       return NULL;
@@ -473,7 +654,7 @@ GpgMsgImpl::getRecipients ()
 
   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
 
-  for (i = j = 0; i < lpRecipientRows->cRows; i++)
+  for (i = j = 0; (unsigned int)i < lpRecipientRows->cRows; i++)
     {
       LPSPropValue row;
 
@@ -489,7 +670,7 @@ GpgMsgImpl::getRecipients ()
             j++;
           else
             log_debug ("%s:%s: error converting recipient to utf8\n",
-                       __FILE__, __func__);
+                       SRCNAME, __func__);
           break;
       
         case PT_STRING8: /* Assume Ascii. */
@@ -498,7 +679,7 @@ GpgMsgImpl::getRecipients ()
           
         default:
           log_debug ("%s:%s: proptag=0x%08lx not supported\n",
-                     __FILE__, __func__, row->ulPropTag);
+                     SRCNAME, __func__, row->ulPropTag);
           break;
         }
     }
@@ -510,48 +691,802 @@ GpgMsgImpl::getRecipients ()
     FreeProws(lpRecipientRows);        
   
   log_debug ("%s:%s: got %d recipients:\n",
-             __FILE__, __func__, j);
+             SRCNAME, __func__, j);
   for (i=0; rset[i]; i++)
-    log_debug ("%s:%s: \t`%s'\n", __FILE__, __func__, rset[i]);
+    log_debug ("%s:%s: \t`%s'\n", SRCNAME, __func__, rset[i]);
 
   return rset;
 }
 
 
+/* Write an Attestation to the current message. */
+void
+GpgMsgImpl::writeAttestation (void)
+{
+  HRESULT hr;
+  ULONG newpos;
+  SPropValue prop;
+  LPATTACH newatt = NULL;
+  LPSTREAM to = NULL;
+  char *buffer = NULL;
+  char *p, *pend;
+  ULONG nwritten;
 
+  if (!message || !attestation)
+    return;
 
+  hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't create attachment: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
+          
+  prop.ulPropTag = PR_ATTACH_METHOD;
+  prop.Value.ul = ATTACH_BY_VALUE;
+  hr = HrSetOneProp (newatt, &prop);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set attach method: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
+  
+  /* It seem that we need to insert a short filename.  Without it the
+     _displayed_ list of attachments won't get updated although the
+     attachment has been created. */
+  prop.ulPropTag = PR_ATTACH_FILENAME_A;
+  prop.Value.lpszA = "gpgtstt0.txt";
+  hr = HrSetOneProp (newatt, &prop);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
 
-/* Returns whether the message has any attachments. */
-bool
-GpgMsgImpl::hasAttachments (void)
-{
-  return !!getAttachments ();
-}
+  /* And not for the real name. */
+  prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
+  prop.Value.lpszA = "GPGol-Attestation.txt";
+  hr = HrSetOneProp (newatt, &prop);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
 
+  prop.ulPropTag = PR_ATTACH_TAG;
+  prop.Value.bin.cb  = sizeof oid_mimetag;
+  prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
+  hr = HrSetOneProp (newatt, &prop);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
 
-/* Reads the attachment information and returns the number of
-   attachments. */
-unsigned int
-GpgMsgImpl::getAttachments (void)
-{
-  SizedSPropTagArray (1L, propAttNum) = {
-    1L, {PR_ATTACH_NUM}
-  };
-  HRESULT hr;    
-  LPMAPITABLE table;
-  LPSRowSet   rows;
+  prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
+  prop.Value.lpszA = "text/plain; charset=utf-8";
+  hr = HrSetOneProp (newatt, &prop);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
 
-  if (!message)
-    return 0;
+  hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
+                             MAPI_CREATE|MAPI_MODIFY, (LPUNKNOWN*)&to);
+  if (FAILED (hr)) 
+    {
+      log_error ("%s:%s: can't create output stream: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
+  
 
-  if (!attach.table)
+  if (gpgme_data_write (attestation, "", 1) != 1
+      || !(buffer = gpgme_data_release_and_get_mem (attestation, NULL)))
     {
-      hr = message->GetAttachmentTable (0, &table);
-      if (FAILED (hr))
+      attestation = NULL;
+      log_error ("%s:%s: gpgme_data_write failed\n", SRCNAME, __func__); 
+      goto leave;
+    }
+  attestation = NULL;
+
+  log_debug ("writing attestation `%s'\n", buffer);
+  hr = S_OK;
+  if (!*buffer)
+    {
+      const char *s = _("[No attestation computed "
+                        "(e.g. messages was not signed)");
+      hr = to->Write (s, strlen (s), &nwritten);
+    }
+  else
+    {
+      for (p=buffer; hr == S_OK && (pend = strchr (p, '\n')); p = pend+1)
         {
-          log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
-                     __FILE__, __func__, hr);
-          return 0;
+          hr = to->Write (p, pend - p, &nwritten);
+          if (hr == S_OK)
+            hr = to->Write ("\r\n", 2, &nwritten);
+        }
+      if (*p && hr == S_OK)
+        hr = to->Write (p, strlen (p), &nwritten);
+    }
+  if (hr != S_OK)
+    {
+      log_debug ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
+      goto leave;
+    }
+      
+  
+  to->Commit (0);
+  to->Release ();
+  to = NULL;
+  
+  hr = newatt->SaveChanges (0);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
+  hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: SaveChanges(message) failed: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      goto leave;
+    }
+
+
+ leave:
+  if (to)
+    {
+      to->Revert ();
+      to->Release ();
+    }
+  if (newatt)
+    newatt->Release ();
+  gpgme_free (buffer);
+}
+
+
+
+/* Decrypt the message MSG and update the window.  HWND identifies the
+   current window. */
+int 
+GpgMsgImpl::decrypt (HWND hwnd)
+{
+  log_debug ("%s:%s: enter\n", SRCNAME, __func__);
+  openpgp_t mtype;
+  char *plaintext = NULL;
+  attach_info_t table = NULL;
+  int err;
+  unsigned int pos;
+  unsigned int n_attach = 0;
+  unsigned int n_encrypted = 0;
+  unsigned int n_signed = 0;
+  HRESULT hr;
+  int pgpmime_succeeded = 0;
+
+  mtype = getMessageType ();
+
+  /* Check whether this possibly encrypted message has encrypted
+     attachments.  We check right now because we need to get into the
+     decryption code even if the body is not encrypted but attachments
+     are available. */
+  table = gatherAttachmentInfo ();
+  if (table)
+    {
+      for (pos=0; !table[pos].end_of_table; pos++)
+        if (table[pos].is_encrypted)
+          n_encrypted++;
+        else if (table[pos].is_signed)
+          n_signed++;
+      n_attach = pos;
+    }
+  log_debug ("%s:%s: message has %u attachments with "
+             "%u signed and %d encrypted\n",
+             SRCNAME, __func__, n_attach, n_signed, n_encrypted);
+  if (mtype == OPENPGP_NONE && !n_encrypted && !n_signed) 
+    {
+      /* Because we usually work around the OL object model, it can't
+         notice that we changed the windows's text behind its back (by
+         means of update_display and the SetWindowText API).  Thus it
+         happens sometimes that the ciphertext is still displayed
+         although the MAPI calls in loadBody returned the plaintext
+         (because we once used set_message_body).  The effect is that
+         when clicking the decrypt button, we won't have any
+         ciphertext to decrypt and thus get to here.  We try solving
+         this by updating the window if we also have a cached entry.
+
+         Another solution would be to always update the windows's text
+         using a cached plaintext (in OnRead). I have some fear that
+         this might lead to unexpected behaviour in certain cases, so
+         we better only do it on demand and only if the old reply hack
+         has been enabled. */
+      void *refhandle;
+      const char *s;
+
+      if (!opt.compat.old_reply_hack
+          && (s = msgcache_get_from_mapi (message, &refhandle)))
+        {
+          xfree (body_plain);
+          body_plain = xstrdup (s);
+          update_display (hwnd, this, exchange_cb, is_html_body (s));
+          msgcache_unref (refhandle);
+          log_debug ("%s:%s: leave (already decrypted)\n", SRCNAME, __func__);
+        }
+      else
+        {
+          MessageBox (hwnd, "No valid OpenPGP data found.",
+                      "GPG Decryption", MB_ICONWARNING|MB_OK);
+          log_debug ("%s:%s: leave (no OpenPGP data)\n", SRCNAME, __func__);
+        }
+      
+      release_attach_info (table);
+      return 0;
+    }
+
+  /* We always want an attestation.  Note that we ignore any error
+     because that would anyway be a out of core situation and thus we
+     can't do much about it. */
+  if (has_attestation)
+    {
+      if (attestation)
+        gpgme_data_release (attestation);
+      log_debug ("%s:%s: we already have an attestation\n",
+                 SRCNAME, __func__);
+    }
+  else if (!attestation && !opt.compat.no_attestation)
+    gpgme_data_new (&attestation);
+  
+
+  /* Process according to type of message. */
+  if (is_pgpmime)
+    {
+      LPATTACH att;
+      int method;
+      LPSTREAM from;
+      
+      hr = message->OpenAttach (1, NULL, MAPI_BEST_ACCESS, &att);      
+      if (FAILED (hr))
+        {
+          log_error ("%s:%s: can't open PGP/MIME attachment 2: hr=%#lx",
+                     SRCNAME, __func__, hr);
+          MessageBox (hwnd, "Problem decrypting PGP/MIME message",
+                      "GPG Decryption", MB_ICONERROR|MB_OK);
+          log_debug ("%s:%s: leave (PGP/MIME problem)\n", SRCNAME, __func__);
+          release_attach_info (table);
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      method = get_attach_method (att);
+      if (method != ATTACH_BY_VALUE)
+        {
+          log_error ("%s:%s: unsupported method %d for PGP/MIME attachment 2",
+                     SRCNAME, __func__, method);
+          MessageBox (hwnd, "Problem decrypting PGP/MIME message",
+                      "GPG Decryption", MB_ICONERROR|MB_OK);
+          log_debug ("%s:%s: leave (bad PGP/MIME method)\n",SRCNAME,__func__);
+          att->Release ();
+          release_attach_info (table);
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
+                              0, 0, (LPUNKNOWN*) &from);
+      if (FAILED (hr))
+        {
+          log_error ("%s:%s: can't open data of attachment 2: hr=%#lx",
+                     SRCNAME, __func__, hr);
+          MessageBox (hwnd, "Problem decrypting PGP/MIME message",
+                      "GPG Decryption", MB_ICONERROR|MB_OK);
+          log_debug ("%s:%s: leave (OpenProperty failed)\n",SRCNAME,__func__);
+          att->Release ();
+          release_attach_info (table);
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      err = pgpmime_decrypt (from, opt.passwd_ttl, &plaintext, attestation,
+                             hwnd);
+      
+      from->Release ();
+      att->Release ();
+      if (!err)
+        pgpmime_succeeded = 1;
+    }
+  else if (mtype == OPENPGP_CLEARSIG)
+    err = op_verify (getOrigText (false), NULL, NULL, attestation);
+  else if (*getOrigText(false))
+    err = op_decrypt (getOrigText (false), &plaintext, opt.passwd_ttl,
+                      NULL, attestation);
+  else
+    err = gpg_error (GPG_ERR_NO_DATA);
+  if (err)
+    {
+      if (!is_pgpmime && n_attach && gpg_err_code (err) == GPG_ERR_NO_DATA)
+        ;
+      else if (mtype == OPENPGP_CLEARSIG)
+        MessageBox (hwnd, op_strerror (err),
+                    "GPG verification failed", MB_ICONERROR|MB_OK);
+      else
+        MessageBox (hwnd, op_strerror (err),
+                    "GPG decryption failed", MB_ICONERROR|MB_OK);
+    }
+  else if (plaintext && *plaintext)
+    {  
+      int is_html = is_html_body (plaintext);
+
+      log_debug ("decrypt isHtml=%d\n", is_html);
+
+      /* Do we really need to set the body?  update_display below
+         should be sufficient.  The problem with this is that we did
+         changes in the MAPI and OL will later ask whether to save
+         them.  The original reason for this kludge was to get the
+         plaintext into the reply (by setting the property without
+         calling SaveChanges) - with OL2003 it didn't worked reliable
+         and thus we implemented the trick with the msgcache. For now
+         we will disable it but add a compatibility flag to re-enable
+         it. */
+      if (opt.compat.old_reply_hack)
+        set_message_body (message, plaintext, is_html);
+
+      xfree (body_plain);
+      body_plain = plaintext;
+      plaintext = NULL;
+      msgcache_put (body_plain, 0, message);
+
+      if (opt.save_decrypted_attach)
+        {
+          /* User wants us to replace the encrypted message with the
+             plaintext version. */
+          hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+          if (FAILED (hr))
+            log_debug ("%s:%s: SaveChanges failed: hr=%#lx",
+                       SRCNAME, __func__, hr);
+          update_display (hwnd, this, exchange_cb, is_html);
+          
+        }
+      else if (!silent && update_display (hwnd, this, exchange_cb, is_html)) 
+        {
+          const char s[] = 
+            "The message text cannot be displayed.\n"
+            "You have to save the decrypted message to view it.\n"
+            "Then you need to re-open the message.\n\n"
+            "Do you want to save the decrypted message?";
+          int what;
+          
+          what = MessageBox (hwnd, s, "GPG Decryption",
+                             MB_YESNO|MB_ICONWARNING);
+          if (what == IDYES) 
+            {
+              log_debug ("decrypt: saving plaintext message.\n");
+              hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+              if (FAILED (hr))
+                log_debug ("%s:%s: SaveChanges failed: hr=%#lx",
+                           SRCNAME, __func__, hr);
+            }
+       }
+    }
+
+
+  /* If we have signed attachments.  Ask whether the signatures should
+     be verified; we do this is case of large attachments where
+     verification might take long. */
+  if (!silent && n_signed && !pgpmime_succeeded)
+    {
+      const char s[] = 
+        "Signed attachments found.\n\n"
+        "@LIST@\n"
+        "Do you want to verify the signatures?";
+      int what;
+      char *text;
+
+      text = text_from_attach_info (table, s, 2);
+      
+      what = MessageBox (hwnd, text, "Attachment Verification",
+                         MB_YESNO|MB_ICONINFORMATION);
+      xfree (text);
+      if (what == IDYES) 
+        {
+          for (pos=0; !table[pos].end_of_table; pos++)
+            if (table[pos].is_signed)
+              {
+                assert (table[pos].sig_pos < n_attach);
+                verifyAttachment (hwnd, table, pos, table[pos].sig_pos);
+              }
+        }
+    }
+
+  if (!silent && n_encrypted && !pgpmime_succeeded)
+    {
+      const char s[] = 
+        "Encrypted attachments found.\n\n"
+        "@LIST@\n"
+        "Do you want to decrypt and save them?";
+      int what;
+      char *text;
+
+      text = text_from_attach_info (table, s, 4);
+      what = MessageBox (hwnd, text, "Attachment Decryption",
+                         MB_YESNO|MB_ICONINFORMATION);
+      xfree (text);
+      if (what == IDYES) 
+        {
+          for (pos=0; !table[pos].end_of_table; pos++)
+            if (table[pos].is_encrypted)
+              decryptAttachment (hwnd, pos, true, opt.passwd_ttl,
+                                 table[pos].filename);
+        }
+    }
+
+  writeAttestation ();
+
+  release_attach_info (table);
+  log_debug ("%s:%s: leave (rc=%d)\n", SRCNAME, __func__, err);
+  return err;
+}
+
+
+
+
+\f
+/* Sign the current message. Returns 0 on success. */
+int
+GpgMsgImpl::sign (HWND hwnd)
+{
+  HRESULT hr;
+  const char *plaintext;
+  char *signedtext = NULL;
+  int err = 0;
+  gpgme_key_t sign_key = NULL;
+  SPropValue prop;
+
+  log_debug ("%s:%s: enter message=%p\n", SRCNAME, __func__, message);
+  
+  /* We don't sign an empty body - a signature on a zero length string
+     is pretty much useless. */
+  if (!*(plaintext = getOrigText (false)) && !hasAttachments ()) 
+    {
+      log_debug ("%s:%s: leave (empty)", SRCNAME, __func__);
+      return 0; 
+    }
+
+  /* Pop up a dialog box to ask for the signer of the message. */
+  if (signer_dialog_box (&sign_key, NULL, 0) == -1)
+    {
+      log_debug ("%s.%s: leave (dialog failed)\n", SRCNAME, __func__);
+      return gpg_error (GPG_ERR_CANCELED);  
+    }
+
+  if (*plaintext)
+    {
+      err = op_sign (plaintext, &signedtext, 
+                     OP_SIG_CLEAR, sign_key, opt.passwd_ttl);
+      if (err)
+        {
+          MessageBox (hwnd, op_strerror (err),
+                      "GPG Sign", MB_ICONERROR|MB_OK);
+          goto leave;
+        }
+    }
+
+  if (opt.auto_sign_attach && hasAttachments ())
+    {
+      unsigned int n;
+      
+      n = getAttachments ();
+      log_debug ("%s:%s: message has %u attachments\n", SRCNAME, __func__, n);
+      for (unsigned int i=0; i < n; i++) 
+        signAttachment (hwnd, i, sign_key, opt.passwd_ttl);
+      /* FIXME: we should throw an error if signing of any attachment
+         failed. */
+    }
+
+  set_x_header (message, "GPGOL-VERSION", PACKAGE_VERSION);
+
+  /* Now that we successfully processed the attachments, we can save
+     the changes to the body.  */
+  if (*plaintext)
+    {
+      err = set_message_body (message, signedtext, 0);
+      if (err)
+        goto leave;
+
+      /* In case we don't have attachments, Outlook will really insert
+         the following content type into the header.  We use this to
+         declare that the encrypted content of the message is utf-8
+         encoded. */
+      prop.ulPropTag=PR_CONTENT_TYPE_A;
+      prop.Value.lpszA="text/plain; charset=utf-8"; 
+      hr = HrSetOneProp (message, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set content type: hr=%#lx\n",
+                     SRCNAME, __func__, hr);
+        }
+    }
+  
+  hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: SaveChanges(message) failed: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+
+ leave:
+  xfree (signedtext);
+  gpgme_key_release (sign_key);
+  log_debug ("%s:%s: leave (err=%s)\n", SRCNAME, __func__, op_strerror (err));
+  return err;
+}
+
+
+\f
+/* Encrypt and optionally sign (if SIGN_FLAG is true) the entire
+   message including all attachments.  If WANT_HTML is true, the text
+   to encrypt will be taken from the html property. Returns 0 on
+   success. */
+int 
+GpgMsgImpl::encrypt_and_sign (HWND hwnd, bool want_html, bool sign_flag)
+{
+  log_debug ("%s:%s: enter\n", SRCNAME, __func__);
+  HRESULT hr;
+  gpgme_key_t *keys = NULL;
+  gpgme_key_t sign_key = NULL;
+  const char *plaintext;
+  char *ciphertext = NULL;
+  char **recipients = NULL;
+  char **unknown = NULL;
+  int err = 0;
+  size_t n_keys, n_unknown, n_recp;
+  SPropValue prop;
+    
+  
+  if (!*(plaintext = getOrigText (want_html)) && !hasAttachments ()) 
+    {
+      log_debug ("%s:%s: leave (empty)", SRCNAME, __func__);
+      return 0; 
+    }
+
+  /* Pop up a dialog box to ask for the signer of the message. */
+  if (sign_flag)
+    {
+      if (signer_dialog_box (&sign_key, NULL, 1) == -1)
+        {
+          log_debug ("%s.%s: leave (dialog failed)\n", SRCNAME, __func__);
+          return gpg_error (GPG_ERR_CANCELED);  
+        }
+    }
+
+  /* Gather the keys for the recipients. */
+  recipients = getRecipients ();
+  if ( op_lookup_keys (recipients, &keys, &unknown) )
+    {
+      log_debug ("%s.%s: leave (lookup keys failed)\n", SRCNAME, __func__);
+      return gpg_error (GPG_ERR_GENERAL);  
+    }
+  n_recp = count_strings (recipients);
+  n_keys = count_keys (keys);
+  n_unknown = count_strings (unknown);
+
+  
+  log_debug ("%s:%s: found %d recipients, need %d, unknown=%d\n",
+             SRCNAME, __func__, (int)n_keys, (int)n_recp, (int)n_unknown);
+  
+  if (n_keys != n_recp)
+    {
+      unsigned int opts;
+      gpgme_key_t *keys2;
+
+      log_debug ("%s:%s: calling recipient_dialog_box2", SRCNAME, __func__);
+      opts = recipient_dialog_box2 (keys, unknown, &keys2);
+      free_key_array (keys);
+      keys = keys2;
+      if (opts & OPT_FLAG_CANCEL) 
+        {
+          err = gpg_error (GPG_ERR_CANCELED);
+          goto leave;
+       }
+    }
+
+  if (sign_key)
+    log_debug ("%s:%s: signer: 0x%s %s\n",  SRCNAME, __func__,
+               keyid_from_key (sign_key), userid_from_key (sign_key));
+  else
+    log_debug ("%s:%s: no signer\n", SRCNAME, __func__);
+  if (keys)
+    {
+      for (int i=0; keys[i] != NULL; i++)
+        log_debug ("%s.%s: recp.%d 0x%s %s\n", SRCNAME, __func__,
+                   i, keyid_from_key (keys[i]), userid_from_key (keys[i]));
+    }
+
+  if (*plaintext)
+    {
+      err = op_encrypt (plaintext, &ciphertext, 
+                        keys, sign_key, opt.passwd_ttl);
+      if (err)
+        {
+          MessageBox (hwnd, op_strerror (err),
+                      "GPG Encryption", MB_ICONERROR|MB_OK);
+          goto leave;
+        }
+
+      if (want_html) 
+        {
+          char *tmp = add_html_line_endings (ciphertext);
+          xfree (ciphertext);
+          ciphertext = tmp;
+        }
+
+//       {
+//         SPropValue prop;
+//         prop.ulPropTag=PR_MESSAGE_CLASS_A;
+//         prop.Value.lpszA="IPM.Note.OPENPGP";
+//         hr = HrSetOneProp (message, &prop);
+//         if (hr != S_OK)
+//           {
+//             log_error ("%s:%s: can't set message class: hr=%#lx\n",
+//                        SRCNAME, __func__, hr); 
+//           }
+//       }
+
+    }
+
+  if (hasAttachments ())
+    {
+      unsigned int n;
+      
+      n = getAttachments ();
+      log_debug ("%s:%s: message has %u attachments\n", SRCNAME, __func__, n);
+      for (unsigned int i=0; !err && i < n; i++) 
+        err = encryptAttachment (hwnd, i, keys, NULL, 0);
+      if (err)
+        {
+          MessageBox (hwnd, op_strerror (err),
+                      "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
+          goto leave;
+        }
+    }
+
+  set_x_header (message, "GPGOL-VERSION", PACKAGE_VERSION);
+
+  /* Now that we successfully processed the attachments, we can save
+     the changes to the body.  */
+  if (*plaintext)
+    {
+      if (want_html)
+        {
+          /* We better update the body of the OOM too. */
+          if (put_outlook_property (exchange_cb, "Body", ciphertext))
+            log_error ("%s:%s: put OOM property Body failed\n",
+                       SRCNAME, __func__);
+          /* And set the format to plain text. */
+          if (put_outlook_property_int (exchange_cb, "BodyFormat", 1))
+            log_error ("%s:%s: put OOM property BodyFormat failed\n",
+                       SRCNAME, __func__);
+        }
+
+
+      err = set_message_body (message, ciphertext, want_html);
+      if (err)
+        goto leave;
+
+      /* In case we don't have attachments, Outlook will really insert
+         the following content type into the header.  We use this to
+         declare that the encrypted content of the message is utf-8
+         encoded.  Note that we use plain/text even for HTML because
+         it is base64 encoded. */
+      prop.ulPropTag=PR_CONTENT_TYPE_A;
+      prop.Value.lpszA="text/plain; charset=utf-8"; 
+      hr = HrSetOneProp (message, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set content type: hr=%#lx\n",
+                     SRCNAME, __func__, hr);
+        }
+
+    }
+  
+  hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: SaveChanges(message) failed: hr=%#lx\n",
+                 SRCNAME, __func__, hr); 
+      err = gpg_error (GPG_ERR_GENERAL);
+      goto leave;
+    }
+
+ leave:
+  /* FIXME: What to do with already encrypted attachments if some of
+     the encrypted (or other operations) failed? */
+
+  free_key_array (keys);
+  free_string_array (recipients);
+  free_string_array (unknown);
+  xfree (ciphertext);
+  log_debug ("%s:%s: leave (err=%s)\n", SRCNAME, __func__, op_strerror (err));
+  return err;
+}
+
+
+
+\f
+/* Attach a public key to a message. */
+int 
+GpgMsgImpl::attachPublicKey (const char *keyid)
+{
+    /* @untested@ */
+#if 0
+    const char *patt[1];
+    char *keyfile;
+    int err, pos = 0;
+    LPATTACH newatt;
+
+    keyfile = generateTempname (keyid);
+    patt[0] = xstrdup (keyid);
+    err = op_export_keys (patt, keyfile);
+
+    newatt = createAttachment (NULL/*FIXME*/,pos);
+    setAttachMethod (newatt, ATTACH_BY_VALUE);
+    setAttachFilename (newatt, keyfile, false);
+    /* XXX: set proper RFC3156 MIME types. */
+
+    if (streamFromFile (keyfile, newatt)) {
+       log_debug ("attachPublicKey: commit changes.\n");
+       newatt->SaveChanges (FORCE_SAVE);
+    }
+    releaseAttachment (newatt);
+    xfree (keyfile);
+    xfree ((void *)patt[0]);
+    return err;
+#endif
+    return -1;
+}
+
+
+
+
+\f
+/* Returns whether the message has any attachments. */
+bool
+GpgMsgImpl::hasAttachments (void)
+{
+  return !!getAttachments ();
+}
+
+
+/* Reads the attachment information and returns the number of
+   attachments. */
+unsigned int
+GpgMsgImpl::getAttachments (void)
+{
+  SizedSPropTagArray (1L, propAttNum) = {
+    1L, {PR_ATTACH_NUM}
+  };
+  HRESULT hr;    
+  LPMAPITABLE table;
+  LPSRowSet   rows;
+
+  if (!message)
+    return 0;
+
+  if (!attach.att_table)
+    {
+      hr = message->GetAttachmentTable (0, &table);
+      if (FAILED (hr))
+        {
+          log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
+                     SRCNAME, __func__, hr);
+          return 0;
         }
       
       hr = HrQueryAllRows (table, (LPSPropTagArray)&propAttNum,
@@ -559,17 +1494,19 @@ GpgMsgImpl::getAttachments (void)
       if (FAILED (hr))
         {
           log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
-                     __FILE__, __func__, hr);
+                     SRCNAME, __func__, hr);
           table->Release ();
           return 0;
         }
-      attach.table = table;
+      attach.att_table = table;
       attach.rows = rows;
     }
 
   return attach.rows->cRows > 0? attach.rows->cRows : 0;
 }
 
+
+
 /* Return the attachment method for attachment OBJ. In case of error we
    return 0 which happens to be not defined. */
 static int
@@ -583,7 +1520,7 @@ get_attach_method (LPATTACH obj)
   if (FAILED (hr))
     {
       log_error ("%s:%s: error getting attachment method: hr=%#lx",
-                 __FILE__, __func__, hr);
+                 SRCNAME, __func__, hr);
       return 0; 
     }
   /* We don't bother checking whether we really get a PT_LONG ulong
@@ -595,6 +1532,177 @@ get_attach_method (LPATTACH obj)
 }
 
 
+/* Return the content-type of the attachment OBJ or NULL if it does not
+   exists.  Caller must free. */
+static char *
+get_attach_mime_tag (LPATTACH obj)
+{
+  HRESULT hr;
+  LPSPropValue propval = NULL;
+  char *name;
+
+  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
+  if (FAILED (hr))
+    {
+      log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      return NULL; 
+    }
+  switch ( PROP_TYPE (propval->ulPropTag) )
+    {
+    case PT_UNICODE:
+      name = wchar_to_utf8 (propval->Value.lpszW);
+      if (!name)
+        log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
+      break;
+      
+    case PT_STRING8:
+      name = xstrdup (propval->Value.lpszA);
+      break;
+      
+    default:
+      log_debug ("%s:%s: proptag=%#lx not supported\n",
+                 SRCNAME, __func__, propval->ulPropTag);
+      name = NULL;
+      break;
+    }
+  MAPIFreeBuffer (propval);
+  return name;
+}
+
+
+/* Return the data property of an attachments or NULL in case of an
+   error.  Caller must free.  Note, that this routine should only be
+   used for short data objects like detached signatures. */
+static char *
+get_short_attach_data (LPATTACH obj)
+{
+  HRESULT hr;
+  LPSPropValue propval = NULL;
+  char *data;
+
+  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_DATA_BIN, &propval);
+  if (FAILED (hr))
+    {
+      log_error ("%s:%s: error getting attachment's data: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      return NULL; 
+    }
+  switch ( PROP_TYPE (propval->ulPropTag) )
+    {
+    case PT_BINARY:
+      /* This is a binary obnject but we know that it must be plain
+         ASCII due to the armoed format.  */
+      data = (char*)xmalloc (propval->Value.bin.cb + 1);
+      memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
+      data[propval->Value.bin.cb] = 0;
+      break;
+      
+    default:
+      log_debug ("%s:%s: proptag=%#lx not supported\n",
+                 SRCNAME, __func__, propval->ulPropTag);
+      data = NULL;
+      break;
+    }
+  MAPIFreeBuffer (propval);
+  return data;
+}
+
+
+/* Check whether the attachment at position POS in the attachment
+   table is the first part of a PGP/MIME message.  This routine should
+   only be called if it has already been checked that the content-type
+   of the attachment is application/pgp-encrypted. */
+bool
+GpgMsgImpl::isPgpmimeVersionPart (int pos)
+{
+  HRESULT hr;
+  LPATTACH att;
+  LPSPropValue propval = NULL;
+  bool result = false;
+
+  hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att);        
+  if (FAILED(hr))
+    return false;
+
+  hr = HrGetOneProp ((LPMAPIPROP)att, PR_ATTACH_SIZE, &propval);
+  if (FAILED (hr))
+    {
+      att->Release ();
+      return false;
+    }
+  if ( PROP_TYPE (propval->ulPropTag) != PT_LONG
+      || propval->Value.l < 10 || propval->Value.l > 1000 )
+    {
+      MAPIFreeBuffer (propval);
+      att->Release ();
+      return false;
+    }
+  MAPIFreeBuffer (propval);
+
+  hr = HrGetOneProp ((LPMAPIPROP)att, PR_ATTACH_DATA_BIN, &propval);
+  if (SUCCEEDED (hr))
+    {
+      if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
+        {
+          if (propval->Value.bin.cb > 10 && propval->Value.bin.cb < 15 
+              && !memcmp (propval->Value.bin.lpb, "Version: 1", 10)
+              && ( propval->Value.bin.lpb[10] == '\r'
+                   || propval->Value.bin.lpb[10] == '\n'))
+            result = true;
+        }
+      MAPIFreeBuffer (propval);
+    }
+  att->Release ();
+  return result;
+}
+
+
+
+/* Set an arbitary header in the message MSG with NAME to the value
+   VAL. */
+static bool 
+set_x_header (LPMESSAGE msg, const char *name, const char *val)
+{  
+  HRESULT hr;
+  LPSPropTagArray pProps = NULL;
+  SPropValue pv;
+  MAPINAMEID mnid, *pmnid;     
+  /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
+  GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
+                                            0x00, 0x00, 0x00, 0x46} };
+
+  if (!msg)
+    return false;
+
+  memset (&mnid, 0, sizeof mnid);
+  mnid.lpguid = &guid;
+  mnid.ulKind = MNID_STRING;
+  mnid.Kind.lpwstrName = utf8_to_wchar (name);
+  pmnid = &mnid;
+  hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
+  xfree (mnid.Kind.lpwstrName);
+  if (FAILED (hr)) 
+    {
+      log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
+                 SRCNAME, __func__, name, hr); 
+      return false;
+    }
+    
+  pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
+  pv.Value.lpszA = (char *)val;
+  hr = HrSetOneProp(msg, &pv); 
+  if (hr != S_OK)
+    {
+      log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
+                 SRCNAME, __func__, name, hr); 
+      return false;
+    }
+  return true;
+}
+
+
+
 /* Return the filename from the attachment as a malloced string.  The
    encoding we return will be utf8, however the MAPI docs declare that
    MAPI does only handle plain ANSI and thus we don't really care
@@ -605,94 +1713,390 @@ static char *
 get_attach_filename (LPATTACH obj)
 {
   HRESULT hr;
-  LPSPropValue propval;
-  char *name = NULL;
+  LPSPropValue propval;
+  char *name = NULL;
+
+  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
+  if (FAILED(hr)) 
+    hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
+  if (FAILED(hr))
+    {
+      log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
+      return NULL;
+    }
+
+  switch ( PROP_TYPE (propval->ulPropTag) )
+    {
+    case PT_UNICODE:
+      name = wchar_to_utf8 (propval->Value.lpszW);
+      if (!name)
+        log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
+      break;
+      
+    case PT_STRING8:
+      name = xstrdup (propval->Value.lpszA);
+      break;
+      
+    default:
+      log_debug ("%s:%s: proptag=%#lx not supported\n",
+                 SRCNAME, __func__, propval->ulPropTag);
+      name = NULL;
+      break;
+    }
+  MAPIFreeBuffer (propval);
+  return name;
+}
+
+
+
+\f
+/* Read the attachment ATT and try to detect whether this is a PGP
+   Armored message.  METHOD is the attach method of ATT.  Returns 0 if
+   it is not a PGP attachment. */
+static armor_t
+get_pgp_armor_type (LPATTACH att, int method)
+{
+  HRESULT hr;
+  LPSTREAM stream;
+  char buffer [128];
+  ULONG nread;
+  const char *s;
+
+  if (method != ATTACH_BY_VALUE)
+    return ARMOR_NONE;
+  
+  hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
+                          0, 0, (LPUNKNOWN*) &stream);
+  if (FAILED (hr))
+    {
+      log_debug ("%s:%s: can't attachment data: hr=%#lx",
+                 SRCNAME, __func__,  hr);
+      return ARMOR_NONE;
+    }
+
+  hr = stream->Read (buffer, sizeof buffer -1, &nread);
+  if ( hr != S_OK )
+    {
+      log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
+      stream->Release ();
+      return ARMOR_NONE;
+    }
+  buffer[nread] = 0;
+  stream->Release ();
+
+  s = strstr (buffer, "-----BEGIN PGP ");
+  if (!s)
+    return ARMOR_NONE;
+  s += 15;
+  if (!strncmp (s, "MESSAGE-----", 12))
+    return ARMOR_MESSAGE;
+  else if (!strncmp (s, "SIGNATURE-----", 14))
+    return ARMOR_SIGNATURE;
+  else if (!strncmp (s, "SIGNED MESSAGE-----", 19))
+    return ARMOR_SIGNED;
+  else if (!strncmp (s, "ARMORED FILE-----", 17))
+    return ARMOR_FILE;
+  else if (!strncmp (s, "PUBLIC KEY BLOCK-----", 21))
+    return ARMOR_PUBKEY;
+  else if (!strncmp (s, "PRIVATE KEY BLOCK-----", 22))
+    return ARMOR_SECKEY;
+  else if (!strncmp (s, "SECRET KEY BLOCK-----", 21))
+    return ARMOR_SECKEY;
+  else
+    return ARMOR_NONE;
+}
+
+
+/* Gather information about attachments and return a new object with
+   these information.  Caller must release the returned information.
+   The routine will return NULL in case of an error or if no
+   attachments are available. */
+attach_info_t
+GpgMsgImpl::gatherAttachmentInfo (void)
+{    
+  HRESULT hr;
+  attach_info_t table;
+  unsigned int pos, n_attach;
+  const char *s;
+  unsigned int attestation_count = 0;
+  unsigned int invalid_count = 0;
+
+  is_pgpmime = false;
+  has_attestation = false;
+  n_attach = getAttachments ();
+  log_debug ("%s:%s: message has %u attachments\n",
+             SRCNAME, __func__, n_attach);
+  if (!n_attach)
+      return NULL;
+
+  table = (attach_info_t)xcalloc (n_attach+1, sizeof *table);
+  for (pos=0; pos < n_attach; pos++) 
+    {
+      LPATTACH att;
+
+      hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att);    
+      if (FAILED (hr))
+        {
+          log_error ("%s:%s: can't open attachment %d: hr=%#lx",
+                     SRCNAME, __func__, pos, hr);
+          table[pos].invalid = 1;
+          invalid_count++;
+          continue;
+        }
+
+      table[pos].method = get_attach_method (att);
+      table[pos].filename = get_attach_filename (att);
+      table[pos].content_type = get_attach_mime_tag (att);
+      if (table[pos].content_type)
+        {
+          char *p = strchr (table[pos].content_type, ';');
+          if (p)
+            {
+              *p++ = 0;
+              trim_trailing_spaces (table[pos].content_type);
+              while (strchr (" \t\r\n", *p))
+                p++;
+              trim_trailing_spaces (p);
+              table[pos].content_type_parms = p;
+            }
+          if (!stricmp (table[pos].content_type, "text/plain")
+              && table[pos].filename 
+              && (s = strrchr (table[pos].filename, '.'))
+              && !stricmp (s, ".asc"))
+            table[pos].armor_type = get_pgp_armor_type (att,table[pos].method);
+        }
+      if (table[pos].filename
+          && !stricmp (table[pos].filename, "GPGol-Attestation.txt")
+          && table[pos].content_type
+          && !stricmp (table[pos].content_type, "text/plain"))
+        {
+          has_attestation = true;
+          attestation_count++;
+        }
+
+      att->Release ();
+    }
+  table[pos].end_of_table = 1;
+
+  /* Figure out whether there are encrypted attachments. */
+  for (pos=0; !table[pos].end_of_table; pos++)
+    {
+      if (table[pos].invalid)
+        continue;
+      if (table[pos].armor_type == ARMOR_MESSAGE)
+        table[pos].is_encrypted = 1;
+      else if (table[pos].filename && (s = strrchr (table[pos].filename, '.'))
+               &&  (!stricmp (s, ".pgp") || !stricmp (s, ".gpg")))
+        table[pos].is_encrypted = 1;
+      else if (table[pos].content_type  
+               && ( !stricmp (table[pos].content_type,
+                              "application/pgp-encrypted")
+                   || (!stricmp (table[pos].content_type,
+                                 "multipart/encrypted")
+                       && table[pos].content_type_parms
+                       && strstr (table[pos].content_type_parms,
+                                  "application/pgp-encrypted"))
+                   || (!stricmp (table[pos].content_type,
+                                 "application/pgp")
+                       && table[pos].content_type_parms
+                       && strstr (table[pos].content_type_parms,
+                                  "x-action=encrypt"))))
+        table[pos].is_encrypted = 1;
+    }
+     
+  /* Figure out what attachments are signed. */
+  for (pos=0; !table[pos].end_of_table; pos++)
+    {
+      if (table[pos].invalid)
+        continue;
+      if (table[pos].filename && (s = strrchr (table[pos].filename, '.'))
+          &&  !stricmp (s, ".asc")
+          && table[pos].content_type  
+          && !stricmp (table[pos].content_type, "application/pgp-signature"))
+        {
+          size_t len = (s - table[pos].filename);
+
+          /* We mark the actual file, assuming that the .asc is a
+             detached signature.  To correlate the data file and the
+             signature we keep track of the POS. */
+          for (unsigned int i=0; !table[i].end_of_table; i++)
+            {
+              if (table[i].invalid)
+                continue;
+              if (i != pos && table[i].filename 
+                  && strlen (table[i].filename) == len
+                  && !strncmp (table[i].filename, table[pos].filename, len))
+                {
+                  table[i].is_signed = 1;
+                  table[i].sig_pos = pos;
+                }
+            }
+          
+        }
+      else if (table[pos].content_type  
+               && (!stricmp (table[pos].content_type, "application/pgp")
+                   && table[pos].content_type_parms
+                   && strstr (table[pos].content_type_parms,"x-action=sign")))
+        table[pos].is_signed = 1;
+    }
+
+  log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
+  for (pos=0; !table[pos].end_of_table; pos++)
+    {
+      if (table[pos].invalid)
+        continue;
+      log_debug ("\t%d %d %d %u %d `%s' `%s' `%s'\n",
+                 pos, table[pos].is_encrypted,
+                 table[pos].is_signed, table[pos].sig_pos,
+                 table[pos].armor_type,
+                 table[pos].filename, table[pos].content_type,
+                 table[pos].content_type_parms);
+    }
+
+  /* Simple check whether this is PGP/MIME encrypted.  At least with
+     OL2003 the content-type of the body is also correctly set but we
+     don't make use of this as it is not clear whether this is true
+     for other storage providers.  We use a hack to ignore extra
+     attesttation attachments: Those are assumed to come after the
+     both PGP/MIME parts. */
+  if (opt.compat.no_pgpmime)
+    ;
+  else if (pos == 2 + attestation_count + invalid_count
+           && table[0].content_type && table[1].content_type
+           && !stricmp (table[0].content_type, "application/pgp-encrypted")
+           && !stricmp (table[1].content_type, "application/octet-stream")
+           && isPgpmimeVersionPart (0))
+    {
+      log_debug ("\tThis is a PGP/MIME encrypted message - table adjusted");
+      table[0].is_encrypted = 0;
+      table[1].is_encrypted = 1;
+      is_pgpmime = true;
+    }
+
+  return table;
+}
+
+
+
+\f
+/* Verify the attachment as recorded in TABLE and at table position
+   POS_DATA against the signature at position POS_SIG.  Display the
+   status for each signature. */
+void
+GpgMsgImpl::verifyAttachment (HWND hwnd, attach_info_t table,
+                              unsigned int pos_data,
+                              unsigned int pos_sig)
+
+{    
+  HRESULT hr;
+  LPATTACH att;
+  int err;
+  char *sig_data;
 
-  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
-  if (FAILED(hr)) 
-    hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
-  if (FAILED(hr))
+  log_debug ("%s:%s: verifying attachment %d/%d",
+             SRCNAME, __func__, pos_data, pos_sig);
+
+  assert (table);
+  assert (message);
+
+  /* First we copy the actual signature into a memory buffer.  Such a
+     signature is expected to be samll enough to be readable directly
+     (i.e.less that 16k as suggested by the MS MAPI docs). */
+  hr = message->OpenAttach (pos_sig, NULL, MAPI_BEST_ACCESS, &att);    
+  if (FAILED (hr))
     {
-      log_debug ("%s:%s: no filename property found", __FILE__, __func__);
-      return NULL;
+      log_error ("%s:%s: can't open attachment %d (sig): hr=%#lx",
+                 SRCNAME, __func__, pos_sig, hr);
+      return;
     }
 
-  switch ( PROP_TYPE (propval->ulPropTag) )
+  if ( table[pos_sig].method == ATTACH_BY_VALUE )
+    sig_data = get_short_attach_data (att);
+  else
     {
-    case PT_UNICODE:
-      name = wchar_to_utf8 (propval->Value.lpszW);
-      if (!name)
-        log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
-      break;
-      
-    case PT_STRING8:
-      name = xstrdup (propval->Value.lpszA);
-      break;
-      
-    default:
-      log_debug ("%s:%s: proptag=%xlx not supported\n",
-                 __FILE__, __func__, propval->ulPropTag);
-      break;
+      log_error ("%s:%s: attachment %d (sig): method %d not supported",
+                 SRCNAME, __func__, pos_sig, table[pos_sig].method);
+      att->Release ();
+      return;
     }
-  MAPIFreeBuffer (propval);
-  return name;
-}
-
-
+  att->Release ();
+  if (!sig_data)
+    return; /* Problem getting signature; error has already been
+               logged. */
 
+  /* Now get on with the actual signed data. */
+  hr = message->OpenAttach (pos_data, NULL, MAPI_BEST_ACCESS, &att);   
+  if (FAILED (hr))
+    {
+      log_error ("%s:%s: can't open attachment %d (data): hr=%#lx",
+                 SRCNAME, __func__, pos_data, hr);
+      xfree (sig_data);
+      return;
+    }
 
-/* Return a filename to be used for saving an attachment. Returns an
-   malloced string on success. HWND is the current Window and SRCNAME
-   the filename to be used as suggestion.  On error; i.e. cancel NULL
-   is returned. */
-static char *
-get_save_filename (HWND root, const char *srcname)
-                                    
-{
-  char filter[] = "All Files (*.*)\0*.*\0\0";
-  char fname[MAX_PATH+1];
-  const char *s;
-  OPENFILENAME ofn;
+  if ( table[pos_data].method == ATTACH_BY_VALUE )
+    {
+      LPSTREAM stream;
 
-  memset (fname, 0, sizeof (fname));
-  memset (&ofn, 0, sizeof (ofn));
-  ofn.lStructSize = sizeof (ofn);
-  ofn.hwndOwner = root;
-  ofn.lpstrFile = fname;
-  ofn.nMaxFile = MAX_PATH;
-  ofn.lpstrFileTitle = NULL;
-  ofn.nMaxFileTitle = 0;
-  ofn.Flags |= OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
-  ofn.lpstrTitle = "GPG - Save decrypted attachments";
-  ofn.lpstrFilter = filter;
+      hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
+                              0, 0, (LPUNKNOWN*) &stream);
+      if (FAILED (hr))
+        {
+          log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
+                     SRCNAME, __func__, pos_data, hr);
+          goto leave;
+        }
+      err = op_verify_detached_sig (stream, sig_data,
+                                    table[pos_data].filename, attestation);
+      if (err)
+        {
+          log_debug ("%s:%s: verify detached signature failed: %s",
+                     SRCNAME, __func__, op_strerror (err)); 
+          MessageBox (hwnd, op_strerror (err),
+                      "GPG Attachment Verification", MB_ICONERROR|MB_OK);
+        }
+      stream->Release ();
+    }
+  else
+    {
+      log_error ("%s:%s: attachment %d (data): method %d not supported",
+                 SRCNAME, __func__, pos_data, table[pos_data].method);
+    }
 
-  if (GetSaveFileName (&ofn))
-    return xstrdup (fname);
-  return NULL;
+ leave:
+  /* Close this attachment. */
+  xfree (sig_data);
+  att->Release ();
 }
 
 
-
 /* Decrypt the attachment with the internal number POS.
    SAVE_PLAINTEXT must be true to save the attachemnt; displaying a
-   attachment is not yet supported. */
+   attachment is not yet supported.  If FILENAME is not NULL it will
+   be displayed along with status outputs. */
 void
-GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext)
+GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext,
+                               int ttl, const char *filename)
 {    
   HRESULT hr;
   LPATTACH att;
   int method, err;
+  LPATTACH newatt = NULL;
+  char *outname = NULL;
+  
+
+  log_debug ("%s:%s: processing attachment %d", SRCNAME, __func__, pos);
 
   /* Make sure that we can access the attachment table. */
   if (!message || !getAttachments ())
     {
-      log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
+      log_debug ("%s:%s: no attachemnts at all", SRCNAME, __func__);
       return;
     }
 
   if (!save_plaintext)
     {
-      log_error ("%s:%s: save_plaintext not requested", __FILE__, __func__);
+      log_error ("%s:%s: save_plaintext not requested", SRCNAME, __func__);
       return;
     }
 
@@ -700,7 +2104,7 @@ GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext)
   if (FAILED (hr))
     {
       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
-                 __FILE__, __func__, pos, hr);
+                 SRCNAME, __func__, pos, hr);
       return;
     }
 
@@ -721,7 +2125,7 @@ GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext)
       if (FAILED (hr))
         {
           log_error ("%s:%s: can't open data obj of attachment %d: hr=%#lx",
-                     __FILE__, __func__, pos, hr);
+                     SRCNAME, __func__, pos, hr);
           goto leave;
         }
 
@@ -737,93 +2141,180 @@ GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext)
     }
   else if (method == ATTACH_BY_VALUE)
     {
-      char *outname;
+      char *s;
       char *suggested_name;
       LPSTREAM from, to;
 
       suggested_name = get_attach_filename (att);
-      /* FIXME: WHY do we need this check?
-        if (checkAttachmentExtension (strrchr (tmp, '.')) == false)
-          {
-             log_debug ( "%s: no pgp extension found.\n", tmp);
-             xfree (tmp);
-             xfree (inname);
-             r eturn TRUE;
-             } */
-      outname = get_save_filename (hwnd, suggested_name);
-      xfree (suggested_name);
-
+      if (suggested_name)
+        log_debug ("%s:%s: attachment %d, filename `%s'", 
+                   SRCNAME, __func__, pos, suggested_name);
+      /* Strip of know extensions or use a default name. */
+      if (!suggested_name)
+        {
+          xfree (suggested_name);
+          suggested_name = (char*)xmalloc (50);
+          snprintf (suggested_name, 49, "unnamed-%d.dat", pos);
+        }
+      else if ((s = strrchr (suggested_name, '.'))
+               && (!stricmp (s, ".pgp") 
+                   || !stricmp (s, ".gpg") 
+                   || !stricmp (s, ".asc")) )
+        {
+          *s = 0;
+        }
+      if (opt.save_decrypted_attach)
+        outname = suggested_name;
+      else
+        {
+          outname = get_save_filename (hwnd, suggested_name);
+          xfree (suggested_name);
+        }
+      
       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
                               0, 0, (LPUNKNOWN*) &from);
       if (FAILED (hr))
         {
           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
-                     __FILE__, __func__, pos, hr);
-          xfree (outname);
+                     SRCNAME, __func__, pos, hr);
           goto leave;
         }
 
-      /* If we would want to write to a temporary file, we would use:
-         hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
-                                (SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
-                                 |STGM_CREATE | STGM_READWRITE),
-                                 NULL, "gpg", &to);    */
-      hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
-                             (STGM_CREATE | STGM_READWRITE),
-                             outname, NULL, &to); 
-      if (FAILED (hr)) 
+
+      if (opt.save_decrypted_attach) /* Decrypt and save in the MAPI. */
         {
-          log_error ("%s:%s: can't create stream for `%s': hr=%#lx\n",
-                     __FILE__, __func__, outname, hr); 
+          ULONG newpos;
+          SPropValue prop;
+
+          hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
+          if (hr != S_OK)
+            {
+              log_error ("%s:%s: can't create attachment: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+              goto leave;
+            }
+          
+          prop.ulPropTag = PR_ATTACH_METHOD;
+          prop.Value.ul = ATTACH_BY_VALUE;
+          hr = HrSetOneProp (newatt, &prop);
+          if (hr != S_OK)
+            {
+              log_error ("%s:%s: can't set attach method: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+              goto leave;
+            }
+          
+          prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
+          prop.Value.lpszA = outname;   
+          hr = HrSetOneProp (newatt, &prop);
+          if (hr != S_OK)
+            {
+              log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+              goto leave;
+            }
+          log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
+                     SRCNAME, __func__, pos, newpos, outname);
+          
+
+          hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
+                                     MAPI_CREATE|MAPI_MODIFY, (LPUNKNOWN*)&to);
+          if (FAILED (hr)) 
+            {
+              log_error ("%s:%s: can't create output stream: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+              goto leave;
+            }
+      
+          err = op_decrypt_stream (from, to, ttl, filename, attestation);
+          if (err)
+            {
+              log_debug ("%s:%s: decrypt stream failed: %s",
+                         SRCNAME, __func__, op_strerror (err)); 
+              to->Revert ();
+              to->Release ();
+              from->Release ();
+              MessageBox (hwnd, op_strerror (err),
+                          "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
+              goto leave;
+            }
+        
+          to->Commit (0);
+          to->Release ();
           from->Release ();
-          xfree (outname);
-          goto leave;
+
+          hr = newatt->SaveChanges (0);
+          if (hr != S_OK)
+            {
+              log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
+                         SRCNAME, __func__, hr); 
+              goto leave;
+            }
+
+          /* Delete the orginal attachment. FIXME: Should we really do
+             that or better just mark it in the table and delete
+             later? */
+          att->Release ();
+          att = NULL;
+          if (message->DeleteAttach (pos, 0, NULL, 0) == S_OK)
+            log_error ("%s:%s: failed to delete attachment %d: %s",
+                       SRCNAME, __func__, pos, op_strerror (err)); 
+          
         }
-      
-      err = op_decrypt_stream (from, to);
-      if (err)
+      else  /* Save attachment to a file. */
         {
-          log_debug ("%s:%s: decrypt stream failed: %s",
-                     __FILE__, __func__, op_strerror (err)); 
-          to->Revert ();
+          hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
+                                 (STGM_CREATE | STGM_READWRITE),
+                                 outname, NULL, &to); 
+          if (FAILED (hr)) 
+            {
+              log_error ("%s:%s: can't create stream for `%s': hr=%#lx\n",
+                         SRCNAME, __func__, outname, hr); 
+              from->Release ();
+              goto leave;
+            }
+      
+          err = op_decrypt_stream (from, to, ttl, filename, attestation);
+          if (err)
+            {
+              log_debug ("%s:%s: decrypt stream failed: %s",
+                         SRCNAME, __func__, op_strerror (err)); 
+              to->Revert ();
+              to->Release ();
+              from->Release ();
+              MessageBox (hwnd, op_strerror (err),
+                          "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
+              /* FIXME: We might need to delete outname now.  However a
+                 sensible implementation of the stream object should have
+                 done it through the Revert call. */
+              goto leave;
+            }
+        
+          to->Commit (0);
           to->Release ();
           from->Release ();
-          MessageBox (NULL, op_strerror (err),
-                      "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
-          /* FIXME: We might need to delete outname now.  However a
-             sensible implementation of the stream object should have
-             done it trhough the Revert call. */
-          xfree (outname);
-          goto leave;
         }
-        
-      to->Commit (0);
-      to->Release ();
-      from->Release ();
-
-      /*  Hmmm: Why are we deleting the attachment now????? 
-          Disabled until clarified.   FIXME */
-      //if (message->DeleteAttach (pos, 0, NULL, 0) == S_OK)
-      //   show error;
-
-      xfree (outname);
+      
     }
   else
     {
       log_error ("%s:%s: attachment %d: method %d not supported",
-                 __FILE__, __func__, pos, method);
+                 SRCNAME, __func__, pos, method);
     }
 
  leave:
-  /* Close this attachment. */
-  att->Release ();
+  xfree (outname);
+  if (newatt)
+    newatt->Release ();
+  if (att)
+    att->Release ();
 }
 
 
-/* Sign the attachment with the internal number POS.  TTL is caching
+/* Sign the attachment with the internal number POS.  TTL is the caching
    time for a required passphrase. */
 void
-GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
+GpgMsgImpl::signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl)
 {    
   HRESULT hr;
   LPATTACH att;
@@ -836,7 +2327,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
   /* Make sure that we can access the attachment table. */
   if (!message || !getAttachments ())
     {
-      log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
+      log_debug ("%s:%s: no attachemnts at all", SRCNAME, __func__);
       return;
     }
 
@@ -844,7 +2335,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
   if (FAILED (hr))
     {
       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
-                 __FILE__, __func__, pos, hr);
+                 SRCNAME, __func__, pos, hr);
       return;
     }
 
@@ -868,7 +2359,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
   if (method == ATTACH_EMBEDDED_MSG)
     {
       log_debug ("%s:%s: signing embedded attachments is not supported",
-                 __FILE__, __func__);
+                 SRCNAME, __func__);
     }
   else if (method == ATTACH_BY_VALUE)
     {
@@ -880,7 +2371,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
       if (FAILED (hr))
         {
           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
-                     __FILE__, __func__, pos, hr);
+                     SRCNAME, __func__, pos, hr);
           goto leave;
         }
 
@@ -888,7 +2379,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
       if (hr != S_OK)
         {
           log_error ("%s:%s: can't create attachment: hr=%#lx\n",
-                     __FILE__, __func__, hr); 
+                     SRCNAME, __func__, hr); 
           goto leave;
         }
 
@@ -898,7 +2389,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
       if (hr != S_OK)
         {
           log_error ("%s:%s: can't set attach method: hr=%#lx\n",
-                     __FILE__, __func__, hr); 
+                     SRCNAME, __func__, hr); 
           goto leave;
         }
       
@@ -908,28 +2399,59 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
       if (hr != S_OK)
         {
           log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
-                     __FILE__, __func__, hr); 
+                     SRCNAME, __func__, hr); 
           goto leave;
         }
       log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
-                 __FILE__, __func__, pos, newpos, signame);
+                 SRCNAME, __func__, pos, newpos, signame);
+
+      prop.ulPropTag = PR_ATTACH_EXTENSION_A;
+      prop.Value.lpszA = ".pgpsig";   
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          goto leave;
+        }
+
+      prop.ulPropTag = PR_ATTACH_TAG;
+      prop.Value.bin.cb  = sizeof oid_mimetag;
+      prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          goto leave;
+        }
+
+      prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
+      prop.Value.lpszA = "application/pgp-signature";
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          goto leave;
+        }
 
       hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
                                  MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
       if (FAILED (hr)) 
         {
           log_error ("%s:%s: can't create output stream: hr=%#lx\n",
-                     __FILE__, __func__, hr); 
+                     SRCNAME, __func__, hr); 
           goto leave;
         }
       
-      err = op_sign_stream (from, to, OP_SIG_DETACH, ttl);
+      err = op_sign_stream (from, to, OP_SIG_DETACH, sign_key, ttl);
       if (err)
         {
           log_debug ("%s:%s: sign stream failed: %s",
-                     __FILE__, __func__, op_strerror (err)); 
+                     SRCNAME, __func__, op_strerror (err)); 
           to->Revert ();
-          MessageBox (NULL, op_strerror (err),
+          MessageBox (hwnd, op_strerror (err),
                       "GPG Attachment Signing", MB_ICONERROR|MB_OK);
           goto leave;
         }
@@ -943,7 +2465,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
       if (hr != S_OK)
         {
           log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
-                     __FILE__, __func__, hr); 
+                     SRCNAME, __func__, hr); 
           goto leave;
         }
 
@@ -951,7 +2473,7 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
   else
     {
       log_error ("%s:%s: attachment %d: method %d not supported",
-                 __FILE__, __func__, pos, method);
+                 SRCNAME, __func__, pos, method);
     }
 
  leave:
@@ -965,3 +2487,206 @@ GpgMsgImpl::signAttachment (HWND hwnd, int pos, int ttl)
 
   att->Release ();
 }
+
+/* Encrypt the attachment with the internal number POS.  KEYS is a
+   NULL terminates array with recipients to whom the message should be
+   encrypted.  If SIGN_KEY is not NULL the attachment will also get
+   signed. TTL is the passphrase caching time and only used if
+   SIGN_KEY is not NULL. Returns 0 on success. */
+int
+GpgMsgImpl::encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
+                               gpgme_key_t sign_key, int ttl)
+{    
+  HRESULT hr;
+  LPATTACH att;
+  int method, err;
+  LPSTREAM from = NULL;
+  LPSTREAM to = NULL;
+  char *filename = NULL;
+  LPATTACH newatt = NULL;
+
+  /* Make sure that we can access the attachment table. */
+  if (!message || !getAttachments ())
+    {
+      log_debug ("%s:%s: no attachemnts at all", SRCNAME, __func__);
+      return 0;
+    }
+
+  hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att);        
+  if (FAILED (hr))
+    {
+      log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
+                 SRCNAME, __func__, pos, hr);
+      err = gpg_error (GPG_ERR_GENERAL);
+      return err;
+    }
+
+  /* Construct a filename for the new attachment. */
+  {
+    char *tmpname = get_attach_filename (att);
+    if (!tmpname)
+      {
+        filename = (char*)xmalloc (70);
+        snprintf (filename, 70, "gpg-encrypted-%d.pgp", pos);
+      }
+    else
+      {
+        filename = (char*)xmalloc (strlen (tmpname) + 4 + 1);
+        strcpy (stpcpy (filename, tmpname), ".pgp");
+        xfree (tmpname);
+      }
+  }
+
+  method = get_attach_method (att);
+  if (method == ATTACH_EMBEDDED_MSG)
+    {
+      log_debug ("%s:%s: encrypting embedded attachments is not supported",
+                 SRCNAME, __func__);
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+  else if (method == ATTACH_BY_VALUE)
+    {
+      ULONG newpos;
+      SPropValue prop;
+
+      hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
+                              0, 0, (LPUNKNOWN*)&from);
+      if (FAILED (hr))
+        {
+          log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
+                     SRCNAME, __func__, pos, hr);
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't create attachment: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      prop.ulPropTag = PR_ATTACH_METHOD;
+      prop.Value.ul = ATTACH_BY_VALUE;
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach method: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+      
+      prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
+      prop.Value.lpszA = filename;   
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+      log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
+                 SRCNAME, __func__, pos, newpos, filename);
+
+      prop.ulPropTag = PR_ATTACH_EXTENSION_A;
+      prop.Value.lpszA = ".pgpenc";   
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      prop.ulPropTag = PR_ATTACH_TAG;
+      prop.Value.bin.cb  = sizeof oid_mimetag;
+      prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
+      prop.Value.lpszA = "application/pgp-encrypted";
+      hr = HrSetOneProp (newatt, &prop);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
+                                 MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
+      if (FAILED (hr)) 
+        {
+          log_error ("%s:%s: can't create output stream: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+      
+      err = op_encrypt_stream (from, to, keys, sign_key, ttl);
+      if (err)
+        {
+          log_debug ("%s:%s: encrypt stream failed: %s",
+                     SRCNAME, __func__, op_strerror (err)); 
+          to->Revert ();
+          MessageBox (hwnd, op_strerror (err),
+                      "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
+          goto leave;
+        }
+      from->Release ();
+      from = NULL;
+      to->Commit (0);
+      to->Release ();
+      to = NULL;
+
+      hr = newatt->SaveChanges (0);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+      hr = message->DeleteAttach (pos, 0, NULL, 0);
+      if (hr != S_OK)
+        {
+          log_error ("%s:%s: DeleteAtatch failed: hr=%#lx\n",
+                     SRCNAME, __func__, hr); 
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
+
+    }
+  else
+    {
+      log_error ("%s:%s: attachment %d: method %d not supported",
+                 SRCNAME, __func__, pos, method);
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
+ leave:
+  if (from)
+    from->Release ();
+  if (to)
+    to->Release ();
+  xfree (filename);
+  if (newatt)
+    newatt->Release ();
+
+  att->Release ();
+  return err;
+}