After disabling RTFSync it works again.
[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 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
45                                        __FILE__, __func__, __LINE__); \
46                         } while (0)
47
48 struct attach_info
49 {
50   int end_of_table;  /* True if this is the last plus one entry of the
51                         table. */
52   int is_encrypted;  /* This is an encrypted attchment. */
53   int is_signed;     /* This is a signed attachment. */
54   unsigned int sig_pos; /* For signed attachments the index of the
55                            attachment with the detached signature. */
56   
57   int method;        /* MAPI attachmend method. */
58   char *filename;    /* Malloced filename of this attachment or NULL. */
59   char *content_type;/* Malloced string with the mime attrib or NULL.
60                         Parameters are stripped off thus a compare
61                         against "type/subtype" is sufficient. */
62   const char *content_type_parms; /* If not NULL the parameters of the
63                                      content_type. */
64
65 };
66 typedef struct attach_info *attach_info_t;
67
68
69 static int get_attach_method (LPATTACH obj);
70 static bool set_x_header (LPMESSAGE msg, const char *name, const char *val);
71
72
73
74 /*
75    The implementation class of MapiGPGME.  
76  */
77 class GpgMsgImpl : public GpgMsg
78 {
79 public:    
80   GpgMsgImpl () 
81   {
82     message = NULL;
83     exchange_cb = NULL;
84     body = NULL;
85     body_plain = NULL;
86     is_pgpmime = false;
87     
88
89     attach.att_table = NULL;
90     attach.rows = NULL;
91   }
92
93   ~GpgMsgImpl ()
94   {
95     if (message)
96       message->Release ();
97     xfree (body);
98     xfree (body_plain);
99
100     if (attach.att_table)
101       {
102         attach.att_table->Release ();
103         attach.att_table = NULL;
104       }
105     if (attach.rows)
106       {
107         FreeProws (attach.rows);
108         attach.rows = NULL;
109       }
110   }
111
112   void destroy ()
113   {
114     delete this;
115   }
116
117   void operator delete (void *p) 
118   {
119     ::operator delete (p);
120   }
121
122   void setMapiMessage (LPMESSAGE msg)
123   {
124     if (message)
125       {
126         message->Release ();
127         message = NULL;
128       }
129     if (msg)
130       {
131         msg->AddRef ();
132         message = msg;
133       }
134   }
135
136   /* Set the callback for Exchange. */
137   void setExchangeCallback (void *cb)
138   {
139     exchange_cb = cb;
140   }
141   
142   openpgp_t getMessageType (void);
143   bool hasAttachments (void);
144   const char *getOrigText (void);
145   const char *GpgMsgImpl::getDisplayText (void);
146   const char *getPlainText (void);
147   bool matchesString (const char *string);
148
149   int decrypt (HWND hwnd);
150   int verify (HWND hwnd);
151   int sign (HWND hwnd);
152   int encrypt (HWND hwnd)
153   {
154     return encrypt_and_sign (hwnd, false);
155   }
156   int signEncrypt (HWND hwnd)
157   {
158     return encrypt_and_sign (hwnd, true);
159   }
160   int attachPublicKey (const char *keyid);
161
162   char **getRecipients (void);
163   unsigned int getAttachments (void);
164   void verifyAttachment (HWND hwnd, attach_info_t table,
165                          unsigned int pos_data,
166                          unsigned int pos_sig);
167   void decryptAttachment (HWND hwnd, int pos, bool save_plaintext, int ttl,
168                           const char *filename);
169   void signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl);
170   int encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
171                          gpgme_key_t sign_key, int ttl);
172
173
174 private:
175   LPMESSAGE message;  /* Pointer to the message. */
176   void *exchange_cb;  /* Call back used with the display function. */
177   char *body;         /* utf-8 encoded body string or NULL. */
178   char *body_plain;   /* Plaintext version of BODY or NULL. */
179   bool is_pgpmime;    /* True if the message is a PGP/MIME encrypted one. */
180
181   /* This structure collects the information about attachments. */
182   struct 
183   {
184     LPMAPITABLE att_table;/* The loaded attachment table or NULL. */
185     LPSRowSet   rows;     /* The retrieved set of rows from the table. */
186   } attach;
187   
188   void loadBody (void);
189   bool isPgpmimeVersionPart (int pos);
190   attach_info_t gatherAttachmentInfo (void);
191   int encrypt_and_sign (HWND hwnd, bool sign);
192 };
193
194
195 /* Return a new instance and initialize with the MAPI message object
196    MSG. */
197 GpgMsg *
198 CreateGpgMsg (LPMESSAGE msg)
199 {
200   GpgMsg *m = new GpgMsgImpl ();
201   if (!m)
202     out_of_core ();
203   m->setMapiMessage (msg);
204   return m;
205 }
206
207
208 /* Release an array of GPGME keys. */
209 static void 
210 free_key_array (gpgme_key_t *keys)
211 {
212   if (keys)
213     {
214       for (int i = 0; keys[i]; i++) 
215         gpgme_key_release (keys[i]);
216       xfree (keys);
217     }
218 }
219
220 /* Release an array of strings with recipient names. */
221 static void
222 free_recipient_array (char **recipients)
223 {
224   int i;
225
226   if (recipients)
227     {
228       for (i=0; recipients[i]; i++) 
229         xfree (recipients[i]);  
230       xfree (recipients);
231     }
232 }
233
234 /* Release a table with attachments infos. */
235 static void
236 release_attach_info (attach_info_t table)
237 {
238   int i;
239
240   if (!table)
241     return;
242   for (i=0; !table[i].end_of_table; i++)
243     {
244       xfree (table[i].filename);
245       xfree (table[i].content_type);
246     }
247   xfree (table);
248 }
249
250
251 /* Return the number of recipients in the array RECIPIENTS. */
252 static int 
253 count_recipients (char **recipients)
254 {
255   int i;
256   
257   for (i=0; recipients[i] != NULL; i++)
258     ;
259   return i;
260 }
261
262
263 /* Return a string suitable for displaying in a message box.  The
264    function takes FORMAT and replaces the string "@LIST@" with the
265    names of the attachmets. Depending on the set bits in WHAT only
266    certain attachments are inserted. 
267
268    Defined bits in MODE are:
269       0 = Any attachment
270       1 = signed attachments
271       2 = encrypted attachments
272
273    Caller must free the returned value.  Routine is guaranteed to
274    return a string.
275 */
276 static char *
277 text_from_attach_info (attach_info_t table, const char *format,
278                        unsigned int what)
279 {
280   int pos;
281   size_t length;
282   char *buffer, *p;
283   const char *marker;
284
285   marker = strstr (format, "@LIST@");
286   if (!marker)
287     return xstrdup (format);
288
289 #define CONDITION  (table[pos].filename \
290                     && ( (what&1) \
291                          || ((what & 2) && table[pos].is_signed) \
292                          || ((what & 4) && table[pos].is_encrypted)))
293
294   for (length=0, pos=0; !table[pos].end_of_table; pos++)
295     if (CONDITION)
296       length += 2 + strlen (table[pos].filename) + 1;
297
298   length += strlen (format);
299   buffer = p = (char*)xmalloc (length+1);
300
301   strncpy (p, format, marker - format);
302   p += marker - format;
303
304   for (pos=0; !table[pos].end_of_table; pos++)
305     if (CONDITION)
306       {
307         if (table[pos].is_signed)
308           p = stpcpy (p, "S ");
309         else if (table[pos].is_encrypted)
310           p = stpcpy (p, "E ");
311         else
312           p = stpcpy (p, "* ");
313         p = stpcpy (p, table[pos].filename);
314         p = stpcpy (p, "\n");
315       }
316   strcpy (p, marker+6);
317 #undef CONDITION
318
319   return buffer;
320 }
321
322
323 \f
324 /* Load the body and make it available as an UTF8 string in the
325    instance variable BODY.  */
326 void
327 GpgMsgImpl::loadBody (void)
328 {
329   HRESULT hr;
330   LPSPropValue lpspvFEID = NULL;
331   LPSTREAM stream;
332   SPropValue prop;
333   STATSTG statInfo;
334   ULONG nread;
335
336   if (body || !message)
337     return;
338
339   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
340   if (SUCCEEDED (hr))
341     { /* Message is small enough to be retrieved this way. */
342       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
343         {
344         case PT_UNICODE:
345           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
346           if (!body)
347             log_debug ("%s: error converting to utf8\n", __func__);
348           break;
349           
350         case PT_STRING8:
351           body = xstrdup (lpspvFEID->Value.lpszA);
352           break;
353           
354         default:
355           log_debug ("%s: proptag=0x%08lx not supported\n",
356                      __func__, lpspvFEID->ulPropTag);
357           break;
358         }
359       MAPIFreeBuffer (lpspvFEID);
360     }
361   else /* Message is large; Use a stream to read it. */
362     {
363       hr = message->OpenProperty (PR_BODY, &IID_IStream,
364                                   0, 0, (LPUNKNOWN*)&stream);
365       if ( hr != S_OK )
366         {
367           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
368                      __FILE__, __func__, hr);
369           return;
370         }
371       
372       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
373       if ( hr != S_OK )
374         {
375           log_debug ("%s:%s: Stat failed: hr=%#lx", __FILE__, __func__, hr);
376           stream->Release ();
377           return;
378         }
379       
380       /* Fixme: We might want to read only the first 1k to decide
381          whether this is actually an OpenPGP message and only then
382          continue reading.  This requires some changes in this
383          module. */
384       body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
385       hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
386       if ( hr != S_OK )
387         {
388           log_debug ("%s:%s: Read failed: hr=%#lx", __FILE__, __func__, hr);
389           xfree (body);
390           body = NULL;
391           stream->Release ();
392           return;
393         }
394       body[nread] = 0;
395       body[nread+1] = 0;
396       if (nread != statInfo.cbSize.QuadPart)
397         {
398           log_debug ("%s:%s: not enough bytes returned\n", __FILE__, __func__);
399           xfree (body);
400           body = NULL;
401           stream->Release ();
402           return;
403         }
404       stream->Release ();
405       
406       /* FIXME: We need to optimize this. */
407       {
408         char *tmp;
409         tmp = wchar_to_utf8 ((wchar_t*)body);
410         if (!tmp)
411           log_debug ("%s: error converting to utf8\n", __func__);
412         else
413           {
414             xfree (body);
415             body = tmp;
416           }
417       }
418     }
419
420   if (body)
421     log_debug ("%s:%s: loaded body `%s' at %p\n",
422                __FILE__, __func__, body, body);
423   
424
425 //   prop.ulPropTag = PR_ACCESS;
426 //   prop.Value.l = MAPI_ACCESS_MODIFY;
427 //   hr = HrSetOneProp (message, &prop);
428 //   if (FAILED (hr))
429 //     log_debug ("%s:%s: updating message access to 0x%08lx failed: hr=%#lx",
430 //                    __FILE__, __func__, prop.Value.l, hr);
431 }
432
433
434 /* Return the subject of the message or NULL if it does not
435    exists.  Caller must free. */
436 static char *
437 get_subject (LPMESSAGE obj)
438 {
439   HRESULT hr;
440   LPSPropValue propval = NULL;
441   char *name;
442
443   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_SUBJECT, &propval);
444   if (FAILED (hr))
445     {
446       log_error ("%s:%s: error getting the subject: hr=%#lx",
447                  __FILE__, __func__, hr);
448       return NULL; 
449     }
450   switch ( PROP_TYPE (propval->ulPropTag) )
451     {
452     case PT_UNICODE:
453       name = wchar_to_utf8 (propval->Value.lpszW);
454       if (!name)
455         log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
456       break;
457       
458     case PT_STRING8:
459       name = xstrdup (propval->Value.lpszA);
460       break;
461       
462     default:
463       log_debug ("%s:%s: proptag=%xlx not supported\n",
464                  __FILE__, __func__, propval->ulPropTag);
465       name = NULL;
466       break;
467     }
468   MAPIFreeBuffer (propval);
469   return name;
470 }
471
472 /* Set the subject of the message OBJ to STRING. Returns 0 on
473    success. */
474 static int
475 set_subject (LPMESSAGE obj, const char *string)
476 {
477   HRESULT hr;
478   SPropValue prop;
479   const char *s;
480   
481   /* Decide whether we ned to use the Unicode version. */
482   for (s=string; *s && !(*s & 0x80); s++)
483     ;
484   if (*s)
485     {
486       prop.ulPropTag = PR_SUBJECT_W;
487       prop.Value.lpszW = utf8_to_wchar (string);
488       hr = HrSetOneProp (obj, &prop);
489       xfree (prop.Value.lpszW);
490     }
491   else /* Only plain ASCII. */
492     {
493       prop.ulPropTag = PR_SUBJECT_A;
494       prop.Value.lpszA = (CHAR*)string;
495       hr = HrSetOneProp (obj, &prop);
496     }
497   if (hr != S_OK)
498     {
499       log_debug ("%s:%s: HrSetOneProp failed: hr=%#lx\n",
500                  __FILE__, __func__, hr); 
501       return gpg_error (GPG_ERR_GENERAL);
502     }
503   return 0;
504 }
505
506
507
508 /* Return the type of a message. */
509 openpgp_t
510 GpgMsgImpl::getMessageType (void)
511 {
512   const char *s;
513   
514   loadBody ();
515   
516   if (!body || !(s = strstr (body, "BEGIN PGP ")))
517     return OPENPGP_NONE;
518
519   /* (The extra strstr() above is just a simple optimization.) */
520   if (strstr (body, "BEGIN PGP MESSAGE"))
521     return OPENPGP_MSG;
522   else if (strstr (body, "BEGIN PGP SIGNED MESSAGE"))
523     return OPENPGP_CLEARSIG;
524   else if (strstr (body, "BEGIN PGP SIGNATURE"))
525     return OPENPGP_SIG;
526   else if (strstr (body, "BEGIN PGP PUBLIC KEY"))
527     return OPENPGP_PUBKEY;
528   else if (strstr (body, "BEGIN PGP PRIVATE KEY"))
529     return OPENPGP_SECKEY;
530   else
531     return OPENPGP_NONE;
532 }
533
534
535 /* Return the body text as received or composed.  This is guaranteed
536    to never return NULL.  */
537 const char *
538 GpgMsgImpl::getOrigText ()
539 {
540   loadBody ();
541   
542   return body? body : "";
543 }
544
545
546 /* Return the text of the message to be used for the display.  The
547    message objects has intrinsic knowledge about the correct text.  */
548 const char *
549 GpgMsgImpl::getDisplayText (void)
550 {
551   loadBody ();
552
553   if (body_plain)
554     return body_plain;
555   else if (body)
556     return body;
557   else
558     return "";
559 }
560
561
562 /* Returns true if STRING matches the actual message. */ 
563 bool
564 GpgMsgImpl::matchesString (const char *string)
565 {
566   /* FIXME:  This is a too simple implementation. */
567   if (string && strstr (string, "BEGIN PGP ") )
568     return true;
569   return false;
570 }
571
572
573
574 /* Return an array of strings with the recipients of the message. On
575    success a malloced array is returned containing allocated strings
576    for each recipient.  The end of the array is marked by NULL.
577    Caller is responsible for releasing the array.  On failure NULL is
578    returned.  */
579 char ** 
580 GpgMsgImpl::getRecipients ()
581 {
582   static SizedSPropTagArray (1L, PropRecipientNum) = {1L, {PR_EMAIL_ADDRESS}};
583   HRESULT hr;
584   LPMAPITABLE lpRecipientTable = NULL;
585   LPSRowSet lpRecipientRows = NULL;
586   char **rset;
587   const char *s;
588   int i, j;
589
590   if (!message)
591     return NULL;
592
593   hr = message->GetRecipientTable (0, &lpRecipientTable);
594   if (FAILED (hr)) 
595     {
596       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed",
597                      __FILE__, __func__);
598       return NULL;
599     }
600
601   hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
602                        NULL, NULL, 0L, &lpRecipientRows);
603   if (FAILED (hr)) 
604     {
605       log_debug_w32 (-1, "%s:%s: GHrQueryAllRows failed", __FILE__, __func__);
606       if (lpRecipientTable)
607         lpRecipientTable->Release();
608       return NULL;
609     }
610
611   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
612
613   for (i = j = 0; i < lpRecipientRows->cRows; i++)
614     {
615       LPSPropValue row;
616
617       if (!lpRecipientRows->aRow[j].cValues)
618         continue;
619       row = lpRecipientRows->aRow[j].lpProps;
620
621       switch ( PROP_TYPE (row->ulPropTag) )
622         {
623         case PT_UNICODE:
624           rset[j] = wchar_to_utf8 (row->Value.lpszW);
625           if (rset[j])
626             j++;
627           else
628             log_debug ("%s:%s: error converting recipient to utf8\n",
629                        __FILE__, __func__);
630           break;
631       
632         case PT_STRING8: /* Assume Ascii. */
633           rset[j++] = xstrdup (row->Value.lpszA);
634           break;
635           
636         default:
637           log_debug ("%s:%s: proptag=0x%08lx not supported\n",
638                      __FILE__, __func__, row->ulPropTag);
639           break;
640         }
641     }
642   rset[j] = NULL;
643
644   if (lpRecipientTable)
645     lpRecipientTable->Release();
646   if (lpRecipientRows)
647     FreeProws(lpRecipientRows); 
648   
649   log_debug ("%s:%s: got %d recipients:\n",
650              __FILE__, __func__, j);
651   for (i=0; rset[i]; i++)
652     log_debug ("%s:%s: \t`%s'\n", __FILE__, __func__, rset[i]);
653
654   return rset;
655 }
656
657
658
659
660 /* Decrypt the message MSG and update the window.  HWND identifies the
661    current window. */
662 int 
663 GpgMsgImpl::decrypt (HWND hwnd)
664 {
665   log_debug ("%s:%s: enter\n", __FILE__, __func__);
666   openpgp_t mtype;
667   char *plaintext = NULL;
668   attach_info_t table = NULL;
669   int err;
670   unsigned int pos;
671   unsigned int n_attach = 0;
672   unsigned int n_encrypted = 0;
673   unsigned int n_signed = 0;
674   HRESULT hr;
675   int pgpmime_succeeded = 0;
676
677   mtype = getMessageType ();
678   if (mtype == OPENPGP_CLEARSIG)
679     {
680       log_debug ("%s:%s: leave (passing on to verify)\n", __FILE__, __func__);
681       return verify (hwnd);
682     }
683
684   /* Check whether this possibly encrypted message has encrypted
685      attachments.  We check right now because we need to get into the
686      decryption code even if the body is not encrypted but attachments
687      are available. */
688   table = gatherAttachmentInfo ();
689   if (table)
690     {
691       for (pos=0; !table[pos].end_of_table; pos++)
692         if (table[pos].is_encrypted)
693           n_encrypted++;
694         else if (table[pos].is_signed)
695           n_signed++;
696       n_attach = pos;
697     }
698   log_debug ("%s:%s: message has %u attachments with "
699              "%u signed and %d encrypted\n",
700              __FILE__, __func__, n_attach, n_signed, n_encrypted);
701   if (mtype == OPENPGP_NONE && !n_encrypted && !n_signed) 
702     {
703       /* Fixme: we should display the messsage box only if decryption
704          has explicity be requested. */
705       MessageBox (hwnd, "No valid OpenPGP data found.",
706                   "GPG Decryption", MB_ICONWARNING|MB_OK);
707       log_debug ("%s:%s: leave (no OpenPGP data)\n", __FILE__, __func__);
708       release_attach_info (table);
709       return 0;
710     }
711
712   if (is_pgpmime)
713     {
714       LPATTACH att;
715       int method;
716       LPSTREAM from;
717       
718       hr = message->OpenAttach (1, NULL, MAPI_BEST_ACCESS, &att);       
719       if (FAILED (hr))
720         {
721           log_error ("%s:%s: can't open PGP/MIME attachment 2: hr=%#lx",
722                      __FILE__, __func__, hr);
723           MessageBox (hwnd, "Problem decrypting PGP/MIME message",
724                       "GPG Decryption", MB_ICONERROR|MB_OK);
725           log_debug ("%s:%s: leave (PGP/MIME problem)\n", __FILE__, __func__);
726           release_attach_info (table);
727           return gpg_error (GPG_ERR_GENERAL);
728         }
729
730       method = get_attach_method (att);
731       if (method != ATTACH_BY_VALUE)
732         {
733           log_error ("%s:%s: unsupported method %d for PGP/MIME attachment 2",
734                      __FILE__, __func__, method);
735           MessageBox (hwnd, "Problem decrypting PGP/MIME message",
736                       "GPG Decryption", MB_ICONERROR|MB_OK);
737           log_debug ("%s:%s: leave (bad PGP/MIME method)\n",__FILE__,__func__);
738           att->Release ();
739           release_attach_info (table);
740           return gpg_error (GPG_ERR_GENERAL);
741         }
742
743       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
744                               0, 0, (LPUNKNOWN*) &from);
745       if (FAILED (hr))
746         {
747           log_error ("%s:%s: can't open data of attachment 2: hr=%#lx",
748                      __FILE__, __func__, pos, hr);
749           MessageBox (hwnd, "Problem decrypting PGP/MIME message",
750                       "GPG Decryption", MB_ICONERROR|MB_OK);
751           log_debug ("%s:%s: leave (OpenProperty failed)\n",__FILE__,__func__);
752           att->Release ();
753           release_attach_info (table);
754           return gpg_error (GPG_ERR_GENERAL);
755         }
756
757       err = op_decrypt_stream_to_buffer (from, &plaintext,
758                                          opt.passwd_ttl, NULL);
759       from->Release ();
760       att->Release ();
761       if (!err)
762         pgpmime_succeeded = 1;
763     }
764   else if (*getOrigText())
765     err = op_decrypt (getOrigText (), &plaintext, opt.passwd_ttl, NULL);
766   else
767     err = gpg_error (GPG_ERR_NO_DATA);
768   if (err)
769     {
770       if (!is_pgpmime && n_attach && gpg_err_code (err) == GPG_ERR_NO_DATA)
771         ;
772       else
773         MessageBox (hwnd, op_strerror (err),
774                     "GPG decryption failed", MB_ICONERROR|MB_OK);
775     }
776   else if (plaintext && *plaintext)
777     {   
778       int is_html = is_html_body (plaintext);
779
780       set_message_body (message, plaintext);
781       xfree (body_plain);
782       body_plain = plaintext;
783       plaintext = NULL;
784       msgcache_put (body_plain, 0, message);
785
786       /* Also set PR_BODY but do not use 'SaveChanges' to make it
787          permanently.  This way the user can reply with the
788          plaintext but the ciphertext is still stored. 
789
790          NOTE: That does not work for OL2003!  This is the reason we
791          implemented the (not fully working) msgcache system. */
792       log_debug ("decrypt isHtml=%d\n", is_html);
793
794       /* XXX: find a way to handle text/html message in a better way! */
795       /* I have disabled the kludge to see what happens to a html
796          message. */
797       if (/*is_html ||*/ update_display (hwnd, this, exchange_cb)) 
798         {
799           const char s[] = 
800             "The message text cannot be displayed.\n"
801             "You have to save the decrypted message to view it.\n"
802             "Then you need to re-open the message.\n\n"
803             "Do you want to save the decrypted message?";
804           int what;
805           
806           what = MessageBox (hwnd, s, "GPG Decryption",
807                              MB_YESNO|MB_ICONWARNING);
808           if (what == IDYES) 
809             {
810               log_debug ("decrypt: saving plaintext message.\n");
811               hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
812               if (FAILED (hr))
813                 log_debug ("%s:%s: SaveChanges failed: hr=%#lx",
814                            __FILE__, __func__, hr);
815             }
816         }
817     }
818
819
820   /* If we have signed attachments.  Ask whether the signatures should
821      be verified; we do this is case of large attachments where
822      verification might take long. */
823   if (n_signed && !pgpmime_succeeded)
824     {
825       const char s[] = 
826         "Signed attachments found.\n\n"
827         "@LIST@\n"
828         "Do you want to verify the signatures?";
829       int what;
830       char *text;
831
832       text = text_from_attach_info (table, s, 2);
833       
834       what = MessageBox (hwnd, text, "Attachment Verification",
835                          MB_YESNO|MB_ICONINFORMATION);
836       xfree (text);
837       if (what == IDYES) 
838         {
839           for (pos=0; !table[pos].end_of_table; pos++)
840             if (table[pos].is_signed)
841               {
842                 assert (table[pos].sig_pos < n_attach);
843                 verifyAttachment (hwnd, table, pos, table[pos].sig_pos);
844               }
845         }
846     }
847
848   if (n_encrypted && !pgpmime_succeeded)
849     {
850       const char s[] = 
851         "Encrypted attachments found.\n\n"
852         "@LIST@\n"
853         "Do you want to decrypt and save them?";
854       int what;
855       char *text;
856
857       text = text_from_attach_info (table, s, 4);
858       what = MessageBox (hwnd, text, "Attachment Decryption",
859                          MB_YESNO|MB_ICONINFORMATION);
860       xfree (text);
861       if (what == IDYES) 
862         {
863           for (pos=0; !table[pos].end_of_table; pos++)
864             if (table[pos].is_encrypted)
865               decryptAttachment (hwnd, pos, true, opt.passwd_ttl,
866                                  table[pos].filename);
867         }
868     }
869
870   release_attach_info (table);
871   log_debug ("%s:%s: leave (rc=%d)\n", __FILE__, __func__, err);
872   return err;
873 }
874
875
876 /* Verify the message and display the verification result. */
877 int 
878 GpgMsgImpl::verify (HWND hwnd)
879 {
880   log_debug ("%s:%s: enter\n", __FILE__, __func__);
881   openpgp_t mtype;
882   int err, has_attach;
883   
884   mtype = getMessageType ();
885   has_attach = hasAttachments ();
886   if (mtype == OPENPGP_NONE && !has_attach ) 
887     {
888       log_debug ("%s:%s: leave (no OpenPGP data)\n", __FILE__, __func__);
889       return 0;
890     }
891
892   err = op_verify (getOrigText (), NULL, NULL);
893   if (err)
894     MessageBox (hwnd, op_strerror (err), "GPG Verify", MB_ICONERROR|MB_OK);
895   else
896     update_display (hwnd, this, NULL);
897
898   log_debug ("%s:%s: leave (rc=%d)\n", __FILE__, __func__, err);
899   return err;
900 }
901
902
903
904
905 \f
906 /* Sign the current message. Returns 0 on success. */
907 int
908 GpgMsgImpl::sign (HWND hwnd)
909 {
910   const char *plaintext;
911   char *signedtext = NULL;
912   int err = 0;
913   gpgme_key_t sign_key = NULL;
914
915   log_debug ("%s:%s: enter message=%p\n", __FILE__, __func__, message);
916   
917   /* We don't sign an empty body - a signature on a zero length string
918      is pretty much useless. */
919   if (!*(plaintext = getOrigText ()) && !hasAttachments ()) 
920     {
921       log_debug ("%s:%s: leave (empty)", __FILE__, __func__);
922       return 0; 
923     }
924
925   /* Pop up a dialog box to ask for the signer of the message. */
926   if (signer_dialog_box (&sign_key, NULL) == -1)
927     {
928       log_debug ("%s.%s: leave (dialog failed)\n", __FILE__, __func__);
929       return gpg_error (GPG_ERR_CANCELED);  
930     }
931
932   if (*plaintext)
933     {
934       err = op_sign (plaintext, &signedtext, 
935                      OP_SIG_CLEAR, sign_key, opt.passwd_ttl);
936       if (err)
937         {
938           MessageBox (hwnd, op_strerror (err),
939                       "GPG Sign", MB_ICONERROR|MB_OK);
940           goto leave;
941         }
942     }
943
944   if (opt.auto_sign_attach && hasAttachments ())
945     {
946       unsigned int n;
947       
948       n = getAttachments ();
949       log_debug ("%s:%s: message has %u attachments\n", __FILE__, __func__, n);
950       for (int i=0; i < n; i++) 
951         signAttachment (hwnd, i, sign_key, opt.passwd_ttl);
952       /* FIXME: we should throw an error if signing of any attachment
953          failed. */
954     }
955
956   set_x_header (message, "Outlgpg-Version", PACKAGE_VERSION);
957
958   /* Now that we successfully processed the attachments, we can save
959      the changes to the body.  For unknown reasons we need to set it
960      to empty first. */
961   if (*plaintext)
962     {
963       err = set_message_body (message, "");
964       if (!err)
965         err = set_message_body (message, signedtext);
966       if (err)
967         goto leave;
968     }
969
970
971  leave:
972   xfree (signedtext);
973   gpgme_key_release (sign_key);
974   log_debug ("%s:%s: leave (err=%s)\n", __FILE__, __func__, op_strerror (err));
975   return err;
976 }
977
978
979 \f
980 /* Encrypt and optionally sign (if SIGN is true) the entire message
981    including all attachments. Returns 0 on success. */
982 int 
983 GpgMsgImpl::encrypt_and_sign (HWND hwnd, bool sign)
984 {
985   log_debug ("%s:%s: enter\n", __FILE__, __func__);
986   HRESULT hr;
987   gpgme_key_t *keys = NULL;
988   gpgme_key_t sign_key = NULL;
989   bool is_html;
990   const char *plaintext;
991   char *ciphertext = NULL;
992   char **recipients = NULL;
993   char **unknown = NULL;
994   int err = 0;
995   size_t all = 0;
996   int n_keys;
997     
998   
999   if (!*(plaintext = getOrigText ()) && !hasAttachments ()) 
1000     {
1001       log_debug ("%s:%s: leave (empty)", __FILE__, __func__);
1002       return 0; 
1003     }
1004
1005   /* Pop up a dialog box to ask for the signer of the message. */
1006   if (sign)
1007     {
1008       if (signer_dialog_box (&sign_key, NULL) == -1)
1009         {
1010           log_debug ("%s.%s: leave (dialog failed)\n", __FILE__, __func__);
1011           return gpg_error (GPG_ERR_CANCELED);  
1012         }
1013     }
1014
1015   /* Gather the keys for the recipients. */
1016   recipients = getRecipients ();
1017   n_keys = op_lookup_keys (recipients, &keys, &unknown, &all);
1018
1019   log_debug ("%s:%s: found %d recipients, need %d, unknown=%p\n",
1020              __FILE__, __func__, n_keys, all, unknown);
1021   
1022   if (n_keys != count_recipients (recipients))
1023     {
1024       int opts = 0;
1025       gpgme_key_t *keys2 = NULL;
1026
1027       /* FIXME: The implementation is not correct: op_lookup_keys
1028          returns the number of missing keys but we compare against the
1029          total number of keys; thus the box would pop up even when all
1030          have been found. */
1031       log_debug ("%s:%s: calling recipient_dialog_box2", __FILE__, __func__);
1032       recipient_dialog_box2 (keys, unknown, all, &keys2, &opts);
1033       xfree (keys);
1034       keys = keys2;
1035       if (opts & OPT_FLAG_CANCEL) 
1036         {
1037           err = gpg_error (GPG_ERR_CANCELED);
1038           goto leave;
1039         }
1040     }
1041
1042   if (sign_key)
1043     log_debug ("%s:%s: signer: 0x%s %s\n",  __FILE__, __func__,
1044                keyid_from_key (sign_key), userid_from_key (sign_key));
1045   else
1046     log_debug ("%s:%s: no signer\n", __FILE__, __func__);
1047   if (keys)
1048     {
1049       for (int i=0; keys[i] != NULL; i++)
1050         log_debug ("%s.%s: recp.%d 0x%s %s\n", __FILE__, __func__,
1051                    i, keyid_from_key (keys[i]), userid_from_key (keys[i]));
1052     }
1053
1054   if (*plaintext)
1055     {
1056       is_html = is_html_body (plaintext);
1057
1058       err = op_encrypt (plaintext, &ciphertext, keys, NULL, 0);
1059       if (err)
1060         {
1061           MessageBox (hwnd, op_strerror (err),
1062                       "GPG Encryption", MB_ICONERROR|MB_OK);
1063           goto leave;
1064         }
1065
1066       if (is_html) 
1067         {
1068           char *tmp = add_html_line_endings (ciphertext);
1069           xfree (ciphertext);
1070           ciphertext = tmp;
1071         }
1072
1073 //       {
1074 //         SPropValue prop;
1075
1076 //         prop.ulPropTag=PR_MESSAGE_CLASS_A;
1077 //         prop.Value.lpszA="IPM.Note.OPENPGP";
1078 //         hr = HrSetOneProp (message, &prop);
1079 //         if (hr != S_OK)
1080 //           {
1081 //             log_error ("%s:%s: can't set message class: hr=%#lx\n",
1082 //                        __FILE__, __func__, hr); 
1083 //           }
1084
1085 //         prop.ulPropTag=PR_CONTENT_TYPE_A;
1086 //         prop.Value.lpszA="application/encrypted;foo=bar;type=mytype";
1087 //         hr = HrSetOneProp (message, &prop);
1088 //         if (hr != S_OK)
1089 //           {
1090 //             log_error ("%s:%s: can't set content type: hr=%#lx\n",
1091 //                        __FILE__, __func__, hr); 
1092 //           }
1093 //       }
1094
1095     }
1096
1097   if (hasAttachments ())
1098     {
1099       unsigned int n;
1100       
1101       n = getAttachments ();
1102       log_debug ("%s:%s: message has %u attachments\n", __FILE__, __func__, n);
1103       for (int i=0; !err && i < n; i++) 
1104         err = encryptAttachment (hwnd, i, keys, NULL, 0);
1105       if (err)
1106         {
1107           MessageBox (hwnd, op_strerror (err),
1108                       "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
1109           goto leave;
1110         }
1111     }
1112
1113   set_x_header (message, "Outlgpg-Version", PACKAGE_VERSION);
1114
1115   /* Now that we successfully processed the attachments, we can save
1116      the changes to the body.  For unknown reasons we need to set it
1117      to empty first. */
1118   if (*plaintext)
1119     {
1120       err = set_message_body (message, "");
1121       if (!err)
1122         err = set_message_body (message, ciphertext);
1123       if (err)
1124         goto leave;
1125     }
1126
1127
1128  leave:
1129   /* FIXME: What to do with already encrypted attachments if some of
1130      the encrypted (or other operations) failed? */
1131
1132   for (int i=0; i < n_keys; i++)
1133     xfree (unknown[i]);
1134   if (n_keys)
1135     xfree (unknown);
1136
1137   free_key_array (keys);
1138   free_recipient_array (recipients);
1139   xfree (ciphertext);
1140   log_debug ("%s:%s: leave (err=%s)\n", __FILE__, __func__, op_strerror (err));
1141   return err;
1142 }
1143
1144
1145
1146 \f
1147 /* Attach a public key to a message. */
1148 int 
1149 GpgMsgImpl::attachPublicKey (const char *keyid)
1150 {
1151     /* @untested@ */
1152 #if 0
1153     const char *patt[1];
1154     char *keyfile;
1155     int err, pos = 0;
1156     LPATTACH newatt;
1157
1158     keyfile = generateTempname (keyid);
1159     patt[0] = xstrdup (keyid);
1160     err = op_export_keys (patt, keyfile);
1161
1162     newatt = createAttachment (NULL/*FIXME*/,pos);
1163     setAttachMethod (newatt, ATTACH_BY_VALUE);
1164     setAttachFilename (newatt, keyfile, false);
1165     /* XXX: set proper RFC3156 MIME types. */
1166
1167     if (streamFromFile (keyfile, newatt)) {
1168         log_debug ("attachPublicKey: commit changes.\n");
1169         newatt->SaveChanges (FORCE_SAVE);
1170     }
1171     releaseAttachment (newatt);
1172     xfree (keyfile);
1173     xfree ((void *)patt[0]);
1174     return err;
1175 #endif
1176     return -1;
1177 }
1178
1179
1180
1181
1182 \f
1183 /* Returns whether the message has any attachments. */
1184 bool
1185 GpgMsgImpl::hasAttachments (void)
1186 {
1187   return !!getAttachments ();
1188 }
1189
1190
1191 /* Reads the attachment information and returns the number of
1192    attachments. */
1193 unsigned int
1194 GpgMsgImpl::getAttachments (void)
1195 {
1196   SizedSPropTagArray (1L, propAttNum) = {
1197     1L, {PR_ATTACH_NUM}
1198   };
1199   HRESULT hr;    
1200   LPMAPITABLE table;
1201   LPSRowSet   rows;
1202
1203   if (!message)
1204     return 0;
1205
1206   if (!attach.att_table)
1207     {
1208       hr = message->GetAttachmentTable (0, &table);
1209       if (FAILED (hr))
1210         {
1211           log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
1212                      __FILE__, __func__, hr);
1213           return 0;
1214         }
1215       
1216       hr = HrQueryAllRows (table, (LPSPropTagArray)&propAttNum,
1217                            NULL, NULL, 0, &rows);
1218       if (FAILED (hr))
1219         {
1220           log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
1221                      __FILE__, __func__, hr);
1222           table->Release ();
1223           return 0;
1224         }
1225       attach.att_table = table;
1226       attach.rows = rows;
1227     }
1228
1229   return attach.rows->cRows > 0? attach.rows->cRows : 0;
1230 }
1231
1232
1233
1234 /* Return the attachment method for attachment OBJ. In case of error we
1235    return 0 which happens to be not defined. */
1236 static int
1237 get_attach_method (LPATTACH obj)
1238 {
1239   HRESULT hr;
1240   LPSPropValue propval = NULL;
1241   int method ;
1242
1243   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1244   if (FAILED (hr))
1245     {
1246       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1247                  __FILE__, __func__, hr);
1248       return 0; 
1249     }
1250   /* We don't bother checking whether we really get a PT_LONG ulong
1251      back; if not the system is seriously damaged and we can't do
1252      further harm by returning a possible random value. */
1253   method = propval->Value.l;
1254   MAPIFreeBuffer (propval);
1255   return method;
1256 }
1257
1258
1259 /* Return the content-type of the attachment OBJ or NULL if it dow not
1260    exists.  Caller must free. */
1261 static char *
1262 get_attach_mime_tag (LPATTACH obj)
1263 {
1264   HRESULT hr;
1265   LPSPropValue propval = NULL;
1266   int method ;
1267   char *name;
1268
1269   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
1270   if (FAILED (hr))
1271     {
1272       log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
1273                  __FILE__, __func__, hr);
1274       return NULL; 
1275     }
1276   switch ( PROP_TYPE (propval->ulPropTag) )
1277     {
1278     case PT_UNICODE:
1279       name = wchar_to_utf8 (propval->Value.lpszW);
1280       if (!name)
1281         log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
1282       break;
1283       
1284     case PT_STRING8:
1285       name = xstrdup (propval->Value.lpszA);
1286       break;
1287       
1288     default:
1289       log_debug ("%s:%s: proptag=%xlx not supported\n",
1290                  __FILE__, __func__, propval->ulPropTag);
1291       name = NULL;
1292       break;
1293     }
1294   MAPIFreeBuffer (propval);
1295   return name;
1296 }
1297
1298
1299 /* Return the data property of an attachments or NULL in case of an
1300    error.  Caller must free.  Note, that this routine should only be
1301    used for short data objects like detached signatures. */
1302 static char *
1303 get_short_attach_data (LPATTACH obj)
1304 {
1305   HRESULT hr;
1306   LPSPropValue propval = NULL;
1307   int method ;
1308   char *data;
1309
1310   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_DATA_BIN, &propval);
1311   if (FAILED (hr))
1312     {
1313       log_error ("%s:%s: error getting attachment's data: hr=%#lx",
1314                  __FILE__, __func__, hr);
1315       return NULL; 
1316     }
1317   switch ( PROP_TYPE (propval->ulPropTag) )
1318     {
1319     case PT_BINARY:
1320       /* This is a binary obnject but we know that it must be plain
1321          ASCII due to the armoed format.  */
1322       data = (char*)xmalloc (propval->Value.bin.cb + 1);
1323       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
1324       data[propval->Value.bin.cb] = 0;
1325       break;
1326       
1327     default:
1328       log_debug ("%s:%s: proptag=%#lx not supported\n",
1329                  __FILE__, __func__, propval->ulPropTag);
1330       data = NULL;
1331       break;
1332     }
1333   MAPIFreeBuffer (propval);
1334   return data;
1335 }
1336
1337
1338 /* Check whether the attachment at position POS in the attachment
1339    table is the first part of a PGP/MIME message.  This routine should
1340    only be called if it has already been checked that the content-type
1341    of the attachment is application/pgp-encrypted. */
1342 bool
1343 GpgMsgImpl::isPgpmimeVersionPart (int pos)
1344 {
1345   HRESULT hr;
1346   LPATTACH att;
1347   LPSPropValue propval = NULL;
1348   bool result = false;
1349
1350   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1351   if (FAILED(hr))
1352     return false;
1353
1354   hr = HrGetOneProp ((LPMAPIPROP)att, PR_ATTACH_SIZE, &propval);
1355   if (FAILED (hr))
1356     {
1357       att->Release ();
1358       return false;
1359     }
1360   if ( PROP_TYPE (propval->ulPropTag) != PT_LONG
1361       || propval->Value.l < 10 || propval->Value.l > 1000 )
1362     {
1363       MAPIFreeBuffer (propval);
1364       att->Release ();
1365       return false;
1366     }
1367   MAPIFreeBuffer (propval);
1368
1369   hr = HrGetOneProp ((LPMAPIPROP)att, PR_ATTACH_DATA_BIN, &propval);
1370   if (SUCCEEDED (hr))
1371     {
1372       if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
1373         {
1374           if (propval->Value.bin.cb > 10 && propval->Value.bin.cb < 15 
1375               && !memcmp (propval->Value.bin.lpb, "Version: 1", 10)
1376               && ( propval->Value.bin.lpb[10] == '\r'
1377                    || propval->Value.bin.lpb[10] == '\n'))
1378             result = true;
1379         }
1380       MAPIFreeBuffer (propval);
1381     }
1382   att->Release ();
1383   return result;
1384 }
1385
1386
1387
1388 /* Set an arbitary header in the message MSG with NAME to the value
1389    VAL. */
1390 static bool 
1391 set_x_header (LPMESSAGE msg, const char *name, const char *val)
1392 {  
1393   HRESULT hr;
1394   LPMDB lpMdb = NULL;
1395   LPSPropTagArray pProps = NULL;
1396   SPropValue pv;
1397   MAPINAMEID mnid, *pmnid;      
1398   /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
1399   GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
1400                                             0x00, 0x00, 0x00, 0x46} };
1401
1402   if (!msg)
1403     return false;
1404
1405   memset (&mnid, 0, sizeof mnid);
1406   mnid.lpguid = &guid;
1407   mnid.ulKind = MNID_STRING;
1408   mnid.Kind.lpwstrName = utf8_to_wchar (name);
1409   pmnid = &mnid;
1410   hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
1411   xfree (mnid.Kind.lpwstrName);
1412   if (FAILED (hr)) 
1413     {
1414       log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
1415                  __FILE__, __func__, name, hr); 
1416       return false;
1417     }
1418     
1419   pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
1420   pv.Value.lpszA = (char *)val;
1421   hr = HrSetOneProp(msg, &pv);  
1422   if (hr != S_OK)
1423     {
1424       log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
1425                  __FILE__, __func__, name, hr); 
1426       return false;
1427     }
1428   return true;
1429 }
1430
1431
1432
1433 /* Return the filename from the attachment as a malloced string.  The
1434    encoding we return will be utf8, however the MAPI docs declare that
1435    MAPI does only handle plain ANSI and thus we don't really care
1436    later on.  In fact we would need to convert the filename back to
1437    wchar and use the Unicode versions of the file API.  Returns NULL
1438    on error or if no filename is available. */
1439 static char *
1440 get_attach_filename (LPATTACH obj)
1441 {
1442   HRESULT hr;
1443   LPSPropValue propval;
1444   char *name = NULL;
1445
1446   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1447   if (FAILED(hr)) 
1448     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1449   if (FAILED(hr))
1450     {
1451       log_debug ("%s:%s: no filename property found", __FILE__, __func__);
1452       return NULL;
1453     }
1454
1455   switch ( PROP_TYPE (propval->ulPropTag) )
1456     {
1457     case PT_UNICODE:
1458       name = wchar_to_utf8 (propval->Value.lpszW);
1459       if (!name)
1460         log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
1461       break;
1462       
1463     case PT_STRING8:
1464       name = xstrdup (propval->Value.lpszA);
1465       break;
1466       
1467     default:
1468       log_debug ("%s:%s: proptag=%xlx not supported\n",
1469                  __FILE__, __func__, propval->ulPropTag);
1470       name = NULL;
1471       break;
1472     }
1473   MAPIFreeBuffer (propval);
1474   return name;
1475 }
1476
1477
1478
1479
1480 /* Return a filename to be used for saving an attachment. Returns an
1481    malloced string on success. HWND is the current Window and SRCNAME
1482    the filename to be used as suggestion.  On error; i.e. cancel NULL
1483    is returned. */
1484 static char *
1485 get_save_filename (HWND root, const char *srcname)
1486                                      
1487 {
1488   char filter[] = "All Files (*.*)\0*.*\0\0";
1489   char fname[MAX_PATH+1];
1490   const char *s;
1491   OPENFILENAME ofn;
1492
1493   memset (fname, 0, sizeof (fname));
1494   strncpy (fname, srcname, MAX_PATH-1);
1495   fname[MAX_PATH] = 0;  
1496   
1497
1498   memset (&ofn, 0, sizeof (ofn));
1499   ofn.lStructSize = sizeof (ofn);
1500   ofn.hwndOwner = root;
1501   ofn.lpstrFile = fname;
1502   ofn.nMaxFile = MAX_PATH;
1503   ofn.lpstrFileTitle = NULL;
1504   ofn.nMaxFileTitle = 0;
1505   ofn.Flags |= OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
1506   ofn.lpstrTitle = "GPG - Save decrypted attachment";
1507   ofn.lpstrFilter = filter;
1508
1509   if (GetSaveFileName (&ofn))
1510     return xstrdup (fname);
1511   return NULL;
1512 }
1513
1514 \f
1515 /* Gather information about attachments and return a new object with
1516    these information.  Caller must release the returned information.
1517    The routine will return NULL in case of an error or if no
1518    attachments are available. */
1519 attach_info_t
1520 GpgMsgImpl::gatherAttachmentInfo (void)
1521 {    
1522   HRESULT hr;
1523   attach_info_t table;
1524   unsigned int pos, n_attach;
1525   int method, err;
1526   const char *s;
1527
1528   is_pgpmime = false;
1529   n_attach = getAttachments ();
1530   log_debug ("%s:%s: message has %u attachments\n",
1531              __FILE__, __func__, n_attach);
1532   if (!n_attach)
1533       return NULL;
1534
1535   table = (attach_info_t)xcalloc (n_attach+1, sizeof *table);
1536   for (pos=0; pos < n_attach; pos++) 
1537     {
1538       LPATTACH att;
1539
1540       hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att);     
1541       if (FAILED (hr))
1542         {
1543           log_error ("%s:%s: can't open attachment %d: hr=%#lx",
1544                      __FILE__, __func__, pos, hr);
1545           continue;
1546         }
1547
1548       table[pos].method = get_attach_method (att);
1549       table[pos].filename = get_attach_filename (att);
1550       table[pos].content_type = get_attach_mime_tag (att);
1551       if (table[pos].content_type)
1552         {
1553           char *p = strchr (table[pos].content_type, ';');
1554           if (p)
1555             {
1556               *p++ = 0;
1557               trim_trailing_spaces (table[pos].content_type);
1558               while (strchr (" \t\r\n", *p))
1559                 p++;
1560               trim_trailing_spaces (p);
1561               table[pos].content_type_parms = p;
1562             }
1563         }
1564       att->Release ();
1565     }
1566   table[pos].end_of_table = 1;
1567
1568   /* Figure out whether there are encrypted attachments. */
1569   for (pos=0; !table[pos].end_of_table; pos++)
1570     {
1571       if (table[pos].filename && (s = strrchr (table[pos].filename, '.'))
1572           &&  (!stricmp (s, ".pgp") || !stricmp (s, ".gpg")))
1573         table[pos].is_encrypted = 1;
1574       else if (table[pos].content_type  
1575                && ( !stricmp (table[pos].content_type,
1576                               "application/pgp-encrypted")
1577                    || (!stricmp (table[pos].content_type,
1578                                  "multipart/encrypted")
1579                        && table[pos].content_type_parms
1580                        && strstr (table[pos].content_type_parms,
1581                                   "application/pgp-encrypted"))))
1582         table[pos].is_encrypted = 1;
1583     }
1584      
1585   /* Figure out what attachments are signed. */
1586   for (pos=0; !table[pos].end_of_table; pos++)
1587     {
1588       if (table[pos].filename && (s = strrchr (table[pos].filename, '.'))
1589           &&  !stricmp (s, ".asc")
1590           && table[pos].content_type  
1591           && !stricmp (table[pos].content_type, "application/pgp-signature"))
1592         {
1593           size_t len = (s - table[pos].filename);
1594
1595           /* We mark the actual file, assuming that the .asc is a
1596              detached signature.  To correlate the data file and the
1597              signature we keep track of the POS. */
1598           for (int i=0; !table[i].end_of_table; i++)
1599             if (i != pos && table[i].filename 
1600                 && strlen (table[i].filename) == len
1601                 && !strncmp (table[i].filename, table[pos].filename, len))
1602               {
1603                 table[i].is_signed = 1;
1604                 table[i].sig_pos = pos;
1605               }
1606         }
1607     }
1608
1609   log_debug ("%s:%s: attachment info:\n", __FILE__, __func__);
1610   for (pos=0; !table[pos].end_of_table; pos++)
1611     {
1612       log_debug ("\t%d %d %d %u `%s' `%s' `%s'\n",
1613                  pos, table[pos].is_encrypted,
1614                  table[pos].is_signed, table[pos].sig_pos,
1615                  table[pos].filename, table[pos].content_type,
1616                  table[pos].content_type_parms);
1617     }
1618
1619   /* Simple check whether this is PGP/MIME encrypted.  At least with
1620      OL2003 the conent-type of the body is also correctly set but we
1621      don't make use of this as it is not clear whether this is true
1622      for othyer storage providers. */
1623   if (!opt.compat.no_pgpmime
1624       && pos == 2 && table[0].content_type && table[1].content_type
1625       && !stricmp (table[0].content_type, "application/pgp-encrypted")
1626       && !stricmp (table[1].content_type, "application/octet-stream")
1627       && isPgpmimeVersionPart (0))
1628     {
1629       log_debug ("\tThis is a PGP/MIME encrypted message - table adjusted");
1630       table[0].is_encrypted = 0;
1631       table[1].is_encrypted = 1;
1632       is_pgpmime = true;
1633     }
1634
1635   return table;
1636 }
1637
1638
1639
1640 \f
1641 /* Verify the ATTachment at attachments and table position POS_DATA
1642    agains the signature at position POS_SIG.  Display the status for
1643    each signature. */
1644 void
1645 GpgMsgImpl::verifyAttachment (HWND hwnd, attach_info_t table,
1646                               unsigned int pos_data,
1647                               unsigned int pos_sig)
1648
1649 {    
1650   HRESULT hr;
1651   LPATTACH att;
1652   int err;
1653   char *sig_data;
1654
1655   log_debug ("%s:%s: verifying attachment %d/%d",
1656              __FILE__, __func__, pos_data, pos_sig);
1657
1658   assert (table);
1659   assert (message);
1660
1661   /* First we copy the actual signature into a memory buffer.  Such a
1662      signature is expected to be samll enough to be readable directly
1663      (i.e.less that 16k as suggested by the MS MAPI docs). */
1664   hr = message->OpenAttach (pos_sig, NULL, MAPI_BEST_ACCESS, &att);     
1665   if (FAILED (hr))
1666     {
1667       log_error ("%s:%s: can't open attachment %d (sig): hr=%#lx",
1668                  __FILE__, __func__, pos_sig, hr);
1669       return;
1670     }
1671
1672   if ( table[pos_sig].method == ATTACH_BY_VALUE )
1673     sig_data = get_short_attach_data (att);
1674   else
1675     {
1676       log_error ("%s:%s: attachment %d (sig): method %d not supported",
1677                  __FILE__, __func__, pos_sig, table[pos_sig].method);
1678       att->Release ();
1679       return;
1680     }
1681   att->Release ();
1682   if (!sig_data)
1683     return; /* Problem getting signature; error has already been
1684                logged. */
1685
1686   /* Now get on with the actual signed data. */
1687   hr = message->OpenAttach (pos_data, NULL, MAPI_BEST_ACCESS, &att);    
1688   if (FAILED (hr))
1689     {
1690       log_error ("%s:%s: can't open attachment %d (data): hr=%#lx",
1691                  __FILE__, __func__, pos_data, hr);
1692       xfree (sig_data);
1693       return;
1694     }
1695
1696   if ( table[pos_data].method == ATTACH_BY_VALUE )
1697     {
1698       LPSTREAM stream;
1699
1700       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1701                               0, 0, (LPUNKNOWN*) &stream);
1702       if (FAILED (hr))
1703         {
1704           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1705                      __FILE__, __func__, pos_data, hr);
1706           goto leave;
1707         }
1708       err = op_verify_detached_sig (stream, sig_data,
1709                                     table[pos_data].filename);
1710       if (err)
1711         {
1712           log_debug ("%s:%s: verify detached signature failed: %s",
1713                      __FILE__, __func__, op_strerror (err)); 
1714           MessageBox (hwnd, op_strerror (err),
1715                       "GPG Attachment Verification", MB_ICONERROR|MB_OK);
1716         }
1717       stream->Release ();
1718     }
1719   else
1720     {
1721       log_error ("%s:%s: attachment %d (data): method %d not supported",
1722                  __FILE__, __func__, pos_data, table[pos_data].method);
1723     }
1724
1725  leave:
1726   /* Close this attachment. */
1727   xfree (sig_data);
1728   att->Release ();
1729 }
1730
1731
1732 /* Decrypt the attachment with the internal number POS.
1733    SAVE_PLAINTEXT must be true to save the attachemnt; displaying a
1734    attachment is not yet supported.  If FILENAME is not NULL it will
1735    be displayed along with status outputs. */
1736 void
1737 GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext,
1738                                int ttl, const char *filename)
1739 {    
1740   HRESULT hr;
1741   LPATTACH att;
1742   int method, err;
1743
1744   log_debug ("%s:%s: processing attachment %d", __FILE__, __func__, pos);
1745
1746   /* Make sure that we can access the attachment table. */
1747   if (!message || !getAttachments ())
1748     {
1749       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
1750       return;
1751     }
1752
1753   if (!save_plaintext)
1754     {
1755       log_error ("%s:%s: save_plaintext not requested", __FILE__, __func__);
1756       return;
1757     }
1758
1759   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1760   if (FAILED (hr))
1761     {
1762       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
1763                  __FILE__, __func__, pos, hr);
1764       return;
1765     }
1766
1767   method = get_attach_method (att);
1768   if ( method == ATTACH_EMBEDDED_MSG)
1769     {
1770       /* This is an embedded message.  The orginal G-DATA plugin
1771          decrypted the message and then updated the attachemnt;
1772          i.e. stored the plaintext.  This seemed to ensure that the
1773          attachemnt message was properly displayed.  I am not sure
1774          what we should do - it might be necessary to have a callback
1775          to allow displaying the attachment.  Needs further
1776          experiments. */
1777       LPMESSAGE emb;
1778       
1779       hr = att->OpenProperty (PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 
1780                               MAPI_MODIFY, (LPUNKNOWN*)&emb);
1781       if (FAILED (hr))
1782         {
1783           log_error ("%s:%s: can't open data obj of attachment %d: hr=%#lx",
1784                      __FILE__, __func__, pos, hr);
1785           goto leave;
1786         }
1787
1788       //FIXME  Not sure what to do here.  Did it ever work?
1789       //        setWindow (hwnd);
1790       //        setMessage (emb);
1791       //if (doCmdAttach (action))
1792       //  success = FALSE;
1793       //XXX;
1794       //emb->SaveChanges (FORCE_SAVE);
1795       //att->SaveChanges (FORCE_SAVE);
1796       emb->Release ();
1797     }
1798   else if (method == ATTACH_BY_VALUE)
1799     {
1800       char *s;
1801       char *outname;
1802       char *suggested_name;
1803       LPSTREAM from, to;
1804
1805       suggested_name = get_attach_filename (att);
1806       if (suggested_name)
1807         log_debug ("%s:%s: attachment %d, filename `%s'", 
1808                   __FILE__, __func__, pos, suggested_name);
1809       /* Strip of know extensions or use a default name. */
1810       if (!suggested_name)
1811         {
1812           xfree (suggested_name);
1813           suggested_name = (char*)xmalloc (50);
1814           snprintf (suggested_name, 49, "unnamed-%d.dat", pos);
1815         }
1816       else if ((s = strrchr (suggested_name, '.'))
1817                && (!stricmp (s, ".pgp") 
1818                    || !stricmp (s, ".gpg") 
1819                    || !stricmp (s, ".asc")) )
1820         {
1821           *s = 0;
1822         }
1823       outname = get_save_filename (hwnd, suggested_name);
1824       xfree (suggested_name);
1825
1826       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1827                               0, 0, (LPUNKNOWN*) &from);
1828       if (FAILED (hr))
1829         {
1830           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1831                      __FILE__, __func__, pos, hr);
1832           xfree (outname);
1833           goto leave;
1834         }
1835
1836       hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1837                              (STGM_CREATE | STGM_READWRITE),
1838                              outname, NULL, &to); 
1839       if (FAILED (hr)) 
1840         {
1841           log_error ("%s:%s: can't create stream for `%s': hr=%#lx\n",
1842                      __FILE__, __func__, outname, hr); 
1843           from->Release ();
1844           xfree (outname);
1845           goto leave;
1846         }
1847       
1848       err = op_decrypt_stream (from, to, ttl, filename);
1849       if (err)
1850         {
1851           log_debug ("%s:%s: decrypt stream failed: %s",
1852                      __FILE__, __func__, op_strerror (err)); 
1853           to->Revert ();
1854           to->Release ();
1855           from->Release ();
1856           MessageBox (hwnd, op_strerror (err),
1857                       "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
1858           /* FIXME: We might need to delete outname now.  However a
1859              sensible implementation of the stream object should have
1860              done it trhough the Revert call. */
1861           xfree (outname);
1862           goto leave;
1863         }
1864         
1865       to->Commit (0);
1866       to->Release ();
1867       from->Release ();
1868
1869       /*  Hmmm: Why are we deleting the attachment now????? 
1870           Disabled until clarified.   FIXME */
1871       //if (message->DeleteAttach (pos, 0, NULL, 0) == S_OK)
1872       //   show error;
1873
1874       xfree (outname);
1875     }
1876   else
1877     {
1878       log_error ("%s:%s: attachment %d: method %d not supported",
1879                  __FILE__, __func__, pos, method);
1880     }
1881
1882  leave:
1883   /* Close this attachment. */
1884   att->Release ();
1885 }
1886
1887
1888 /* Sign the attachment with the internal number POS.  TTL is caching
1889    time for a required passphrase. */
1890 void
1891 GpgMsgImpl::signAttachment (HWND hwnd, int pos, gpgme_key_t sign_key, int ttl)
1892 {    
1893   HRESULT hr;
1894   LPATTACH att;
1895   int method, err;
1896   LPSTREAM from = NULL;
1897   LPSTREAM to = NULL;
1898   char *signame = NULL;
1899   LPATTACH newatt = NULL;
1900
1901   /* Make sure that we can access the attachment table. */
1902   if (!message || !getAttachments ())
1903     {
1904       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
1905       return;
1906     }
1907
1908   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
1909   if (FAILED (hr))
1910     {
1911       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
1912                  __FILE__, __func__, pos, hr);
1913       return;
1914     }
1915
1916   /* Construct a filename for the new attachment. */
1917   {
1918     char *tmpname = get_attach_filename (att);
1919     if (!tmpname)
1920       {
1921         signame = (char*)xmalloc (70);
1922         snprintf (signame, 70, "gpg-signature-%d.asc", pos);
1923       }
1924     else
1925       {
1926         signame = (char*)xmalloc (strlen (tmpname) + 4 + 1);
1927         strcpy (stpcpy (signame, tmpname), ".asc");
1928         xfree (tmpname);
1929       }
1930   }
1931
1932   method = get_attach_method (att);
1933   if (method == ATTACH_EMBEDDED_MSG)
1934     {
1935       log_debug ("%s:%s: signing embedded attachments is not supported",
1936                  __FILE__, __func__);
1937     }
1938   else if (method == ATTACH_BY_VALUE)
1939     {
1940       ULONG newpos;
1941       SPropValue prop;
1942
1943       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1944                               0, 0, (LPUNKNOWN*)&from);
1945       if (FAILED (hr))
1946         {
1947           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
1948                      __FILE__, __func__, pos, hr);
1949           goto leave;
1950         }
1951
1952       hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
1953       if (hr != S_OK)
1954         {
1955           log_error ("%s:%s: can't create attachment: hr=%#lx\n",
1956                      __FILE__, __func__, hr); 
1957           goto leave;
1958         }
1959
1960       prop.ulPropTag = PR_ATTACH_METHOD;
1961       prop.Value.ul = ATTACH_BY_VALUE;
1962       hr = HrSetOneProp (newatt, &prop);
1963       if (hr != S_OK)
1964         {
1965           log_error ("%s:%s: can't set attach method: hr=%#lx\n",
1966                      __FILE__, __func__, hr); 
1967           goto leave;
1968         }
1969       
1970       prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
1971       prop.Value.lpszA = signame;   
1972       hr = HrSetOneProp (newatt, &prop);
1973       if (hr != S_OK)
1974         {
1975           log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
1976                      __FILE__, __func__, hr); 
1977           goto leave;
1978         }
1979       log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
1980                  __FILE__, __func__, pos, newpos, signame);
1981
1982       prop.ulPropTag = PR_ATTACH_EXTENSION_A;
1983       prop.Value.lpszA = ".pgpsig";   
1984       hr = HrSetOneProp (newatt, &prop);
1985       if (hr != S_OK)
1986         {
1987           log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
1988                      __FILE__, __func__, hr); 
1989           goto leave;
1990         }
1991
1992       prop.ulPropTag = PR_ATTACH_TAG;
1993       prop.Value.bin.cb  = sizeof oid_mimetag;
1994       prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
1995       hr = HrSetOneProp (newatt, &prop);
1996       if (hr != S_OK)
1997         {
1998           log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
1999                      __FILE__, __func__, hr); 
2000           goto leave;
2001         }
2002
2003       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
2004       prop.Value.lpszA = "application/pgp-signature";
2005       hr = HrSetOneProp (newatt, &prop);
2006       if (hr != S_OK)
2007         {
2008           log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
2009                      __FILE__, __func__, hr); 
2010           goto leave;
2011         }
2012
2013       hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
2014                                  MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
2015       if (FAILED (hr)) 
2016         {
2017           log_error ("%s:%s: can't create output stream: hr=%#lx\n",
2018                      __FILE__, __func__, hr); 
2019           goto leave;
2020         }
2021       
2022       err = op_sign_stream (from, to, OP_SIG_DETACH, sign_key, ttl);
2023       if (err)
2024         {
2025           log_debug ("%s:%s: sign stream failed: %s",
2026                      __FILE__, __func__, op_strerror (err)); 
2027           to->Revert ();
2028           MessageBox (hwnd, op_strerror (err),
2029                       "GPG Attachment Signing", MB_ICONERROR|MB_OK);
2030           goto leave;
2031         }
2032       from->Release ();
2033       from = NULL;
2034       to->Commit (0);
2035       to->Release ();
2036       to = NULL;
2037
2038       hr = newatt->SaveChanges (0);
2039       if (hr != S_OK)
2040         {
2041           log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
2042                      __FILE__, __func__, hr); 
2043           goto leave;
2044         }
2045
2046     }
2047   else
2048     {
2049       log_error ("%s:%s: attachment %d: method %d not supported",
2050                  __FILE__, __func__, pos, method);
2051     }
2052
2053  leave:
2054   if (from)
2055     from->Release ();
2056   if (to)
2057     to->Release ();
2058   xfree (signame);
2059   if (newatt)
2060     newatt->Release ();
2061
2062   att->Release ();
2063 }
2064
2065 /* Encrypt the attachment with the internal number POS.  KEYS is a
2066    NULL terminates array with recipients to whom the message should be
2067    encrypted.  If SIGN_KEY is not NULL the attachment will also get
2068    signed. TTL is the passphrase caching time and only used if
2069    SIGN_KEY is not NULL. Returns 0 on success. */
2070 int
2071 GpgMsgImpl::encryptAttachment (HWND hwnd, int pos, gpgme_key_t *keys,
2072                                gpgme_key_t sign_key, int ttl)
2073 {    
2074   HRESULT hr;
2075   LPATTACH att;
2076   int method, err;
2077   LPSTREAM from = NULL;
2078   LPSTREAM to = NULL;
2079   char *filename = NULL;
2080   LPATTACH newatt = NULL;
2081
2082   /* Make sure that we can access the attachment table. */
2083   if (!message || !getAttachments ())
2084     {
2085       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
2086       return 0;
2087     }
2088
2089   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
2090   if (FAILED (hr))
2091     {
2092       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
2093                  __FILE__, __func__, pos, hr);
2094       err = gpg_error (GPG_ERR_GENERAL);
2095       return err;
2096     }
2097
2098   /* Construct a filename for the new attachment. */
2099   {
2100     char *tmpname = get_attach_filename (att);
2101     if (!tmpname)
2102       {
2103         filename = (char*)xmalloc (70);
2104         snprintf (filename, 70, "gpg-encrypted-%d.pgp", pos);
2105       }
2106     else
2107       {
2108         filename = (char*)xmalloc (strlen (tmpname) + 4 + 1);
2109         strcpy (stpcpy (filename, tmpname), ".pgp");
2110         xfree (tmpname);
2111       }
2112   }
2113
2114   method = get_attach_method (att);
2115   if (method == ATTACH_EMBEDDED_MSG)
2116     {
2117       log_debug ("%s:%s: encrypting embedded attachments is not supported",
2118                  __FILE__, __func__);
2119       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
2120     }
2121   else if (method == ATTACH_BY_VALUE)
2122     {
2123       ULONG newpos;
2124       SPropValue prop;
2125
2126       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
2127                               0, 0, (LPUNKNOWN*)&from);
2128       if (FAILED (hr))
2129         {
2130           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
2131                      __FILE__, __func__, pos, hr);
2132           err = gpg_error (GPG_ERR_GENERAL);
2133           goto leave;
2134         }
2135
2136       hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
2137       if (hr != S_OK)
2138         {
2139           log_error ("%s:%s: can't create attachment: hr=%#lx\n",
2140                      __FILE__, __func__, hr); 
2141           err = gpg_error (GPG_ERR_GENERAL);
2142           goto leave;
2143         }
2144
2145       prop.ulPropTag = PR_ATTACH_METHOD;
2146       prop.Value.ul = ATTACH_BY_VALUE;
2147       hr = HrSetOneProp (newatt, &prop);
2148       if (hr != S_OK)
2149         {
2150           log_error ("%s:%s: can't set attach method: hr=%#lx\n",
2151                      __FILE__, __func__, hr); 
2152           err = gpg_error (GPG_ERR_GENERAL);
2153           goto leave;
2154         }
2155       
2156       prop.ulPropTag = PR_ATTACH_LONG_FILENAME_A;
2157       prop.Value.lpszA = filename;   
2158       hr = HrSetOneProp (newatt, &prop);
2159       if (hr != S_OK)
2160         {
2161           log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
2162                      __FILE__, __func__, hr); 
2163           err = gpg_error (GPG_ERR_GENERAL);
2164           goto leave;
2165         }
2166       log_debug ("%s:%s: setting filename of attachment %d/%ld to `%s'",
2167                  __FILE__, __func__, pos, newpos, filename);
2168
2169       prop.ulPropTag = PR_ATTACH_EXTENSION_A;
2170       prop.Value.lpszA = ".pgpenc";   
2171       hr = HrSetOneProp (newatt, &prop);
2172       if (hr != S_OK)
2173         {
2174           log_error ("%s:%s: can't set attach extension: hr=%#lx\n",
2175                      __FILE__, __func__, hr); 
2176           err = gpg_error (GPG_ERR_GENERAL);
2177           goto leave;
2178         }
2179
2180       prop.ulPropTag = PR_ATTACH_TAG;
2181       prop.Value.bin.cb  = sizeof oid_mimetag;
2182       prop.Value.bin.lpb = (LPBYTE)oid_mimetag;
2183       hr = HrSetOneProp (newatt, &prop);
2184       if (hr != S_OK)
2185         {
2186           log_error ("%s:%s: can't set attach tag: hr=%#lx\n",
2187                      __FILE__, __func__, hr); 
2188           err = gpg_error (GPG_ERR_GENERAL);
2189           goto leave;
2190         }
2191
2192       prop.ulPropTag = PR_ATTACH_MIME_TAG_A;
2193       prop.Value.lpszA = "application/pgp-encrypted";
2194       hr = HrSetOneProp (newatt, &prop);
2195       if (hr != S_OK)
2196         {
2197           log_error ("%s:%s: can't set attach mime tag: hr=%#lx\n",
2198                      __FILE__, __func__, hr); 
2199           err = gpg_error (GPG_ERR_GENERAL);
2200           goto leave;
2201         }
2202
2203       hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
2204                                  MAPI_CREATE | MAPI_MODIFY, (LPUNKNOWN*)&to);
2205       if (FAILED (hr)) 
2206         {
2207           log_error ("%s:%s: can't create output stream: hr=%#lx\n",
2208                      __FILE__, __func__, hr); 
2209           err = gpg_error (GPG_ERR_GENERAL);
2210           goto leave;
2211         }
2212       
2213       err = op_encrypt_stream (from, to, keys, sign_key, ttl);
2214       if (err)
2215         {
2216           log_debug ("%s:%s: encrypt stream failed: %s",
2217                      __FILE__, __func__, op_strerror (err)); 
2218           to->Revert ();
2219           MessageBox (hwnd, op_strerror (err),
2220                       "GPG Attachment Encryption", MB_ICONERROR|MB_OK);
2221           goto leave;
2222         }
2223       from->Release ();
2224       from = NULL;
2225       to->Commit (0);
2226       to->Release ();
2227       to = NULL;
2228
2229       hr = newatt->SaveChanges (0);
2230       if (hr != S_OK)
2231         {
2232           log_error ("%s:%s: SaveChanges failed: hr=%#lx\n",
2233                      __FILE__, __func__, hr); 
2234           err = gpg_error (GPG_ERR_GENERAL);
2235           goto leave;
2236         }
2237
2238       hr = message->DeleteAttach (pos, 0, NULL, 0);
2239       if (hr != S_OK)
2240         {
2241           log_error ("%s:%s: DeleteAtatch failed: hr=%#lx\n",
2242                      __FILE__, __func__, hr); 
2243           err = gpg_error (GPG_ERR_GENERAL);
2244           goto leave;
2245         }
2246
2247     }
2248   else
2249     {
2250       log_error ("%s:%s: attachment %d: method %d not supported",
2251                  __FILE__, __func__, pos, method);
2252       err = gpg_error (GPG_ERR_NOT_SUPPORTED);
2253     }
2254
2255  leave:
2256   if (from)
2257     from->Release ();
2258   if (to)
2259     to->Release ();
2260   xfree (filename);
2261   if (newatt)
2262     newatt->Release ();
2263
2264   att->Release ();
2265   return err;
2266 }