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