Fix regression for multipart/signed mails
[gpgol.git] / src / mapihelp.cpp
1 /* mapihelp.cpp - Helper functions for MAPI
2  * Copyright (C) 2005, 2007, 2008 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.1 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 License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <ctype.h>
25 #include <windows.h>
26
27 #include "mymapi.h"
28 #include "mymapitags.h"
29 #include "common.h"
30 #include "rfc822parse.h"
31 #include "serpent.h"
32 #include "mapihelp.h"
33 #include "parsetlv.h"
34 #include "gpgolstr.h"
35 #include "oomhelp.h"
36
37 #ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA
38 #define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011
39 #endif
40 #ifndef CRYPT_E_ASN1_BADTAG
41 #define CRYPT_E_ASN1_BADTAG 0x8009310B
42 #endif
43
44
45 static int get_attach_method (LPATTACH obj);
46 static int has_smime_filename (LPATTACH obj);
47 static char *get_attach_mime_tag (LPATTACH obj);
48
49
50 \f
51
52 /* Print a MAPI property to the log stream. */
53 void
54 log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
55 {
56   HRESULT hr;
57   LPSPropValue propval = NULL;
58   size_t keylen;
59   void *key;
60   char *buf;
61
62   if (!message)
63     return; /* No message: Nop. */
64
65   hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval);
66   if (FAILED (hr))
67     {
68       log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n",
69                  SRCNAME, __func__, propname, hr);
70       return;
71     }
72     
73   switch ( PROP_TYPE (propval->ulPropTag) )
74     {
75     case PT_BINARY:
76       keylen = propval->Value.bin.cb;
77       key = propval->Value.bin.lpb;
78       log_hexdump (key, keylen, "%s: %20s=", __func__, propname);
79       break;
80
81     case PT_UNICODE:
82       buf = wchar_to_utf8 (propval->Value.lpszW);
83       if (!buf)
84         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
85       else
86         log_debug ("%s: %20s=`%s'", __func__, propname, buf);
87       xfree (buf);
88       break;
89       
90     case PT_STRING8:
91       log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA);
92       break;
93
94     case PT_LONG:
95       log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l);
96       break;
97
98     default:
99       log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n",
100                  SRCNAME, __func__, propname,
101                  PROP_TYPE (propval->ulPropTag) );
102       return;
103     }
104   MAPIFreeBuffer (propval);
105 }
106
107
108 /* Helper to create a named property. */
109 static ULONG 
110 create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func)
111 {
112   HRESULT hr;
113   LPSPropTagArray proparr = NULL;
114   MAPINAMEID mnid, *pmnid;
115   GpgOLStr propname(name);
116   /* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties.  */
117   GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06,
118                                             0x1b, 0x03, 0x10, 0x04}};
119   ULONG result;
120   
121   memset (&mnid, 0, sizeof mnid);
122   mnid.lpguid = &guid;
123   mnid.ulKind = MNID_STRING;
124   mnid.Kind.lpwstrName = propname;
125   pmnid = &mnid;
126   hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
127   if (FAILED (hr))
128     proparr = NULL;
129   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
130     {
131       log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
132                  SRCNAME, func, hr); 
133       result = 0;
134     }
135   else
136     result = (proparr->aulPropTag[0] & 0xFFFF0000);
137   if (proparr)
138     MAPIFreeBuffer (proparr);
139     
140   return result;
141 }
142
143
144 /* Return the property tag for GpgOL Msg Class. */
145 int 
146 get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
147 {
148   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__)))
149     return -1;
150   *r_tag |= PT_STRING8;
151   return 0;
152 }
153
154 /* Return the property tag for GpgOL Old Msg Class.  The Old Msg Class
155    saves the message class as seen before we changed it the first
156    time. */
157 int 
158 get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
159 {
160   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Old Msg Class", __func__)))
161     return -1;
162   *r_tag |= PT_STRING8;
163   return 0;
164 }
165
166
167 /* Return the property tag for GpgOL Attach Type. */
168 int 
169 get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag)
170 {
171   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__)))
172     return -1;
173   *r_tag |= PT_LONG;
174   return 0;
175 }
176
177
178 /* Return the property tag for GpgOL Sig Status. */
179 int 
180 get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag)
181 {
182   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__)))
183     return -1;
184   *r_tag |= PT_STRING8;
185   return 0;
186 }
187
188
189 /* Return the property tag for GpgOL Protect IV. */
190 int 
191 get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag)
192 {
193   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__)))
194     return -1;
195   *r_tag |= PT_BINARY;
196   return 0;
197 }
198
199 /* Return the property tag for GpgOL Last Decrypted. */
200 int 
201 get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag)
202 {
203   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__)))
204     return -1;
205   *r_tag |= PT_BINARY;
206   return 0;
207 }
208
209
210 /* Return the property tag for GpgOL MIME structure. */
211 int 
212 get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag)
213 {
214   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__)))
215     return -1;
216   *r_tag |= PT_STRING8;
217   return 0;
218 }
219
220
221 /* Return the property tag for GpgOL Charset. */
222 int 
223 get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag)
224 {
225   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__)))
226     return -1;
227   *r_tag |= PT_STRING8;
228   return 0;
229 }
230
231
232 /* Return the property tag for GpgOL Draft Info.  */
233 int 
234 get_gpgoldraftinfo_tag (LPMESSAGE message, ULONG *r_tag)
235 {
236   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Draft Info", __func__)))
237     return -1;
238   *r_tag |= PT_STRING8;
239   return 0;
240 }
241
242
243 /* Return the tag of the Internet Charset Body property which seems to
244    hold the PR_BODY as received and thus before charset
245    conversion.  */
246 int
247 get_internetcharsetbody_tag (LPMESSAGE message, ULONG *r_tag)
248 {
249   HRESULT hr;
250   LPSPropTagArray proparr = NULL;
251   MAPINAMEID mnid, *pmnid;      
252   /* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */
253   GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0,
254                                             0x4F, 0xD6, 0x56, 0x85}};
255   GpgOLStr propname (L"Internet Charset Body");
256   int result;
257
258   memset (&mnid, 0, sizeof mnid);
259   mnid.lpguid = &guid;
260   mnid.ulKind = MNID_STRING;
261   mnid.Kind.lpwstrName = propname;
262   pmnid = &mnid;
263   hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr);
264   if (FAILED (hr))
265     proparr = NULL;
266   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
267     {
268       log_error ("%s:%s: can't get the Internet Charset Body property:"
269                  " hr=%#lx\n", SRCNAME, __func__, hr); 
270       result = -1;
271     }
272   else
273     {
274       result = 0;
275       *r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY);
276     }
277
278   if (proparr)
279     MAPIFreeBuffer (proparr);
280   
281   return result;
282 }
283
284 /* Return the property tag for GpgOL UUID Info.  */
285 static int
286 get_gpgoluid_tag (LPMESSAGE message, ULONG *r_tag)
287 {
288   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL UID", __func__)))
289     return -1;
290   *r_tag |= PT_UNICODE;
291   return 0;
292 }
293
294 char *
295 mapi_get_uid (LPMESSAGE msg)
296 {
297   /* If the UUID is not in OOM maybe we find it in mapi. */
298   if (!msg)
299     {
300       log_error ("%s:%s: Called without message",
301                  SRCNAME, __func__);
302       return NULL;
303     }
304   ULONG tag;
305   if (get_gpgoluid_tag (msg, &tag))
306     {
307       log_debug ("%s:%s: Failed to get tag for '%p'",
308                  SRCNAME, __func__, msg);
309       return NULL;
310     }
311   LPSPropValue propval = NULL;
312   HRESULT hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
313   if (hr)
314     {
315       log_debug ("%s:%s: Failed to get prop for '%p'",
316                  SRCNAME, __func__, msg);
317       return NULL;
318     }
319   char *ret = NULL;
320   if (PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
321     {
322       ret = wchar_to_utf8 (propval->Value.lpszW);
323       log_debug ("%s:%s: Fund uuid in MAPI for %p",
324                  SRCNAME, __func__, msg);
325     }
326   else if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
327     {
328       ret = strdup (propval->Value.lpszA);
329       log_debug ("%s:%s: Fund uuid in MAPI for %p",
330                  SRCNAME, __func__, msg);
331     }
332   MAPIFreeBuffer (propval);
333   return ret;
334 }
335
336
337 /* A Wrapper around the SaveChanges method.  This function should be
338    called indirect through the mapi_save_changes macro.  Returns 0 on
339    success. */
340 int
341 mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body,
342                       const char *dbg_file, const char *dbg_func)
343 {
344   HRESULT hr;
345   SPropTagArray proparray;
346   int any = 0;
347   
348   if (mapi_has_last_decrypted (message))
349     {
350       proparray.cValues = 1;
351       proparray.aulPropTag[0] = PR_BODY;
352       hr = message->DeleteProps (&proparray, NULL);
353       if (hr)
354         log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
355                        log_srcname (dbg_file), dbg_func);
356       else
357         any = 1;
358
359       proparray.cValues = 1;
360       proparray.aulPropTag[0] = PR_BODY_HTML;
361       hr = message->DeleteProps (&proparray, NULL);
362       if (hr)
363         log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
364                        log_srcname (dbg_file), dbg_func);
365       else
366         any = 1;
367     }
368
369   if (!only_del_body || any)
370     {
371       int i;
372       for (i = 0, hr = 0; hr && i < 10; i++)
373         {
374           hr = message->SaveChanges (flags);
375           if (hr)
376             {
377               log_debug ("%s:%s: Failed try to save.",
378                          SRCNAME, __func__);
379               Sleep (1000);
380             }
381         }
382       if (hr)
383         {
384           log_error ("%s:%s: SaveChanges(%lu) failed: hr=%#lx\n",
385                      log_srcname (dbg_file), dbg_func,
386                      (unsigned long)flags, hr); 
387           return -1;
388         }
389     }
390   
391   return 0;
392 }
393
394
395 /* Set an arbitary header in the message MSG with NAME to the value
396    VAL. */
397 int
398 mapi_set_header (LPMESSAGE msg, const char *name, const char *val)
399 {  
400   HRESULT hr;
401   LPSPropTagArray pProps = NULL;
402   SPropValue pv;
403   MAPINAMEID mnid, *pmnid;      
404   /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
405   GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
406                                             0x00, 0x00, 0x00, 0x46} };
407   int result;
408
409   if (!msg)
410     return -1;
411
412   memset (&mnid, 0, sizeof mnid);
413   mnid.lpguid = &guid;
414   mnid.ulKind = MNID_STRING;
415   mnid.Kind.lpwstrName = utf8_to_wchar (name);
416   pmnid = &mnid;
417   hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
418   xfree (mnid.Kind.lpwstrName);
419   if (FAILED (hr)) 
420     {
421       pProps = NULL;
422       log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
423                  SRCNAME, __func__, name, hr); 
424       result = -1;
425     }
426   else
427     {
428       pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
429       pv.Value.lpszA = (char *)val;
430       hr = HrSetOneProp(msg, &pv);      
431       if (hr)
432         {
433           log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
434                      SRCNAME, __func__, name, hr); 
435           result = -1;
436         }
437       else
438         result = 0;
439     }
440
441   if (pProps)
442     MAPIFreeBuffer (pProps);
443
444   return result;
445 }
446
447
448
449 /* Return the body as a new IStream object.  Returns NULL on failure.
450    The stream returns the body as an ASCII stream (Use mapi_get_body
451    for an UTF-8 value).  */
452 LPSTREAM
453 mapi_get_body_as_stream (LPMESSAGE message)
454 {
455   HRESULT hr;
456   ULONG tag;
457   LPSTREAM stream;
458
459   if (!message)
460     return NULL;
461
462   if (!get_internetcharsetbody_tag (message, &tag) )
463     {
464       /* The store knows about the Internet Charset Body property,
465          thus try to get the body from this property if it exists.  */
466       
467       hr = message->OpenProperty (tag, &IID_IStream, 0, 0, 
468                                   (LPUNKNOWN*)&stream);
469       if (!hr)
470         return stream;
471
472       log_debug ("%s:%s: OpenProperty tag=%lx failed: hr=%#lx",
473                  SRCNAME, __func__, tag, hr);
474     }
475
476   /* We try to get it as an ASCII body.  If this fails we would either
477      need to implement some kind of stream filter to translated to
478      utf-8 or read everyting into a memory buffer and [provide an
479      istream from that memory buffer.  */
480   hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0, 
481                               (LPUNKNOWN*)&stream);
482   if (hr)
483     {
484       log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
485       return NULL;
486     }
487
488   return stream;
489 }
490
491
492
493 /* Return the body of the message in an allocated buffer.  The buffer
494    is guaranteed to be Nul terminated.  The actual length (ie. the
495    strlen()) will be stored at R_NBYTES.  The body will be returned in
496    UTF-8 encoding. Returns NULL if no body is available.  */
497 char *
498 mapi_get_body (LPMESSAGE message, size_t *r_nbytes)
499 {
500   HRESULT hr;
501   LPSPropValue lpspvFEID = NULL;
502   LPSTREAM stream;
503   STATSTG statInfo;
504   ULONG nread;
505   char *body = NULL;
506
507   if (r_nbytes)
508     *r_nbytes = 0;
509   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
510   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
511     { 
512       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
513         {
514         case PT_UNICODE:
515           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
516           if (!body)
517             log_debug ("%s: error converting to utf8\n", __func__);
518           break;
519           
520         case PT_STRING8:
521           body = xstrdup (lpspvFEID->Value.lpszA);
522           break;
523           
524         default:
525           log_debug ("%s: proptag=0x%08lx not supported\n",
526                      __func__, lpspvFEID->ulPropTag);
527           break;
528         }
529       MAPIFreeBuffer (lpspvFEID);
530     }
531   else /* Message is large; use an IStream to read it.  */
532     {
533       hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, 
534                                   (LPUNKNOWN*)&stream);
535       if (hr)
536         {
537           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
538                      SRCNAME, __func__, hr);
539           return NULL;
540         }
541       
542       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
543       if (hr)
544         {
545           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
546           gpgol_release (stream);
547           return NULL;
548         }
549       
550       /* Fixme: We might want to read only the first 1k to decide
551          whether this is actually an OpenPGP message and only then
552          continue reading.  */
553       body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
554       hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
555       if (hr)
556         {
557           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
558           xfree (body);
559           gpgol_release (stream);
560           return NULL;
561         }
562       body[nread] = 0;
563       body[nread+1] = 0;
564       if (nread != statInfo.cbSize.QuadPart)
565         {
566           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
567           xfree (body);
568           gpgol_release (stream);
569           return NULL;
570         }
571       gpgol_release (stream);
572       
573       {
574         char *tmp;
575         tmp = wchar_to_utf8 ((wchar_t*)body);
576         if (!tmp)
577           log_debug ("%s: error converting to utf8\n", __func__);
578         else
579           {
580             xfree (body);
581             body = tmp;
582           }
583       }
584     }
585
586   if (r_nbytes)
587     *r_nbytes = strlen (body);
588   return body;
589 }
590
591
592
593 /* Look at the body of the MESSAGE and try to figure out whether this
594    is a supported PGP message.  Returns the new message class or NULL
595    if it does not look like a PGP message.  */
596 static char *
597 get_msgcls_from_pgp_lines (LPMESSAGE message)
598 {
599   HRESULT hr;
600   LPSTREAM stream;
601   STATSTG statInfo;
602   ULONG nread;
603   size_t nbytes;
604   char *body = NULL;
605   char *p;
606   char *msgcls = NULL;
607   ULONG tag;
608   int   is_binary = 0;
609
610   if (!opt.mime_ui)
611     {
612       return NULL;
613     }
614
615   hr = 0;
616   if (!get_internetcharsetbody_tag (message, &tag) )
617     {
618       hr = message->OpenProperty (tag, &IID_IStream, 0, 0, 
619                                   (LPUNKNOWN*)&stream);
620       if (!hr)
621         is_binary = 1;
622     }
623   else
624     {
625       log_debug ("%s:%s: Failed to get body tag.",
626                  SRCNAME, __func__);
627       return NULL;
628     }
629   if (hr)
630     {
631       tag = PR_BODY;
632       hr = message->OpenProperty (tag, &IID_IStream, 0, 0, 
633                                   (LPUNKNOWN*)&stream);
634     }
635   if (hr)
636     {
637       log_debug ("%s:%s: OpenProperty(%lx) failed: hr=%#lx",
638                  SRCNAME, __func__, tag, hr);
639       return NULL;
640     }
641
642   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
643   if (hr)
644     {
645       log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
646       gpgol_release (stream);
647       return NULL;
648     }
649   
650   /* We read only the first 1k to decide whether this is actually an
651      OpenPGP armored message .  */
652   nbytes = (size_t)statInfo.cbSize.QuadPart;
653   if (nbytes > 1024*2)
654     nbytes = 1024*2;
655   body = (char*)xmalloc (nbytes + 2);
656   hr = stream->Read (body, nbytes, &nread);
657   if (hr)
658     {
659       log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
660       xfree (body);
661       gpgol_release (stream);
662       return NULL;
663     }
664   body[nread] = 0;
665   body[nread+1] = 0;
666   if (nread != nbytes)
667     {
668       log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
669       
670       xfree (body);
671       gpgol_release (stream);
672       return NULL;
673     }
674   gpgol_release (stream);
675
676   if (!is_binary)
677     {
678       char *tmp;
679       tmp = wchar_to_utf8 ((wchar_t*)body);
680       if (!tmp)
681         log_debug ("%s: error converting to utf8\n", __func__);
682       else
683         {
684           xfree (body);
685           body = tmp;
686         }
687     }
688
689
690   /* The first ~1k of the body of the message is now available in the
691      utf-8 string BODY.  Walk over it to figure out its type.  */
692   for (p=body; p && *p; p = ((p=strchr (p+1, '\n')) ? (p+1) : NULL))
693     {
694       if (!strncmp (p, "-----BEGIN PGP ", 15))
695         {
696           /* Enabling clearsigned detection for Outlook 2010 and later
697              would result in data loss as the signature is not reverted. */
698           if (!strncmp (p+15, "SIGNED MESSAGE-----", 19)
699               && trailing_ws_p (p+15+19))
700             msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned");
701           else if (!strncmp (p+15, "MESSAGE-----", 12)
702                    && trailing_ws_p (p+15+12))
703             msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage");
704           break;
705         }
706
707 #if 0
708       This might be too strict for some broken implementations. Lets
709       look anywhere in the first 1k.
710       else if (!trailing_ws_p (p))
711         break;  /* Text before the PGP message - don't take this as a
712                    proper message.  */
713 #endif
714     }
715
716
717   xfree (body);
718   return msgcls;
719 }
720
721
722 /* Check whether the message is really a CMS encrypted message.  
723    We check here whether the message is really encrypted by looking at
724    the object identifier inside the CMS data.  Returns:
725     -1 := Unknown message type,
726      0 := The message is signed,
727      1 := The message is encrypted.
728
729    This function is required for two reasons: 
730
731    1. Due to a bug in CryptoEx which sometimes assignes the *.CexEnc
732       message class to signed messages and only updates the message
733       class after accessing them.  Thus in old stores there may be a
734       lot of *.CexEnc message which are actually just signed.
735  
736    2. If the smime-type parameter is missing we need another way to
737       decide whether to decrypt or to verify.
738
739    3. Some messages lack a PR_TRANSPORT_MESSAGE_HEADERS and thus it is
740       not possible to deduce the message type from the mail headers.
741       This function may be used to identify the message anyway.
742  */
743 static int
744 is_really_cms_encrypted (LPMESSAGE message)
745 {    
746   HRESULT hr;
747   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
748   LPMAPITABLE mapitable;
749   LPSRowSet   mapirows;
750   unsigned int pos, n_attach;
751   int result = -1; /* Unknown.  */
752   LPATTACH att = NULL;
753   LPSTREAM stream = NULL;
754   char buffer[24];  /* 24 bytes are more than enough to peek at.
755                        Cf. ksba_cms_identify() from the libksba
756                        package.  */
757   const char *p;
758   ULONG nread;
759   size_t n;
760   tlvinfo_t ti;
761
762   hr = message->GetAttachmentTable (0, &mapitable);
763   if (FAILED (hr))
764     {
765       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
766                  SRCNAME, __func__, hr);
767       return -1;
768     }
769       
770   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
771                        NULL, NULL, 0, &mapirows);
772   if (FAILED (hr))
773     {
774       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
775                  SRCNAME, __func__, hr);
776       gpgol_release (mapitable);
777       return -1;
778     }
779   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
780   if (n_attach != 1)
781     {
782       FreeProws (mapirows);
783       gpgol_release (mapitable);
784       log_debug ("%s:%s: not just one attachment", SRCNAME, __func__);
785       return -1;
786     }
787   pos = 0;
788
789   if (mapirows->aRow[pos].cValues < 1)
790     {
791       log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
792       goto leave;
793     }
794   if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
795     {
796       log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
797       goto leave;
798     }
799   hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
800                             NULL, MAPI_BEST_ACCESS, &att);      
801   if (FAILED (hr))
802     {
803       log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
804                  SRCNAME, __func__, pos, 
805                  mapirows->aRow[pos].lpProps[0].Value.l, hr);
806       goto leave;
807     }
808   if (!has_smime_filename (att))
809     {
810       log_debug ("%s:%s: no smime filename", SRCNAME, __func__);
811       goto leave;
812     }
813   if (get_attach_method (att) != ATTACH_BY_VALUE)
814     {
815       log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
816       goto leave;
817     }
818   
819   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
820                           0, 0, (LPUNKNOWN*) &stream);
821   if (FAILED (hr))
822     {
823       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
824                  SRCNAME, __func__, hr);
825       goto leave;
826     }
827
828   hr = stream->Read (buffer, sizeof buffer, &nread);
829   if ( hr != S_OK )
830     {
831       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
832       goto leave;
833     }
834   if (nread < sizeof buffer)
835     {
836       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
837       goto leave;
838     }
839
840   p = buffer;
841   n = nread;
842   if (parse_tlv (&p, &n, &ti))
843     goto leave;
844   if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
845         && ti.is_cons) )
846     goto leave;
847   if (parse_tlv (&p, &n, &ti))
848     goto leave;
849   if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID
850         && !ti.is_cons && ti.length) || ti.length > n)
851     goto leave;
852   /* Now is this enveloped data (1.2.840.113549.1.7.3)
853                  or signed data (1.2.840.113549.1.7.2) ? */
854   if (ti.length == 9)
855     {
856       if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
857         result = 1; /* Encrypted.  */
858       else if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9))
859         result = 0; /* Signed.  */
860     }
861   
862  leave:
863   if (stream)
864     gpgol_release (stream);
865   if (att)
866     gpgol_release (att);
867   FreeProws (mapirows);
868   gpgol_release (mapitable);
869   return result;
870 }
871
872
873
874 /* Return the content-type of the first and only attachment of MESSAGE
875    or NULL if it does not exists.  Caller must free. */
876 static char *
877 get_first_attach_mime_tag (LPMESSAGE message)
878 {    
879   HRESULT hr;
880   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
881   LPMAPITABLE mapitable;
882   LPSRowSet   mapirows;
883   unsigned int pos, n_attach;
884   LPATTACH att = NULL;
885   char *result = NULL;
886
887   hr = message->GetAttachmentTable (0, &mapitable);
888   if (FAILED (hr))
889     {
890       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
891                  SRCNAME, __func__, hr);
892       return NULL;
893     }
894       
895   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
896                        NULL, NULL, 0, &mapirows);
897   if (FAILED (hr))
898     {
899       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
900                  SRCNAME, __func__, hr);
901       gpgol_release (mapitable);
902       return NULL;
903     }
904   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
905   if (n_attach != 1)
906     {
907       FreeProws (mapirows);
908       gpgol_release (mapitable);
909       log_debug ("%s:%s: not just one attachment", SRCNAME, __func__);
910       return NULL;
911     }
912   pos = 0;
913
914   if (mapirows->aRow[pos].cValues < 1)
915     {
916       log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
917       goto leave;
918     }
919   if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
920     {
921       log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
922       goto leave;
923     }
924   hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
925                             NULL, MAPI_BEST_ACCESS, &att);      
926   if (FAILED (hr))
927     {
928       log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
929                  SRCNAME, __func__, pos, 
930                  mapirows->aRow[pos].lpProps[0].Value.l, hr);
931       goto leave;
932     }
933
934   /* Note: We do not expect a filename.  */
935
936   if (get_attach_method (att) != ATTACH_BY_VALUE)
937     {
938       log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
939       goto leave;
940     }
941
942   result = get_attach_mime_tag (att);
943   
944  leave:
945   if (att)
946     gpgol_release (att);
947   FreeProws (mapirows);
948   gpgol_release (mapitable);
949   return result;
950 }
951
952
953 /* Helper for mapi_change_message_class.  Returns the new message
954    class as an allocated string.
955
956    Most message today are of the message class "IPM.Note".  However a
957    PGP/MIME encrypted message also has this class.  We need to see
958    whether we can detect such a mail right here and change the message
959    class accordingly. */
960 static char *
961 change_message_class_ipm_note (LPMESSAGE message)
962 {
963   char *newvalue = NULL;
964   char *ct, *proto;
965
966   ct = mapi_get_message_content_type (message, &proto, NULL);
967   log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__,
968              ct ? ct : "null");
969   if (ct && proto)
970     {
971       log_debug ("%s:%s:     protocol is '%s'", SRCNAME, __func__, proto);
972
973       if (!strcmp (ct, "multipart/encrypted")
974           && !strcmp (proto, "application/pgp-encrypted"))
975         {
976           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
977         }
978       else if (!strcmp (ct, "multipart/signed")
979                && !strcmp (proto, "application/pgp-signature"))
980         {
981           /* Sometimes we receive a PGP/MIME signed message with a
982              class IPM.Note.  */
983           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
984         }
985       xfree (proto);
986     }
987   else if (!ct || !strcmp (ct, "text/plain") ||
988            !strcmp (ct, "multipart/mixed") ||
989            !strcmp (ct, "multipart/alternative") ||
990            !strcmp (ct, "multipart/related") ||
991            !strcmp (ct, "text/html"))
992     {
993       /* It is quite common to have a multipart/mixed or alternative
994          mail with separate encrypted PGP parts.  Look at the body to
995          decide.  */
996       newvalue = get_msgcls_from_pgp_lines (message);
997     }
998
999   xfree (ct);
1000
1001   return newvalue;
1002 }
1003
1004 /* Helper for mapi_change_message_class.  Returns the new message
1005    class as an allocated string.
1006
1007    This function is used for the message class "IPM.Note.SMIME".  It
1008    indicates an S/MIME opaque encrypted or signed message.  This may
1009    also be an PGP/MIME mail. */
1010 static char *
1011 change_message_class_ipm_note_smime (LPMESSAGE message)
1012 {
1013   char *newvalue = NULL;
1014   char *ct, *proto, *smtype;
1015   
1016   ct = mapi_get_message_content_type (message, &proto, &smtype);
1017   if (ct)
1018     {
1019       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1020       if (proto 
1021           && !strcmp (ct, "multipart/signed")
1022           && !strcmp (proto, "application/pgp-signature"))
1023         {
1024           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1025         }
1026       else if (!opt.enable_smime)
1027         ; /* S/MIME not enabled; thus no further checks.  */
1028       else if (smtype)
1029         {
1030           log_debug ("%s:%s:   smime-type is '%s'", SRCNAME, __func__, smtype);
1031           
1032           if (!strcmp (ct, "application/pkcs7-mime")
1033               || !strcmp (ct, "application/x-pkcs7-mime"))
1034             {
1035               if (!strcmp (smtype, "signed-data"))
1036                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1037               else if (!strcmp (smtype, "enveloped-data"))
1038                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1039             }
1040         }
1041       else
1042         {
1043           /* No smime type.  The filename parameter is often not
1044              reliable, thus we better look into the message to see if
1045              it is encrypted and assume an opaque signed one if this
1046              is not the case.  */
1047           switch (is_really_cms_encrypted (message))
1048             {
1049             case 0:
1050               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1051               break;
1052             case 1:
1053               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1054               break;
1055             }
1056
1057         }
1058       xfree (smtype);
1059       xfree (proto);
1060       xfree (ct);
1061     }
1062   else
1063     {
1064       log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1065
1066       /* CryptoEx (or the Toltec Connector) create messages without
1067          the transport headers property and thus we don't know the
1068          content type.  We try to detect the message type anyway by
1069          looking into the first and only attachments.  */
1070       switch (is_really_cms_encrypted (message))
1071         {
1072         case 0:
1073           newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1074           break;
1075         case 1:
1076           newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1077           break;
1078         default: /* Unknown.  */
1079           break;
1080         }
1081     }
1082
1083   /* If we did not found anything but let's change the class anyway.  */
1084   if (!newvalue && opt.enable_smime)
1085     newvalue = xstrdup ("IPM.Note.GpgOL");
1086
1087   return newvalue;
1088 }
1089
1090 /* Helper for mapi_change_message_class.  Returns the new message
1091    class as an allocated string.
1092
1093    This function is used for the message class
1094    "IPM.Note.SMIME.MultipartSigned".  This is an S/MIME message class
1095    but smime support is not enabled.  We need to check whether this is
1096    actually a PGP/MIME message.  */
1097 static char *
1098 change_message_class_ipm_note_smime_multipartsigned (LPMESSAGE message)
1099 {
1100   char *newvalue = NULL;
1101   char *ct, *proto;
1102
1103   ct = mapi_get_message_content_type (message, &proto, NULL);
1104   if (ct)
1105     {
1106       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1107       if (proto 
1108           && !strcmp (ct, "multipart/signed")
1109           && !strcmp (proto, "application/pgp-signature"))
1110         {
1111           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1112         }
1113       xfree (proto);
1114       xfree (ct);
1115     }
1116   else
1117     log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1118   
1119   return newvalue;
1120 }
1121
1122 /* Helper for mapi_change_message_class.  Returns the new message
1123    class as an allocated string.
1124
1125    This function is used for the message classes
1126    "IPM.Note.Secure.CexSig" and "IPM.Note.Secure.Cexenc" (in the
1127    latter case IS_CEXSIG is true).  These are CryptoEx generated
1128    signature or encryption messages.  */
1129 static char *
1130 change_message_class_ipm_note_secure_cex (LPMESSAGE message, int is_cexenc)
1131 {
1132   char *newvalue = NULL;
1133   char *ct, *smtype, *proto;
1134   
1135   ct = mapi_get_message_content_type (message, &proto, &smtype);
1136   if (ct)
1137     {
1138       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1139       if (smtype)
1140         log_debug ("%s:%s:   smime-type is '%s'", SRCNAME, __func__, smtype);
1141       if (proto)
1142         log_debug ("%s:%s:     protocol is '%s'", SRCNAME, __func__, proto);
1143
1144       if (smtype)
1145         {
1146           if (!strcmp (ct, "application/pkcs7-mime")
1147               || !strcmp (ct, "application/x-pkcs7-mime"))
1148             {
1149               if (!strcmp (smtype, "signed-data"))
1150                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1151               else if (!strcmp (smtype, "enveloped-data"))
1152                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1153             }
1154         }
1155
1156       if (!newvalue && proto)
1157         {
1158           if (!strcmp (ct, "multipart/signed")
1159               && (!strcmp (proto, "application/pkcs7-signature")
1160                   || !strcmp (proto, "application/x-pkcs7-signature")))
1161             {
1162               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1163             }
1164           else if (!strcmp (ct, "multipart/signed")
1165                    && (!strcmp (proto, "application/pgp-signature")))
1166             {
1167               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1168             }
1169         }
1170       
1171       if (!newvalue && (!strcmp (ct, "text/plain") ||
1172                         !strcmp (ct, "multipart/alternative") ||
1173                         !strcmp (ct, "multipart/mixed")))
1174         {
1175           newvalue = get_msgcls_from_pgp_lines (message);
1176         }
1177       
1178       if (!newvalue)
1179         {
1180           switch (is_really_cms_encrypted (message))
1181             {
1182             case 0:
1183               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1184               break;
1185             case 1:
1186               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1187               break;
1188             }
1189         }
1190       
1191       xfree (smtype);
1192       xfree (proto);
1193       xfree (ct);
1194     }
1195   else
1196     {
1197       log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1198       if (is_cexenc)
1199         {
1200           switch (is_really_cms_encrypted (message))
1201             {
1202             case 0:
1203               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1204               break;
1205             case 1:
1206               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1207               break;
1208             }
1209         }
1210       else
1211         {
1212           char *mimetag;
1213
1214           mimetag = get_first_attach_mime_tag (message);
1215           if (mimetag && !strcmp (mimetag, "multipart/signed"))
1216             newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1217           xfree (mimetag);
1218         }
1219
1220       if (!newvalue)
1221         {
1222           newvalue = get_msgcls_from_pgp_lines (message);
1223         }
1224     }
1225
1226   if (!newvalue)
1227     newvalue = xstrdup ("IPM.Note.GpgOL");
1228
1229   return newvalue;
1230 }
1231
1232
1233 /* This function checks whether MESSAGE requires processing by us and
1234    adjusts the message class to our own.  By passing true for
1235    SYNC_OVERRIDE the actual MAPI message class will be updated to our
1236    own message class overide.  Return true if the message was
1237    changed. */
1238 int
1239 mapi_change_message_class (LPMESSAGE message, int sync_override)
1240 {
1241   HRESULT hr;
1242   ULONG tag;
1243   SPropValue prop;
1244   LPSPropValue propval = NULL;
1245   char *newvalue = NULL;
1246   int need_save = 0;
1247   int have_override = 0;
1248
1249   if (!message)
1250     return 0; /* No message: Nop. */
1251
1252   if (get_gpgolmsgclass_tag (message, &tag) )
1253     return 0; /* Ooops. */
1254
1255   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1256   if (FAILED (hr))
1257     {
1258       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1259       if (FAILED (hr))
1260         {
1261           log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1262                      SRCNAME, __func__, hr);
1263           return 0;
1264         }
1265     }
1266   else
1267     {
1268       have_override = 1;
1269       log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1270     }
1271     
1272   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1273     {
1274       const char *s = propval->Value.lpszA;
1275       int cexenc = 0;
1276       
1277       log_debug ("%s:%s: checking message class `%s'", 
1278                        SRCNAME, __func__, s);
1279       if (!strcmp (s, "IPM.Note"))
1280         {
1281           newvalue = change_message_class_ipm_note (message);
1282         }
1283       else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME"))
1284         {
1285           newvalue = change_message_class_ipm_note_smime (message);
1286         }
1287       else if (opt.enable_smime
1288                && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.'))
1289         {
1290           /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional
1291              but the previous condition has already taken care of
1292              this).  Note that we can't just insert a new part and
1293              keep the SMIME; we need to change the SMIME part of the
1294              class name so that Outlook does not process it as an
1295              SMIME message. */
1296           newvalue = (char*)xmalloc (strlen (s) + 1);
1297           strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
1298         }
1299       else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
1300         {
1301           /* This is an S/MIME message class but smime support is not
1302              enabled.  We need to check whether this is actually a
1303              PGP/MIME message.  */
1304           newvalue = change_message_class_ipm_note_smime_multipartsigned
1305             (message);
1306         }
1307       else if (sync_override && have_override
1308                && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
1309         {
1310           /* In case the original message class is not yet an GpgOL
1311              class we set it here.  This is needed to convince Outlook
1312              not to do any special processing for IPM.Note.SMIME etc.  */
1313           LPSPropValue propval2 = NULL;
1314
1315           hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
1316                              &propval2);
1317           if (SUCCEEDED (hr) && PROP_TYPE (propval2->ulPropTag) == PT_STRING8
1318               && propval2->Value.lpszA && strcmp (propval2->Value.lpszA, s))
1319             newvalue = (char*)xstrdup (s);
1320           MAPIFreeBuffer (propval2);
1321         }
1322       else if (opt.enable_smime 
1323                && (!strcmp (s, "IPM.Note.Secure.CexSig")
1324                    || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
1325         {
1326           newvalue = change_message_class_ipm_note_secure_cex
1327             (message, cexenc);
1328         }
1329     }
1330
1331   if (!newvalue)
1332     {
1333       /* We use our Sig-Status property to mark messages which passed
1334          this function.  This helps us to avoid later tests.  */
1335       if (!mapi_has_sig_status (message))
1336         {
1337           mapi_set_sig_status (message, "#");
1338           need_save = 1;
1339         }
1340     }
1341   else
1342     {
1343       /* Save old message class if not yet done.  (The second
1344          condition is just a failsafe check). */
1345       if (!get_gpgololdmsgclass_tag (message, &tag)
1346           && PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1347         {
1348           LPSPropValue propval2 = NULL;
1349
1350           hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2);
1351           if (!FAILED (hr))
1352             MAPIFreeBuffer (propval2);
1353           else
1354             {
1355               /* No such property - save it.  */
1356               log_debug ("%s:%s: saving old message class\n",
1357                          SRCNAME, __func__);
1358               prop.ulPropTag = tag;
1359               prop.Value.lpszA = propval->Value.lpszA; 
1360               hr = message->SetProps (1, &prop, NULL);
1361               if (hr)
1362                 {
1363                   log_error ("%s:%s: can't save old message class: hr=%#lx\n",
1364                              SRCNAME, __func__, hr);
1365                   MAPIFreeBuffer (propval);
1366                   return 0;
1367                 }
1368               need_save = 1;
1369             }
1370         }
1371       
1372       /* Change message class.  */
1373       log_debug ("%s:%s: setting message class to `%s'\n",
1374                  SRCNAME, __func__, newvalue);
1375       prop.ulPropTag = PR_MESSAGE_CLASS_A;
1376       prop.Value.lpszA = newvalue; 
1377       hr = message->SetProps (1, &prop, NULL);
1378       xfree (newvalue);
1379       if (hr)
1380         {
1381           log_error ("%s:%s: can't set message class: hr=%#lx\n",
1382                      SRCNAME, __func__, hr);
1383           MAPIFreeBuffer (propval);
1384           return 0;
1385         }
1386       need_save = 1;
1387     }
1388   MAPIFreeBuffer (propval);
1389
1390   if (need_save)
1391     {
1392       if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1393         return 0;
1394     }
1395
1396   return 1;
1397 }
1398
1399
1400 /* Return the message class.  This function will never return NULL so
1401    it is mostly useful for debugging.  Caller needs to release the
1402    returned string.  */
1403 char *
1404 mapi_get_message_class (LPMESSAGE message)
1405 {
1406   HRESULT hr;
1407   LPSPropValue propval = NULL;
1408   char *retstr;
1409
1410   if (!message)
1411     return xstrdup ("[No message]");
1412   
1413   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1414   if (FAILED (hr))
1415     {
1416       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1417                  SRCNAME, __func__, hr);
1418       return xstrdup (hr == MAPI_E_NOT_FOUND?
1419                         "[No message class property]":
1420                         "[Error getting message class property]");
1421     }
1422
1423   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1424     retstr = xstrdup (propval->Value.lpszA);
1425   else
1426     retstr = xstrdup ("[Invalid message class property]");
1427     
1428   MAPIFreeBuffer (propval);
1429   return retstr;
1430 }
1431
1432 /* Return the old message class.  This function returns NULL if no old
1433    message class has been saved.  Caller needs to release the returned
1434    string.  */
1435 char *
1436 mapi_get_old_message_class (LPMESSAGE message)
1437 {
1438   HRESULT hr;
1439   ULONG tag;
1440   LPSPropValue propval = NULL;
1441   char *retstr;
1442
1443   if (!message)
1444     return NULL;
1445   
1446   if (get_gpgololdmsgclass_tag (message, &tag))
1447     return NULL;
1448
1449   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1450   if (FAILED (hr))
1451     {
1452       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1453                  SRCNAME, __func__, hr);
1454       return NULL;
1455     }
1456
1457   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1458     retstr = xstrdup (propval->Value.lpszA);
1459   else
1460     retstr = NULL;
1461     
1462   MAPIFreeBuffer (propval);
1463   return retstr;
1464 }
1465
1466
1467
1468 /* Return the sender of the message.  According to the specs this is
1469    an UTF-8 string; we rely on that the UI server handles
1470    internationalized domain names.  */ 
1471 char *
1472 mapi_get_sender (LPMESSAGE message)
1473 {
1474   HRESULT hr;
1475   LPSPropValue propval = NULL;
1476   char *buf;
1477   char *p0, *p;
1478   
1479   if (!message)
1480     return NULL; /* No message: Nop. */
1481
1482   hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval);
1483   if (FAILED (hr))
1484     {
1485       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1486                  SRCNAME, __func__, hr);
1487       return NULL;
1488     }
1489     
1490   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1491     {
1492       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1493                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1494       MAPIFreeBuffer (propval);
1495       return NULL;
1496     }
1497   
1498   buf = wchar_to_utf8 (propval->Value.lpszW);
1499   MAPIFreeBuffer (propval);
1500   if (!buf)
1501     {
1502       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1503       return NULL;
1504     }
1505   /* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields
1506      using Ctrl-A as delimiter.  The first field looks like the ascii
1507      formatted number of fields to follow, the second field like the
1508      email account and the third seems to be a textual description of
1509      that account.  We return the second field. */
1510   p = strchr (buf, '\x01');
1511   if (!p)
1512     {
1513       log_error ("%s:%s: unknown format of the value `%s'\n",
1514                  SRCNAME, __func__, buf);
1515       xfree (buf);
1516       return NULL;
1517     }
1518   for (p0=buf, p++; *p && *p != '\x01';)
1519     *p0++ = *p++;
1520   *p0 = 0;
1521
1522   /* When using an Exchange account this is an X.509 address and not
1523      an SMTP address.  We try to detect this here and extract only the
1524      CN RDN.  Note that there are two CNs.  This is just a simple
1525      approach and not a real parser.  A better way to do this would be
1526      to ask MAPI to resolve the X.500 name to an SMTP name.  */
1527   if (strstr (buf, "/o=") && strstr (buf, "/ou=") &&
1528       (p = strstr (buf, "/cn=Recipients")) && (p = strstr (p+1, "/cn=")))
1529     {
1530       log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__, buf);
1531       memmove (buf, p+4, strlen (p+4)+1);
1532       if (!strchr (buf, '@'))
1533         {
1534           /* Some Exchange accounts return only the accoutn name and
1535              no rfc821 mail address.  Kleopatra chokes on that, thus
1536              we append a domain name.  Thisis a bad hack.  */
1537           char *newbuf = (char *)xmalloc (strlen (buf) + 6 + 1);
1538           strcpy (stpcpy (newbuf, buf), "@local");
1539           xfree (buf);
1540           buf = newbuf;
1541         }
1542       
1543     }
1544   log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf);
1545   return buf;
1546 }
1547
1548 static char *
1549 resolve_ex_from_address (LPMESSAGE message)
1550 {
1551   HRESULT hr;
1552   char *sender_entryid;
1553   size_t entryidlen;
1554   LPMAPISESSION session;
1555   ULONG utype;
1556   LPUNKNOWN user;
1557   LPSPropValue propval = NULL;
1558   char *buf;
1559
1560   if (g_ol_version_major < 14)
1561     {
1562       log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
1563       return NULL;
1564     }
1565
1566   sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID,
1567                                          &entryidlen);
1568   if (!sender_entryid)
1569     {
1570       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1571       return NULL;
1572     }
1573
1574   session = get_oom_mapi_session ();
1575
1576   if (!session)
1577     {
1578       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1579       xfree (sender_entryid);
1580       return NULL;
1581     }
1582
1583   hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1584                            &IID_IMailUser,
1585                            MAPI_BEST_ACCESS | MAPI_CACHE_ONLY,
1586                            &utype, (IUnknown**)&user);
1587   if (FAILED (hr))
1588     {
1589       log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.",
1590                  SRCNAME, __func__);
1591       hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1592                                &IID_IMailUser,
1593                                MAPI_BEST_ACCESS,
1594                                &utype, (IUnknown**)&user);
1595     }
1596   gpgol_release (session);
1597
1598   if (FAILED (hr))
1599     {
1600       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1601       return NULL;
1602     }
1603
1604   hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval);
1605   if (FAILED (hr))
1606     {
1607       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1608       return NULL;
1609     }
1610
1611   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
1612     {
1613       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1614                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1615       MAPIFreeBuffer (propval);
1616       return NULL;
1617     }
1618   buf = wchar_to_utf8 (propval->Value.lpszW);
1619   MAPIFreeBuffer (propval);
1620
1621   return buf;
1622 }
1623
1624 /* Return the from address of the message as a malloced UTF-8 string.
1625    Returns NULL if that address is not available.  */
1626 char *
1627 mapi_get_from_address (LPMESSAGE message)
1628 {
1629   HRESULT hr;
1630   LPSPropValue propval = NULL;
1631   char *buf;
1632   ULONG try_props[3] = {PidTagSenderSmtpAddress_W,
1633                         PR_SENT_REPRESENTING_SMTP_ADDRESS_W,
1634                         PR_SENDER_EMAIL_ADDRESS_W};
1635
1636   if (!message)
1637     return xstrdup ("[no message]"); /* Ooops.  */
1638
1639   for (int i = 0; i < 3; i++)
1640     {
1641       /* We try to get different properties first as they contain
1642          the SMTP address of the sender. EMAIL address can be
1643          some LDAP stuff for exchange. */
1644       hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i],
1645                          &propval);
1646       if (!FAILED (hr))
1647         {
1648           break;
1649         }
1650     }
1651    /* This is the last result that should always work but not necessarily
1652       contain an SMTP Address. */
1653   if (FAILED (hr))
1654     {
1655       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1656                  SRCNAME, __func__, hr);
1657       return NULL;
1658     }
1659
1660   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1661     {
1662       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1663                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1664       MAPIFreeBuffer (propval);
1665       return NULL;
1666     }
1667   
1668   buf = wchar_to_utf8 (propval->Value.lpszW);
1669   MAPIFreeBuffer (propval);
1670   if (!buf)
1671     {
1672       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1673       return NULL;
1674     }
1675
1676   if (strstr (buf, "/o="))
1677     {
1678       char *buf2;
1679       /* If both SMTP Address properties are not set
1680          we need to fallback to resolve the address
1681          through the address book */
1682       log_debug ("%s:%s: resolving exchange address.",
1683                  SRCNAME, __func__);
1684       buf2 = resolve_ex_from_address (message);
1685       if (buf2)
1686         {
1687           xfree (buf);
1688           return buf2;
1689         }
1690     }
1691
1692   return buf;
1693 }
1694
1695
1696 /* Return the subject of the message as a malloced UTF-8 string.
1697    Returns a replacement string if a subject is missing.  */
1698 char *
1699 mapi_get_subject (LPMESSAGE message)
1700 {
1701   HRESULT hr;
1702   LPSPropValue propval = NULL;
1703   char *buf;
1704   
1705   if (!message)
1706     return xstrdup ("[no message]"); /* Ooops.  */
1707
1708   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SUBJECT_W, &propval);
1709   if (FAILED (hr))
1710     {
1711       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1712                  SRCNAME, __func__, hr);
1713       return xstrdup (_("[no subject]"));
1714     }
1715     
1716   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1717     {
1718       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1719                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1720       MAPIFreeBuffer (propval);
1721       return xstrdup (_("[no subject]"));
1722     }
1723   
1724   buf = wchar_to_utf8 (propval->Value.lpszW);
1725   MAPIFreeBuffer (propval);
1726   if (!buf)
1727     {
1728       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1729       return xstrdup (_("[no subject]"));
1730     }
1731
1732   return buf;
1733 }
1734
1735
1736
1737
1738 /* Return the message type.  This function knows only about our own
1739    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
1740    no special support for.  */
1741 msgtype_t
1742 mapi_get_message_type (LPMESSAGE message)
1743 {
1744   HRESULT hr;
1745   ULONG tag;
1746   LPSPropValue propval = NULL;
1747   msgtype_t msgtype = MSGTYPE_UNKNOWN;
1748
1749   if (!message)
1750     return msgtype; 
1751
1752   if (get_gpgolmsgclass_tag (message, &tag) )
1753     return msgtype; /* Ooops */
1754
1755   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1756   if (FAILED (hr))
1757     {
1758       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1759       if (FAILED (hr))
1760         {
1761           log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
1762                      SRCNAME, __func__, hr);
1763           return msgtype;
1764         }
1765     }
1766   else
1767     log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1768     
1769   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1770     {
1771       const char *s = propval->Value.lpszA;
1772
1773       if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
1774         {
1775           s += 14;
1776           if (!*s)
1777             msgtype = MSGTYPE_GPGOL;
1778           else if (!strcmp (s, ".MultipartSigned"))
1779             msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
1780           else if (!strcmp (s, ".MultipartEncrypted"))
1781             msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
1782           else if (!strcmp (s, ".OpaqueSigned"))
1783             msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
1784           else if (!strcmp (s, ".OpaqueEncrypted"))
1785             msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
1786           else if (!strcmp (s, ".ClearSigned"))
1787             msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
1788           else if (!strcmp (s, ".PGPMessage"))
1789             msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
1790           else
1791             log_debug ("%s:%s: message class `%s' not supported",
1792                        SRCNAME, __func__, s-14);
1793         }
1794       else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
1795         msgtype = MSGTYPE_SMIME;
1796     }
1797   MAPIFreeBuffer (propval);
1798   return msgtype;
1799 }
1800
1801
1802 /* This function is pretty useless because IConverterSession won't
1803    take attachments into account.  Need to write our own version.  */
1804 int
1805 mapi_to_mime (LPMESSAGE message, const char *filename)
1806 {
1807   HRESULT hr;
1808   LPCONVERTERSESSION session;
1809   LPSTREAM stream;
1810
1811   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
1812                          IID_IConverterSession, (void **) &session);
1813   if (FAILED (hr))
1814     {
1815       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
1816                  SRCNAME, __func__, hr);
1817       return -1;
1818     }
1819
1820
1821   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1822                          (STGM_CREATE | STGM_READWRITE),
1823                          (char*)filename, NULL, &stream); 
1824   if (FAILED (hr)) 
1825     {
1826       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
1827                  SRCNAME, __func__, filename, hr); 
1828       hr = -1;
1829     }
1830   else
1831     {
1832       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
1833       if (FAILED (hr))
1834         {
1835           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
1836                      SRCNAME, __func__, hr);
1837           stream->Revert ();
1838           hr = -1;
1839         }
1840       else
1841         {
1842           stream->Commit (0);
1843           hr = 0;
1844         }
1845
1846       gpgol_release (stream);
1847     }
1848
1849   gpgol_release (session);
1850   return hr;
1851 }
1852
1853
1854 /* Return a binary property in a malloced buffer with its length stored
1855    at R_NBYTES.  Returns NULL on error.  */
1856 char *
1857 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
1858 {
1859   HRESULT hr;
1860   LPSPropValue propval = NULL;
1861   char *data;
1862
1863   *r_nbytes = 0;
1864   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
1865   if (FAILED (hr))
1866     {
1867       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1868                  SRCNAME, __func__, proptype, hr);
1869       return NULL; 
1870     }
1871   switch ( PROP_TYPE (propval->ulPropTag) )
1872     {
1873     case PT_BINARY:
1874       /* This is a binary object but we know that it must be plain
1875          ASCII due to the armored format.  */
1876       data = (char*)xmalloc (propval->Value.bin.cb + 1);
1877       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
1878       data[propval->Value.bin.cb] = 0;
1879       *r_nbytes = propval->Value.bin.cb;
1880       break;
1881       
1882     default:
1883       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1884                  SRCNAME, __func__, proptype, propval->ulPropTag);
1885       data = NULL;
1886       break;
1887     }
1888   MAPIFreeBuffer (propval);
1889   return data;
1890 }
1891
1892 /* Return an integer property at R_VALUE.  On error the function
1893    returns -1 and sets R_VALUE to 0, on success 0 is returned.  */
1894 int
1895 mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value)
1896 {
1897   int rc = -1;
1898   HRESULT hr;
1899   LPSPropValue propval = NULL;
1900
1901   *r_value = 0;
1902   hr = HrGetOneProp (object, proptype, &propval);
1903   if (FAILED (hr))
1904     {
1905       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1906                  SRCNAME, __func__, proptype, hr);
1907       return -1; 
1908     }
1909   switch ( PROP_TYPE (propval->ulPropTag) )
1910     {
1911     case PT_LONG:
1912       *r_value = propval->Value.l;
1913       rc = 0;
1914       
1915       break;
1916       
1917     default:
1918       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1919                  SRCNAME, __func__, proptype, propval->ulPropTag);
1920       break;
1921     }
1922   MAPIFreeBuffer (propval);
1923   return rc;
1924 }
1925
1926
1927 /* Return the attachment method for attachment OBJ.  In case of error
1928    we return 0 which happens not to be defined.  */
1929 static int
1930 get_attach_method (LPATTACH obj)
1931 {
1932   HRESULT hr;
1933   LPSPropValue propval = NULL;
1934   int method ;
1935
1936   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1937   if (FAILED (hr))
1938     {
1939       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1940                  SRCNAME, __func__, hr);
1941       return 0; 
1942     }
1943   /* We don't bother checking whether we really get a PT_LONG ulong
1944      back; if not the system is seriously damaged and we can't do
1945      further harm by returning a possible random value.  */
1946   method = propval->Value.l;
1947   MAPIFreeBuffer (propval);
1948   return method;
1949 }
1950
1951
1952
1953 /* Return the filename from the attachment as a malloced string.  The
1954    encoding we return will be UTF-8, however the MAPI docs declare
1955    that MAPI does only handle plain ANSI and thus we don't really care
1956    later on.  In fact we would need to convert the filename back to
1957    wchar and use the Unicode versions of the file API.  Returns NULL
1958    on error or if no filename is available. */
1959 static char *
1960 get_attach_filename (LPATTACH obj)
1961 {
1962   HRESULT hr;
1963   LPSPropValue propval;
1964   char *name = NULL;
1965
1966   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1967   if (FAILED(hr)) 
1968     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1969   if (FAILED(hr))
1970     {
1971       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
1972       return NULL;
1973     }
1974
1975   switch ( PROP_TYPE (propval->ulPropTag) )
1976     {
1977     case PT_UNICODE:
1978       name = wchar_to_utf8 (propval->Value.lpszW);
1979       if (!name)
1980         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1981       break;
1982       
1983     case PT_STRING8:
1984       name = xstrdup (propval->Value.lpszA);
1985       break;
1986       
1987     default:
1988       log_debug ("%s:%s: proptag=%#lx not supported\n",
1989                  SRCNAME, __func__, propval->ulPropTag);
1990       name = NULL;
1991       break;
1992     }
1993   MAPIFreeBuffer (propval);
1994   return name;
1995 }
1996
1997 /* Return the content-id of the attachment OBJ or NULL if it does
1998    not exists.  Caller must free. */
1999 static char *
2000 get_attach_content_id (LPATTACH obj)
2001 {
2002   HRESULT hr;
2003   LPSPropValue propval = NULL;
2004   char *name;
2005
2006   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval);
2007   if (FAILED (hr))
2008     {
2009       if (hr != MAPI_E_NOT_FOUND)
2010         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2011                    SRCNAME, __func__, hr);
2012       return NULL;
2013     }
2014   switch ( PROP_TYPE (propval->ulPropTag) )
2015     {
2016     case PT_UNICODE:
2017       name = wchar_to_utf8 (propval->Value.lpszW);
2018       if (!name)
2019         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2020       break;
2021
2022     case PT_STRING8:
2023       name = xstrdup (propval->Value.lpszA);
2024       break;
2025
2026     default:
2027       log_debug ("%s:%s: proptag=%#lx not supported\n",
2028                  SRCNAME, __func__, propval->ulPropTag);
2029       name = NULL;
2030       break;
2031     }
2032   MAPIFreeBuffer (propval);
2033   return name;
2034 }
2035
2036 /* Return the content-type of the attachment OBJ or NULL if it does
2037    not exists.  Caller must free. */
2038 static char *
2039 get_attach_mime_tag (LPATTACH obj)
2040 {
2041   HRESULT hr;
2042   LPSPropValue propval = NULL;
2043   char *name;
2044
2045   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
2046   if (FAILED (hr))
2047     {
2048       if (hr != MAPI_E_NOT_FOUND)
2049         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2050                    SRCNAME, __func__, hr);
2051       return NULL; 
2052     }
2053   switch ( PROP_TYPE (propval->ulPropTag) )
2054     {
2055     case PT_UNICODE:
2056       name = wchar_to_utf8 (propval->Value.lpszW);
2057       if (!name)
2058         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2059       break;
2060       
2061     case PT_STRING8:
2062       name = xstrdup (propval->Value.lpszA);
2063       break;
2064       
2065     default:
2066       log_debug ("%s:%s: proptag=%#lx not supported\n",
2067                  SRCNAME, __func__, propval->ulPropTag);
2068       name = NULL;
2069       break;
2070     }
2071   MAPIFreeBuffer (propval);
2072   return name;
2073 }
2074
2075
2076 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
2077    the tag of that property. */
2078 attachtype_t
2079 get_gpgolattachtype (LPATTACH obj, ULONG tag)
2080 {
2081   HRESULT hr;
2082   LPSPropValue propval = NULL;
2083   attachtype_t retval;
2084
2085   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2086   if (FAILED (hr))
2087     {
2088       if (hr != MAPI_E_NOT_FOUND)
2089         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
2090                    SRCNAME, __func__, hr);
2091       return ATTACHTYPE_UNKNOWN; 
2092     }
2093   retval = (attachtype_t)propval->Value.l;
2094   MAPIFreeBuffer (propval);
2095   return retval;
2096 }
2097
2098
2099 /* Gather information about attachments and return a new table of
2100    attachments.  Caller must release the returned table.s The routine
2101    will return NULL in case of an error or if no attachments are
2102    available.  With FAST set only some information gets collected. */
2103 mapi_attach_item_t *
2104 mapi_create_attach_table (LPMESSAGE message, int fast)
2105 {    
2106   HRESULT hr;
2107   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
2108   LPMAPITABLE mapitable;
2109   LPSRowSet   mapirows;
2110   mapi_attach_item_t *table; 
2111   unsigned int pos, n_attach;
2112   ULONG moss_tag;
2113
2114   if (get_gpgolattachtype_tag (message, &moss_tag) )
2115     return NULL;
2116
2117   /* Open the attachment table.  */
2118   hr = message->GetAttachmentTable (0, &mapitable);
2119   if (FAILED (hr))
2120     {
2121       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
2122                  SRCNAME, __func__, hr);
2123       return NULL;
2124     }
2125       
2126   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
2127                        NULL, NULL, 0, &mapirows);
2128   if (FAILED (hr))
2129     {
2130       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
2131                  SRCNAME, __func__, hr);
2132       gpgol_release (mapitable);
2133       return NULL;
2134     }
2135   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
2136
2137   log_debug ("%s:%s: message has %u attachments\n",
2138              SRCNAME, __func__, n_attach);
2139   if (!n_attach)
2140     {
2141       FreeProws (mapirows);
2142       gpgol_release (mapitable);
2143       return NULL;
2144     }
2145
2146   /* Allocate our own table.  */
2147   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
2148   for (pos=0; pos < n_attach; pos++) 
2149     {
2150       LPATTACH att;
2151
2152       if (mapirows->aRow[pos].cValues < 1)
2153         {
2154           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
2155           table[pos].mapipos = -1;
2156           continue;
2157         }
2158       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
2159         {
2160           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
2161           table[pos].mapipos = -1;
2162           continue;
2163         }
2164       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
2165
2166       hr = message->OpenAttach (table[pos].mapipos, NULL,
2167                                 MAPI_BEST_ACCESS, &att);        
2168       if (FAILED (hr))
2169         {
2170           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
2171                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
2172           table[pos].mapipos = -1;
2173           continue;
2174         }
2175
2176       table[pos].method = get_attach_method (att);
2177       table[pos].filename = fast? NULL : get_attach_filename (att);
2178       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
2179       table[pos].content_id = fast? NULL : get_attach_content_id (att);
2180       if (table[pos].content_type)
2181         {
2182           char *p = strchr (table[pos].content_type, ';');
2183           if (p)
2184             {
2185               *p++ = 0;
2186               trim_trailing_spaces (table[pos].content_type);
2187               while (strchr (" \t\r\n", *p))
2188                 p++;
2189               trim_trailing_spaces (p);
2190               table[pos].content_type_parms = p;
2191             }
2192         }
2193       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
2194       gpgol_release (att);
2195     }
2196   table[0].private_mapitable = mapitable;
2197   FreeProws (mapirows);
2198   table[pos].end_of_table = 1;
2199   mapitable = NULL;
2200
2201   if (fast)
2202     {
2203       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
2204                  SRCNAME, __func__);
2205     }
2206   else
2207     {
2208       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
2209       for (pos=0; !table[pos].end_of_table; pos++)
2210         {
2211           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
2212                      table[pos].mapipos,
2213                      table[pos].attach_type,
2214                      table[pos].filename, table[pos].content_type,
2215                      table[pos].content_type_parms);
2216         }
2217     }
2218
2219   return table;
2220 }
2221
2222
2223 /* Release a table as created by mapi_create_attach_table. */
2224 void
2225 mapi_release_attach_table (mapi_attach_item_t *table)
2226 {
2227   unsigned int pos;
2228   LPMAPITABLE mapitable;
2229
2230   if (!table)
2231     return;
2232
2233   mapitable = (LPMAPITABLE)table[0].private_mapitable;
2234   if (mapitable)
2235     gpgol_release (mapitable);
2236   for (pos=0; !table[pos].end_of_table; pos++)
2237     {
2238       xfree (table[pos].filename);
2239       xfree (table[pos].content_type);
2240       xfree (table[pos].content_id);
2241     }
2242   xfree (table);
2243 }
2244
2245
2246 /* Return an attachment as a new IStream object.  Returns NULL on
2247    failure.  If R_ATTACH is not NULL the actual attachment will not be
2248    released but stored at that address; the caller needs to release it
2249    in this case.  */
2250 LPSTREAM
2251 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
2252                            LPATTACH *r_attach)
2253 {
2254   HRESULT hr;
2255   LPATTACH att;
2256   LPSTREAM stream;
2257
2258   if (r_attach)
2259     *r_attach = NULL;
2260
2261   if (!item || item->end_of_table || item->mapipos == -1)
2262     return NULL;
2263
2264   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2265   if (FAILED (hr))
2266     {
2267       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2268                  SRCNAME, __func__, item->mapipos, hr);
2269       return NULL;
2270     }
2271   if (item->method != ATTACH_BY_VALUE)
2272     {
2273       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2274       gpgol_release (att);
2275       return NULL;
2276     }
2277
2278   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
2279                           0, 0, (LPUNKNOWN*) &stream);
2280   if (FAILED (hr))
2281     {
2282       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2283                  SRCNAME, __func__, hr);
2284       gpgol_release (att);
2285       return NULL;
2286     }
2287
2288   if (r_attach)
2289     *r_attach = att;
2290   else
2291     gpgol_release (att);
2292
2293   return stream;
2294 }
2295
2296
2297 /* Return a malloced buffer with the content of the attachment. If
2298    R_NBYTES is not NULL the number of bytes will get stored there.
2299    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
2300    NULL on error.  If UNPROTECT is set and the appropriate crypto
2301    attribute is available, the function returns the unprotected
2302    version of the atatchment. */
2303 static char *
2304 attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect, 
2305                   int *r_was_protected)
2306 {
2307   HRESULT hr;
2308   LPSTREAM stream;
2309   STATSTG statInfo;
2310   ULONG nread;
2311   char *buffer;
2312   symenc_t symenc = NULL;
2313
2314   if (r_was_protected)
2315     *r_was_protected = 0;
2316
2317   if (unprotect)
2318     {
2319       ULONG tag;
2320       char *iv;
2321       size_t ivlen;
2322
2323       if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag) 
2324           && (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
2325         {
2326           symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
2327           xfree (iv);
2328           if (!symenc)
2329             log_error ("%s:%s: can't open encryption context", 
2330                        SRCNAME, __func__);
2331         }
2332     }
2333   
2334
2335   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
2336                           0, 0, (LPUNKNOWN*) &stream);
2337   if (FAILED (hr))
2338     {
2339       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2340                  SRCNAME, __func__, hr);
2341       return NULL;
2342     }
2343
2344   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
2345   if ( hr != S_OK )
2346     {
2347       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
2348       gpgol_release (stream);
2349       return NULL;
2350     }
2351       
2352   /* Allocate one byte more so that we can terminate the string.  */
2353   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
2354
2355   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
2356   if ( hr != S_OK )
2357     {
2358       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
2359       xfree (buffer);
2360       gpgol_release (stream);
2361       return NULL;
2362     }
2363   if (nread != statInfo.cbSize.QuadPart)
2364     {
2365       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
2366       xfree (buffer);
2367       buffer = NULL;
2368     }
2369   gpgol_release (stream);
2370
2371   if (buffer && symenc)
2372     {
2373       symenc_cfb_decrypt (symenc, buffer, buffer, nread);
2374       if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
2375         {
2376           xfree (buffer);
2377           buffer = native_to_utf8 
2378             (_("[The content of this message is not visible because it has "
2379                "been decrypted by another Outlook session.  Use the "
2380                "\"decrypt/verify\" command to make it visible]"));
2381           nread = strlen (buffer);
2382         }
2383       else
2384         {
2385           memmove (buffer, buffer+16, nread-16);
2386           nread -= 16;
2387           if (r_was_protected)
2388             *r_was_protected = 1;
2389         }
2390     }
2391
2392   /* Make sure that the buffer is a C string.  */
2393   if (buffer)
2394     buffer[nread] = 0;
2395
2396   symenc_close (symenc);
2397   if (r_nbytes)
2398     *r_nbytes = nread;
2399   return buffer;
2400 }
2401
2402
2403
2404 /* Return an attachment as a malloced buffer.  The size of the buffer
2405    will be stored at R_NBYTES.  If unprotect is true, the atatchment
2406    will be unprotected.  Returns NULL on failure. */
2407 char *
2408 mapi_get_attach (LPMESSAGE message, int unprotect, 
2409                  mapi_attach_item_t *item, size_t *r_nbytes)
2410 {
2411   HRESULT hr;
2412   LPATTACH att;
2413   char *buffer;
2414
2415   if (!item || item->end_of_table || item->mapipos == -1)
2416     return NULL;
2417
2418   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2419   if (FAILED (hr))
2420     {
2421       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2422                  SRCNAME, __func__, item->mapipos, hr);
2423       return NULL;
2424     }
2425   if (item->method != ATTACH_BY_VALUE)
2426     {
2427       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2428       gpgol_release (att);
2429       return NULL;
2430     }
2431
2432   buffer = attach_to_buffer (att, r_nbytes, unprotect, NULL);
2433   gpgol_release (att);
2434
2435   return buffer;
2436 }
2437
2438
2439 /* Mark this attachment as the original MOSS message.  We set a custom
2440    property as well as the hidden flag.  */
2441 int 
2442 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
2443 {
2444   int retval = -1;
2445   HRESULT hr;
2446   LPATTACH att;
2447   SPropValue prop;
2448
2449   if (!item || item->end_of_table || item->mapipos == -1)
2450     return -1;
2451
2452   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2453   if (FAILED (hr))
2454     {
2455       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2456                  SRCNAME, __func__, item->mapipos, hr);
2457       return -1;
2458     }
2459
2460   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
2461     goto leave;
2462   prop.Value.l = ATTACHTYPE_MOSS;
2463   hr = HrSetOneProp (att, &prop);       
2464   if (hr)
2465     {
2466       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2467                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
2468       return false;
2469     }
2470
2471   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2472   prop.Value.b = TRUE;
2473   hr = HrSetOneProp (att, &prop);
2474   if (hr)
2475     {
2476       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2477                  SRCNAME, __func__, hr); 
2478       goto leave;
2479     }
2480   
2481
2482   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
2483   if (hr)
2484     {
2485       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2486                  SRCNAME, __func__, hr); 
2487       goto leave;
2488     }
2489   
2490   retval = 0;
2491     
2492  leave:
2493   gpgol_release (att);
2494   return retval;
2495 }
2496
2497
2498 /* If the hidden property has not been set on ATTACH, set it and save
2499    the changes. */
2500 int 
2501 mapi_set_attach_hidden (LPATTACH attach)
2502 {
2503   int retval = -1;
2504   HRESULT hr;
2505   LPSPropValue propval;
2506   SPropValue prop;
2507
2508   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2509   if (SUCCEEDED (hr) 
2510       && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
2511       && propval->Value.b)
2512     return 0;/* Already set to hidden. */
2513
2514   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2515   prop.Value.b = TRUE;
2516   hr = HrSetOneProp (attach, &prop);
2517   if (hr)
2518     {
2519       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2520                  SRCNAME, __func__, hr); 
2521       goto leave;
2522     }
2523   
2524   hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
2525   if (hr)
2526     {
2527       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2528                  SRCNAME, __func__, hr); 
2529       goto leave;
2530     }
2531   
2532   retval = 0;
2533     
2534  leave:
2535   return retval;
2536 }
2537
2538
2539 /* Returns true if ATTACH has the hidden flag set to true.  */
2540 int
2541 mapi_test_attach_hidden (LPATTACH attach)
2542 {
2543   HRESULT hr;
2544   LPSPropValue propval = NULL;
2545   int result = 0;
2546   
2547   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2548   if (FAILED (hr))
2549     return result; /* No.  */  
2550   
2551   if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b)
2552     result = 1; /* Yes.  */
2553
2554   MAPIFreeBuffer (propval);
2555   return result;
2556 }
2557
2558
2559
2560
2561 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
2562 int
2563 mapi_has_sig_status (LPMESSAGE msg)
2564 {
2565   HRESULT hr;
2566   LPSPropValue propval = NULL;
2567   ULONG tag;
2568   int yes;
2569
2570   if (get_gpgolsigstatus_tag (msg, &tag) )
2571     return 0; /* Error:  Assume No.  */
2572   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2573   if (FAILED (hr))
2574     return 0; /* No.  */  
2575   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2576     yes = 1;
2577   else
2578     yes = 0;
2579
2580   MAPIFreeBuffer (propval);
2581   return yes;
2582 }
2583
2584
2585 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
2586    is not set to unchecked.  */
2587 int
2588 mapi_test_sig_status (LPMESSAGE msg)
2589 {
2590   HRESULT hr;
2591   LPSPropValue propval = NULL;
2592   ULONG tag;
2593   int yes;
2594
2595   if (get_gpgolsigstatus_tag (msg, &tag) )
2596     return 0; /* Error:  Assume No.  */
2597   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2598   if (FAILED (hr))
2599     return 0; /* No.  */  
2600
2601   /* We return False if we have an unknown signature status (?) or the
2602      message has been sent by us and not yet checked (@).  */
2603   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2604     yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
2605                                      || !strcmp (propval->Value.lpszA, "@")));
2606   else
2607     yes = 0;
2608
2609   MAPIFreeBuffer (propval);
2610   return yes;
2611 }
2612
2613
2614 /* Return the signature status as an allocated string.  Will never
2615    return NULL.  */
2616 char *
2617 mapi_get_sig_status (LPMESSAGE msg)
2618 {
2619   HRESULT hr;
2620   LPSPropValue propval = NULL;
2621   ULONG tag;
2622   char *retstr;
2623
2624   if (get_gpgolsigstatus_tag (msg, &tag) )
2625     return xstrdup ("[Error getting tag for sig status]");
2626   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2627   if (FAILED (hr))
2628     return xstrdup ("");
2629   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2630     retstr = xstrdup (propval->Value.lpszA);
2631   else
2632     retstr = xstrdup ("[Sig status has an invalid type]");
2633
2634   MAPIFreeBuffer (propval);
2635   return retstr;
2636 }
2637
2638
2639
2640
2641 /* Set the signature status property to STATUS_STRING.  There are a
2642    few special values:
2643
2644      "#" The message is not of interest to us.
2645      "@" The message has been created and signed or encrypted by us.
2646      "?" The signature status has not been checked.
2647      "!" The signature verified okay 
2648      "~" The signature was not fully verified.
2649      "-" The signature is bad
2650
2651    Note that this function does not call SaveChanges.  */
2652 int 
2653 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
2654 {
2655   HRESULT hr;
2656   SPropValue prop;
2657
2658   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
2659     return -1;
2660   prop.Value.lpszA = xstrdup (status_string);
2661   hr = HrSetOneProp (message, &prop);   
2662   xfree (prop.Value.lpszA);
2663   if (hr)
2664     {
2665       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2666                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
2667       return -1;
2668     }
2669
2670   return 0;
2671 }
2672
2673
2674 /* When sending a message we need to fake the message class so that OL
2675    processes it according to our needs.  However, if we later try to
2676    get the message class from the sent message, OL still has the SMIME
2677    message class and tries to hide this by trying to decrypt the
2678    message and return the message class from the plaintext.  To
2679    mitigate the problem we define our own msg class override
2680    property.  */
2681 int 
2682 mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
2683 {
2684   HRESULT hr;
2685   SPropValue prop;
2686
2687   if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
2688     return -1;
2689   prop.Value.lpszA = xstrdup (name);
2690   hr = HrSetOneProp (message, &prop);   
2691   xfree (prop.Value.lpszA);
2692   if (hr)
2693     {
2694       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2695                  SRCNAME, __func__, "GpgOL Msg Class", hr); 
2696       return -1;
2697     }
2698
2699   return 0;
2700 }
2701
2702
2703 /* Return the charset as assigned by GpgOL to an attachment.  This may
2704    return NULL it is has not been assigned or is the standard
2705    (UTF-8). */
2706 char *
2707 mapi_get_gpgol_charset (LPMESSAGE obj)
2708 {
2709   HRESULT hr;
2710   LPSPropValue propval = NULL;
2711   ULONG tag;
2712   char *retstr;
2713
2714   if (get_gpgolcharset_tag (obj, &tag) )
2715     return NULL; /* Error.  */
2716   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2717   if (FAILED (hr))
2718     return NULL;
2719   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2720     {
2721       if (!strcmp (propval->Value.lpszA, "utf-8"))
2722         retstr = NULL;
2723       else
2724         retstr = xstrdup (propval->Value.lpszA);
2725     }
2726   else
2727     retstr = NULL;
2728
2729   MAPIFreeBuffer (propval);
2730   return retstr;
2731 }
2732
2733
2734 /* Set the GpgOl charset property to an attachment. 
2735    Note that this function does not call SaveChanges.  */
2736 int 
2737 mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
2738 {
2739   HRESULT hr;
2740   SPropValue prop;
2741   char *p;
2742
2743   /* Note that we lowercase the value and cut it to a max of 32
2744      characters.  The latter is required to make sure that
2745      HrSetOneProp will always work.  */
2746   if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
2747     return -1;
2748   prop.Value.lpszA = xstrdup (charset);
2749   for (p=prop.Value.lpszA; *p; p++)
2750     *p = tolower (*(unsigned char*)p);
2751   if (strlen (prop.Value.lpszA) > 32)
2752     prop.Value.lpszA[32] = 0;
2753   hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);   
2754   xfree (prop.Value.lpszA);
2755   if (hr)
2756     {
2757       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2758                  SRCNAME, __func__, "GpgOL Charset", hr); 
2759       return -1;
2760     }
2761
2762   return 0;
2763 }
2764
2765
2766
2767 /* Return GpgOL's draft info string as an allocated string.  If no
2768    draft info is available, NULL is returned.  */
2769 char *
2770 mapi_get_gpgol_draft_info (LPMESSAGE msg)
2771 {
2772   HRESULT hr;
2773   LPSPropValue propval = NULL;
2774   ULONG tag;
2775   char *retstr;
2776
2777   if (get_gpgoldraftinfo_tag (msg, &tag) )
2778     return NULL;
2779   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2780   if (FAILED (hr))
2781     return NULL;
2782   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2783     retstr = xstrdup (propval->Value.lpszA);
2784   else
2785     retstr = NULL;
2786
2787   MAPIFreeBuffer (propval);
2788   return retstr;
2789 }
2790
2791
2792 /* Set GpgOL's draft info string to STRING.  This string is defined as:
2793
2794    Character 1:  'E' = encrypt selected,
2795                  'e' = encrypt not selected.
2796                  '-' = don't care
2797    Character 2:  'S' = sign selected,
2798                  's' = sign not selected.
2799                  '-' = don't care
2800    Character 3:  'A' = Auto protocol 
2801                  'P' = OpenPGP protocol
2802                  'X' = S/MIME protocol
2803                  '-' = don't care
2804                  
2805    If string is NULL, the property will get deleted.
2806
2807    Note that this function does not call SaveChanges.  */
2808 int 
2809 mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string)
2810 {
2811   HRESULT hr;
2812   SPropValue prop;
2813   SPropTagArray proparray;
2814
2815   if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) )
2816     return -1;
2817   if (string)
2818     {
2819       prop.Value.lpszA = xstrdup (string);
2820       hr = HrSetOneProp (message, &prop);       
2821       xfree (prop.Value.lpszA);
2822     }
2823   else
2824     {
2825       proparray.cValues = 1;
2826       proparray.aulPropTag[0] = prop.ulPropTag;
2827       hr = message->DeleteProps (&proparray, NULL);
2828     }
2829   if (hr)
2830     {
2831       log_error ("%s:%s: can't %s %s property: hr=%#lx\n",
2832                  SRCNAME, __func__, string?"set":"delete",
2833                  "GpgOL Draft Info", hr); 
2834       return -1;
2835     }
2836
2837   return 0;
2838 }
2839
2840
2841 /* Return the MIME info as an allocated string.  Will never return
2842    NULL.  */
2843 char *
2844 mapi_get_mime_info (LPMESSAGE msg)
2845 {
2846   HRESULT hr;
2847   LPSPropValue propval = NULL;
2848   ULONG tag;
2849   char *retstr;
2850
2851   if (get_gpgolmimeinfo_tag (msg, &tag) )
2852     return xstrdup ("[Error getting tag for MIME info]");
2853   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2854   if (FAILED (hr))
2855     return xstrdup ("");
2856   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2857     retstr = xstrdup (propval->Value.lpszA);
2858   else
2859     retstr = xstrdup ("[MIME info has an invalid type]");
2860
2861   MAPIFreeBuffer (propval);
2862   return retstr;
2863 }
2864
2865
2866 /* Helper around mapi_get_gpgol_draft_info to avoid
2867    the string handling.
2868    Return values are:
2869    0 -> Do nothing
2870    1 -> Encrypt
2871    2 -> Sign
2872    3 -> Encrypt & Sign*/
2873 int
2874 get_gpgol_draft_info_flags (LPMESSAGE message)
2875 {
2876   char *buf = mapi_get_gpgol_draft_info (message);
2877   int ret = 0;
2878   if (!buf)
2879     {
2880       return 0;
2881     }
2882   if (buf[0] == 'E')
2883     {
2884       ret |= 1;
2885     }
2886   if (buf[1] == 'S')
2887     {
2888       ret |= 2;
2889     }
2890   xfree (buf);
2891   return ret;
2892 }
2893
2894 /* Sets the draft info flags. Protocol is always Auto.
2895    flags should be the same as defined by
2896    get_gpgol_draft_info_flags
2897 */
2898 int
2899 set_gpgol_draft_info_flags (LPMESSAGE message, int flags)
2900 {
2901   char buf[4];
2902   buf[3] = '\0';
2903   buf[2] = 'A'; /* Protocol */
2904   buf[1] = flags & 2 ? 'S' : 's';
2905   buf[0] = flags & 1 ? 'E' : 'e';
2906
2907   return mapi_set_gpgol_draft_info (message, buf);
2908 }
2909
2910
2911 /* Helper for mapi_get_msg_content_type() */
2912 static int
2913 get_message_content_type_cb (void *dummy_arg,
2914                              rfc822parse_event_t event, rfc822parse_t msg)
2915 {
2916   (void)dummy_arg;
2917   (void)msg;
2918
2919   if (event == RFC822PARSE_T2BODY)
2920     return 42; /* Hack to stop the parsing after having read the
2921                   outer headers. */
2922   return 0;
2923 }
2924
2925
2926 /* Return Content-Type of the current message.  This one is taken
2927    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
2928    string with the protocol parameter will be stored at this address,
2929    if no protocol is given NULL will be stored.  If R_SMTYPE is not
2930    NULL a string with the smime-type parameter will be stored there.
2931    Caller must release all returned strings.  */
2932 char *
2933 mapi_get_message_content_type (LPMESSAGE message,
2934                                char **r_protocol, char **r_smtype)
2935 {
2936   HRESULT hr;
2937   LPSPropValue propval = NULL;
2938   rfc822parse_t msg;
2939   const char *header_lines, *s;
2940   rfc822parse_field_t ctx;
2941   size_t length;
2942   char *retstr = NULL;
2943   
2944   if (r_protocol)
2945     *r_protocol = NULL;
2946   if (r_smtype)
2947     *r_smtype = NULL;
2948
2949   hr = HrGetOneProp ((LPMAPIPROP)message,
2950                      PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
2951   if (FAILED (hr))
2952     {
2953       log_error ("%s:%s: error getting the headers lines: hr=%#lx",
2954                  SRCNAME, __func__, hr);
2955       return NULL; 
2956     }
2957   if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
2958     {
2959       /* As per rfc822, header lines must be plain ascii, so no need
2960          to cope with unicode etc. */
2961       log_error ("%s:%s: proptag=%#lx not supported\n",
2962                  SRCNAME, __func__, propval->ulPropTag);
2963       MAPIFreeBuffer (propval);
2964       return NULL;
2965     }
2966   header_lines = propval->Value.lpszA;
2967
2968   /* Read the headers into an rfc822 object. */
2969   msg = rfc822parse_open (get_message_content_type_cb, NULL);
2970   if (!msg)
2971     {
2972       log_error ("%s:%s: rfc822parse_open failed\n", SRCNAME, __func__);
2973       MAPIFreeBuffer (propval);
2974       return NULL;
2975     }
2976   
2977   while ((s = strchr (header_lines, '\n')))
2978     {
2979       length = (s - header_lines);
2980       if (length && s[-1] == '\r')
2981         length--;
2982       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
2983       header_lines = s+1;
2984     }
2985   
2986   /* Parse the content-type field. */
2987   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
2988   if (ctx)
2989     {
2990       const char *s1, *s2;
2991       s1 = rfc822parse_query_media_type (ctx, &s2);
2992       if (s1)
2993         {
2994           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
2995           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
2996
2997           if (r_protocol)
2998             {
2999               s = rfc822parse_query_parameter (ctx, "protocol", 0);
3000               if (s)
3001                 *r_protocol = xstrdup (s);
3002             }
3003           if (r_smtype)
3004             {
3005               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
3006               if (s)
3007                 *r_smtype = xstrdup (s);
3008             }
3009         }
3010       rfc822parse_release_field (ctx);
3011     }
3012
3013   rfc822parse_close (msg);
3014   MAPIFreeBuffer (propval);
3015   return retstr;
3016 }
3017
3018
3019 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
3020    This indicates that there should be no PR_BODY tag.  */
3021 int
3022 mapi_has_last_decrypted (LPMESSAGE message)
3023 {
3024   HRESULT hr;
3025   LPSPropValue propval = NULL;
3026   ULONG tag;
3027   int yes = 0;
3028   
3029   if (get_gpgollastdecrypted_tag (message, &tag) )
3030     return 0; /* No.  */
3031   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
3032   if (FAILED (hr))
3033     return 0; /* No.  */  
3034   
3035   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
3036     yes = 1;
3037
3038   MAPIFreeBuffer (propval);
3039   return yes;
3040 }
3041
3042
3043 /* Returns True if MESSAGE has a GpgOL Last Decrypted property and
3044    that matches the current session. */
3045 int
3046 mapi_test_last_decrypted (LPMESSAGE message)
3047 {
3048   HRESULT hr;
3049   LPSPropValue propval = NULL;
3050   ULONG tag;
3051   int yes = 0;
3052
3053   if (get_gpgollastdecrypted_tag (message, &tag) )
3054     goto leave; /* No.  */
3055   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
3056   if (FAILED (hr))
3057     goto leave; /* No.  */  
3058
3059   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
3060       && propval->Value.bin.cb == 8
3061       && !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
3062     yes = 1;
3063
3064   MAPIFreeBuffer (propval);
3065  leave:
3066   log_debug ("%s:%s: message decrypted during this session: %s\n",
3067              SRCNAME, __func__, yes?"yes":"no");
3068   return yes;
3069 }
3070
3071
3072
3073 /* Helper for mapi_get_gpgol_body_attachment.  */
3074 static int
3075 has_gpgol_body_name (LPATTACH obj)
3076 {
3077   HRESULT hr;
3078   LPSPropValue propval;
3079   int yes = 0;
3080
3081   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
3082   if (FAILED(hr))
3083     return 0;
3084
3085   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
3086     {
3087       if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt"))
3088         yes = 1;
3089       else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm"))
3090         yes = 2;
3091     }
3092   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3093     {
3094       if (!strcmp (propval->Value.lpszA, "gpgol000.txt"))
3095         yes = 1;
3096       else if (!strcmp (propval->Value.lpszA, "gpgol000.htm"))
3097         yes = 2;
3098     }
3099   MAPIFreeBuffer (propval);
3100   return yes;
3101 }
3102
3103 /* Helper to check whether the file name of OBJ is "smime.p7m".
3104    Returns on true if so.  */
3105 static int
3106 has_smime_filename (LPATTACH obj)
3107 {
3108   HRESULT hr;
3109   LPSPropValue propval;
3110   int yes = 0;
3111
3112   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
3113   if (FAILED(hr))
3114     {
3115       hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
3116       if (FAILED(hr))
3117         return 0;
3118     }
3119
3120   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
3121     {
3122       if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
3123         yes = 1;
3124     }
3125   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3126     {
3127       if (!strcmp (propval->Value.lpszA, "smime.p7m"))
3128         yes = 1;
3129     }
3130   MAPIFreeBuffer (propval);
3131   return yes;
3132 }
3133
3134
3135 /* Return the content of the body attachment of MESSAGE.  The body
3136    attachment is a hidden attachment created by us for later display.
3137    If R_NBYTES is not NULL the number of bytes in the returned buffer
3138    is stored there.  If R_ISHTML is not NULL a flag indicating whether
3139    the HTML is html formatted is stored there.  If R_PROTECTED is not
3140    NULL a flag indicating whether the message was protected is stored
3141    there.  If no body attachment can be found or on any other error an
3142    error codes is returned and NULL is stored at R_BODY.  Caller must
3143    free the returned string.  If NULL is passed for R_BODY, the
3144    function will only test whether a body attachment is available and
3145    return an error code if not.  R_IS_HTML and R_PROTECTED are not
3146    defined in this case.  */
3147 int
3148 mapi_get_gpgol_body_attachment (LPMESSAGE message, 
3149                                 char **r_body, size_t *r_nbytes, 
3150                                 int *r_ishtml, int *r_protected)
3151 {    
3152   HRESULT hr;
3153   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
3154   LPMAPITABLE mapitable;
3155   LPSRowSet   mapirows;
3156   unsigned int pos, n_attach;
3157   ULONG moss_tag;
3158   char *body = NULL;
3159   int bodytype;
3160   int found = 0;
3161
3162   if (r_body)
3163     *r_body = NULL;
3164   if (r_ishtml)
3165     *r_ishtml = 0;
3166   if (r_protected)
3167     *r_protected = 0;
3168
3169   if (get_gpgolattachtype_tag (message, &moss_tag) )
3170     return -1;
3171
3172   hr = message->GetAttachmentTable (0, &mapitable);
3173   if (FAILED (hr))
3174     {
3175       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
3176                  SRCNAME, __func__, hr);
3177       return -1;
3178     }
3179       
3180   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
3181                        NULL, NULL, 0, &mapirows);
3182   if (FAILED (hr))
3183     {
3184       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
3185                  SRCNAME, __func__, hr);
3186       gpgol_release (mapitable);
3187       return -1;
3188     }
3189   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
3190   if (!n_attach)
3191     {
3192       FreeProws (mapirows);
3193       gpgol_release (mapitable);
3194       log_debug ("%s:%s: No attachments at all", SRCNAME, __func__);
3195       return -1;
3196     }
3197   log_debug ("%s:%s: message has %u attachments\n",
3198              SRCNAME, __func__, n_attach);
3199
3200   for (pos=0; pos < n_attach; pos++) 
3201     {
3202       LPATTACH att;
3203
3204       if (mapirows->aRow[pos].cValues < 1)
3205         {
3206           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
3207           continue;
3208         }
3209       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
3210         {
3211           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
3212           continue;
3213         }
3214       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3215                                 NULL, MAPI_BEST_ACCESS, &att);  
3216       if (FAILED (hr))
3217         {
3218           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
3219                      SRCNAME, __func__, pos, 
3220                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
3221           continue;
3222         }
3223       if ((bodytype=has_gpgol_body_name (att))
3224            && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
3225         {
3226           found = 1;
3227           if (!r_body)
3228             ; /* Body content has not been requested. */
3229           else if (opt.body_as_attachment && !mapi_test_attach_hidden (att))
3230             {
3231               /* The body is to be shown as an attachment. */
3232               body = native_to_utf8 
3233                 (bodytype == 2
3234                  ? ("[Open the attachment \"gpgol000.htm\""
3235                     " to view the message.]")
3236                  : ("[Open the attachment \"gpgol000.txt\""
3237                     " to view the message.]"));
3238               found = 1;
3239             }
3240           else
3241             {
3242               char *charset;
3243               
3244               if (get_attach_method (att) == ATTACH_BY_VALUE)
3245                 body = attach_to_buffer (att, r_nbytes, 1, r_protected);
3246               if (body && (charset = mapi_get_gpgol_charset ((LPMESSAGE)att)))
3247                 {
3248                   /* We only support transcoding from Latin-1 for now.  */
3249                   if (strcmp (charset, "iso-8859-1") 
3250                       && !strcmp (charset, "latin-1"))
3251                     log_debug ("%s:%s: Using Latin-1 instead of %s",
3252                                SRCNAME, __func__, charset);
3253                   xfree (charset);
3254                   charset = latin1_to_utf8 (body);
3255                   xfree (body);
3256                   body = charset;
3257                 }
3258             }
3259           gpgol_release (att);
3260           if (r_ishtml)
3261             *r_ishtml = (bodytype == 2);
3262           break;
3263         }
3264       gpgol_release (att);
3265     }
3266   FreeProws (mapirows);
3267   gpgol_release (mapitable);
3268   if (!found)
3269     {
3270       log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__);
3271       if (r_body)
3272         *r_body = native_to_utf8 
3273           (_("[The content of this message is not visible"
3274              " due to an processing error in GpgOL.]"));
3275       return -1;
3276     }
3277
3278   if (r_body)
3279     *r_body = body;
3280   else
3281     xfree (body);  /* (Should not happen.)  */
3282   return 0;
3283 }
3284
3285
3286 /* Delete a possible body atatchment.  Returns true if an atatchment
3287    has been deleted.  */
3288 int
3289 mapi_delete_gpgol_body_attachment (LPMESSAGE message)
3290 {    
3291   HRESULT hr;
3292   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
3293   LPMAPITABLE mapitable;
3294   LPSRowSet   mapirows;
3295   unsigned int pos, n_attach;
3296   ULONG moss_tag;
3297   int found = 0;
3298
3299   if (get_gpgolattachtype_tag (message, &moss_tag) )
3300     return 0;
3301
3302   hr = message->GetAttachmentTable (0, &mapitable);
3303   if (FAILED (hr))
3304     {
3305       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
3306                  SRCNAME, __func__, hr);
3307       return 0;
3308     }
3309       
3310   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
3311                        NULL, NULL, 0, &mapirows);
3312   if (FAILED (hr))
3313     {
3314       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
3315                  SRCNAME, __func__, hr);
3316       gpgol_release (mapitable);
3317       return 0;
3318     }
3319   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
3320   if (!n_attach)
3321     {
3322       FreeProws (mapirows);
3323       gpgol_release (mapitable);
3324       return 0; /* No Attachments.  */
3325     }
3326
3327   for (pos=0; pos < n_attach; pos++) 
3328     {
3329       LPATTACH att;
3330
3331       if (mapirows->aRow[pos].cValues < 1)
3332         {
3333           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
3334           continue;
3335         }
3336       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
3337         {
3338           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
3339           continue;
3340         }
3341       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3342                                 NULL, MAPI_BEST_ACCESS, &att);  
3343       if (FAILED (hr))
3344         {
3345           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
3346                      SRCNAME, __func__, pos, 
3347                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
3348           continue;
3349         }
3350       if (has_gpgol_body_name (att)
3351           && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
3352         {
3353           gpgol_release (att);
3354           hr = message->DeleteAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3355                                       0, NULL, 0);
3356           if (hr)
3357             log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
3358                          SRCNAME, __func__, hr); 
3359           else
3360             {
3361               log_debug ("%s:%s: body attachment deleted\n", 
3362                          SRCNAME, __func__); 
3363               found = 1;
3364               
3365             }
3366           break;
3367         }
3368       gpgol_release (att);
3369     }
3370   FreeProws (mapirows);
3371   gpgol_release (mapitable);
3372   return found;
3373 }
3374
3375
3376 /* Copy the attachment ITEM of the message MESSAGE verbatim to the
3377    PR_BODY property.  Returns 0 on success.  This function does not
3378    call SaveChanges. */
3379 int
3380 mapi_attachment_to_body (LPMESSAGE message, mapi_attach_item_t *item)
3381 {
3382   int result = -1;
3383   HRESULT hr; 
3384   LPATTACH att = NULL;
3385   LPSTREAM instream = NULL;
3386   LPSTREAM outstream = NULL;
3387   LPUNKNOWN punk;
3388
3389   if (!message || !item || item->end_of_table || item->mapipos == -1)
3390     return -1; /* Error.  */
3391
3392   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
3393   if (FAILED (hr))
3394     {
3395       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
3396                  SRCNAME, __func__, item->mapipos, hr);
3397       goto leave;
3398     }
3399   if (item->method != ATTACH_BY_VALUE)
3400     {
3401       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
3402       goto leave;
3403     }
3404
3405   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
3406                           0, 0, (LPUNKNOWN*) &instream);
3407   if (FAILED (hr))
3408     {
3409       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
3410                  SRCNAME, __func__, hr);
3411       goto leave;
3412     }
3413
3414
3415   punk = (LPUNKNOWN)outstream;
3416   hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0,
3417                               MAPI_CREATE|MAPI_MODIFY, &punk);
3418   if (FAILED (hr))
3419     {
3420       log_error ("%s:%s: can't open body stream for update: hr=%#lx",
3421                  SRCNAME, __func__, hr);
3422       goto leave;
3423     }
3424   outstream = (LPSTREAM)punk;
3425
3426   {
3427     ULARGE_INTEGER cb;
3428     cb.QuadPart = 0xffffffffffffffffll;
3429     hr = instream->CopyTo (outstream, cb, NULL, NULL);
3430   }
3431   if (hr)
3432     {
3433       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
3434                  SRCNAME, __func__, hr); 
3435       goto leave;
3436     }
3437   hr = outstream->Commit (0);
3438   if (hr)
3439     {
3440       log_error ("%s:%s: commiting output stream failed: hr=%#lx",
3441                  SRCNAME, __func__, hr);
3442       goto leave;
3443     }
3444   result = 0;
3445   
3446  leave:
3447   if (outstream)
3448     {
3449       if (result)
3450         outstream->Revert ();
3451       gpgol_release (outstream);
3452     }
3453   if (instream)
3454     gpgol_release (instream);
3455   if (att)
3456     gpgol_release (att);
3457   return result;
3458 }
3459
3460 /* Copy the MAPI body to a PGPBODY type attachment. */
3461 int
3462 mapi_body_to_attachment (LPMESSAGE message)
3463 {
3464   HRESULT hr;
3465   LPSTREAM instream;
3466   ULONG newpos;
3467   LPATTACH newatt = NULL;
3468   SPropValue prop;
3469   LPSTREAM outstream = NULL;
3470   LPUNKNOWN punk;
3471   GpgOLStr body_filename (PGPBODYFILENAME);
3472
3473   instream = mapi_get_body_as_stream (message);
3474   if (!instream)
3475     return -1;
3476
3477   hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
3478   if (hr)
3479     {
3480       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
3481                  SRCNAME, __func__, hr);
3482       goto leave;
3483     }
3484
3485   prop.ulPropTag = PR_ATTACH_METHOD;
3486   prop.Value.ul = ATTACH_BY_VALUE;
3487   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3488   if (hr)
3489     {
3490       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
3491                  SRCNAME, __func__, hr);
3492       goto leave;
3493     }
3494
3495   /* Mark that attachment so that we know why it has been created.  */
3496   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
3497     goto leave;
3498   prop.Value.l = ATTACHTYPE_PGPBODY;
3499   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3500   if (hr)
3501     {
3502       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
3503                  SRCNAME, __func__, "GpgOL Attach Type", hr);
3504       goto leave;
3505     }
3506
3507   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
3508   prop.Value.b = TRUE;
3509   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3510   if (hr)
3511     {
3512       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
3513                  SRCNAME, __func__, hr);
3514       goto leave;
3515     }
3516
3517   prop.ulPropTag = PR_ATTACH_FILENAME_A;
3518   prop.Value.lpszA = body_filename;
3519   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3520   if (hr)
3521     {
3522       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
3523                  SRCNAME, __func__, hr);
3524       goto leave;
3525     }
3526
3527   punk = (LPUNKNOWN)outstream;
3528   hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
3529                              MAPI_CREATE|MAPI_MODIFY, &punk);
3530   if (FAILED (hr))
3531     {
3532       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
3533                  SRCNAME, __func__, hr);
3534       goto leave;
3535     }
3536   outstream = (LPSTREAM)punk;
3537
3538   /* Insert a blank line so that our mime parser skips over the mail
3539      headers.  */
3540   hr = outstream->Write ("\r\n", 2, NULL);
3541   if (hr)
3542     {
3543       log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
3544       goto leave;
3545     }
3546
3547   {
3548     ULARGE_INTEGER cb;
3549     cb.QuadPart = 0xffffffffffffffffll;
3550     hr = instream->CopyTo (outstream, cb, NULL, NULL);
3551   }
3552   if (hr)
3553     {
3554       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
3555                  SRCNAME, __func__, hr);
3556       goto leave;
3557     }
3558   hr = outstream->Commit (0);
3559   if (hr)
3560     {
3561       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
3562                  SRCNAME, __func__, hr);
3563       goto leave;
3564     }
3565   gpgol_release (outstream);
3566   outstream = NULL;
3567   hr = newatt->SaveChanges (0);
3568   if (hr)
3569     {
3570       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
3571                  SRCNAME, __func__, hr);
3572       goto leave;
3573     }
3574   gpgol_release (newatt);
3575   newatt = NULL;
3576   hr = mapi_save_changes (message, KEEP_OPEN_READWRITE);
3577
3578  leave:
3579   if (outstream)
3580     {
3581       outstream->Revert ();
3582       gpgol_release (outstream);
3583     }
3584   if (newatt)
3585     gpgol_release (newatt);
3586   gpgol_release (instream);
3587   return hr? -1:0;
3588 }
3589
3590 int
3591 mapi_mark_or_create_moss_attach (LPMESSAGE message, msgtype_t msgtype)
3592 {
3593   int i;
3594   if (msgtype == MSGTYPE_UNKNOWN ||
3595       msgtype == MSGTYPE_GPGOL)
3596     {
3597       return 0;
3598     }
3599
3600   /* First check if we already have one marked. */
3601   mapi_attach_item_t *table = mapi_create_attach_table (message, 0);
3602   int part1 = 0,
3603       part2 = 0;
3604   for (i = 0; table && !table[i].end_of_table; i++)
3605     {
3606       if (table[i].attach_type == ATTACHTYPE_PGPBODY ||
3607           table[i].attach_type == ATTACHTYPE_MOSS ||
3608           table[i].attach_type == ATTACHTYPE_MOSSTEMPL)
3609         {
3610           if (!part1)
3611             {
3612               part1 = i + 1;
3613             }
3614           else if (!part2)
3615             {
3616               /* If we have two MOSS attachments we use
3617                  the second one. */
3618               part2 = i + 1;
3619               break;
3620             }
3621         }
3622     }
3623   if (part1 || part2)
3624     {
3625       /* Found existing moss attachment */
3626       mapi_release_attach_table (table);
3627       /* Remark to ensure that it is hidden. As our revert
3628          code must unhide it so that it is not stored in winmail.dat
3629          but used as the mosstmpl. */
3630       mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1);
3631       LPATTACH att;
3632       if (message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att) != S_OK)
3633         {
3634           log_error ("%s:%s: can't open attachment at %d",
3635                      SRCNAME, __func__, item->mapipos);
3636           return -1;
3637         }
3638       if (!mapi_test_attach_hidden (att))
3639         {
3640           mapi_set_attach_hidden (att);
3641         }
3642       gpgol_release (att);
3643       if (part2)
3644         return part2;
3645       return part1;
3646     }
3647
3648   if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED ||
3649       msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
3650     {
3651       /* Inline message we need to create body attachment so that we
3652          are able to restore the content. */
3653       if (mapi_body_to_attachment (message))
3654         {
3655           log_error ("%s:%s: Failed to create body attachment.",
3656                      SRCNAME, __func__);
3657           return 0;
3658         }
3659       log_debug ("%s:%s: Created body attachment. Repeating lookup.",
3660                  SRCNAME, __func__);
3661       /* The position of the MOSS attach might change depending on
3662          the attachment count of the mail. So repeat the check to get
3663          the right position. */
3664       return mapi_mark_or_create_moss_attach (message, msgtype);
3665     }
3666   if (!table)
3667     {
3668       log_debug ("%s:%s: Neither pgp inline nor an attachment table.",
3669                  SRCNAME, __func__);
3670       return 0;