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