Many more cleanups. MapiGPGME has gone.
[gpgol.git] / src / gpgmsg.cpp
1 /* gpgmsg.cpp - Implementation ofthe GpgMsg class
2  *      Copyright (C) 2005 g10 Code GmbH
3  *
4  * This file is part of OutlGPG.
5  * 
6  * OutlGPG is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  * 
11  * OutlGPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU Lesser General Public License for more details.
15  * 
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <windows.h>
27 #include <assert.h>
28 #include <string.h>
29
30 #include "mymapi.h"
31 #include "mymapitags.h"
32
33 #include "intern.h"
34 #include "gpgmsg.hh"
35 #include "util.h"
36 #include "msgcache.h"
37 #include "engine.h"
38 #include "display.h"
39
40 static const char oid_mimetag[] =
41   {0x2A, 0x86, 0x48, 0x86, 0xf7, 0x14, 0x03, 0x0a, 0x04};
42
43
44 /* The string used as the standard XXXXX of decrypted attachments. */
45 #define ATT_FILE_PREFIX ".pgpenc"
46
47 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
48                                        __FILE__, __func__, __LINE__); \
49                         } while (0)
50
51
52
53 /*
54    The implementation class of MapiGPGME.  
55  */
56 class GpgMsgImpl : public GpgMsg
57 {
58 public:    
59   GpgMsgImpl () 
60   {
61     message = NULL;
62     body = NULL;
63     body_plain = NULL;
64     body_cipher = NULL;
65     body_signed = NULL;
66     body_cipher_is_html = false;
67
68     attach.table = NULL;
69     attach.rows = NULL;
70   }
71
72   ~GpgMsgImpl ()
73   {
74     if (message)
75       message->Release ();
76     xfree (body);
77     xfree (body_plain);
78     xfree (body_cipher);
79     xfree (body_signed);
80
81     if (attach.table)
82       {
83         attach.table->Release ();
84         attach.table = NULL;
85       }
86     if (attach.rows)
87       {
88         FreeProws (attach.rows);
89         attach.rows = NULL;
90       }
91   }
92
93   void destroy ()
94   {
95     delete this;
96   }
97
98   void operator delete (void *p) 
99   {
100     ::operator delete (p);
101   }
102
103   void setMapiMessage (LPMESSAGE msg)
104   {
105     if (message)
106       {
107         message->Release ();
108         message = NULL;
109       }
110     if (msg)
111       {
112         msg->AddRef ();
113         message = msg;
114       }
115   }
116   
117   openpgp_t getMessageType (void);
118   bool hasAttachments (void);
119   const char *getOrigText (void);
120   const char *GpgMsgImpl::getDisplayText (void);
121   const char *getPlainText (void);
122   void setPlainText (char *string);
123   void setCipherText (char *string, bool html);
124   void setSignedText (char *string);
125   void saveChanges (bool permanent);
126   bool matchesString (const char *string);
127
128   int decrypt (HWND hwnd);
129   int verify (HWND hwnd);
130   int sign (HWND hwnd);
131   int encrypt (HWND hwnd)
132   {
133     return encrypt_and_sign (hwnd, false);
134   }
135   int signEncrypt (HWND hwnd)
136   {
137     return encrypt_and_sign (hwnd, true);
138   }
139   int attachPublicKey (const char *keyid);
140
141   char **getRecipients (void);
142   unsigned int getAttachments (void);
143   void decryptAttachment (HWND hwnd, int pos, bool save_plaintext, int ttl);
144   void signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl);
145   int encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
146                          gpgme_key_t sign_key, int ttl);
147
148
149 private:
150   LPMESSAGE message;  /* Pointer to the message. */
151   char *body;         /* utf-8 encoded body string or NULL. */
152   char *body_plain;   /* Plaintext version of BODY or NULL. */
153   char *body_cipher;  /* Enciphered version of BODY or NULL. */
154   char *body_signed;  /* Signed version of BODY or NULL. */
155   bool body_cipher_is_html; /* Indicating that BODY_CIPHER holds HTML. */
156
157   /* This structure collects the information about attachments. */
158   struct 
159   {
160     LPMAPITABLE table;/* The loaded attachment table or NULL. */
161     LPSRowSet   rows; /* The retrieved set of rows from the table. */
162   } attach;
163   
164   void loadBody (void);
165   int encrypt_and_sign (HWND hwnd, bool sign);
166 };
167
168
169 /* Return a new instance and initialize with the MAPI message object
170    MSG. */
171 GpgMsg *
172 CreateGpgMsg (LPMESSAGE msg)
173 {
174   GpgMsg *m = new GpgMsgImpl ();
175   if (!m)
176     out_of_core ();
177   m->setMapiMessage (msg);
178   return m;
179 }
180
181
182 /* Release an array of GPGME keys. */
183 static void 
184 free_key_array (gpgme_key_t *keys)
185 {
186   if (keys)
187     {
188       for (int i = 0; keys[i]; i++) 
189         gpgme_key_release (keys[i]);
190       xfree (keys);
191     }
192 }
193
194 /* Release an array of strings with recipient names. */
195 static void
196 free_recipient_array (char **recipients)
197 {
198   int i;
199
200   if (recipients)
201     {
202       for (i=0; recipients[i]; i++) 
203         xfree (recipients[i]);  
204       xfree (recipients);
205     }
206 }
207
208 /* Return the number of recipients in the array RECIPIENTS. */
209 static int 
210 count_recipients (char **recipients)
211 {
212   int i;
213   
214   for (i=0; recipients[i] != NULL; i++)
215     ;
216   return i;
217 }
218
219
220 \f
221 /* Load the body and make it available as an UTF8 string in the
222    instance variable BODY.  */
223 void
224 GpgMsgImpl::loadBody (void)
225 {
226   HRESULT hr;
227   LPSPropValue lpspvFEID = NULL;
228   LPSTREAM stream;
229   SPropValue prop;
230   STATSTG statInfo;
231   ULONG nread;
232
233   if (body || !message)
234     return;
235   
236 #if 1
237   hr = message->OpenProperty (PR_BODY, &IID_IStream,
238                               0, 0, (LPUNKNOWN*)&stream);
239   if ( hr != S_OK )
240     {
241       log_debug_w32 (hr, "%s:%s: OpenProperty failed", __FILE__, __func__);
242       return;
243     }
244
245   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
246   if ( hr != S_OK )
247     {
248       log_debug_w32 (hr, "%s:%s: Stat failed", __FILE__, __func__);
249       stream->Release ();
250       return;
251     }
252   
253   /* FIXME: We might want to read only the first 1k to decide whetehr
254      this is actually an OpenPGP message and only then continue
255      reading.  This requires some changes in this module. */
256   body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
257   hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
258   if ( hr != S_OK )
259     {
260       log_debug_w32 (hr, "%s:%s: Read failed", __FILE__, __func__);
261       xfree (body);
262       body = NULL;
263       stream->Release ();
264       return;
265     }
266   body[nread] = 0;
267   body[nread+1] = 0;
268   if (nread != statInfo.cbSize.QuadPart)
269     {
270       log_debug ("%s:%s: not enough bytes returned\n", __FILE__, __func__);
271       xfree (body);
272       body = NULL;
273       stream->Release ();
274       return;
275     }
276   stream->Release ();
277
278   /* Fixme: We need to optimize this. */
279   {
280     char *tmp;
281     tmp = wchar_to_utf8 ((wchar_t*)body);
282     if (!tmp)
283       log_debug ("%s: error converting to utf8\n", __func__);
284     else
285       {
286         xfree (body);
287         body = tmp;
288       }
289   }
290
291 #else /* Old method. */
292   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
293   if (FAILED (hr))
294     {
295       log_debug ("%s: HrGetOneProp failed\n", __func__);
296       return;
297     }
298     
299   switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
300     {
301     case PT_UNICODE:
302       body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
303       if (!body)
304         log_debug ("%s: error converting to utf8\n", __func__);
305       break;
306       
307     case PT_STRING8:
308       body = xstrdup (lpspvFEID->Value.lpszA);
309       break;
310       
311     default:
312       log_debug ("%s: proptag=0x%08lx not supported\n",
313                  __func__, lpspvFEID->ulPropTag);
314       break;
315     }
316   MAPIFreeBuffer (lpspvFEID);
317 #endif  
318
319   if (body)
320     log_debug ("%s:%s: loaded body `%s' at %p\n",
321                __FILE__, __func__, body, body);
322   
323
324 //   prop.ulPropTag = PR_ACCESS;
325 //   prop.Value.l = MAPI_ACCESS_MODIFY;
326 //   hr = HrSetOneProp (message, &prop);
327 //   if (FAILED (hr))
328 //     log_debug_w32 (-1,"%s:%s: updating access to 0x%08lx failed",
329 //                    __FILE__, __func__, prop.Value.l);
330 }
331
332
333 /* Return the type of a message. */
334 openpgp_t
335 GpgMsgImpl::getMessageType (void)
336 {
337   const char *s;
338   
339   loadBody ();
340   
341   if (!body || !(s = strstr (body, "BEGIN PGP ")))
342     return OPENPGP_NONE;
343
344   /* (The extra strstr() above is just a simple optimization.) */
345   if (strstr (body, "BEGIN PGP MESSAGE"))
346     return OPENPGP_MSG;
347   else if (strstr (body, "BEGIN PGP SIGNED MESSAGE"))
348     return OPENPGP_CLEARSIG;
349   else if (strstr (body, "BEGIN PGP SIGNATURE"))
350     return OPENPGP_SIG;
351   else if (strstr (body, "BEGIN PGP PUBLIC KEY"))
352     return OPENPGP_PUBKEY;
353   else if (strstr (body, "BEGIN PGP PRIVATE KEY"))
354     return OPENPGP_SECKEY;
355   else
356     return OPENPGP_NONE;
357 }
358
359
360 /* Return the body text as received or composed.  This is guaranteed
361    to never return NULL.  */
362 const char *
363 GpgMsgImpl::getOrigText ()
364 {
365   loadBody ();
366   
367   return body? body : "";
368 }
369
370
371 /* Return the text of the message to be used for the display.  The
372    message objects has intrinsic knowledge about the correct text.  */
373 const char *
374 GpgMsgImpl::getDisplayText (void)
375 {
376   loadBody ();
377
378   if (body_plain)
379     return body_plain;
380   else if (body)
381     return body;
382   else
383     return "";
384 }
385
386
387
388 /* Save STRING as the plaintext version of the message.  WARNING:
389    ownership of STRING is transferred to this object. */
390 void
391 GpgMsgImpl::setPlainText (char *string)
392 {
393   xfree (body_plain);
394   body_plain = string;
395   msgcache_put (body_plain, 0, message);
396 }
397
398 /* Save STRING as the ciphertext version of the message.  WARNING:
399    ownership of STRING is transferred to this object. HTML indicates
400    whether the ciphertext was originally HTML. */
401 void
402 GpgMsgImpl::setCipherText (char *string, bool html)
403 {
404   xfree (body_cipher);
405   body_cipher = string;
406   body_cipher_is_html = html;
407 }
408
409 /* Save STRING as the signed version of the message.  WARNING:
410    ownership of STRING is transferred to this object. */
411 void
412 GpgMsgImpl::setSignedText (char *string)
413 {
414   xfree (body_signed);
415   body_signed = string;
416 }
417
418 /* Save the changes made to the message.  With PERMANENT set to true
419    they are really stored, when not set they are only saved
420    temporary. */
421 void
422 GpgMsgImpl::saveChanges (bool permanent)
423 {
424   SPropValue sProp; 
425   HRESULT hr;
426   int rc = TRUE;
427
428   if (!body_plain)
429     return; /* Nothing to save. */
430
431   if (!permanent)
432     return;
433   
434   /* Make sure that the Plaintext and the Richtext are in sync. */
435 //   if (message)
436 //     {
437 //       BOOL changed;
438
439 //       sProp.ulPropTag = PR_BODY_A;
440 //       sProp.Value.lpszA = "";
441 //       hr = HrSetOneProp(message, &sProp);
442 //       changed = false;
443 //       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
444 //       sProp.Value.lpszA = body_plain;
445 //       hr = HrSetOneProp(message, &sProp);
446 //       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
447 //     }
448
449   sProp.ulPropTag = PR_BODY_W;
450   sProp.Value.lpszW = utf8_to_wchar (body_plain);
451   if (!sProp.Value.lpszW)
452     {
453       log_debug_w32 (-1, "%s:%s: error converting from utf8\n",
454                      __FILE__, __func__);
455       return;
456     }
457   hr = HrSetOneProp (message, &sProp);
458   xfree (sProp.Value.lpszW);
459   if (hr < 0)
460     log_debug_w32 (-1, "%s:%s: HrSetOneProp failed", __FILE__, __func__);
461   else
462     {
463       log_debug ("%s:%s: PR_BODY set to `%s'\n",
464                  __FILE__, __func__, body_plain);
465       {
466         GpgMsg *xmsg = CreateGpgMsg (message);
467         log_debug ("%s:%s:    cross check `%s'\n",
468                    __FILE__, __func__, xmsg->getOrigText ());
469         delete xmsg;
470       }
471       if (permanent && message)
472         {
473           hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
474           if (hr < 0)
475             log_debug_w32 (-1, "%s:%s: SaveChanges failed",
476                            __FILE__, __func__);
477         }
478     }
479
480   log_debug ("%s:%s: leave\n", __FILE__, __func__);
481 }
482
483
484 /* Returns true if STRING matches the actual message. */ 
485 bool
486 GpgMsgImpl::matchesString (const char *string)
487 {
488   /* FIXME:  This is a too simple implementation. */
489   if (string && strstr (string, "BEGIN PGP ") )
490     return true;
491   return false;
492 }
493
494
495
496 /* Return an array of strings with the recipients of the message. On
497    success a malloced array is returned containing allocated strings
498    for each recipient.  The end of the array is marked by NULL.
499    Caller is responsible for releasing the array.  On failure NULL is
500    returned.  */
501 char ** 
502 GpgMsgImpl::getRecipients ()
503 {
504   static SizedSPropTagArray (1L, PropRecipientNum) = {1L, {PR_EMAIL_ADDRESS}};
505   HRESULT hr;
506   LPMAPITABLE lpRecipientTable = NULL;
507   LPSRowSet lpRecipientRows = NULL;
508   char **rset;
509   const char *s;
510   int i, j;
511
512   if (!message)
513     return NULL;
514
515   hr = message->GetRecipientTable (0, &lpRecipientTable);
516   if (FAILED (hr)) 
517     {
518       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed",
519                      __FILE__, __func__);
520       return NULL;
521     }
522
523   hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
524                        NULL, NULL, 0L, &lpRecipientRows);
525   if (FAILED (hr)) 
526     {
527       log_debug_w32 (-1, "%s:%s: GHrQueryAllRows failed", __FILE__, __func__);
528       if (lpRecipientTable)
529         lpRecipientTable->Release();
530       return NULL;
531     }
532
533   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
534
535   for (i = j = 0; i < lpRecipientRows->cRows; i++)
536     {
537       LPSPropValue row;
538
539       if (!lpRecipientRows->aRow[j].cValues)
540         continue;
541       row = lpRecipientRows->aRow[j].lpProps;
542
543       switch ( PROP_TYPE (row->ulPropTag) )
544         {
545         case PT_UNICODE:
546           rset[j] = wchar_to_utf8 (row->Value.lpszW);
547           if (rset[j])
548             j++;
549           else
550             log_debug ("%s:%s: error converting recipient to utf8\n",
551                        __FILE__, __func__);
552           break;
553       
554         case PT_STRING8: /* Assume Ascii. */
555           rset[j++] = xstrdup (row->Value.lpszA);
556           break;
557           
558         default:
559           log_debug ("%s:%s: proptag=0x%08lx not supported\n",
560                      __FILE__, __func__, row->ulPropTag);
561           break;
562         }
563     }
564   rset[j] = NULL;
565
566   if (lpRecipientTable)
567     lpRecipientTable->Release();
568   if (lpRecipientRows)
569     FreeProws(lpRecipientRows); 
570   
571   log_debug ("%s:%s: got %d recipients:\n",
572              __FILE__, __func__, j);
573   for (i=0; rset[i]; i++)
574     log_debug ("%s:%s: \t`%s'\n", __FILE__, __func__, rset[i]);
575
576   return rset;
577 }
578
579
580
581
582 /* Decrypt the message MSG and update the window.  HWND identifies the
583    current window. */
584 int 
585 GpgMsgImpl::decrypt (HWND hwnd)
586 {
587   log_debug ("%s:%s: enter\n", __FILE__, __func__);
588   openpgp_t mtype;
589   char *plaintext = NULL;
590   int has_attach;
591   int err;
592
593   mtype = getMessageType ();
594   if (mtype == OPENPGP_CLEARSIG)
595     {
596       log_debug ("%s:%s: leave (passing on to verify)\n", __FILE__, __func__);
597       return verify (hwnd);
598     }
599
600   /* Check whether this possibly encrypted message as attachments.  We
601      check right now because we need to get into the decryptio code
602      even if the body is not encrypted but attachments are
603      available. FIXME: I am not sure whether this is the best
604      solution, we might want to skip the decryption step later and
605      also test for encrypted attachments right now.*/
606   has_attach = hasAttachments ();
607   if (mtype == OPENPGP_NONE && !has_attach ) 
608     {
609       MessageBox (NULL, "No valid OpenPGP data found.",
610                   "GPG Decryption", MB_ICONERROR|MB_OK);
611       log_debug ("%s:%s: leave (no OpenPGP data)\n", __FILE__, __func__);
612       return 0;
613     }
614
615   err = op_decrypt (getOrigText (), &plaintext, opt.passwd_ttl);
616   if (err)
617     {
618       if (has_attach && gpg_err_code (err) == GPG_ERR_NO_DATA)
619         ;
620       else
621         MessageBox (NULL, op_strerror (err),
622                     "GPG Decryption", MB_ICONERROR|MB_OK);
623     }
624   else if (plaintext && *plaintext)
625     {   
626       int is_html = is_html_body (plaintext);
627
628       setPlainText (plaintext);
629       plaintext = NULL;
630
631       /* Also set PR_BODY but do not use 'SaveChanges' to make it
632          permanently.  This way the user can reply with the
633          plaintext but the ciphertext is still stored. */
634       log_debug ("decrypt isHtml=%d\n", is_html);
635
636       /* XXX: find a way to handle text/html message in a better way! */
637       if (is_html || update_display (hwnd, this))
638         {
639           const char s[] = 
640             "The message text cannot be displayed.\n"
641             "You have to save the decrypted message to view it.\n"
642             "Then you need to re-open the message.\n\n"
643             "Do you want to save the decrypted message?";
644           int what;
645           
646           what = MessageBox (NULL, s, "GPG Decryption",
647                              MB_YESNO|MB_ICONWARNING);
648           if (what == IDYES) 
649             {
650               log_debug ("decrypt: saving plaintext message.\n");
651               saveChanges (1);
652             }
653         }
654       else
655         saveChanges (0);
656     }
657
658   if (has_attach)
659     {
660       unsigned int n;
661       
662       n = getAttachments ();
663       log_debug ("%s:%s: message has %u attachments\n", __FILE__, __func__, n);
664       for (int i=0; i < n; i++) 
665         decryptAttachment (hwnd, i, true, opt.passwd_ttl);
666     }
667
668   log_debug ("%s:%s: leave (rc=%d)\n", __FILE__, __func__, err);
669   return err;
670 }
671
672
673 /* Verify the message and displaythe verification result. */
674 int 
675 GpgMsgImpl::verify (HWND hwnd)
676 {
677   log_debug ("%s:%s: enter\n", __FILE__, __func__);
678   openpgp_t mtype;
679   int err, has_attach;
680   
681   mtype = getMessageType ();
682   has_attach = hasAttachments ();
683   if (mtype == OPENPGP_NONE && !has_attach ) 
684     {
685       log_debug ("%s:%s: leave (no OpenPGP data)\n", __FILE__, __func__);
686       return 0;
687     }
688
689   err = op_verify (getOrigText (), NULL);
690   if (err)
691     MessageBox (NULL, op_strerror (err), "GPG Verify", MB_ICONERROR|MB_OK);
692   else
693     update_display (hwnd, this);
694
695   log_debug ("%s:%s: leave (rc=%d)\n", __FILE__, __func__, err);
696   return err;
697 }
698
699
700
701
702 \f
703 /* Sign the current message. Returns 0 on success. */
704 int
705 GpgMsgImpl::sign (HWND hwnd)
706 {
707   const char *plaintext;
708   char *signedtext = NULL;
709   int err = 0;
710   gpgme_key_t sign_key = NULL;
711
712   log_debug ("%s:%s: enter\n", __FILE__, __func__);
713   
714   /* We don't sign an empty body - a signature on a zero length string
715      is pretty much useless. */
716   if (!*(plaintext = getOrigText ()) && !hasAttachments ()) 
717     {
718       log_debug ("%s:%s: leave (empty)", __FILE__, __func__);
719       return 0; 
720     }
721
722   /* Pop up a dialog box to ask for the signer of the message. */
723   if (signer_dialog_box (&sign_key, NULL) == -1)
724     {
725       log_debug ("%s.%s: leave (dialog failed)\n", __FILE__, __func__);
726       return gpg_error (GPG_ERR_CANCELED);  
727     }
728
729   if (*plaintext)
730     {
731       err = op_sign (plaintext, &signedtext, 
732                      OP_SIG_CLEAR, sign_key, opt.passwd_ttl);
733       if (err)
734         {
735           MessageBox (NULL, op_strerror (err),
736                       "GPG Sign", MB_ICONERROR|MB_OK);
737           goto leave;
738         }
739       setSignedText (signedtext);
740       signedtext = NULL;
741     }
742
743   if (opt.auto_sign_attach && hasAttachments ())
744     {
745       unsigned int n;
746       
747       n = getAttachments ();
748       log_debug ("%s:%s: message has %u attachments\n", __FILE__, __func__, n);
749       for (int i=0; i < n; i++) 
750         signAttachment (hwnd, i, sign_key, opt.passwd_ttl);
751       /* FIXME: we should throw an error if signing of any attachment
752          failed. */
753     }
754
755  leave:
756   xfree (signedtext);
757   gpgme_key_release (sign_key);
758   log_debug ("%s:%s: leave (err=%s)\n", __FILE__, __func__, op_strerror (err));
759   return err;
760 }
761
762
763 \f
764 /* Encrypt and optionally sign (if SIGN is true) the entire message
765    including all attachments. Returns 0 on success. */
766 int 
767 GpgMsgImpl::encrypt_and_sign (HWND hwnd, bool sign)
768 {
769   log_debug ("%s:%s: enter\n", __FILE__, __func__);
770   gpgme_key_t *keys = NULL;
771   gpgme_key_t sign_key = NULL;
772   bool is_html;
773   const char *plaintext;
774   char *ciphertext = NULL;
775   char **recipients = NULL;
776   char **unknown = NULL;
777   int err = 0;
778   size_t all = 0;
779   int n_keys;
780   
781   if (!*(plaintext = getOrigText ()) && !hasAttachments ()) 
782     {
783       log_debug ("%s:%s: leave (empty)", __FILE__, __func__);
784       return 0; 
785     }
786
787   /* Pop up a dialog box to ask for the signer of the message. */
788   if (sign)
789     {
790       if (signer_dialog_box (&sign_key, NULL) == -1)
791         {
792           log_debug ("%s.%s: leave (dialog failed)\n", __FILE__, __func__);
793           return gpg_error (GPG_ERR_CANCELED);  
794         }
795     }
796
797   /* Gather the keys for the recipients. */
798   recipients = getRecipients ();
799   n_keys = op_lookup_keys (recipients, &keys, &unknown, &all);
800
801   log_debug ("%s:%s: found %d recipients, need %d, unknown=%p\n",
802              __FILE__, __func__, n_keys, all, unknown);
803   
804   if (n_keys != count_recipients (recipients))
805     {
806       int opts = 0;
807       gpgme_key_t *keys2 = NULL;
808
809       /* FIXME: The implementation is not correct: op_lookup_keys
810          returns the number of missing keys but we compare against the
811          total number of keys; thus the box would pop up even when all
812          have been found. */
813       log_debug ("%s:%s: calling recipient_dialog_box2", __FILE__, __func__);
814       recipient_dialog_box2 (keys, unknown, all, &keys2, &opts);
815       xfree (keys);
816       keys = keys2;
817       if (opts & OPT_FLAG_CANCEL) 
818         {
819           err = gpg_error (GPG_ERR_CANCELED);
820           goto leave;
821         }
822     }
823
824   if (sign_key)
825     log_debug ("%s:%s: signer: 0x%s %s\n",  __FILE__, __func__,
826                keyid_from_key (sign_key), userid_from_key (sign_key));
827   else
828     log_debug ("%s:%s: no signer\n", __FILE__, __func__);
829   if (keys)
830     {
831       for (int i=0; keys[i] != NULL; i++)
832         log_debug ("%s.%s: recp.%d 0x%s %s\n", __FILE__, __func__,
833                    i, keyid_from_key (keys[i]), userid_from_key (keys[i]));
834     }
835
836
837   if (*plaintext)
838     {
839       is_html = is_html_body (plaintext);
840
841       err = op_encrypt (plaintext, &ciphertext, keys, NULL, 0);
842       if (err)
843         {
844           MessageBox (NULL, op_strerror (err),
845                       "GPG Encryption", MB_ICONERROR|MB_OK);
846           goto leave;
847         }
848
849       if (is_html) 
850         setCipherText (add_html_line_endings (ciphertext), true);
851       else
852         setCipherText (ciphertext, false);
853       ciphertext = NULL;
854
855 //       {
856 //         HRESULT hr;
857 //         SPropValue prop;
858
859 //         prop.ulPropTag=PR_MESSAGE_CLASS_A;
860 //         prop.Value.lpszA="IPM.Note.OPENPGP";
861 //         hr = HrSetOneProp (message, &prop);
862 //         if (hr != S_OK)
863 //           {
864 //             log_error ("%s:%s: can't set message class: hr=%#lx\n",
865 //                        __FILE__, __func__, hr); 
866 //           }
867
868 //         prop.ulPropTag=PR_CONTENT_TYPE_A;
869 //         prop.Value.lpszA="application/encrypted;foo=bar;type=mytype";
870 //         hr = HrSetOneProp (message, &prop);
871 //         if (hr != S_OK)
872 //           {
873 //             log_error ("%s:%s: can't set content type: hr=%#lx\n",
874 //                        __FILE__, __func__, hr); 
875 //           }
876 //       }
877
878     }
879
880   if (hasAttachments ())
881     {
882       unsigned int n;
883       
884       n = getAttachments ();
885       log_debug ("%s:%s: message has %u attachments\n", __FILE__, __func__, n);
886       for (int i=0; !err && i < n; i++) 
887         err = encryptAttachment (hwnd, i, keys, NULL, 0);
888       if (err)
889         MessageBox (NULL, op_strerror (err),
890                     "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
891     }
892
893  leave:
894   for (int i=0; i < n_keys; i++)
895     xfree (unknown[i]);
896   if (n_keys)
897     xfree (unknown);
898
899   free_key_array (keys);
900   free_recipient_array (recipients);
901   xfree (ciphertext);
902   log_debug ("%s:%s: leave (err=%s)\n", __FILE__, __func__, op_strerror (err));
903   return err;
904 }
905
906
907
908 \f
909 /* Attach a public key to a message. */
910 int 
911 GpgMsgImpl::attachPublicKey (const char *keyid)
912 {
913     /* @untested@ */
914 #if 0
915     const char *patt[1];
916     char *keyfile;
917     int err, pos = 0;
918     LPATTACH newatt;
919
920     keyfile = generateTempname (keyid);
921     patt[0] = xstrdup (keyid);
922     err = op_export_keys (patt, keyfile);
923
924     newatt = createAttachment (NULL/*FIXME*/,pos);
925     setAttachMethod (newatt, ATTACH_BY_VALUE);
926     setAttachFilename (newatt, keyfile, false);
927     /* XXX: set proper RFC3156 MIME types. */
928
929     if (streamFromFile (keyfile, newatt)) {
930         log_debug ("attachPublicKey: commit changes.\n");
931         newatt->SaveChanges (FORCE_SAVE);
932     }
933     releaseAttachment (newatt);
934     xfree (keyfile);
935     xfree ((void *)patt[0]);
936     return err;
937 #endif
938     return -1;
939 }
940
941
942
943
944 \f
945 /* Returns whether the message has any attachments. */
946 bool
947 GpgMsgImpl::hasAttachments (void)
948 {
949   return !!getAttachments ();
950 }
951
952
953 /* Reads the attachment information and returns the number of
954    attachments. */
955 unsigned int
956 GpgMsgImpl::getAttachments (void)
957 {
958   SizedSPropTagArray (1L, propAttNum) = {
959     1L, {PR_ATTACH_NUM}
960   };
961   HRESULT hr;    
962   LPMAPITABLE table;
963   LPSRowSet   rows;
964
965   if (!message)
966     return 0;
967
968   if (!attach.table)
969     {
970       hr = message->GetAttachmentTable (0, &table);
971       if (FAILED (hr))
972         {
973           log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
974                      __FILE__, __func__, hr);
975           return 0;
976         }
977       
978       hr = HrQueryAllRows (table, (LPSPropTagArray)&propAttNum,
979                            NULL, NULL, 0, &rows);
980       if (FAILED (hr))
981         {
982           log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
983                      __FILE__, __func__, hr);
984           table->Release ();
985           return 0;
986         }
987       attach.table = table;
988       attach.rows = rows;
989     }
990
991   return attach.rows->cRows > 0? attach.rows->cRows : 0;
992 }
993
994 /* Return the attachment method for attachment OBJ. In case of error we
995    return 0 which happens to be not defined. */
996 static int
997 get_attach_method (LPATTACH obj)
998 {
999   HRESULT hr;
1000   LPSPropValue propval = NULL;
1001   int method ;
1002
1003   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1004   if (FAILED (hr))
1005     {
1006       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1007                  __FILE__, __func__, hr);
1008       return 0; 
1009     }
1010   /* We don't bother checking whether we really get a PT_LONG ulong
1011      back; if not the system is seriously damaged and we can't do
1012      further harm by returning a possible random value. */
1013   method = propval->Value.l;
1014   MAPIFreeBuffer (propval);
1015   return method;
1016 }
1017
1018
1019
1020 /* Example Code. */
1021 static bool 
1022 set_x_header (GpgMsg * msg, const char *name, const char *val)
1023 {  
1024 #ifndef __MINGW32__
1025     USES_CONVERSION;
1026 #endif
1027     LPMDB lpMdb = NULL;
1028     HRESULT hr = 0;
1029     LPSPropTagArray pProps = NULL;
1030     SPropValue pv;
1031     MAPINAMEID mnid[1]; 
1032     // {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers   
1033     GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
1034                  0x00, 0x00, 0x00, 0x46} };
1035
1036     memset (&mnid[0], 0, sizeof (MAPINAMEID));
1037     mnid[0].lpguid = &guid;
1038     mnid[0].ulKind = MNID_STRING;
1039 //     mnid[0].Kind.lpwstrName = A2W (name);
1040 // FIXME
1041 //    hr = msg->GetIDsFromNames (1, (LPMAPINAMEID*)mnid, MAPI_CREATE, &pProps);
1042     if (FAILED (hr)) {
1043         log_debug ("set X-Header failed.\n");
1044         return false;
1045     }
1046     
1047     pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
1048     pv.Value.lpszA = (char *)val;
1049 //FIXME     hr = HrSetOneProp(msg, &pv);        
1050     if (!SUCCEEDED (hr)) {
1051         log_debug ("set X-Header failed.\n");
1052         return false;
1053     }
1054
1055     log_debug ("set X-Header succeeded.\n");
1056     return true;
1057 }
1058
1059
1060
1061
1062 /* Return the filename from the attachment as a malloced string.  The
1063    encoding we return will be utf8, however the MAPI docs declare that
1064    MAPI does only handle plain ANSI and thus we don't really care
1065    later on.  In fact we would need to convert the filename back to
1066    wchar and use the Unicode versions of the file API.  Returns NULL
1067    on error or if no filename is available. */
1068 static char *
1069 get_attach_filename (LPATTACH obj)
1070 {
1071   HRESULT hr;
1072   LPSPropValue propval;
1073   char *name = NULL;
1074
1075   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1076   if (FAILED(hr)) 
1077     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1078   if (FAILED(hr))
1079     {
1080       log_debug ("%s:%s: no filename property found", __FILE__, __func__);
1081       return NULL;
1082     }
1083
1084   switch ( PROP_TYPE (propval->ulPropTag) )
1085     {
1086     case PT_UNICODE:
1087       name = wchar_to_utf8 (propval->Value.lpszW);
1088       if (!name)
1089         log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
1090       break;
1091       
1092     case PT_STRING8:
1093       name = xstrdup (propval->Value.lpszA);
1094       break;
1095       
1096     default:
1097       log_debug ("%s:%s: proptag=%xlx not supported\n",
1098                  __FILE__, __func__, propval->ulPropTag);
1099       break;
1100     }
1101   MAPIFreeBuffer (propval);
1102   return name;
1103 }
1104
1105
1106
1107
1108 /* Return a filename to be used for saving an attachment. Returns an
1109    malloced string on success. HWND is the current Window and SRCNAME
1110    the filename to be used as suggestion.  On error; i.e. cancel NULL
1111    is returned. */
1112 static char *
1113 get_save_filename (HWND root, const char *srcname)
1114                                      
1115 {
1116   char filter[] = "All Files (*.*)\0*.*\0\0";
1117   char fname[MAX_PATH+1];
1118   const char *s;
1119   OPENFILENAME ofn;
1120
1121   memset (fname, 0, sizeof (fname));
1122   memset (&ofn, 0, sizeof (ofn));
1123   ofn.lStructSize = sizeof (ofn);
1124   ofn.hwndOwner = root;
1125   ofn.lpstrFile = fname;
1126   ofn.nMaxFile = MAX_PATH;
1127   ofn.lpstrFileTitle = NULL;
1128   ofn.nMaxFileTitle = 0;
1129   ofn.Flags |= OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
1130   ofn.lpstrTitle = "GPG - Save decrypted attachments";
1131   ofn.lpstrFilter = filter;
1132
1133   if (GetSaveFileName (&ofn))
1134     return xstrdup (fname);
1135   return NULL;
1136 }
1137
1138
1139 /* Decrypt the attachment with the internal number POS.
1140    SAVE_PLAINTEXT must be true to save the attachemnt; displaying a
1141    attachment is not yet supported. */
1142 void
1143 GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext,
1144                                int ttl)
1145 {    
1146   HRESULT hr;
1147   LPATTACH att;
1148   int method, err;
1149
1150   /* Make sure that we can access the attachment table. */
1151   if (!message || !getAttachments ())
1152     {
1153       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
1154       return;
1155     }
1156
1157   if (!save_plaintext)
1158     {
1159       log_error ("%s:%s: save_plaintext not requested", __FILE__, __func__);
1160       return;
1161     }
1162
1163   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1164   if (FAILED (hr))
1165     {
1166       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
1167                  __FILE__, __func__, pos, hr);
1168       return;
1169     }
1170
1171   method = get_attach_method (att);
1172   if ( method == ATTACH_EMBEDDED_MSG)
1173     {
1174       /* This is an embedded message.  The orginal G-DATA plugin
1175          decrypted the message and then updated the attachemnt;
1176          i.e. stored the plaintext.  This seemed to ensure that the
1177          attachemnt message was properly displayed.  I am not sure
1178          what we should do - it might be necessary to have a callback
1179          to allow displaying the attachment.  Needs further
1180          experiments. */
1181       LPMESSAGE emb;
1182       
1183       hr = att->OpenProperty (PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 
1184                               MAPI_MODIFY, (LPUNKNOWN*)&emb);
1185       if (FAILED (hr))
1186         {
1187           log_error ("%s:%s: can't open data obj of attachment %d: hr=%#lx",
1188                      __FILE__, __func__, pos, hr);
1189           goto leave;
1190         }
1191
1192       //FIXME  Not sure what to do here.  Did it ever work?
1193       //        setWindow (hwnd);
1194       //        setMessage (emb);
1195       //if (doCmdAttach (action))
1196       //  success = FALSE;
1197       //XXX;
1198       //emb->SaveChanges (FORCE_SAVE);
1199       //att->SaveChanges (FORCE_SAVE);
1200       emb->Release ();
1201     }
1202   else if (method == ATTACH_BY_VALUE)
1203     {
1204       const char *s;
1205       char *outname;
1206       char *suggested_name;
1207       LPSTREAM from, to;
1208
1209       suggested_name = get_attach_filename (att);
1210       /* We only want to automatically decrypt attachmenst with
1211          certain extensions.  FIXME: Also look for content-types. */
1212       if (!suggested_name 
1213           || !(s = strrchr (suggested_name, '.'))
1214           || (stricmp (s, ".pgp") 
1215               && stricmp (s, ".gpg") 
1216               && stricmp (s, ".asc")))
1217         {
1218           log_debug ( "%s:%s: attachment %d has no pgp extension\n", 
1219                       __FILE__, __func__);
1220           goto leave;
1221         }
1222       outname = get_save_filename (hwnd, suggested_name);
1223       xfree (suggested_name);
1224
1225       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1226                               0, 0, (LPUNKNOWN*) &from);
1227       if (FAILED (hr))
1228         {
1229           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1230                      __FILE__, __func__, pos, hr);
1231           xfree (outname);
1232           goto leave;
1233         }
1234
1235       hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1236                              (STGM_CREATE | STGM_READWRITE),
1237                              outname, NULL, &to); 
1238       if (FAILED (hr)) 
1239         {
1240           log_error ("%s:%s: can't create stream for `%s': hr=%#lx\n",
1241                      __FILE__, __func__, outname, hr); 
1242           from->Release ();
1243           xfree (outname);
1244           goto leave;
1245         }
1246       
1247       err = op_decrypt_stream (from, to, ttl);
1248       if (err)
1249         {
1250           log_debug ("%s:%s: decrypt stream failed: %s",
1251                      __FILE__, __func__, op_strerror (err)); 
1252           to->Revert ();
1253           to->Release ();
1254           from->Release ();
1255           MessageBox (NULL, op_strerror (err),
1256                       "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
1257           /* FIXME: We might need to delete outname now.  However a
1258              sensible implementation of the stream object should have
1259              done it trhough the Revert call. */
1260           xfree (outname);
1261           goto leave;
1262         }
1263         
1264       to->Commit (0);
1265       to->Release ();
1266       from->Release ();
1267
1268       /*  Hmmm: Why are we deleting the attachment now????? 
1269           Disabled until clarified.   FIXME */
1270       //if (message->DeleteAttach (pos, 0, NULL, 0) == S_OK)
1271       //   show error;
1272
1273       xfree (outname);
1274     }
1275   else
1276     {
1277       log_error ("%s:%s: attachment %d: method %d not supported",
1278                  __FILE__, __func__, pos, method);
1279     }
1280
1281  leave:
1282   /* Close this attachment. */
1283   att->Release ();
1284 }
1285
1286
1287 /* Sign the attachment with the internal number POS.  TTL is caching
1288    time for a required passphrase. */
1289 void
1290 GpgMsgImpl::signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl)
1291 {    
1292   HRESULT hr;
1293   LPATTACH att;
1294   int method, err;
1295   LPSTREAM from = NULL;
1296   LPSTREAM to = NULL;
1297   char *signame = NULL;
1298   LPATTACH newatt = NULL;
1299
1300   /* Make sure that we can access the attachment table. */
1301   if (!message || !getAttachments ())
1302     {
1303       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
1304       return;
1305     }
1306
1307   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1308   if (FAILED (hr))
1309     {
1310       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
1311                  __FILE__, __func__, pos, hr);
1312       return;
1313     }
1314
1315   /* Construct a filename for the new attachment. */
1316   {
1317     char *tmpname = get_attach_filename (att);
1318     if (!tmpname)
1319       {
1320         signame = (char*)xmalloc (70);
1321         snprintf (signame, 70, "gpg-signature-%d.asc", pos);
1322       }
1323     else
1324       {
1325         signame = (char*)xmalloc (strlen (tmpname) + 4 + 1);
1326         strcpy (stpcpy (signame, tmpname), ".asc");
1327         xfree (tmpname);
1328       }
1329   }
1330
1331   method = get_attach_method (att);
1332   if (method == ATTACH_EMBEDDED_MSG)
1333     {
1334       log_debug ("%s:%s: signing embedded attachments is not supported",
1335                  __FILE__, __func__);
1336     }
1337   else if (method == ATTACH_BY_VALUE)
1338     {
1339       ULONG newpos;
1340       SPropValue prop;
1341
1342       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1343                               0, 0, (LPUNKNOWN*)&from);
1344       if (FAILED (hr))
1345         {
1346           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1347                      __FILE__, __func__, pos, hr);
1348           goto leave;
1349         }
1350
1351       hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
1352       if (hr != S_OK)
1353         {
1354           log_error ("%s:%s: can't create attachment: hr=%#lx\n",
1355                      __FILE__, __func__, hr); 
1356           goto leave;
1357         }
1358
1359       prop.ulPropTag = PR_ATTACH_METHOD;
1360       prop.Value.ul = ATTACH_BY_VALUE;
1361       hr = HrSetOneProp (newatt, &prop);
1362       if (hr != S_OK)
1363         {
1364           log_error ("%s:%s: can't set attach method: hr=%#lx\n",
1365                      __FILE__, __func__, hr); 
1366           goto leave;
1367         }
1368       
1369       prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
1370       prop.Value.lpszA = signame;   
1371       hr = HrSetOneProp (newatt, &prop);
1372       if (hr != S_OK)
1373         {
1374           log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
1375                      __FILE__, __func__, hr); 
1376           goto leave;
1377         }
1378       log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
1379                  __FILE__, __func__, pos, newpos, signame);
1380
1381       prop.ulPropTag = PR_ATTACH_EXTENSION_A;
1382       prop.Value.lpszA = ".pgpsig";   
1383       hr = HrSetOneProp (newatt, &prop);
1384       if (hr != S_OK)
1385         {
1386           log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
1387                      __FILE__, __func__, hr); 
1388           goto leave;
1389         }
1390
1391       prop.ulPropTag = PR_ATTACH_TAG;
1392       prop.Value.bin.cb  = sizeof oid_mimetag;
1393       prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
1394       hr = HrSetOneProp (newatt, &prop);
1395       if (hr != S_OK)
1396         {
1397           log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
1398                      __FILE__, __func__, hr); 
1399           goto leave;
1400         }
1401
1402       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
1403       prop.Value.lpszA = "application/pgp-signature";
1404       hr = HrSetOneProp (newatt, &prop);
1405       if (hr != S_OK)
1406         {
1407           log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
1408                      __FILE__, __func__, hr); 
1409           goto leave;
1410         }
1411
1412       hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
1413                                  MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
1414       if (FAILED (hr)) 
1415         {
1416           log_error ("%s:%s: can't create output stream: hr=%#lx\n",
1417                      __FILE__, __func__, hr); 
1418           goto leave;
1419         }
1420       
1421       err = op_sign_stream (from, to, OP_SIG_DETACH, sign_key, ttl);
1422       if (err)
1423         {
1424           log_debug ("%s:%s: sign stream failed: %s",
1425                      __FILE__, __func__, op_strerror (err)); 
1426           to->Revert ();
1427           MessageBox (NULL, op_strerror (err),
1428                       "GPG Attachment Signing", MB_ICONERROR|MB_OK);
1429           goto leave;
1430         }
1431       from->Release ();
1432       from = NULL;
1433       to->Commit (0);
1434       to->Release ();
1435       to = NULL;
1436
1437       hr = newatt->SaveChanges (0);
1438       if (hr != S_OK)
1439         {
1440           log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
1441                      __FILE__, __func__, hr); 
1442           goto leave;
1443         }
1444
1445     }
1446   else
1447     {
1448       log_error ("%s:%s: attachment %d: method %d not supported",
1449                  __FILE__, __func__, pos, method);
1450     }
1451
1452  leave:
1453   if (from)
1454     from->Release ();
1455   if (to)
1456     to->Release ();
1457   xfree (signame);
1458   if (newatt)
1459     newatt->Release ();
1460
1461   att->Release ();
1462 }
1463
1464 /* Encrypt the attachment with the internal number POS.  KEYS is a
1465    NULL terminates array with recipients to whom the message should be
1466    encrypted.  If SIGN_KEY is not NULL the attachment will also get
1467    signed. TTL is the passphrase caching time and only used if
1468    SIGN_KEY is not NULL. Returns 0 on success. */
1469 int
1470 GpgMsgImpl::encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
1471                                gpgme_key_t sign_key, int ttl)
1472 {    
1473   HRESULT hr;
1474   LPATTACH att;
1475   int method, err;
1476   LPSTREAM from = NULL;
1477   LPSTREAM to = NULL;
1478   char *filename = NULL;
1479   LPATTACH newatt = NULL;
1480
1481   /* Make sure that we can access the attachment table. */
1482   if (!message || !getAttachments ())
1483     {
1484       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
1485       return 0;
1486     }
1487
1488   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1489   if (FAILED (hr))
1490     {
1491       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
1492                  __FILE__, __func__, pos, hr);
1493       err = gpg_error (GPG_ERR_GENERAL);
1494       return err;
1495     }
1496
1497   /* Construct a filename for the new attachment. */
1498   {
1499     char *tmpname = get_attach_filename (att);
1500     if (!tmpname)
1501       {
1502         filename = (char*)xmalloc (70);
1503         snprintf (filename, 70, "gpg-encrypted-%d.pgp", pos);
1504       }
1505     else
1506       {
1507         filename = (char*)xmalloc (strlen (tmpname) + 4 + 1);
1508         strcpy (stpcpy (filename, tmpname), ".pgp");
1509         xfree (tmpname);
1510       }
1511   }
1512
1513   method = get_attach_method (att);
1514   if (method == ATTACH_EMBEDDED_MSG)
1515     {
1516       log_debug ("%s:%s: encrypting embedded attachments is not supported",
1517                  __FILE__, __func__);
1518       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
1519     }
1520   else if (method == ATTACH_BY_VALUE)
1521     {
1522       ULONG newpos;
1523       SPropValue prop;
1524
1525       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1526                               0, 0, (LPUNKNOWN*)&from);
1527       if (FAILED (hr))
1528         {
1529           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1530                      __FILE__, __func__, pos, hr);
1531           err = gpg_error (GPG_ERR_GENERAL);
1532           goto leave;
1533         }
1534
1535       hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
1536       if (hr != S_OK)
1537         {
1538           log_error ("%s:%s: can't create attachment: hr=%#lx\n",
1539                      __FILE__, __func__, hr); 
1540           err = gpg_error (GPG_ERR_GENERAL);
1541           goto leave;
1542         }
1543
1544       prop.ulPropTag = PR_ATTACH_METHOD;
1545       prop.Value.ul = ATTACH_BY_VALUE;
1546       hr = HrSetOneProp (newatt, &prop);
1547       if (hr != S_OK)
1548         {
1549           log_error ("%s:%s: can't set attach method: hr=%#lx\n",
1550                      __FILE__, __func__, hr); 
1551           err = gpg_error (GPG_ERR_GENERAL);
1552           goto leave;
1553         }
1554       
1555       prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
1556       prop.Value.lpszA = filename;   
1557       hr = HrSetOneProp (newatt, &prop);
1558       if (hr != S_OK)
1559         {
1560           log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
1561                      __FILE__, __func__, hr); 
1562           err = gpg_error (GPG_ERR_GENERAL);
1563           goto leave;
1564         }
1565       log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
1566                  __FILE__, __func__, pos, newpos, filename);
1567
1568       prop.ulPropTag = PR_ATTACH_EXTENSION_A;
1569       prop.Value.lpszA = ".pgpenc";   
1570       hr = HrSetOneProp (newatt, &prop);
1571       if (hr != S_OK)
1572         {
1573           log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
1574                      __FILE__, __func__, hr); 
1575           err = gpg_error (GPG_ERR_GENERAL);
1576           goto leave;
1577         }
1578
1579       prop.ulPropTag = PR_ATTACH_TAG;
1580       prop.Value.bin.cb  = sizeof oid_mimetag;
1581       prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
1582       hr = HrSetOneProp (newatt, &prop);
1583       if (hr != S_OK)
1584         {
1585           log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
1586                      __FILE__, __func__, hr); 
1587           err = gpg_error (GPG_ERR_GENERAL);
1588           goto leave;
1589         }
1590
1591       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
1592       prop.Value.lpszA = "application/pgp-encrypted;foomode=baz";
1593       hr = HrSetOneProp (newatt, &prop);
1594       if (hr != S_OK)
1595         {
1596           log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
1597                      __FILE__, __func__, hr); 
1598           err = gpg_error (GPG_ERR_GENERAL);
1599           goto leave;
1600         }
1601
1602       hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
1603                                  MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
1604       if (FAILED (hr)) 
1605         {
1606           log_error ("%s:%s: can't create output stream: hr=%#lx\n",
1607                      __FILE__, __func__, hr); 
1608           err = gpg_error (GPG_ERR_GENERAL);
1609           goto leave;
1610         }
1611       
1612       err = op_encrypt_stream (from, to, keys, sign_key, ttl);
1613       if (err)
1614         {
1615           log_debug ("%s:%s: encrypt stream failed: %s",
1616                      __FILE__, __func__, op_strerror (err)); 
1617           to->Revert ();
1618           MessageBox (NULL, op_strerror (err),
1619                       "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
1620           goto leave;
1621         }
1622       from->Release ();
1623       from = NULL;
1624       to->Commit (0);
1625       to->Release ();
1626       to = NULL;
1627
1628       hr = newatt->SaveChanges (0);
1629       if (hr != S_OK)
1630         {
1631           log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
1632                      __FILE__, __func__, hr); 
1633           err = gpg_error (GPG_ERR_GENERAL);
1634           goto leave;
1635         }
1636
1637       hr = message->DeleteAttach (pos, 0, NULL, 0);
1638       if (hr != S_OK)
1639         {
1640           log_error ("%s:%s: DeleteAtatch failed: hr=%#lx\n",
1641                      __FILE__, __func__, hr); 
1642           err = gpg_error (GPG_ERR_GENERAL);
1643           goto leave;
1644         }
1645
1646     }
1647   else
1648     {
1649       log_error ("%s:%s: attachment %d: method %d not supported",
1650                  __FILE__, __func__, pos, method);
1651       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
1652     }
1653
1654  leave:
1655   if (from)
1656     from->Release ();
1657   if (to)
1658     to->Release ();
1659   xfree (filename);
1660   if (newatt)
1661     newatt->Release ();
1662
1663   att->Release ();
1664   return err;
1665 }