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