02a6556e4e0238bf3de13a0af54c9895444a7447
[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 (opt.enable_smime &&
1300                !strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
1301         {
1302           /* This is an S/MIME message class but smime support is not
1303              enabled.  We need to check whether this is actually a
1304              PGP/MIME message.  */
1305           newvalue = change_message_class_ipm_note_smime_multipartsigned
1306             (message);
1307         }
1308       else if (sync_override && have_override
1309                && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
1310         {
1311           /* In case the original message class is not yet an GpgOL
1312              class we set it here.  This is needed to convince Outlook
1313              not to do any special processing for IPM.Note.SMIME etc.  */
1314           LPSPropValue propval2 = NULL;
1315
1316           hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
1317                              &propval2);
1318           if (SUCCEEDED (hr) && PROP_TYPE (propval2->ulPropTag) == PT_STRING8
1319               && propval2->Value.lpszA && strcmp (propval2->Value.lpszA, s))
1320             newvalue = (char*)xstrdup (s);
1321           MAPIFreeBuffer (propval2);
1322         }
1323       else if (opt.enable_smime 
1324                && (!strcmp (s, "IPM.Note.Secure.CexSig")
1325                    || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
1326         {
1327           newvalue = change_message_class_ipm_note_secure_cex
1328             (message, cexenc);
1329         }
1330     }
1331
1332   if (!newvalue)
1333     {
1334       /* We use our Sig-Status property to mark messages which passed
1335          this function.  This helps us to avoid later tests.  */
1336       if (!mapi_has_sig_status (message))
1337         {
1338           mapi_set_sig_status (message, "#");
1339           need_save = 1;
1340         }
1341     }
1342   else
1343     {
1344       /* Save old message class if not yet done.  (The second
1345          condition is just a failsafe check). */
1346       if (!get_gpgololdmsgclass_tag (message, &tag)
1347           && PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1348         {
1349           LPSPropValue propval2 = NULL;
1350
1351           hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2);
1352           if (!FAILED (hr))
1353             MAPIFreeBuffer (propval2);
1354           else
1355             {
1356               /* No such property - save it.  */
1357               log_debug ("%s:%s: saving old message class\n",
1358                          SRCNAME, __func__);
1359               prop.ulPropTag = tag;
1360               prop.Value.lpszA = propval->Value.lpszA; 
1361               hr = message->SetProps (1, &prop, NULL);
1362               if (hr)
1363                 {
1364                   log_error ("%s:%s: can't save old message class: hr=%#lx\n",
1365                              SRCNAME, __func__, hr);
1366                   MAPIFreeBuffer (propval);
1367                   return 0;
1368                 }
1369               need_save = 1;
1370             }
1371         }
1372       
1373       /* Change message class.  */
1374       log_debug ("%s:%s: setting message class to `%s'\n",
1375                  SRCNAME, __func__, newvalue);
1376       prop.ulPropTag = PR_MESSAGE_CLASS_A;
1377       prop.Value.lpszA = newvalue; 
1378       hr = message->SetProps (1, &prop, NULL);
1379       xfree (newvalue);
1380       if (hr)
1381         {
1382           log_error ("%s:%s: can't set message class: hr=%#lx\n",
1383                      SRCNAME, __func__, hr);
1384           MAPIFreeBuffer (propval);
1385           return 0;
1386         }
1387       need_save = 1;
1388     }
1389   MAPIFreeBuffer (propval);
1390
1391   if (need_save)
1392     {
1393       if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1394         return 0;
1395     }
1396
1397   return 1;
1398 }
1399
1400
1401 /* Return the message class.  This function will never return NULL so
1402    it is mostly useful for debugging.  Caller needs to release the
1403    returned string.  */
1404 char *
1405 mapi_get_message_class (LPMESSAGE message)
1406 {
1407   HRESULT hr;
1408   LPSPropValue propval = NULL;
1409   char *retstr;
1410
1411   if (!message)
1412     return xstrdup ("[No message]");
1413   
1414   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1415   if (FAILED (hr))
1416     {
1417       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1418                  SRCNAME, __func__, hr);
1419       return xstrdup (hr == MAPI_E_NOT_FOUND?
1420                         "[No message class property]":
1421                         "[Error getting message class property]");
1422     }
1423
1424   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1425     retstr = xstrdup (propval->Value.lpszA);
1426   else
1427     retstr = xstrdup ("[Invalid message class property]");
1428     
1429   MAPIFreeBuffer (propval);
1430   return retstr;
1431 }
1432
1433 /* Return the old message class.  This function returns NULL if no old
1434    message class has been saved.  Caller needs to release the returned
1435    string.  */
1436 char *
1437 mapi_get_old_message_class (LPMESSAGE message)
1438 {
1439   HRESULT hr;
1440   ULONG tag;
1441   LPSPropValue propval = NULL;
1442   char *retstr;
1443
1444   if (!message)
1445     return NULL;
1446   
1447   if (get_gpgololdmsgclass_tag (message, &tag))
1448     return NULL;
1449
1450   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1451   if (FAILED (hr))
1452     {
1453       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1454                  SRCNAME, __func__, hr);
1455       return NULL;
1456     }
1457
1458   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1459     retstr = xstrdup (propval->Value.lpszA);
1460   else
1461     retstr = NULL;
1462     
1463   MAPIFreeBuffer (propval);
1464   return retstr;
1465 }
1466
1467
1468
1469 /* Return the sender of the message.  According to the specs this is
1470    an UTF-8 string; we rely on that the UI server handles
1471    internationalized domain names.  */ 
1472 char *
1473 mapi_get_sender (LPMESSAGE message)
1474 {
1475   HRESULT hr;
1476   LPSPropValue propval = NULL;
1477   char *buf;
1478   char *p0, *p;
1479   
1480   if (!message)
1481     return NULL; /* No message: Nop. */
1482
1483   hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval);
1484   if (FAILED (hr))
1485     {
1486       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1487                  SRCNAME, __func__, hr);
1488       return NULL;
1489     }
1490     
1491   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1492     {
1493       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1494                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1495       MAPIFreeBuffer (propval);
1496       return NULL;
1497     }
1498   
1499   buf = wchar_to_utf8 (propval->Value.lpszW);
1500   MAPIFreeBuffer (propval);
1501   if (!buf)
1502     {
1503       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1504       return NULL;
1505     }
1506   /* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields
1507      using Ctrl-A as delimiter.  The first field looks like the ascii
1508      formatted number of fields to follow, the second field like the
1509      email account and the third seems to be a textual description of
1510      that account.  We return the second field. */
1511   p = strchr (buf, '\x01');
1512   if (!p)
1513     {
1514       log_error ("%s:%s: unknown format of the value `%s'\n",
1515                  SRCNAME, __func__, buf);
1516       xfree (buf);
1517       return NULL;
1518     }
1519   for (p0=buf, p++; *p && *p != '\x01';)
1520     *p0++ = *p++;
1521   *p0 = 0;
1522
1523   /* When using an Exchange account this is an X.509 address and not
1524      an SMTP address.  We try to detect this here and extract only the
1525      CN RDN.  Note that there are two CNs.  This is just a simple
1526      approach and not a real parser.  A better way to do this would be
1527      to ask MAPI to resolve the X.500 name to an SMTP name.  */
1528   if (strstr (buf, "/o=") && strstr (buf, "/ou=") &&
1529       (p = strstr (buf, "/cn=Recipients")) && (p = strstr (p+1, "/cn=")))
1530     {
1531       log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__, buf);
1532       memmove (buf, p+4, strlen (p+4)+1);
1533       if (!strchr (buf, '@'))
1534         {
1535           /* Some Exchange accounts return only the accoutn name and
1536              no rfc821 mail address.  Kleopatra chokes on that, thus
1537              we append a domain name.  Thisis a bad hack.  */
1538           char *newbuf = (char *)xmalloc (strlen (buf) + 6 + 1);
1539           strcpy (stpcpy (newbuf, buf), "@local");
1540           xfree (buf);
1541           buf = newbuf;
1542         }
1543       
1544     }
1545   log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf);
1546   return buf;
1547 }
1548
1549 static char *
1550 resolve_ex_from_address (LPMESSAGE message)
1551 {
1552   HRESULT hr;
1553   char *sender_entryid;
1554   size_t entryidlen;
1555   LPMAPISESSION session;
1556   ULONG utype;
1557   LPUNKNOWN user;
1558   LPSPropValue propval = NULL;
1559   char *buf;
1560
1561   if (g_ol_version_major < 14)
1562     {
1563       log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
1564       return NULL;
1565     }
1566
1567   sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID,
1568                                          &entryidlen);
1569   if (!sender_entryid)
1570     {
1571       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1572       return NULL;
1573     }
1574
1575   session = get_oom_mapi_session ();
1576
1577   if (!session)
1578     {
1579       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1580       xfree (sender_entryid);
1581       return NULL;
1582     }
1583
1584   hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1585                            &IID_IMailUser,
1586                            MAPI_BEST_ACCESS | MAPI_CACHE_ONLY,
1587                            &utype, (IUnknown**)&user);
1588   if (FAILED (hr))
1589     {
1590       log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.",
1591                  SRCNAME, __func__);
1592       hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1593                                &IID_IMailUser,
1594                                MAPI_BEST_ACCESS,
1595                                &utype, (IUnknown**)&user);
1596     }
1597   gpgol_release (session);
1598
1599   if (FAILED (hr))
1600     {
1601       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1602       return NULL;
1603     }
1604
1605   hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval);
1606   if (FAILED (hr))
1607     {
1608       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1609       return NULL;
1610     }
1611
1612   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
1613     {
1614       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1615                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1616       MAPIFreeBuffer (propval);
1617       return NULL;
1618     }
1619   buf = wchar_to_utf8 (propval->Value.lpszW);
1620   MAPIFreeBuffer (propval);
1621
1622   return buf;
1623 }
1624
1625 /* Return the from address of the message as a malloced UTF-8 string.
1626    Returns NULL if that address is not available.  */
1627 char *
1628 mapi_get_from_address (LPMESSAGE message)
1629 {
1630   HRESULT hr;
1631   LPSPropValue propval = NULL;
1632   char *buf;
1633   ULONG try_props[3] = {PidTagSenderSmtpAddress_W,
1634                         PR_SENT_REPRESENTING_SMTP_ADDRESS_W,
1635                         PR_SENDER_EMAIL_ADDRESS_W};
1636
1637   if (!message)
1638     return xstrdup ("[no message]"); /* Ooops.  */
1639
1640   for (int i = 0; i < 3; i++)
1641     {
1642       /* We try to get different properties first as they contain
1643          the SMTP address of the sender. EMAIL address can be
1644          some LDAP stuff for exchange. */
1645       hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i],
1646                          &propval);
1647       if (!FAILED (hr))
1648         {
1649           break;
1650         }
1651     }
1652    /* This is the last result that should always work but not necessarily
1653       contain an SMTP Address. */
1654   if (FAILED (hr))
1655     {
1656       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1657                  SRCNAME, __func__, hr);
1658       return NULL;
1659     }
1660
1661   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1662     {
1663       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1664                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1665       MAPIFreeBuffer (propval);
1666       return NULL;
1667     }
1668   
1669   buf = wchar_to_utf8 (propval->Value.lpszW);
1670   MAPIFreeBuffer (propval);
1671   if (!buf)
1672     {
1673       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1674       return NULL;
1675     }
1676
1677   if (strstr (buf, "/o="))
1678     {
1679       char *buf2;
1680       /* If both SMTP Address properties are not set
1681          we need to fallback to resolve the address
1682          through the address book */
1683       log_debug ("%s:%s: resolving exchange address.",
1684                  SRCNAME, __func__);
1685       buf2 = resolve_ex_from_address (message);
1686       if (buf2)
1687         {
1688           xfree (buf);
1689           return buf2;
1690         }
1691     }
1692
1693   return buf;
1694 }
1695
1696
1697 /* Return the subject of the message as a malloced UTF-8 string.
1698    Returns a replacement string if a subject is missing.  */
1699 char *
1700 mapi_get_subject (LPMESSAGE message)
1701 {
1702   HRESULT hr;
1703   LPSPropValue propval = NULL;
1704   char *buf;
1705   
1706   if (!message)
1707     return xstrdup ("[no message]"); /* Ooops.  */
1708
1709   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SUBJECT_W, &propval);
1710   if (FAILED (hr))
1711     {
1712       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1713                  SRCNAME, __func__, hr);
1714       return xstrdup (_("[no subject]"));
1715     }
1716     
1717   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1718     {
1719       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1720                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1721       MAPIFreeBuffer (propval);
1722       return xstrdup (_("[no subject]"));
1723     }
1724   
1725   buf = wchar_to_utf8 (propval->Value.lpszW);
1726   MAPIFreeBuffer (propval);
1727   if (!buf)
1728     {
1729       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1730       return xstrdup (_("[no subject]"));
1731     }
1732
1733   return buf;
1734 }
1735
1736
1737
1738
1739 /* Return the message type.  This function knows only about our own
1740    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
1741    no special support for.  */
1742 msgtype_t
1743 mapi_get_message_type (LPMESSAGE message)
1744 {
1745   HRESULT hr;
1746   ULONG tag;
1747   LPSPropValue propval = NULL;
1748   msgtype_t msgtype = MSGTYPE_UNKNOWN;
1749
1750   if (!message)
1751     return msgtype; 
1752
1753   if (get_gpgolmsgclass_tag (message, &tag) )
1754     return msgtype; /* Ooops */
1755
1756   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1757   if (FAILED (hr))
1758     {
1759       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1760       if (FAILED (hr))
1761         {
1762           log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
1763                      SRCNAME, __func__, hr);
1764           return msgtype;
1765         }
1766     }
1767   else
1768     log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1769     
1770   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1771     {
1772       const char *s = propval->Value.lpszA;
1773
1774       if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
1775         {
1776           s += 14;
1777           if (!*s)
1778             msgtype = MSGTYPE_GPGOL;
1779           else if (!strcmp (s, ".MultipartSigned"))
1780             msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
1781           else if (!strcmp (s, ".MultipartEncrypted"))
1782             msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
1783           else if (!strcmp (s, ".OpaqueSigned"))
1784             msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
1785           else if (!strcmp (s, ".OpaqueEncrypted"))
1786             msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
1787           else if (!strcmp (s, ".ClearSigned"))
1788             msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
1789           else if (!strcmp (s, ".PGPMessage"))
1790             msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
1791           else
1792             log_debug ("%s:%s: message class `%s' not supported",
1793                        SRCNAME, __func__, s-14);
1794         }
1795       else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
1796         msgtype = MSGTYPE_SMIME;
1797     }
1798   MAPIFreeBuffer (propval);
1799   return msgtype;
1800 }
1801
1802
1803 /* This function is pretty useless because IConverterSession won't
1804    take attachments into account.  Need to write our own version.  */
1805 int
1806 mapi_to_mime (LPMESSAGE message, const char *filename)
1807 {
1808   HRESULT hr;
1809   LPCONVERTERSESSION session;
1810   LPSTREAM stream;
1811
1812   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
1813                          IID_IConverterSession, (void **) &session);
1814   if (FAILED (hr))
1815     {
1816       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
1817                  SRCNAME, __func__, hr);
1818       return -1;
1819     }
1820
1821
1822   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1823                          (STGM_CREATE | STGM_READWRITE),
1824                          (char*)filename, NULL, &stream); 
1825   if (FAILED (hr)) 
1826     {
1827       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
1828                  SRCNAME, __func__, filename, hr); 
1829       hr = -1;
1830     }
1831   else
1832     {
1833       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
1834       if (FAILED (hr))
1835         {
1836           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
1837                      SRCNAME, __func__, hr);
1838           stream->Revert ();
1839           hr = -1;
1840         }
1841       else
1842         {
1843           stream->Commit (0);
1844           hr = 0;
1845         }
1846
1847       gpgol_release (stream);
1848     }
1849
1850   gpgol_release (session);
1851   return hr;
1852 }
1853
1854
1855 /* Return a binary property in a malloced buffer with its length stored
1856    at R_NBYTES.  Returns NULL on error.  */
1857 char *
1858 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
1859 {
1860   HRESULT hr;
1861   LPSPropValue propval = NULL;
1862   char *data;
1863
1864   *r_nbytes = 0;
1865   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
1866   if (FAILED (hr))
1867     {
1868       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1869                  SRCNAME, __func__, proptype, hr);
1870       return NULL; 
1871     }
1872   switch ( PROP_TYPE (propval->ulPropTag) )
1873     {
1874     case PT_BINARY:
1875       /* This is a binary object but we know that it must be plain
1876          ASCII due to the armored format.  */
1877       data = (char*)xmalloc (propval->Value.bin.cb + 1);
1878       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
1879       data[propval->Value.bin.cb] = 0;
1880       *r_nbytes = propval->Value.bin.cb;
1881       break;
1882       
1883     default:
1884       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1885                  SRCNAME, __func__, proptype, propval->ulPropTag);
1886       data = NULL;
1887       break;
1888     }
1889   MAPIFreeBuffer (propval);
1890   return data;
1891 }
1892
1893 /* Return an integer property at R_VALUE.  On error the function
1894    returns -1 and sets R_VALUE to 0, on success 0 is returned.  */
1895 int
1896 mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value)
1897 {
1898   int rc = -1;
1899   HRESULT hr;
1900   LPSPropValue propval = NULL;
1901
1902   *r_value = 0;
1903   hr = HrGetOneProp (object, proptype, &propval);
1904   if (FAILED (hr))
1905     {
1906       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1907                  SRCNAME, __func__, proptype, hr);
1908       return -1; 
1909     }
1910   switch ( PROP_TYPE (propval->ulPropTag) )
1911     {
1912     case PT_LONG:
1913       *r_value = propval->Value.l;
1914       rc = 0;
1915       
1916       break;
1917       
1918     default:
1919       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1920                  SRCNAME, __func__, proptype, propval->ulPropTag);
1921       break;
1922     }
1923   MAPIFreeBuffer (propval);
1924   return rc;
1925 }
1926
1927
1928 /* Return the attachment method for attachment OBJ.  In case of error
1929    we return 0 which happens not to be defined.  */
1930 static int
1931 get_attach_method (LPATTACH obj)
1932 {
1933   HRESULT hr;
1934   LPSPropValue propval = NULL;
1935   int method ;
1936
1937   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1938   if (FAILED (hr))
1939     {
1940       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1941                  SRCNAME, __func__, hr);
1942       return 0; 
1943     }
1944   /* We don't bother checking whether we really get a PT_LONG ulong
1945      back; if not the system is seriously damaged and we can't do
1946      further harm by returning a possible random value.  */
1947   method = propval->Value.l;
1948   MAPIFreeBuffer (propval);
1949   return method;
1950 }
1951
1952
1953
1954 /* Return the filename from the attachment as a malloced string.  The
1955    encoding we return will be UTF-8, however the MAPI docs declare
1956    that MAPI does only handle plain ANSI and thus we don't really care
1957    later on.  In fact we would need to convert the filename back to
1958    wchar and use the Unicode versions of the file API.  Returns NULL
1959    on error or if no filename is available. */
1960 static char *
1961 get_attach_filename (LPATTACH obj)
1962 {
1963   HRESULT hr;
1964   LPSPropValue propval;
1965   char *name = NULL;
1966
1967   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1968   if (FAILED(hr)) 
1969     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1970   if (FAILED(hr))
1971     {
1972       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
1973       return NULL;
1974     }
1975
1976   switch ( PROP_TYPE (propval->ulPropTag) )
1977     {
1978     case PT_UNICODE:
1979       name = wchar_to_utf8 (propval->Value.lpszW);
1980       if (!name)
1981         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1982       break;
1983       
1984     case PT_STRING8:
1985       name = xstrdup (propval->Value.lpszA);
1986       break;
1987       
1988     default:
1989       log_debug ("%s:%s: proptag=%#lx not supported\n",
1990                  SRCNAME, __func__, propval->ulPropTag);
1991       name = NULL;
1992       break;
1993     }
1994   MAPIFreeBuffer (propval);
1995   return name;
1996 }
1997
1998 /* Return the content-id of the attachment OBJ or NULL if it does
1999    not exists.  Caller must free. */
2000 static char *
2001 get_attach_content_id (LPATTACH obj)
2002 {
2003   HRESULT hr;
2004   LPSPropValue propval = NULL;
2005   char *name;
2006
2007   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval);
2008   if (FAILED (hr))
2009     {
2010       if (hr != MAPI_E_NOT_FOUND)
2011         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2012                    SRCNAME, __func__, hr);
2013       return NULL;
2014     }
2015   switch ( PROP_TYPE (propval->ulPropTag) )
2016     {
2017     case PT_UNICODE:
2018       name = wchar_to_utf8 (propval->Value.lpszW);
2019       if (!name)
2020         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2021       break;
2022
2023     case PT_STRING8:
2024       name = xstrdup (propval->Value.lpszA);
2025       break;
2026
2027     default:
2028       log_debug ("%s:%s: proptag=%#lx not supported\n",
2029                  SRCNAME, __func__, propval->ulPropTag);
2030       name = NULL;
2031       break;
2032     }
2033   MAPIFreeBuffer (propval);
2034   return name;
2035 }
2036
2037 /* Return the content-type of the attachment OBJ or NULL if it does
2038    not exists.  Caller must free. */
2039 static char *
2040 get_attach_mime_tag (LPATTACH obj)
2041 {
2042   HRESULT hr;
2043   LPSPropValue propval = NULL;
2044   char *name;
2045
2046   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
2047   if (FAILED (hr))
2048     {
2049       if (hr != MAPI_E_NOT_FOUND)
2050         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2051                    SRCNAME, __func__, hr);
2052       return NULL; 
2053     }
2054   switch ( PROP_TYPE (propval->ulPropTag) )
2055     {
2056     case PT_UNICODE:
2057       name = wchar_to_utf8 (propval->Value.lpszW);
2058       if (!name)
2059         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2060       break;
2061       
2062     case PT_STRING8:
2063       name = xstrdup (propval->Value.lpszA);
2064       break;
2065       
2066     default:
2067       log_debug ("%s:%s: proptag=%#lx not supported\n",
2068                  SRCNAME, __func__, propval->ulPropTag);
2069       name = NULL;
2070       break;
2071     }
2072   MAPIFreeBuffer (propval);
2073   return name;
2074 }
2075
2076
2077 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
2078    the tag of that property. */
2079 attachtype_t
2080 get_gpgolattachtype (LPATTACH obj, ULONG tag)
2081 {
2082   HRESULT hr;
2083   LPSPropValue propval = NULL;
2084   attachtype_t retval;
2085
2086   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2087   if (FAILED (hr))
2088     {
2089       if (hr != MAPI_E_NOT_FOUND)
2090         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
2091                    SRCNAME, __func__, hr);
2092       return ATTACHTYPE_UNKNOWN; 
2093     }
2094   retval = (attachtype_t)propval->Value.l;
2095   MAPIFreeBuffer (propval);
2096   return retval;
2097 }
2098
2099
2100 /* Gather information about attachments and return a new table of
2101    attachments.  Caller must release the returned table.s The routine
2102    will return NULL in case of an error or if no attachments are
2103    available.  With FAST set only some information gets collected. */
2104 mapi_attach_item_t *
2105 mapi_create_attach_table (LPMESSAGE message, int fast)
2106 {    
2107   HRESULT hr;
2108   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
2109   LPMAPITABLE mapitable;
2110   LPSRowSet   mapirows;
2111   mapi_attach_item_t *table; 
2112   unsigned int pos, n_attach;
2113   ULONG moss_tag;
2114
2115   if (get_gpgolattachtype_tag (message, &moss_tag) )
2116     return NULL;
2117
2118   /* Open the attachment table.  */
2119   hr = message->GetAttachmentTable (0, &mapitable);
2120   if (FAILED (hr))
2121     {
2122       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
2123                  SRCNAME, __func__, hr);
2124       return NULL;
2125     }
2126       
2127   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
2128                        NULL, NULL, 0, &mapirows);
2129   if (FAILED (hr))
2130     {
2131       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
2132                  SRCNAME, __func__, hr);
2133       gpgol_release (mapitable);
2134       return NULL;
2135     }
2136   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
2137
2138   log_debug ("%s:%s: message has %u attachments\n",
2139              SRCNAME, __func__, n_attach);
2140   if (!n_attach)
2141     {
2142       FreeProws (mapirows);
2143       gpgol_release (mapitable);
2144       return NULL;
2145     }
2146
2147   /* Allocate our own table.  */
2148   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
2149   for (pos=0; pos < n_attach; pos++) 
2150     {
2151       LPATTACH att;
2152
2153       if (mapirows->aRow[pos].cValues < 1)
2154         {
2155           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
2156           table[pos].mapipos = -1;
2157           continue;
2158         }
2159       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
2160         {
2161           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
2162           table[pos].mapipos = -1;
2163           continue;
2164         }
2165       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
2166
2167       hr = message->OpenAttach (table[pos].mapipos, NULL,
2168                                 MAPI_BEST_ACCESS, &att);        
2169       if (FAILED (hr))
2170         {
2171           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
2172                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
2173           table[pos].mapipos = -1;
2174           continue;
2175         }
2176
2177       table[pos].method = get_attach_method (att);
2178       table[pos].filename = fast? NULL : get_attach_filename (att);
2179       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
2180       table[pos].content_id = fast? NULL : get_attach_content_id (att);
2181       if (table[pos].content_type)
2182         {
2183           char *p = strchr (table[pos].content_type, ';');
2184           if (p)
2185             {
2186               *p++ = 0;
2187               trim_trailing_spaces (table[pos].content_type);
2188               while (strchr (" \t\r\n", *p))
2189                 p++;
2190               trim_trailing_spaces (p);
2191               table[pos].content_type_parms = p;
2192             }
2193         }
2194       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
2195       gpgol_release (att);
2196     }
2197   table[0].private_mapitable = mapitable;
2198   FreeProws (mapirows);
2199   table[pos].end_of_table = 1;
2200   mapitable = NULL;
2201
2202   if (fast)
2203     {
2204       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
2205                  SRCNAME, __func__);
2206     }
2207   else
2208     {
2209       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
2210       for (pos=0; !table[pos].end_of_table; pos++)
2211         {
2212           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
2213                      table[pos].mapipos,
2214                      table[pos].attach_type,
2215                      table[pos].filename, table[pos].content_type,
2216                      table[pos].content_type_parms);
2217         }
2218     }
2219
2220   return table;
2221 }
2222
2223
2224 /* Release a table as created by mapi_create_attach_table. */
2225 void
2226 mapi_release_attach_table (mapi_attach_item_t *table)
2227 {
2228   unsigned int pos;
2229   LPMAPITABLE mapitable;
2230
2231   if (!table)
2232     return;
2233
2234   mapitable = (LPMAPITABLE)table[0].private_mapitable;
2235   if (mapitable)
2236     gpgol_release (mapitable);
2237   for (pos=0; !table[pos].end_of_table; pos++)
2238     {
2239       xfree (table[pos].filename);
2240       xfree (table[pos].content_type);
2241       xfree (table[pos].content_id);
2242     }
2243   xfree (table);
2244 }
2245
2246
2247 /* Return an attachment as a new IStream object.  Returns NULL on
2248    failure.  If R_ATTACH is not NULL the actual attachment will not be
2249    released but stored at that address; the caller needs to release it
2250    in this case.  */
2251 LPSTREAM
2252 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
2253                            LPATTACH *r_attach)
2254 {
2255   HRESULT hr;
2256   LPATTACH att;
2257   LPSTREAM stream;
2258
2259   if (r_attach)
2260     *r_attach = NULL;
2261
2262   if (!item || item->end_of_table || item->mapipos == -1)
2263     return NULL;
2264
2265   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2266   if (FAILED (hr))
2267     {
2268       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2269                  SRCNAME, __func__, item->mapipos, hr);
2270       return NULL;
2271     }
2272   if (item->method != ATTACH_BY_VALUE)
2273     {
2274       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2275       gpgol_release (att);
2276       return NULL;
2277     }
2278
2279   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
2280                           0, 0, (LPUNKNOWN*) &stream);
2281   if (FAILED (hr))
2282     {
2283       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2284                  SRCNAME, __func__, hr);
2285       gpgol_release (att);
2286       return NULL;
2287     }
2288
2289   if (r_attach)
2290     *r_attach = att;
2291   else
2292     gpgol_release (att);
2293
2294   return stream;
2295 }
2296
2297
2298 /* Return a malloced buffer with the content of the attachment. If
2299    R_NBYTES is not NULL the number of bytes will get stored there.
2300    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
2301    NULL on error.  If UNPROTECT is set and the appropriate crypto
2302    attribute is available, the function returns the unprotected
2303    version of the atatchment. */
2304 static char *
2305 attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect, 
2306                   int *r_was_protected)
2307 {
2308   HRESULT hr;
2309   LPSTREAM stream;
2310   STATSTG statInfo;
2311   ULONG nread;
2312   char *buffer;
2313   symenc_t symenc = NULL;
2314
2315   if (r_was_protected)
2316     *r_was_protected = 0;
2317
2318   if (unprotect)
2319     {
2320       ULONG tag;
2321       char *iv;
2322       size_t ivlen;
2323
2324       if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag) 
2325           && (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
2326         {
2327           symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
2328           xfree (iv);
2329           if (!symenc)
2330             log_error ("%s:%s: can't open encryption context", 
2331                        SRCNAME, __func__);
2332         }
2333     }
2334   
2335
2336   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
2337                           0, 0, (LPUNKNOWN*) &stream);
2338   if (FAILED (hr))
2339     {
2340       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2341                  SRCNAME, __func__, hr);
2342       return NULL;
2343     }
2344
2345   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
2346   if ( hr != S_OK )
2347     {
2348       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
2349       gpgol_release (stream);
2350       return NULL;
2351     }
2352       
2353   /* Allocate one byte more so that we can terminate the string.  */
2354   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
2355
2356   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
2357   if ( hr != S_OK )
2358     {
2359       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
2360       xfree (buffer);
2361       gpgol_release (stream);
2362       return NULL;
2363     }
2364   if (nread != statInfo.cbSize.QuadPart)
2365     {
2366       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
2367       xfree (buffer);
2368       buffer = NULL;
2369     }
2370   gpgol_release (stream);
2371
2372   if (buffer && symenc)
2373     {
2374       symenc_cfb_decrypt (symenc, buffer, buffer, nread);
2375       if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
2376         {
2377           xfree (buffer);
2378           buffer = native_to_utf8 
2379             (_("[The content of this message is not visible because it has "
2380                "been decrypted by another Outlook session.  Use the "
2381                "\"decrypt/verify\" command to make it visible]"));
2382           nread = strlen (buffer);
2383         }
2384       else
2385         {
2386           memmove (buffer, buffer+16, nread-16);
2387           nread -= 16;
2388           if (r_was_protected)
2389             *r_was_protected = 1;
2390         }
2391     }
2392
2393   /* Make sure that the buffer is a C string.  */
2394   if (buffer)
2395     buffer[nread] = 0;
2396
2397   symenc_close (symenc);
2398   if (r_nbytes)
2399     *r_nbytes = nread;
2400   return buffer;
2401 }
2402
2403
2404
2405 /* Return an attachment as a malloced buffer.  The size of the buffer
2406    will be stored at R_NBYTES.  If unprotect is true, the atatchment
2407    will be unprotected.  Returns NULL on failure. */
2408 char *
2409 mapi_get_attach (LPMESSAGE message, int unprotect, 
2410                  mapi_attach_item_t *item, size_t *r_nbytes)
2411 {
2412   HRESULT hr;
2413   LPATTACH att;
2414   char *buffer;
2415
2416   if (!item || item->end_of_table || item->mapipos == -1)
2417     return NULL;
2418
2419   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2420   if (FAILED (hr))
2421     {
2422       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2423                  SRCNAME, __func__, item->mapipos, hr);
2424       return NULL;
2425     }
2426   if (item->method != ATTACH_BY_VALUE)
2427     {
2428       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2429       gpgol_release (att);
2430       return NULL;
2431     }
2432
2433   buffer = attach_to_buffer (att, r_nbytes, unprotect, NULL);
2434   gpgol_release (att);
2435
2436   return buffer;
2437 }
2438
2439
2440 /* Mark this attachment as the original MOSS message.  We set a custom
2441    property as well as the hidden flag.  */
2442 int 
2443 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
2444 {
2445   int retval = -1;
2446   HRESULT hr;
2447   LPATTACH att;
2448   SPropValue prop;
2449
2450   if (!item || item->end_of_table || item->mapipos == -1)
2451     return -1;
2452
2453   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2454   if (FAILED (hr))
2455     {
2456       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2457                  SRCNAME, __func__, item->mapipos, hr);
2458       return -1;
2459     }
2460
2461   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
2462     goto leave;
2463   prop.Value.l = ATTACHTYPE_MOSS;
2464   hr = HrSetOneProp (att, &prop);       
2465   if (hr)
2466     {
2467       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2468                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
2469       return false;
2470     }
2471
2472   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2473   prop.Value.b = TRUE;
2474   hr = HrSetOneProp (att, &prop);
2475   if (hr)
2476     {
2477       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2478                  SRCNAME, __func__, hr); 
2479       goto leave;
2480     }
2481   
2482
2483   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
2484   if (hr)
2485     {
2486       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2487                  SRCNAME, __func__, hr); 
2488       goto leave;
2489     }
2490   
2491   retval = 0;
2492     
2493  leave:
2494   gpgol_release (att);
2495   return retval;
2496 }
2497
2498
2499 /* If the hidden property has not been set on ATTACH, set it and save
2500    the changes. */
2501 int 
2502 mapi_set_attach_hidden (LPATTACH attach)
2503 {
2504   int retval = -1;
2505   HRESULT hr;
2506   LPSPropValue propval;
2507   SPropValue prop;
2508
2509   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2510   if (SUCCEEDED (hr) 
2511       && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
2512       && propval->Value.b)
2513     return 0;/* Already set to hidden. */
2514
2515   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2516   prop.Value.b = TRUE;
2517   hr = HrSetOneProp (attach, &prop);
2518   if (hr)
2519     {
2520       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2521                  SRCNAME, __func__, hr); 
2522       goto leave;
2523     }
2524   
2525   hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
2526   if (hr)
2527     {
2528       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2529                  SRCNAME, __func__, hr); 
2530       goto leave;
2531     }
2532   
2533   retval = 0;
2534     
2535  leave:
2536   return retval;
2537 }
2538
2539
2540 /* Returns true if ATTACH has the hidden flag set to true.  */
2541 int
2542 mapi_test_attach_hidden (LPATTACH attach)
2543 {
2544   HRESULT hr;
2545   LPSPropValue propval = NULL;
2546   int result = 0;
2547   
2548   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2549   if (FAILED (hr))
2550     return result; /* No.  */  
2551   
2552   if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b)
2553     result = 1; /* Yes.  */
2554
2555   MAPIFreeBuffer (propval);
2556   return result;
2557 }
2558
2559
2560
2561
2562 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
2563 int
2564 mapi_has_sig_status (LPMESSAGE msg)
2565 {
2566   HRESULT hr;
2567   LPSPropValue propval = NULL;
2568   ULONG tag;
2569   int yes;
2570
2571   if (get_gpgolsigstatus_tag (msg, &tag) )
2572     return 0; /* Error:  Assume No.  */
2573   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2574   if (FAILED (hr))
2575     return 0; /* No.  */  
2576   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2577     yes = 1;
2578   else
2579     yes = 0;
2580
2581   MAPIFreeBuffer (propval);
2582   return yes;
2583 }
2584
2585
2586 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
2587    is not set to unchecked.  */
2588 int
2589 mapi_test_sig_status (LPMESSAGE msg)
2590 {
2591   HRESULT hr;
2592   LPSPropValue propval = NULL;
2593   ULONG tag;
2594   int yes;
2595
2596   if (get_gpgolsigstatus_tag (msg, &tag) )
2597     return 0; /* Error:  Assume No.  */
2598   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2599   if (FAILED (hr))
2600     return 0; /* No.  */  
2601
2602   /* We return False if we have an unknown signature status (?) or the
2603      message has been sent by us and not yet checked (@).  */
2604   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2605     yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
2606                                      || !strcmp (propval->Value.lpszA, "@")));
2607   else
2608     yes = 0;
2609
2610   MAPIFreeBuffer (propval);
2611   return yes;
2612 }
2613
2614
2615 /* Return the signature status as an allocated string.  Will never
2616    return NULL.  */
2617 char *
2618 mapi_get_sig_status (LPMESSAGE msg)
2619 {
2620   HRESULT hr;
2621   LPSPropValue propval = NULL;
2622   ULONG tag;
2623   char *retstr;
2624
2625   if (get_gpgolsigstatus_tag (msg, &tag) )
2626     return xstrdup ("[Error getting tag for sig status]");
2627   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2628   if (FAILED (hr))
2629     return xstrdup ("");
2630   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2631     retstr = xstrdup (propval->Value.lpszA);
2632   else
2633     retstr = xstrdup ("[Sig status has an invalid type]");
2634
2635   MAPIFreeBuffer (propval);
2636   return retstr;
2637 }
2638
2639
2640
2641
2642 /* Set the signature status property to STATUS_STRING.  There are a
2643    few special values:
2644
2645      "#" The message is not of interest to us.
2646      "@" The message has been created and signed or encrypted by us.
2647      "?" The signature status has not been checked.
2648      "!" The signature verified okay 
2649      "~" The signature was not fully verified.
2650      "-" The signature is bad
2651
2652    Note that this function does not call SaveChanges.  */
2653 int 
2654 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
2655 {
2656   HRESULT hr;
2657   SPropValue prop;
2658
2659   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
2660     return -1;
2661   prop.Value.lpszA = xstrdup (status_string);
2662   hr = HrSetOneProp (message, &prop);   
2663   xfree (prop.Value.lpszA);
2664   if (hr)
2665     {
2666       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2667                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
2668       return -1;
2669     }
2670
2671   return 0;
2672 }
2673
2674
2675 /* When sending a message we need to fake the message class so that OL
2676    processes it according to our needs.  However, if we later try to
2677    get the message class from the sent message, OL still has the SMIME
2678    message class and tries to hide this by trying to decrypt the
2679    message and return the message class from the plaintext.  To
2680    mitigate the problem we define our own msg class override
2681    property.  */
2682 int 
2683 mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
2684 {
2685   HRESULT hr;
2686   SPropValue prop;
2687
2688   if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
2689     return -1;
2690   prop.Value.lpszA = xstrdup (name);
2691   hr = HrSetOneProp (message, &prop);   
2692   xfree (prop.Value.lpszA);
2693   if (hr)
2694     {
2695       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2696                  SRCNAME, __func__, "GpgOL Msg Class", hr); 
2697       return -1;
2698     }
2699
2700   return 0;
2701 }
2702
2703
2704 /* Return the charset as assigned by GpgOL to an attachment.  This may
2705    return NULL it is has not been assigned or is the standard
2706    (UTF-8). */
2707 char *
2708 mapi_get_gpgol_charset (LPMESSAGE obj)
2709 {
2710   HRESULT hr;
2711   LPSPropValue propval = NULL;
2712   ULONG tag;
2713   char *retstr;
2714
2715   if (get_gpgolcharset_tag (obj, &tag) )
2716     return NULL; /* Error.  */
2717   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2718   if (FAILED (hr))
2719     return NULL;
2720   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2721     {
2722       if (!strcmp (propval->Value.lpszA, "utf-8"))
2723         retstr = NULL;
2724       else
2725         retstr = xstrdup (propval->Value.lpszA);
2726     }
2727   else
2728     retstr = NULL;
2729
2730   MAPIFreeBuffer (propval);
2731   return retstr;
2732 }
2733
2734
2735 /* Set the GpgOl charset property to an attachment. 
2736    Note that this function does not call SaveChanges.  */
2737 int 
2738 mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
2739 {
2740   HRESULT hr;
2741   SPropValue prop;
2742   char *p;
2743
2744   /* Note that we lowercase the value and cut it to a max of 32
2745      characters.  The latter is required to make sure that
2746      HrSetOneProp will always work.  */
2747   if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
2748     return -1;
2749   prop.Value.lpszA = xstrdup (charset);
2750   for (p=prop.Value.lpszA; *p; p++)
2751     *p = tolower (*(unsigned char*)p);
2752   if (strlen (prop.Value.lpszA) > 32)
2753     prop.Value.lpszA[32] = 0;
2754   hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);   
2755   xfree (prop.Value.lpszA);
2756   if (hr)
2757     {
2758       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2759                  SRCNAME, __func__, "GpgOL Charset", hr); 
2760       return -1;
2761     }
2762
2763   return 0;
2764 }
2765
2766
2767
2768 /* Return GpgOL's draft info string as an allocated string.  If no
2769    draft info is available, NULL is returned.  */
2770 char *
2771 mapi_get_gpgol_draft_info (LPMESSAGE msg)
2772 {
2773   HRESULT hr;
2774   LPSPropValue propval = NULL;
2775   ULONG tag;
2776   char *retstr;
2777
2778   if (get_gpgoldraftinfo_tag (msg, &tag) )
2779     return NULL;
2780   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2781   if (FAILED (hr))
2782     return NULL;
2783   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2784     retstr = xstrdup (propval->Value.lpszA);
2785   else
2786     retstr = NULL;
2787
2788   MAPIFreeBuffer (propval);
2789   return retstr;
2790 }
2791
2792
2793 /* Set GpgOL's draft info string to STRING.  This string is defined as:
2794
2795    Character 1:  'E' = encrypt selected,
2796                  'e' = encrypt not selected.
2797                  '-' = don't care
2798    Character 2:  'S' = sign selected,
2799                  's' = sign not selected.
2800                  '-' = don't care
2801    Character 3:  'A' = Auto protocol 
2802                  'P' = OpenPGP protocol
2803                  'X' = S/MIME protocol
2804                  '-' = don't care
2805                  
2806    If string is NULL, the property will get deleted.
2807
2808    Note that this function does not call SaveChanges.  */
2809 int 
2810 mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string)
2811 {
2812   HRESULT hr;
2813   SPropValue prop;
2814   SPropTagArray proparray;
2815
2816   if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) )
2817     return -1;
2818   if (string)
2819     {
2820       prop.Value.lpszA = xstrdup (string);
2821       hr = HrSetOneProp (message, &prop);       
2822       xfree (prop.Value.lpszA);
2823     }
2824   else
2825     {
2826       proparray.cValues = 1;
2827       proparray.aulPropTag[0] = prop.ulPropTag;
2828       hr = message->DeleteProps (&proparray, NULL);
2829     }
2830   if (hr)
2831     {
2832       log_error ("%s:%s: can't %s %s property: hr=%#lx\n",
2833                  SRCNAME, __func__, string?"set":"delete",
2834                  "GpgOL Draft Info", hr); 
2835       return -1;
2836     }
2837
2838   return 0;
2839 }
2840
2841
2842 /* Return the MIME info as an allocated string.  Will never return
2843    NULL.  */
2844 char *
2845 mapi_get_mime_info (LPMESSAGE msg)
2846 {
2847   HRESULT hr;
2848   LPSPropValue propval = NULL;
2849   ULONG tag;
2850   char *retstr;
2851
2852   if (get_gpgolmimeinfo_tag (msg, &tag) )
2853     return xstrdup ("[Error getting tag for MIME info]");
2854   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2855   if (FAILED (hr))
2856     return xstrdup ("");
2857   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2858     retstr = xstrdup (propval->Value.lpszA);
2859   else
2860     retstr = xstrdup ("[MIME info has an invalid type]");
2861
2862   MAPIFreeBuffer (propval);
2863   return retstr;
2864 }
2865
2866
2867 /* Helper around mapi_get_gpgol_draft_info to avoid
2868    the string handling.
2869    Return values are:
2870    0 -> Do nothing
2871    1 -> Encrypt
2872    2 -> Sign
2873    3 -> Encrypt & Sign*/
2874 int
2875 get_gpgol_draft_info_flags (LPMESSAGE message)
2876 {
2877   char *buf = mapi_get_gpgol_draft_info (message);
2878   int ret = 0;
2879   if (!buf)
2880     {
2881       return 0;
2882     }
2883   if (buf[0] == 'E')
2884     {
2885       ret |= 1;
2886     }
2887   if (buf[1] == 'S')
2888     {
2889       ret |= 2;
2890     }
2891   xfree (buf);
2892   return ret;
2893 }
2894
2895 /* Sets the draft info flags. Protocol is always Auto.
2896    flags should be the same as defined by
2897    get_gpgol_draft_info_flags
2898 */
2899 int
2900 set_gpgol_draft_info_flags (LPMESSAGE message, int flags)
2901 {
2902   char buf[4];
2903   buf[3] = '\0';
2904   buf[2] = 'A'; /* Protocol */
2905   buf[1] = flags & 2 ? 'S' : 's';
2906   buf[0] = flags & 1 ? 'E' : 'e';
2907
2908   return mapi_set_gpgol_draft_info (message, buf);
2909 }
2910
2911
2912 /* Helper for mapi_get_msg_content_type() */
2913 static int
2914 get_message_content_type_cb (void *dummy_arg,
2915                              rfc822parse_event_t event, rfc822parse_t msg)
2916 {
2917   (void)dummy_arg;
2918   (void)msg;
2919
2920   if (event == RFC822PARSE_T2BODY)
2921     return 42; /* Hack to stop the parsing after having read the
2922                   outer headers. */
2923   return 0;
2924 }
2925
2926
2927 /* Return Content-Type of the current message.  This one is taken
2928    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
2929    string with the protocol parameter will be stored at this address,
2930    if no protocol is given NULL will be stored.  If R_SMTYPE is not
2931    NULL a string with the smime-type parameter will be stored there.
2932    Caller must release all returned strings.  */
2933 char *
2934 mapi_get_message_content_type (LPMESSAGE message,
2935                                char **r_protocol, char **r_smtype)
2936 {
2937   HRESULT hr;
2938   LPSPropValue propval = NULL;
2939   rfc822parse_t msg;
2940   const char *header_lines, *s;
2941   rfc822parse_field_t ctx;
2942   size_t length;
2943   char *retstr = NULL;
2944   
2945   if (r_protocol)
2946     *r_protocol = NULL;
2947   if (r_smtype)
2948     *r_smtype = NULL;
2949
2950   hr = HrGetOneProp ((LPMAPIPROP)message,
2951                      PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
2952   if (FAILED (hr))
2953     {
2954       log_error ("%s:%s: error getting the headers lines: hr=%#lx",
2955                  SRCNAME, __func__, hr);
2956       return NULL; 
2957     }
2958   if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
2959     {
2960       /* As per rfc822, header lines must be plain ascii, so no need
2961          to cope with unicode etc. */
2962       log_error ("%s:%s: proptag=%#lx not supported\n",
2963                  SRCNAME, __func__, propval->ulPropTag);
2964       MAPIFreeBuffer (propval);
2965       return NULL;
2966     }
2967   header_lines = propval->Value.lpszA;
2968
2969   /* Read the headers into an rfc822 object. */
2970   msg = rfc822parse_open (get_message_content_type_cb, NULL);
2971   if (!msg)
2972     {
2973       log_error ("%s:%s: rfc822parse_open failed\n", SRCNAME, __func__);
2974       MAPIFreeBuffer (propval);
2975       return NULL;
2976     }
2977   
2978   while ((s = strchr (header_lines, '\n')))
2979     {
2980       length = (s - header_lines);
2981       if (length && s[-1] == '\r')
2982         length--;
2983       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
2984       header_lines = s+1;
2985     }
2986   
2987   /* Parse the content-type field. */
2988   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
2989   if (ctx)
2990     {
2991       const char *s1, *s2;
2992       s1 = rfc822parse_query_media_type (ctx, &s2);
2993       if (s1)
2994         {
2995           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
2996           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
2997
2998           if (r_protocol)
2999             {
3000               s = rfc822parse_query_parameter (ctx, "protocol", 0);
3001               if (s)
3002                 *r_protocol = xstrdup (s);
3003             }
3004           if (r_smtype)
3005             {
3006               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
3007               if (s)
3008                 *r_smtype = xstrdup (s);
3009             }
3010         }
3011       rfc822parse_release_field (ctx);
3012     }
3013
3014   rfc822parse_close (msg);
3015   MAPIFreeBuffer (propval);
3016   return retstr;
3017 }
3018
3019
3020 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
3021    This indicates that there should be no PR_BODY tag.  */
3022 int
3023 mapi_has_last_decrypted (LPMESSAGE message)
3024 {
3025   HRESULT hr;
3026   LPSPropValue propval = NULL;
3027   ULONG tag;
3028   int yes = 0;
3029   
3030   if (get_gpgollastdecrypted_tag (message, &tag) )
3031     return 0; /* No.  */
3032   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
3033   if (FAILED (hr))
3034     return 0; /* No.  */  
3035   
3036   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
3037     yes = 1;
3038
3039   MAPIFreeBuffer (propval);
3040   return yes;
3041 }
3042
3043
3044 /* Returns True if MESSAGE has a GpgOL Last Decrypted property and
3045    that matches the current session. */
3046 int
3047 mapi_test_last_decrypted (LPMESSAGE message)
3048 {
3049   HRESULT hr;
3050   LPSPropValue propval = NULL;
3051   ULONG tag;
3052   int yes = 0;
3053
3054   if (get_gpgollastdecrypted_tag (message, &tag) )
3055     goto leave; /* No.  */
3056   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
3057   if (FAILED (hr))
3058     goto leave; /* No.  */  
3059
3060   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
3061       && propval->Value.bin.cb == 8
3062       && !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
3063     yes = 1;
3064
3065   MAPIFreeBuffer (propval);
3066  leave:
3067   log_debug ("%s:%s: message decrypted during this session: %s\n",
3068              SRCNAME, __func__, yes?"yes":"no");
3069   return yes;
3070 }
3071
3072
3073
3074 /* Helper for mapi_get_gpgol_body_attachment.  */
3075 static int
3076 has_gpgol_body_name (LPATTACH obj)
3077 {
3078   HRESULT hr;
3079   LPSPropValue propval;
3080   int yes = 0;
3081
3082   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
3083   if (FAILED(hr))
3084     return 0;
3085
3086   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
3087     {
3088       if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt"))
3089         yes = 1;
3090       else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm"))
3091         yes = 2;
3092     }
3093   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3094     {
3095       if (!strcmp (propval->Value.lpszA, "gpgol000.txt"))
3096         yes = 1;
3097       else if (!strcmp (propval->Value.lpszA, "gpgol000.htm"))
3098         yes = 2;
3099     }
3100   MAPIFreeBuffer (propval);
3101   return yes;
3102 }
3103
3104 /* Helper to check whether the file name of OBJ is "smime.p7m".
3105    Returns on true if so.  */
3106 static int
3107 has_smime_filename (LPATTACH obj)
3108 {
3109   HRESULT hr;
3110   LPSPropValue propval;
3111   int yes = 0;
3112
3113   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
3114   if (FAILED(hr))
3115     {
3116       hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
3117       if (FAILED(hr))
3118         return 0;
3119     }
3120
3121   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
3122     {
3123       if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
3124         yes = 1;
3125     }
3126   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3127     {
3128       if (!strcmp (propval->Value.lpszA, "smime.p7m"))
3129         yes = 1;
3130     }
3131   MAPIFreeBuffer (propval);
3132   return yes;
3133 }
3134
3135
3136 /* Return the content of the body attachment of MESSAGE.  The body
3137    attachment is a hidden attachment created by us for later display.
3138    If R_NBYTES is not NULL the number of bytes in the returned buffer
3139    is stored there.  If R_ISHTML is not NULL a flag indicating whether
3140    the HTML is html formatted is stored there.  If R_PROTECTED is not
3141    NULL a flag indicating whether the message was protected is stored
3142    there.  If no body attachment can be found or on any other error an
3143    error codes is returned and NULL is stored at R_BODY.  Caller must
3144    free the returned string.  If NULL is passed for R_BODY, the
3145    function will only test whether a body attachment is available and
3146    return an error code if not.  R_IS_HTML and R_PROTECTED are not
3147    defined in this case.  */
3148 int
3149 mapi_get_gpgol_body_attachment (LPMESSAGE message, 
3150                                 char **r_body, size_t *r_nbytes, 
3151                                 int *r_ishtml, int *r_protected)
3152 {    
3153   HRESULT hr;
3154   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
3155   LPMAPITABLE mapitable;
3156   LPSRowSet   mapirows;
3157   unsigned int pos, n_attach;
3158   ULONG moss_tag;
3159   char *body = NULL;
3160   int bodytype;
3161   int found = 0;
3162
3163   if (r_body)
3164     *r_body = NULL;
3165   if (r_ishtml)
3166     *r_ishtml = 0;
3167   if (r_protected)
3168     *r_protected = 0;
3169
3170   if (get_gpgolattachtype_tag (message, &moss_tag) )
3171     return -1;
3172
3173   hr = message->GetAttachmentTable (0, &mapitable);
3174   if (FAILED (hr))
3175     {
3176       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
3177                  SRCNAME, __func__, hr);
3178       return -1;
3179     }
3180       
3181   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
3182                        NULL, NULL, 0, &mapirows);
3183   if (FAILED (hr))
3184     {
3185       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
3186                  SRCNAME, __func__, hr);
3187       gpgol_release (mapitable);
3188       return -1;
3189     }
3190   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
3191   if (!n_attach)
3192     {
3193       FreeProws (mapirows);
3194       gpgol_release (mapitable);
3195       log_debug ("%s:%s: No attachments at all", SRCNAME, __func__);
3196       return -1;
3197     }
3198   log_debug ("%s:%s: message has %u attachments\n",
3199              SRCNAME, __func__, n_attach);
3200
3201   for (pos=0; pos < n_attach; pos++) 
3202     {
3203       LPATTACH att;
3204
3205       if (mapirows->aRow[pos].cValues < 1)
3206         {
3207           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
3208           continue;
3209         }
3210       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
3211         {
3212           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
3213           continue;
3214         }
3215       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3216                                 NULL, MAPI_BEST_ACCESS, &att);  
3217       if (FAILED (hr))
3218         {
3219           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
3220                      SRCNAME, __func__, pos, 
3221                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
3222           continue;
3223         }
3224       if ((bodytype=has_gpgol_body_name (att))
3225            && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
3226         {
3227           found = 1;
3228           if (!r_body)
3229             ; /* Body content has not been requested. */
3230           else if (opt.body_as_attachment && !mapi_test_attach_hidden (att))
3231             {
3232               /* The body is to be shown as an attachment. */
3233               body = native_to_utf8 
3234                 (bodytype == 2
3235                  ? ("[Open the attachment \"gpgol000.htm\""
3236                     " to view the message.]")
3237                  : ("[Open the attachment \"gpgol000.txt\""
3238                     " to view the message.]"));
3239               found = 1;
3240             }
3241           else
3242             {
3243               char *charset;
3244               
3245               if (get_attach_method (att) == ATTACH_BY_VALUE)
3246                 body = attach_to_buffer (att, r_nbytes, 1, r_protected);
3247               if (body && (charset = mapi_get_gpgol_charset ((LPMESSAGE)att)))
3248                 {
3249                   /* We only support transcoding from Latin-1 for now.  */
3250                   if (strcmp (charset, "iso-8859-1") 
3251                       && !strcmp (charset, "latin-1"))
3252                     log_debug ("%s:%s: Using Latin-1 instead of %s",
3253                                SRCNAME, __func__, charset);
3254                   xfree (charset);
3255                   charset = latin1_to_utf8 (body);
3256                   xfree (body);
3257                   body = charset;
3258                 }
3259             }
3260           gpgol_release (att);
3261           if (r_ishtml)
3262             *r_ishtml = (bodytype == 2);
3263           break;
3264         }
3265       gpgol_release (att);
3266     }
3267   FreeProws (mapirows);
3268   gpgol_release (mapitable);
3269   if (!found)
3270     {
3271       log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__);
3272       if (r_body)
3273         *r_body = native_to_utf8 
3274           (_("[The content of this message is not visible"
3275              " due to an processing error in GpgOL.]"));
3276       return -1;
3277     }
3278
3279   if (r_body)
3280     *r_body = body;
3281   else
3282     xfree (body);  /* (Should not happen.)  */
3283   return 0;
3284 }
3285
3286
3287 /* Delete a possible body atatchment.  Returns true if an atatchment
3288    has been deleted.  */
3289 int
3290 mapi_delete_gpgol_body_attachment (LPMESSAGE message)
3291 {    
3292   HRESULT hr;
3293   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
3294   LPMAPITABLE mapitable;
3295   LPSRowSet   mapirows;
3296   unsigned int pos, n_attach;
3297   ULONG moss_tag;
3298   int found = 0;
3299
3300   if (get_gpgolattachtype_tag (message, &moss_tag) )
3301     return 0;
3302
3303   hr = message->GetAttachmentTable (0, &mapitable);
3304   if (FAILED (hr))
3305     {
3306       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
3307                  SRCNAME, __func__, hr);
3308       return 0;
3309     }
3310       
3311   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
3312                        NULL, NULL, 0, &mapirows);
3313   if (FAILED (hr))
3314     {
3315       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
3316                  SRCNAME, __func__, hr);
3317       gpgol_release (mapitable);
3318       return 0;
3319     }
3320   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
3321   if (!n_attach)
3322     {
3323       FreeProws (mapirows);
3324       gpgol_release (mapitable);
3325       return 0; /* No Attachments.  */
3326     }
3327
3328   for (pos=0; pos < n_attach; pos++) 
3329     {
3330       LPATTACH att;
3331
3332       if (mapirows->aRow[pos].cValues < 1)
3333         {
3334           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
3335           continue;
3336         }
3337       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
3338         {
3339           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
3340           continue;
3341         }
3342       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3343                                 NULL, MAPI_BEST_ACCESS, &att);  
3344       if (FAILED (hr))
3345         {
3346           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
3347                      SRCNAME, __func__, pos, 
3348                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
3349           continue;
3350         }
3351       if (has_gpgol_body_name (att)
3352           && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
3353         {
3354           gpgol_release (att);
3355           hr = message->DeleteAttach (mapirows->aRow[pos].lpProps[0].Value.l,
3356                                       0, NULL, 0);
3357           if (hr)
3358             log_error ("%s:%s: DeleteAttach failed: hr=%#lx\n",
3359                          SRCNAME, __func__, hr); 
3360           else
3361             {
3362               log_debug ("%s:%s: body attachment deleted\n", 
3363                          SRCNAME, __func__); 
3364               found = 1;
3365               
3366             }
3367           break;
3368         }
3369       gpgol_release (att);
3370     }
3371   FreeProws (mapirows);
3372   gpgol_release (mapitable);
3373   return found;
3374 }
3375
3376
3377 /* Copy the attachment ITEM of the message MESSAGE verbatim to the
3378    PR_BODY property.  Returns 0 on success.  This function does not
3379    call SaveChanges. */
3380 int
3381 mapi_attachment_to_body (LPMESSAGE message, mapi_attach_item_t *item)
3382 {
3383   int result = -1;
3384   HRESULT hr; 
3385   LPATTACH att = NULL;
3386   LPSTREAM instream = NULL;
3387   LPSTREAM outstream = NULL;
3388   LPUNKNOWN punk;
3389
3390   if (!message || !item || item->end_of_table || item->mapipos == -1)
3391     return -1; /* Error.  */
3392
3393   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
3394   if (FAILED (hr))
3395     {
3396       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
3397                  SRCNAME, __func__, item->mapipos, hr);
3398       goto leave;
3399     }
3400   if (item->method != ATTACH_BY_VALUE)
3401     {
3402       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
3403       goto leave;
3404     }
3405
3406   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
3407                           0, 0, (LPUNKNOWN*) &instream);
3408   if (FAILED (hr))
3409     {
3410       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
3411                  SRCNAME, __func__, hr);
3412       goto leave;
3413     }
3414
3415
3416   punk = (LPUNKNOWN)outstream;
3417   hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0,
3418                               MAPI_CREATE|MAPI_MODIFY, &punk);
3419   if (FAILED (hr))
3420     {
3421       log_error ("%s:%s: can't open body stream for update: hr=%#lx",
3422                  SRCNAME, __func__, hr);
3423       goto leave;
3424     }
3425   outstream = (LPSTREAM)punk;
3426
3427   {
3428     ULARGE_INTEGER cb;
3429     cb.QuadPart = 0xffffffffffffffffll;
3430     hr = instream->CopyTo (outstream, cb, NULL, NULL);
3431   }
3432   if (hr)
3433     {
3434       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
3435                  SRCNAME, __func__, hr); 
3436       goto leave;
3437     }
3438   hr = outstream->Commit (0);
3439   if (hr)
3440     {
3441       log_error ("%s:%s: commiting output stream failed: hr=%#lx",
3442                  SRCNAME, __func__, hr);
3443       goto leave;
3444     }
3445   result = 0;
3446   
3447  leave:
3448   if (outstream)
3449     {
3450       if (result)
3451         outstream->Revert ();
3452       gpgol_release (outstream);
3453     }
3454   if (instream)
3455     gpgol_release (instream);
3456   if (att)
3457     gpgol_release (att);
3458   return result;
3459 }
3460
3461 /* Copy the MAPI body to a PGPBODY type attachment. */
3462 int
3463 mapi_body_to_attachment (LPMESSAGE message)
3464 {
3465   HRESULT hr;
3466   LPSTREAM instream;
3467   ULONG newpos;
3468   LPATTACH newatt = NULL;
3469   SPropValue prop;
3470   LPSTREAM outstream = NULL;
3471   LPUNKNOWN punk;
3472   GpgOLStr body_filename (PGPBODYFILENAME);
3473
3474   instream = mapi_get_body_as_stream (message);
3475   if (!instream)
3476     return -1;
3477
3478   hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
3479   if (hr)
3480     {
3481       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
3482                  SRCNAME, __func__, hr);
3483       goto leave;
3484     }
3485
3486   prop.ulPropTag = PR_ATTACH_METHOD;
3487   prop.Value.ul = ATTACH_BY_VALUE;
3488   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3489   if (hr)
3490     {
3491       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
3492                  SRCNAME, __func__, hr);
3493       goto leave;
3494     }
3495
3496   /* Mark that attachment so that we know why it has been created.  */
3497   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
3498     goto leave;
3499   prop.Value.l = ATTACHTYPE_PGPBODY;
3500   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3501   if (hr)
3502     {
3503       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
3504                  SRCNAME, __func__, "GpgOL Attach Type", hr);
3505       goto leave;
3506     }
3507
3508   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
3509   prop.Value.b = TRUE;
3510   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3511   if (hr)
3512     {
3513       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
3514                  SRCNAME, __func__, hr);
3515       goto leave;
3516     }
3517
3518   prop.ulPropTag = PR_ATTACH_FILENAME_A;
3519   prop.Value.lpszA = body_filename;
3520   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3521   if (hr)
3522     {
3523       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
3524                  SRCNAME, __func__, hr);
3525       goto leave;
3526     }
3527
3528   punk = (LPUNKNOWN)outstream;
3529   hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
3530                              MAPI_CREATE|MAPI_MODIFY, &punk);
3531   if (FAILED (hr))
3532     {
3533       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
3534                  SRCNAME, __func__, hr);
3535       goto leave;
3536     }
3537   outstream = (LPSTREAM)punk;
3538
3539   /* Insert a blank line so that our mime parser skips over the mail
3540      headers.  */
3541   hr = outstream->Write ("\r\n", 2, NULL);
3542   if (hr)
3543     {
3544       log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
3545       goto leave;
3546     }
3547
3548   {
3549     ULARGE_INTEGER cb;
3550     cb.QuadPart = 0xffffffffffffffffll;
3551     hr = instream->CopyTo (outstream, cb, NULL, NULL);
3552   }
3553   if (hr)
3554     {
3555       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
3556                  SRCNAME, __func__, hr);
3557       goto leave;
3558     }
3559   hr = outstream->Commit (0);
3560   if (hr)
3561     {
3562       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
3563                  SRCNAME, __func__, hr);
3564       goto leave;
3565     }
3566   gpgol_release (outstream);
3567   outstream = NULL;
3568   hr = newatt->SaveChanges (0);
3569   if (hr)
3570     {
3571       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
3572                  SRCNAME, __func__, hr);
3573       goto leave;
3574     }
3575   gpgol_release (newatt);
3576   newatt = NULL;
3577   hr = mapi_save_changes (message, KEEP_OPEN_READWRITE);
3578
3579  leave:
3580   if (outstream)
3581     {
3582       outstream->Revert ();
3583       gpgol_release (outstream);
3584     }
3585   if (newatt)
3586     gpgol_release (newatt);
3587   gpgol_release (instream);
3588   return hr? -1:0;
3589 }
3590
3591 int
3592 mapi_mark_or_create_moss_attach (LPMESSAGE message, msgtype_t msgtype)
3593 {
3594   int i;
3595   if (msgtype == MSGTYPE_UNKNOWN ||
3596       msgtype == MSGTYPE_GPGOL)
3597     {
3598       return 0;
3599     }
3600
3601   /* First check if we already have one marked. */
3602   mapi_attach_item_t *table = mapi_create_attach_table (message, 0);
3603   int part1 = 0,
3604       part2 = 0;
3605   for (i = 0; table && !table[i].end_of_table; i++)
3606     {
3607       if (table[i].attach_type == ATTACHTYPE_PGPBODY ||
3608           table[i].attach_type == ATTACHTYPE_MOSS ||
3609           table[i].attach_type == ATTACHTYPE_MOSSTEMPL)
3610         {
3611           if (!part1)
3612             {
3613               part1 = i + 1;
3614             }
3615           else if (!part2)
3616             {
3617               /* If we have two MOSS attachments we use
3618                  the second one. */
3619               part2 = i + 1;
3620               break;
3621             }
3622         }
3623     }
3624   if (part1 || part2)
3625     {
3626       /* Found existing moss attachment */
3627       mapi_release_attach_table (table);
3628       /* Remark to ensure that it is hidden. As our revert
3629          code must unhide it so that it is not stored in winmail.dat
3630          but used as the mosstmpl. */
3631       mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1);
3632       LPATTACH att;
3633       if (message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att) != S_OK)
3634         {
3635           log_error ("%s:%s: can't open attachment at %d",
3636                      SRCNAME, __func__, item->mapipos);
3637           return -1;
3638         }
3639       if (!mapi_test_attach_hidden (att))
3640         {
3641           mapi_set_attach_hidden (att);
3642         }
3643       gpgol_release (att);
3644       if (part2)
3645         return part2;
3646       return part1;
3647     }
3648
3649   if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED ||
3650       msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
3651     {
3652       /* Inline message we need to create body attachment so that we
3653          are able to restore the content. */
3654       if (mapi_body_to_attachment (message))
3655         {
3656           log_error ("%s:%s: Failed to create body attachment.",
3657                      SRCNAME, __func__);
3658           return 0;
3659         }
3660       log_debug ("%s:%s: Created body attachment. Repeating lookup.",
3661                  SRCNAME, __func__);
3662       /* The position of the MOSS attach might change depending on
3663          the attachment count of the mail. So repeat the check to get
3664          the right position. */
3665       return mapi_mark_or_create_moss_attach (message, msgtype);
3666     }
3667   if (!table)
3668     {
3669       log_debug ("%s:%s: Neither pgp inline nor an attachment table.",
3670                  SRCNAME, __func__);
3671       return 0;
3672     }
3673
3674   /* MIME Mails check for S/MIME first. */
3675   for (i = 0; !table[i].end_of_table; i++)
3676     {
3677       if (table[i].content_type
3678           && (!strcmp (table[i].content_type, "application/pkcs7-mime")
3679               || !strcmp (table[i].content_type,
3680                           "application/x-pkcs7-mime"))
3681           && table[i].filename
3682           && !strcmp (table[i].filename, "smime.p7m"))
3683         break;
3684     }
3685   if (!table[i].end_of_table)
3686     {
3687       mapi_mark_moss_attach (message, table + i);
3688       mapi_release_attach_table (table);
3689       return i + 1;
3690     }
3691
3692   /* PGP/MIME or S/MIME stuff.  */
3693   /* Multipart/encrypted message: We expect 2 attachments.
3694      The first one with the version number and the second one
3695      with the ciphertext.  As we don't know wether we are
3696      called the first time, we first try to find these
3697      attachments by looking at all attachments.  Only if this
3698      fails we identify them by their order (i.e. the first 2
3699      attachments) and mark them as part1 and part2.  */
3700   for (i = 0; !table[i].end_of_table; i++); /* Count entries */
3701   if (i >= 2)
3702     {
3703       int part1_idx = -1,
3704           part2_idx = -1;
3705       /* At least 2 attachments but none are marked.  Thus we
3706          assume that this is the first time we see this
3707          message and we will set the mark now if we see
3708          appropriate content types. */
3709       if (table[0].content_type
3710           && !strcmp (table[0].content_type,
3711                       "application/pgp-encrypted"))
3712         part1_idx = 0;
3713       if (table[1].content_type
3714           && !strcmp (table[1].content_type,
3715                       "application/octet-stream"))
3716         part2_idx = 1;
3717       if (part1_idx != -1 && part2_idx != -1)
3718         {
3719           mapi_mark_moss_attach (message, table+part1_idx);
3720           mapi_mark_moss_attach (message, table+part2_idx);
3721           mapi_release_attach_table (table);
3722           return 2;
3723         }
3724     }
3725
3726   if (!table[0].end_of_table && table[1].end_of_table)
3727     {
3728       /* No MOSS flag found in the table but there is only one
3729          attachment.  Due to the message type we know that this is
3730          the original MOSS message.  We mark this attachment as
3731          hidden, so that it won't get displayed.  We further mark
3732          it as our original MOSS attachment so that after parsing
3733          we have a mean to find it again (see above).  */
3734       mapi_mark_moss_attach (message, table + 0);
3735       mapi_release_attach_table (table);
3736       return 1;
3737     }
3738
3739    mapi_release_attach_table (table);
3740    return 0; /* No original attachment - this should not happen.  */
3741 }