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