Compiles but I would be very surprised if it runs.
[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 OutlGPG.
5  * 
6  * OutlGPG 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  * OutlGPG 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
34 #include "gpgmsg.hh"
35 #include "util.h"
36 #include "msgcache.h"
37 #include "engine.h"
38
39 /* The string used as the standard XXXXX of decrypted attachments. */
40 #define ATT_FILE_PREFIX ".pgpenc"
41
42
43
44 /*
45    The implementation class of MapiGPGME.  
46  */
47 class GpgMsgImpl : public GpgMsg
48 {
49 public:    
50   GpgMsgImpl () 
51   {
52     message = NULL;
53     body = NULL;
54     body_plain = NULL;
55     body_cipher = NULL;
56     body_signed = NULL;
57     body_cipher_is_html = false;
58
59     attach.table = NULL;
60     attach.rows = NULL;
61   }
62
63   ~GpgMsgImpl ()
64   {
65     if (message)
66       message->Release ();
67     xfree (body);
68     xfree (body_plain);
69     xfree (body_cipher);
70     xfree (body_signed);
71
72     if (attach.table)
73       {
74         attach.table->Release ();
75         attach.table = NULL;
76       }
77     if (attach.rows)
78       {
79         FreeProws (attach.rows);
80         attach.rows = NULL;
81       }
82   }
83
84   void destroy ()
85   {
86     delete this;
87   }
88
89   void operator delete (void *p) 
90   {
91     ::operator delete (p);
92   }
93
94   void setMapiMessage (LPMESSAGE msg)
95   {
96     if (message)
97       {
98         message->Release ();
99         message = NULL;
100       }
101     if (msg)
102       {
103       log_debug ("%s:%s:%d: here\n", __FILE__, __func__, __LINE__);
104         msg->AddRef ();
105       log_debug ("%s:%s:%d: here\n", __FILE__, __func__, __LINE__);
106         message = msg;
107       }
108   }
109   
110   openpgp_t getMessageType (void);
111   bool hasAttachments (void);
112   const char *getOrigText (void);
113   const char *GpgMsgImpl::getDisplayText (void);
114   const char *getPlainText (void);
115   void setPlainText (char *string);
116   void setCipherText (char *string, bool html);
117   void setSignedText (char *string);
118   void saveChanges (bool permanent);
119   bool matchesString (const char *string);
120   char **getRecipients (void);
121   unsigned int getAttachments (void);
122   void decryptAttachment (HWND hwnd, int pos, bool save_plaintext);
123
124
125 private:
126   LPMESSAGE message;  /* Pointer to the message. */
127   char *body;         /* utf-8 encoded body string or NULL. */
128   char *body_plain;   /* Plaintext version of BODY or NULL. */
129   char *body_cipher;  /* Enciphered version of BODY or NULL. */
130   char *body_signed;  /* Signed version of BODY or NULL. */
131   bool body_cipher_is_html; /* Indicating that BODY_CIPHER holds HTML. */
132
133   /* This structure collects the information about attachments. */
134   struct 
135   {
136     LPMAPITABLE table;/* The loaded attachment table or NULL. */
137     LPSRowSet   rows; /* The retrieved set of rows from the table. */
138   } attach;
139   
140   void loadBody (void);
141 };
142
143
144 /* Return a new instance and initialize with the MAPI message object
145    MSG. */
146 GpgMsg *
147 CreateGpgMsg (LPMESSAGE msg)
148 {
149   GpgMsg *m = new GpgMsgImpl ();
150   if (!m)
151     out_of_core ();
152   m->setMapiMessage (msg);
153   return m;
154 }
155
156
157 /* Load the body and make it available as an UTF8 string in the
158    instance variable BODY.  */
159 void
160 GpgMsgImpl::loadBody (void)
161 {
162   HRESULT hr;
163   LPSPropValue lpspvFEID = NULL;
164   LPSTREAM stream;
165   SPropValue prop;
166   STATSTG statInfo;
167   ULONG nread;
168
169   if (body || !message)
170     return;
171   
172 #if 1
173   hr = message->OpenProperty (PR_BODY, &IID_IStream,
174                               0, 0, (LPUNKNOWN*)&stream);
175   if ( hr != S_OK )
176     {
177       log_debug_w32 (hr, "%s:%s: OpenProperty failed", __FILE__, __func__);
178       return;
179     }
180
181   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
182   if ( hr != S_OK )
183     {
184       log_debug_w32 (hr, "%s:%s: Stat failed", __FILE__, __func__);
185       stream->Release ();
186       return;
187     }
188   
189   /* FIXME: We might want to read only the first 1k to decide whetehr
190      this is actually an OpenPGP message and only then continue
191      reading.  This requires some changes in this module. */
192   body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
193   hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
194   if ( hr != S_OK )
195     {
196       log_debug_w32 (hr, "%s:%s: Read failed", __FILE__, __func__);
197       xfree (body);
198       body = NULL;
199       stream->Release ();
200       return;
201     }
202   body[nread] = 0;
203   body[nread+1] = 0;
204   if (nread != statInfo.cbSize.QuadPart)
205     {
206       log_debug ("%s:%s: not enough bytes returned\n", __FILE__, __func__);
207       xfree (body);
208       body = NULL;
209       stream->Release ();
210       return;
211     }
212   stream->Release ();
213
214   /* Fixme: We need to optimize this. */
215   {
216     char *tmp;
217     tmp = wchar_to_utf8 ((wchar_t*)body);
218     if (!tmp)
219       log_debug ("%s: error converting to utf8\n", __func__);
220     else
221       {
222         xfree (body);
223         body = tmp;
224       }
225   }
226
227 #else /* Old method. */
228   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
229   if (FAILED (hr))
230     {
231       log_debug ("%s: HrGetOneProp failed\n", __func__);
232       return;
233     }
234     
235   switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
236     {
237     case PT_UNICODE:
238       body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
239       if (!body)
240         log_debug ("%s: error converting to utf8\n", __func__);
241       break;
242       
243     case PT_STRING8:
244       body = xstrdup (lpspvFEID->Value.lpszA);
245       break;
246       
247     default:
248       log_debug ("%s: proptag=0x%08lx not supported\n",
249                  __func__, lpspvFEID->ulPropTag);
250       break;
251     }
252   MAPIFreeBuffer (lpspvFEID);
253 #endif  
254
255   if (body)
256     log_debug ("%s:%s: loaded body `%s' at %p\n",
257                __FILE__, __func__, body, body);
258   
259
260 //   prop.ulPropTag = PR_ACCESS;
261 //   prop.Value.l = MAPI_ACCESS_MODIFY;
262 //   hr = HrSetOneProp (message, &prop);
263 //   if (FAILED (hr))
264 //     log_debug_w32 (-1,"%s:%s: updating access to 0x%08lx failed",
265 //                    __FILE__, __func__, prop.Value.l);
266 }
267
268
269 /* Return the type of a message. */
270 openpgp_t
271 GpgMsgImpl::getMessageType (void)
272 {
273   const char *s;
274   
275   loadBody ();
276   
277   if (!body || !(s = strstr (body, "BEGIN PGP ")))
278     return OPENPGP_NONE;
279
280   /* (The extra strstr() above is just a simple optimization.) */
281   if (strstr (body, "BEGIN PGP MESSAGE"))
282     return OPENPGP_MSG;
283   else if (strstr (body, "BEGIN PGP SIGNED MESSAGE"))
284     return OPENPGP_CLEARSIG;
285   else if (strstr (body, "BEGIN PGP SIGNATURE"))
286     return OPENPGP_SIG;
287   else if (strstr (body, "BEGIN PGP PUBLIC KEY"))
288     return OPENPGP_PUBKEY;
289   else if (strstr (body, "BEGIN PGP PRIVATE KEY"))
290     return OPENPGP_SECKEY;
291   else
292     return OPENPGP_NONE;
293 }
294
295
296 /* Return the body text as received or composed.  This is guaranteed
297    to never return NULL.  */
298 const char *
299 GpgMsgImpl::getOrigText ()
300 {
301   loadBody ();
302   
303   return body? body : "";
304 }
305
306
307 /* Return the text of the message to be used for the display.  The
308    message objects has intrinsic knowledge about the correct text.  */
309 const char *
310 GpgMsgImpl::getDisplayText (void)
311 {
312   loadBody ();
313
314   if (body_plain)
315     return body_plain;
316   else if (body)
317     return body;
318   else
319     return "";
320 }
321
322
323
324 /* Save STRING as the plaintext version of the message.  WARNING:
325    ownership of STRING is transferred to this object. */
326 void
327 GpgMsgImpl::setPlainText (char *string)
328 {
329   xfree (body_plain);
330   body_plain = string;
331   msgcache_put (body_plain, 0, message);
332 }
333
334 /* Save STRING as the ciphertext version of the message.  WARNING:
335    ownership of STRING is transferred to this object. HTML indicates
336    whether the ciphertext was originally HTML. */
337 void
338 GpgMsgImpl::setCipherText (char *string, bool html)
339 {
340   xfree (body_cipher);
341   body_cipher = string;
342   body_cipher_is_html = html;
343 }
344
345 /* Save STRING as the signed version of the message.  WARNING:
346    ownership of STRING is transferred to this object. */
347 void
348 GpgMsgImpl::setSignedText (char *string)
349 {
350   xfree (body_signed);
351   body_signed = string;
352 }
353
354 /* Save the changes made to the message.  With PERMANENT set to true
355    they are really stored, when not set they are only saved
356    temporary. */
357 void
358 GpgMsgImpl::saveChanges (bool permanent)
359 {
360   SPropValue sProp; 
361   HRESULT hr;
362   int rc = TRUE;
363
364   if (!body_plain)
365     return; /* Nothing to save. */
366
367   if (!permanent)
368     return;
369   
370   /* Make sure that the Plaintext and the Richtext are in sync. */
371 //   if (message)
372 //     {
373 //       BOOL changed;
374
375 //       sProp.ulPropTag = PR_BODY_A;
376 //       sProp.Value.lpszA = "";
377 //       hr = HrSetOneProp(message, &sProp);
378 //       changed = false;
379 //       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
380 //       sProp.Value.lpszA = body_plain;
381 //       hr = HrSetOneProp(message, &sProp);
382 //       RTFSync(message, RTF_SYNC_BODY_CHANGED, &changed);
383 //     }
384
385   sProp.ulPropTag = PR_BODY_W;
386   sProp.Value.lpszW = utf8_to_wchar (body_plain);
387   if (!sProp.Value.lpszW)
388     {
389       log_debug_w32 (-1, "%s:%s: error converting from utf8\n",
390                      __FILE__, __func__);
391       return;
392     }
393   hr = HrSetOneProp (message, &sProp);
394   xfree (sProp.Value.lpszW);
395   if (hr < 0)
396     log_debug_w32 (-1, "%s:%s: HrSetOneProp failed", __FILE__, __func__);
397   else
398     {
399       log_debug ("%s:%s: PR_BODY set to `%s'\n",
400                  __FILE__, __func__, body_plain);
401       {
402         GpgMsg *xmsg = CreateGpgMsg (message);
403         log_debug ("%s:%s:    cross check `%s'\n",
404                    __FILE__, __func__, xmsg->getOrigText ());
405         delete xmsg;
406       }
407       if (permanent && message)
408         {
409           hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
410           if (hr < 0)
411             log_debug_w32 (-1, "%s:%s: SaveChanges failed",
412                            __FILE__, __func__);
413         }
414     }
415
416   log_debug ("%s:%s: leave\n", __FILE__, __func__);
417 }
418
419
420 /* Returns true if STRING matches the actual message. */ 
421 bool
422 GpgMsgImpl::matchesString (const char *string)
423 {
424   /* FIXME:  This is a too simple implementation. */
425   if (string && strstr (string, "BEGIN PGP ") )
426     return true;
427   return false;
428 }
429
430
431
432 /* Return an array of strings with the recipients of the message. On
433    success a malloced array is returned containing allocated strings
434    for each recipient.  The end of the array is marked by NULL.
435    Caller is responsible for releasing the array.  On failure NULL is
436    returned.  */
437 char ** 
438 GpgMsgImpl::getRecipients ()
439 {
440   static SizedSPropTagArray (1L, PropRecipientNum) = {1L, {PR_EMAIL_ADDRESS}};
441   HRESULT hr;
442   LPMAPITABLE lpRecipientTable = NULL;
443   LPSRowSet lpRecipientRows = NULL;
444   char **rset;
445   const char *s;
446   int i, j;
447
448   if (!message)
449     return NULL;
450
451   hr = message->GetRecipientTable (0, &lpRecipientTable);
452   if (FAILED (hr)) 
453     {
454       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed",
455                      __FILE__, __func__);
456       return NULL;
457     }
458
459   hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
460                        NULL, NULL, 0L, &lpRecipientRows);
461   if (FAILED (hr)) 
462     {
463       log_debug_w32 (-1, "%s:%s: GHrQueryAllRows failed", __FILE__, __func__);
464       if (lpRecipientTable)
465         lpRecipientTable->Release();
466       return NULL;
467     }
468
469   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
470
471   for (i = j = 0; i < lpRecipientRows->cRows; i++)
472     {
473       LPSPropValue row;
474
475       if (!lpRecipientRows->aRow[j].cValues)
476         continue;
477       row = lpRecipientRows->aRow[j].lpProps;
478
479       switch ( PROP_TYPE (row->ulPropTag) )
480         {
481         case PT_UNICODE:
482           rset[j] = wchar_to_utf8 (row->Value.lpszW);
483           if (rset[j])
484             j++;
485           else
486             log_debug ("%s:%s: error converting recipient to utf8\n",
487                        __FILE__, __func__);
488           break;
489       
490         case PT_STRING8: /* Assume Ascii. */
491           rset[j++] = xstrdup (row->Value.lpszA);
492           break;
493           
494         default:
495           log_debug ("%s:%s: proptag=0x%08lx not supported\n",
496                      __FILE__, __func__, row->ulPropTag);
497           break;
498         }
499     }
500   rset[j] = NULL;
501
502   if (lpRecipientTable)
503     lpRecipientTable->Release();
504   if (lpRecipientRows)
505     FreeProws(lpRecipientRows); 
506   
507   log_debug ("%s:%s: got %d recipients:\n",
508              __FILE__, __func__, j);
509   for (i=0; rset[i]; i++)
510     log_debug ("%s:%s: \t`%s'\n", __FILE__, __func__, rset[i]);
511
512   return rset;
513 }
514
515
516
517
518
519 /* Returns whether the message has any attachments. */
520 bool
521 GpgMsgImpl::hasAttachments (void)
522 {
523   return !!getAttachments ();
524 }
525
526
527 /* Reads the attachment information and returns the number of
528    attachments. */
529 unsigned int
530 GpgMsgImpl::getAttachments (void)
531 {
532   SizedSPropTagArray (1L, propAttNum) = {
533     1L, {PR_ATTACH_NUM}
534   };
535   HRESULT hr;    
536   LPMAPITABLE table;
537   LPSRowSet   rows;
538
539   if (!message)
540     return 0;
541
542   if (!attach.table)
543     {
544       hr = message->GetAttachmentTable (0, &table);
545       if (FAILED (hr))
546         {
547           log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
548                      __FILE__, __func__, hr);
549           return 0;
550         }
551       
552       hr = HrQueryAllRows (table, (LPSPropTagArray)&propAttNum,
553                            NULL, NULL, 0, &rows);
554       if (FAILED (hr))
555         {
556           table->Release ();
557           return 0;
558         }
559       attach.table = table;
560       attach.rows = rows;
561     }
562
563   return rows->cRows > 0? rows->cRows : 0;
564 }
565
566 /* Return the attachemnt method for attachmet OBJ. In case of error we
567    return 0 which happens to be not defined. */
568 static int
569 get_attach_method (LPATTACH obj)
570 {
571   HRESULT hr;
572   LPSPropValue propval = NULL;
573   int method ;
574
575   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
576   if (FAILED (hr))
577     {
578       log_error ("%s:%s: error getting attachment method: hr=%#lx",
579                  __FILE__, __func__, hr);
580       return 0; 
581     }
582   /* We don't bother checking whether we really get a PT_LONG ulong
583      back; if not the system is seriously damaged and we can't do
584      further harm by returning a possible random value. */
585   method = propval->Value.l;
586   MAPIFreeBuffer (propval);
587   return method;
588 }
589
590
591 /* Return the filename from the attachment as a malloced string.  The
592    encoding we return will be utf8, however the MAPI docs declare that
593    MAPI does only handle plain ANSI and thus we don't really care
594    later on.  In fact we would need to convert the filename back to
595    wchar and use the Unicode versions of the file API.  Returns NULL
596    on error or if no filename is available. */
597 static char *
598 get_attach_filename (LPATTACH obj)
599 {
600   HRESULT hr;
601   LPSPropValue propval;
602   char *name = NULL;
603
604   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
605   if (FAILED(hr)) 
606     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
607   if (FAILED(hr))
608     {
609       log_debug ("%s:%s: no filename property found", __FILE__, __func__);
610       return NULL;
611     }
612
613   switch ( PROP_TYPE (propval->ulPropTag) )
614     {
615     case PT_UNICODE:
616       name = wchar_to_utf8 (propval->Value.lpszW);
617       if (!name)
618         log_debug ("%s:%s: error converting to utf8\n", __FILE__, __func__);
619       break;
620       
621     case PT_STRING8:
622       name = xstrdup (propval->Value.lpszA);
623       break;
624       
625     default:
626       log_debug ("%s:%s: proptag=%xlx not supported\n",
627                  __FILE__, __func__, propval->ulPropTag);
628       break;
629     }
630   MAPIFreeBuffer (propval);
631   return name;
632 }
633
634
635
636
637 /* Return a filename to be used for saving an attachment. Returns an
638    malloced string on success. HWND is the current Window and SRCNAME
639    the filename to be used as suggestion.  On error; i.e. cancel NULL
640    is returned. */
641 static char *
642 get_save_filename (HWND root, const char *srcname)
643                                      
644 {
645   char filter[] = "All Files (*.*)\0*.*\0\0";
646   char fname[MAX_PATH+1];
647   const char *s;
648   OPENFILENAME ofn;
649
650   memset (fname, 0, sizeof (fname));
651
652 #if 0
653   /* FIXME: What the heck does this code? Looking for a prefix in a
654      string an removing it.  Why?.  Also: possible buffer overflow
655      with possible user supplied data.  --- My guess is that we don't
656      need it anymore, now that we are wrinting directly to the
657      outfile. */
658   s = strstr (srcname, ATT_FILE_PREFIX);
659   if (!s)
660     strncpy (fname, srcname, MAX_PATH);
661   else 
662     {
663       strncpy (fname, srcname, (p-srcname));
664       strcat (fname, srcname+(p-srcname)+strlen (ATT_FILE_PREFIX));     
665     }
666 #endif
667
668   memset (&ofn, 0, sizeof (ofn));
669   ofn.lStructSize = sizeof (ofn);
670   ofn.hwndOwner = root;
671   ofn.lpstrFile = fname;
672   ofn.nMaxFile = MAX_PATH;
673   ofn.lpstrFileTitle = NULL;
674   ofn.nMaxFileTitle = 0;
675   ofn.Flags |= OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
676   ofn.lpstrTitle = "GPG - Save decrypted attachments";
677   ofn.lpstrFilter = filter;
678
679   if (GetSaveFileName (&ofn))
680     return xstrdup (fname);
681   return NULL;
682 }
683
684
685
686 /* Decrypt the attachment with the internal number POS.
687    SAVE_PLAINTEXT must be true to save the attachemnt; displaying a
688    attachment is not yet supported. */
689 void
690 GpgMsgImpl::decryptAttachment (HWND hwnd, int pos, bool save_plaintext)
691 {    
692   HRESULT hr;
693   LPATTACH att;
694   int method, err;
695   BOOL success = TRUE;
696
697   /* Make sure that we can access the attachment table. */
698   if (!message || !getAttachments ())
699     {
700       log_debug ("%s:%s: no attachemnts at all", __FILE__, __func__);
701       return;
702     }
703
704   if (!save_plaintext)
705     {
706       log_error ("%s:%s: save_plaintext not requested", __FILE__, __func__);
707       return;
708     }
709
710   hr = message->OpenAttach (pos, NULL, MAPI_BEST_ACCESS, &att); 
711   if (FAILED (hr))
712     {
713       log_debug ("%s:%s: can't open attachment %d: hr=%#lx",
714                  __FILE__, __func__, pos, hr);
715       return;
716     }
717
718   method = get_attach_method (att);
719   if ( method == ATTACH_EMBEDDED_MSG)
720     {
721       /* This is an embedded message.  The orginal G-DATA plugin
722          decrypted the message and then updated the attachemnt;
723          i.e. stored the plaintext.  This seemed to ensure that the
724          attachemnt message was properly displayed.  I am not sure
725          what we should do - it might be necessary to have a callback
726          to allow displaying the attachment.  Needs further
727          experiments. */
728       LPMESSAGE emb;
729       
730       hr = att->OpenProperty (PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 
731                               MAPI_MODIFY, (LPUNKNOWN*)&emb);
732       if (FAILED (hr))
733         {
734           log_error ("%s:%s: can't open data obj of attachment %d: hr=%#lx",
735                      __FILE__, __func__, pos, hr);
736           goto leave;
737         }
738
739       //FIXME  Not sure what to do here.  Did it ever work?
740       //        setWindow (hwnd);
741       //        setMessage (emb);
742       //if (doCmdAttach (action))
743       //  success = FALSE;
744       //XXX;
745       //emb->SaveChanges (FORCE_SAVE);
746       //att->SaveChanges (FORCE_SAVE);
747       emb->Release ();
748     }
749   else if (method == ATTACH_BY_VALUE)
750     {
751       char *outname;
752       char *suggested_name;
753       LPSTREAM from, to;
754
755       suggested_name = get_attach_filename (att);
756       /* FIXME: WHY do we need this check?
757         if (checkAttachmentExtension (strrchr (tmp, '.')) == false)
758           {
759              log_debug ( "%s: no pgp extension found.\n", tmp);
760              xfree (tmp);
761              xfree (inname);
762              r eturn TRUE;
763              } */
764       outname = get_save_filename (hwnd, suggested_name);
765       xfree (suggested_name);
766
767       hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
768                               0, 0, (LPUNKNOWN*) &from);
769       if (FAILED (hr))
770         {
771           log_error ("%s:%s: can't open data of attachment %d: hr=%#lx",
772                      __FILE__, __func__, pos, hr);
773           xfree (outname);
774           goto leave;
775         }
776
777       /* If we would want to write to a temporary file, we would use:
778          hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
779                                 (SOF_UNIQUEFILENAME | STGM_DELETEONRELEASE
780                                  |STGM_CREATE | STGM_READWRITE),
781                                  NULL, "gpg", &to);    */
782       hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
783                              (STGM_CREATE | STGM_READWRITE),
784                              outname, NULL, &to); 
785       if (FAILED (hr)) 
786         {
787           log_error ("%s:%s: can't create stream for `%s': hr=%#lx\n",
788                      __FILE__, __func__, outname, hr); 
789           from->Release ();
790           xfree (outname);
791           goto leave;
792         }
793       
794       err = op_decrypt_stream (from, to);
795       if (err)
796         {
797           log_debug ("%s:%s: decrypt stream failed: %s",
798                      __FILE__, __func__, op_strerror (err)); 
799           to->Revert ();
800           to->Release ();
801           from->Release ();
802           MessageBox (NULL, op_strerror (err),
803                       "GPG Attachment Decryption", MB_ICONERROR|MB_OK);
804           /* FIXME: We might need to delete outname now.  However a
805              sensible implementation of the stream object should have
806              done it trhough the Revert call. */
807           xfree (outname);
808           goto leave;
809         }
810         
811       to->Commit (0);
812       to->Release ();
813       from->Release ();
814
815       /*  Hmmm: Why are we deleting the attachment now????? 
816           Disabled until clarified.   FIXME */
817       //if (message->DeleteAttach (pos, 0, NULL, 0) == S_OK)
818       //   show error;
819
820       xfree (outname);
821     }
822   else
823     {
824       log_error ("%s:%s: attachment %d: method %d not supported",
825                  __FILE__, __func__, pos, method);
826     }
827
828  leave:
829   /* Close this attachment. */
830   att->Release ();
831 }