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