63d31d23a419a23b411bf815d7b8a2e3b0929a31
[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
34 #ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA
35 #define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011
36 #endif
37 #ifndef CRYPT_E_ASN1_BADTAG
38 #define CRYPT_E_ASN1_BADTAG 0x8009310B
39 #endif
40
41
42 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
43                                      SRCNAME, __func__, __LINE__); \
44                         } while (0)
45
46
47 static int get_attach_method (LPATTACH obj);
48 static int has_smime_filename (LPATTACH obj);
49
50
51 \f
52
53 /* Print a MAPI property to the log stream. */
54 void
55 log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
56 {
57   HRESULT hr;
58   LPSPropValue propval = NULL;
59   size_t keylen;
60   void *key;
61   char *buf;
62
63   if (!message)
64     return; /* No message: Nop. */
65
66   hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval);
67   if (FAILED (hr))
68     {
69       log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n",
70                  SRCNAME, __func__, propname, hr);
71       return;
72     }
73     
74   switch ( PROP_TYPE (propval->ulPropTag) )
75     {
76     case PT_BINARY:
77       keylen = propval->Value.bin.cb;
78       key = propval->Value.bin.lpb;
79       log_hexdump (key, keylen, "%s: %20s=", __func__, propname);
80       break;
81
82     case PT_UNICODE:
83       buf = wchar_to_utf8 (propval->Value.lpszW);
84       if (!buf)
85         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
86       else
87         log_debug ("%s: %20s=`%s'", __func__, propname, buf);
88       xfree (buf);
89       break;
90       
91     case PT_STRING8:
92       log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA);
93       break;
94
95     case PT_LONG:
96       log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l);
97       break;
98
99     default:
100       log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n",
101                  SRCNAME, __func__, propname,
102                  PROP_TYPE (propval->ulPropTag) );
103       return;
104     }
105   MAPIFreeBuffer (propval);
106 }
107
108
109 /* Helper to create a named property. */
110 static ULONG 
111 create_gpgol_tag (LPMESSAGE message, wchar_t *name, const char *func)
112 {
113   HRESULT hr;
114   LPSPropTagArray proparr = NULL;
115   MAPINAMEID mnid, *pmnid;      
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
120   memset (&mnid, 0, sizeof mnid);
121   mnid.lpguid = &guid;
122   mnid.ulKind = MNID_STRING;
123   mnid.Kind.lpwstrName = name;
124   pmnid = &mnid;
125   hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
126   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
127     {
128       log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
129                  SRCNAME, func, hr); 
130       return 0;
131     }
132     
133   return (proparr->aulPropTag[0] & 0xFFFF0000);
134 }
135
136
137 /* Return the property tag for GpgOL Msg Class. */
138 int 
139 get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
140 {
141   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__)))
142     return -1;
143   *r_tag |= PT_STRING8;
144   return 0;
145 }
146
147
148 /* Return the property tag for GpgOL Attach Type. */
149 int 
150 get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag)
151 {
152   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__)))
153     return -1;
154   *r_tag |= PT_LONG;
155   return 0;
156 }
157
158
159 /* Return the property tag for GpgOL Sig Status. */
160 int 
161 get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag)
162 {
163   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__)))
164     return -1;
165   *r_tag |= PT_STRING8;
166   return 0;
167 }
168
169
170 /* Return the property tag for GpgOL Protect IV. */
171 int 
172 get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag)
173 {
174   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__)))
175     return -1;
176   *r_tag |= PT_BINARY;
177   return 0;
178 }
179
180 /* Return the property tag for GpgOL Last Decrypted. */
181 int 
182 get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag)
183 {
184   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__)))
185     return -1;
186   *r_tag |= PT_BINARY;
187   return 0;
188 }
189
190
191 /* Return the property tag for GpgOL MIME structure. */
192 int 
193 get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag)
194 {
195   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__)))
196     return -1;
197   *r_tag |= PT_STRING8;
198   return 0;
199 }
200
201
202 /* Return the property tag for GpgOL Charset. */
203 int 
204 get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag)
205 {
206   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__)))
207     return -1;
208   *r_tag |= PT_STRING8;
209   return 0;
210 }
211
212
213 /* Set an arbitary header in the message MSG with NAME to the value
214    VAL. */
215 int
216 mapi_set_header (LPMESSAGE msg, const char *name, const char *val)
217 {  
218   HRESULT hr;
219   LPSPropTagArray pProps = NULL;
220   SPropValue pv;
221   MAPINAMEID mnid, *pmnid;      
222   /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
223   GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
224                                             0x00, 0x00, 0x00, 0x46} };
225   
226   if (!msg)
227     return -1;
228
229   memset (&mnid, 0, sizeof mnid);
230   mnid.lpguid = &guid;
231   mnid.ulKind = MNID_STRING;
232   mnid.Kind.lpwstrName = utf8_to_wchar (name);
233   pmnid = &mnid;
234   hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
235   xfree (mnid.Kind.lpwstrName);
236   if (FAILED (hr)) 
237     {
238       log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
239                  SRCNAME, __func__, name, hr); 
240       return -1;
241     }
242     
243   pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
244   pv.Value.lpszA = (char *)val;
245   hr = HrSetOneProp(msg, &pv);  
246   if (hr)
247     {
248       log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
249                  SRCNAME, __func__, name, hr); 
250       return -1;
251     }
252   return 0;
253 }
254
255
256
257 /* Return the body as a new IStream object.  Returns NULL on failure.
258    The stream returns the body as an ASCII stream (Use mapi_get_body
259    for an UTF-8 value).  */
260 LPSTREAM
261 mapi_get_body_as_stream (LPMESSAGE message)
262 {
263   HRESULT hr;
264   LPSTREAM stream;
265
266   if (!message)
267     return NULL;
268
269   /* We try to get it as an ASCII body.  If this fails we would either
270      need to implement some kind of stream filter to translated to
271      utf-8 or read everyting into a memory buffer and [provide an
272      istream from that memory buffer.  */
273   hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0, 
274                               (LPUNKNOWN*)&stream);
275   if (hr)
276     {
277       log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
278       return NULL;
279     }
280
281   return stream;
282 }
283
284
285
286 /* Return the body of the message in an allocated buffer.  The buffer
287    is guaranteed to be Nul terminated.  The actual length (ie. the
288    strlen()) will be stored at R_NBYTES.  The body will be returned in
289    UTF-8 encoding. Returns NULL if no body is available.  */
290 char *
291 mapi_get_body (LPMESSAGE message, size_t *r_nbytes)
292 {
293   HRESULT hr;
294   LPSPropValue lpspvFEID = NULL;
295   LPSTREAM stream;
296   STATSTG statInfo;
297   ULONG nread;
298   char *body = NULL;
299
300   if (r_nbytes)
301     *r_nbytes = 0;
302   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
303   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
304     { 
305       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
306         {
307         case PT_UNICODE:
308           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
309           if (!body)
310             log_debug ("%s: error converting to utf8\n", __func__);
311           break;
312           
313         case PT_STRING8:
314           body = xstrdup (lpspvFEID->Value.lpszA);
315           break;
316           
317         default:
318           log_debug ("%s: proptag=0x%08lx not supported\n",
319                      __func__, lpspvFEID->ulPropTag);
320           break;
321         }
322       MAPIFreeBuffer (lpspvFEID);
323     }
324   else /* Message is large; use an IStream to read it.  */
325     {
326       hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, 
327                                   (LPUNKNOWN*)&stream);
328       if (hr)
329         {
330           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
331                      SRCNAME, __func__, hr);
332           return NULL;
333         }
334       
335       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
336       if (hr)
337         {
338           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
339           stream->Release ();
340           return NULL;
341         }
342       
343       /* Fixme: We might want to read only the first 1k to decide
344          whether this is actually an OpenPGP message and only then
345          continue reading.  */
346       body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
347       hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
348       if (hr)
349         {
350           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
351           xfree (body);
352           stream->Release ();
353           return NULL;
354         }
355       body[nread] = 0;
356       body[nread+1] = 0;
357       if (nread != statInfo.cbSize.QuadPart)
358         {
359           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
360           xfree (body);
361           stream->Release ();
362           return NULL;
363         }
364       stream->Release ();
365       
366       {
367         char *tmp;
368         tmp = wchar_to_utf8 ((wchar_t*)body);
369         if (!tmp)
370           log_debug ("%s: error converting to utf8\n", __func__);
371         else
372           {
373             xfree (body);
374             body = tmp;
375           }
376       }
377     }
378
379   if (r_nbytes)
380     *r_nbytes = strlen (body);
381   return body;
382 }
383
384
385
386 /* Look at the body of the MESSAGE and try to figure out whether this
387    is a supported PGP message.  Returns the new message class on
388    return or NULL if not.  */
389 static char *
390 get_msgcls_from_pgp_lines (LPMESSAGE message)
391 {
392   HRESULT hr;
393   LPSPropValue lpspvFEID = NULL;
394   LPSTREAM stream;
395   STATSTG statInfo;
396   ULONG nread;
397   size_t nbytes;
398   char *body = NULL;
399   char *p;
400   char *msgcls = NULL;
401
402   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
403   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
404     { 
405       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
406         {
407         case PT_UNICODE:
408           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
409           if (!body)
410             log_debug ("%s: error converting to utf8\n", __func__);
411           break;
412           
413         case PT_STRING8:
414           body = xstrdup (lpspvFEID->Value.lpszA);
415           break;
416           
417         default:
418           log_debug ("%s: proptag=0x%08lx not supported\n",
419                      __func__, lpspvFEID->ulPropTag);
420           break;
421         }
422       MAPIFreeBuffer (lpspvFEID);
423     }
424   else /* Message is large; use an IStream to read it.  */
425     {
426       hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, 
427                                   (LPUNKNOWN*)&stream);
428       if (hr)
429         {
430           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
431                      SRCNAME, __func__, hr);
432           return NULL;
433         }
434       
435       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
436       if (hr)
437         {
438           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
439           stream->Release ();
440           return NULL;
441         }
442       
443       /* We read only the first 1k to decide whether this is actually
444          an OpenPGP armored message .  */
445       nbytes = (size_t)statInfo.cbSize.QuadPart;
446       if (nbytes > 1024*2)
447         nbytes = 1024*2;
448       body = (char*)xmalloc (nbytes + 2);
449       hr = stream->Read (body, nbytes, &nread);
450       if (hr)
451         {
452           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
453           xfree (body);
454           stream->Release ();
455           return NULL;
456         }
457       body[nread] = 0;
458       body[nread+1] = 0;
459       if (nread != statInfo.cbSize.QuadPart)
460         {
461           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
462           xfree (body);
463           stream->Release ();
464           return NULL;
465         }
466       stream->Release ();
467       
468       {
469         char *tmp;
470         tmp = wchar_to_utf8 ((wchar_t*)body);
471         if (!tmp)
472           log_debug ("%s: error converting to utf8\n", __func__);
473         else
474           {
475             xfree (body);
476             body = tmp;
477           }
478       }
479     }
480
481   /* The first ~1k of the body of the message is now availble in the
482      utf-8 string BODY.  Walk over it to figure out its type.  */
483   for (p=body; p && *p; p = (p=strchr (p+1, '\n')? (p+1):NULL))
484     if (!strncmp (p, "-----BEGIN PGP ", 15))
485       {
486         if (!strncmp (p+15, "SIGNED MESSAGE-----", 19)
487             && trailing_ws_p (p+15+19))
488           msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned");
489         else if (!strncmp (p+15, "MESSAGE-----", 12)
490                  && trailing_ws_p (p+15+12))
491           msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage");
492         break;
493       }
494     else if (!trailing_ws_p (p))
495       break;  /* Text before the PGP message - don't take this as a
496                  proper message.  */
497          
498   xfree (body);
499   return msgcls;
500 }
501
502
503 /* Check whether the message is really a CMS encrypted message.  This
504    function is required due to a bug in CryptoEx which sometimes
505    assignes the *.CexEnc message class to signed messages and only
506    updates the message class after accessing them.  Thus in old stores
507    there may be a lot of *.CexEnc message which are actually just
508    signed.  We check here whether such a message is really encrypted
509    by looking at the object identifier inside the CMS data.  Returns
510    true if the message is really encrypted.  */
511 static int
512 is_really_cms_encrypted (LPMESSAGE message)
513 {    
514   HRESULT hr;
515   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
516   LPMAPITABLE mapitable;
517   LPSRowSet   mapirows;
518   unsigned int pos, n_attach;
519   int is_encrypted = 0;
520   LPATTACH att = NULL;
521   LPSTREAM stream = NULL;
522   char buffer[24];  /* 24 bytes are more than enough to peek at.
523                        Cf. ksba_cms_identify() from the libksba
524                        package.  */
525   const char *p;
526   ULONG nread;
527   size_t n;
528   tlvinfo_t ti;
529
530   hr = message->GetAttachmentTable (0, &mapitable);
531   if (FAILED (hr))
532     {
533       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
534                  SRCNAME, __func__, hr);
535       return 0;
536     }
537       
538   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
539                        NULL, NULL, 0, &mapirows);
540   if (FAILED (hr))
541     {
542       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
543                  SRCNAME, __func__, hr);
544       mapitable->Release ();
545       return 0;
546     }
547   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
548   if (n_attach != 1)
549     {
550       FreeProws (mapirows);
551       mapitable->Release ();
552       log_debug ("%s:%s: not just one attachments", SRCNAME, __func__);
553       return 0;
554     }
555   pos = 0;
556
557   if (mapirows->aRow[pos].cValues < 1)
558     {
559       log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
560       goto leave;
561     }
562   if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
563     {
564       log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
565       goto leave;
566     }
567   hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
568                             NULL, MAPI_BEST_ACCESS, &att);      
569   if (FAILED (hr))
570     {
571       log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
572                  SRCNAME, __func__, pos, 
573                  mapirows->aRow[pos].lpProps[0].Value.l, hr);
574       goto leave;
575     }
576   if (!has_smime_filename (att))
577     goto leave;
578   if (get_attach_method (att) != ATTACH_BY_VALUE)
579     goto leave;
580   
581   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
582                           0, 0, (LPUNKNOWN*) &stream);
583   if (FAILED (hr))
584     {
585       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
586                  SRCNAME, __func__, hr);
587       goto leave;
588     }
589
590   hr = stream->Read (buffer, sizeof buffer, &nread);
591   if ( hr != S_OK )
592     {
593       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
594       goto leave;
595     }
596   if (nread < sizeof buffer)
597     {
598       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
599       goto leave;
600     }
601
602   p = buffer;
603   n = nread;
604   if (parse_tlv (&p, &n, &ti))
605     goto leave;
606   if (!(ti.cls == MY_ASN_CLASS_UNIVERSAL && ti.tag == MY_ASN_TAG_SEQUENCE
607         && ti.is_cons) )
608     goto leave;
609   if (parse_tlv (&p, &n, &ti))
610     goto leave;
611   if (!(ti.cls == MY_ASN_CLASS_UNIVERSAL && ti.tag == MY_ASN_TAG_OBJECT_ID
612         && !ti.is_cons && ti.length) || ti.length > n)
613     goto leave;
614   /* Now is this enveloped data (1.2.840.113549.1.7.3)?  */
615   if (ti.length == 9 && !memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
616     is_encrypted = 1;
617
618
619  leave:
620   if (stream)
621     stream->Release ();
622   if (att)
623     att->Release ();
624   FreeProws (mapirows);
625   mapitable->Release ();
626   return !!is_encrypted;
627 }
628
629
630
631 /* This function checks whether MESSAGE requires processing by us and
632    adjusts the message class to our own.  By passing true for
633    SYNC_OVERRIDE the actual MAPI message class will be updated to our
634    own message class overide.  Return true if the message was
635    changed. */
636 int
637 mapi_change_message_class (LPMESSAGE message, int sync_override)
638 {
639   HRESULT hr;
640   ULONG tag;
641   SPropValue prop;
642   LPSPropValue propval = NULL;
643   char *newvalue = NULL;
644   int need_save = 0;
645   int have_override = 0;
646
647   if (!message)
648     return 0; /* No message: Nop. */
649
650   if (get_gpgolmsgclass_tag (message, &tag) )
651     return 0; /* Ooops. */
652
653   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
654   if (FAILED (hr))
655     {
656       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
657       if (FAILED (hr))
658         {
659           log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
660                      SRCNAME, __func__, hr);
661           return 0;
662         }
663     }
664   else
665     {
666       have_override = 1;
667       log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
668     }
669     
670   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
671     {
672       const char *s = propval->Value.lpszA;
673       int cexenc = 0;
674       
675       log_debug ("%s:%s: checking message class `%s'", 
676                        SRCNAME, __func__, s);
677       if (!strcmp (s, "IPM.Note"))
678         {
679           /* Most message today are of this type.  However a PGP/MIME
680              encrypted message also has this class here.  We need
681              to see whether we can detect such a mail right here and
682              change the message class accordingly. */
683           char *ct, *proto;
684
685           ct = mapi_get_message_content_type (message, &proto, NULL);
686           if (!ct)
687             log_debug ("%s:%s: message has no content type", 
688                        SRCNAME, __func__);
689           else
690             {
691               log_debug ("%s:%s: content type is '%s'", 
692                          SRCNAME, __func__, ct);
693               if (proto)
694                 {
695                   log_debug ("%s:%s:     protocol is '%s'", 
696                              SRCNAME, __func__, proto);
697               
698                   if (!strcmp (ct, "multipart/encrypted")
699                       && !strcmp (proto, "application/pgp-encrypted"))
700                     {
701                       newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
702                     }
703                   else if (!strcmp (ct, "multipart/signed")
704                            && !strcmp (proto, "application/pgp-signature"))
705                     {
706                       /* Sometimes we receive a PGP/MIME signed
707                          message with a class IPM.Note.  */
708                       newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
709                     }
710                   xfree (proto);
711                 }
712               else if (!strcmp (ct, "text/plain"))
713                 {
714                   newvalue = get_msgcls_from_pgp_lines (message);
715                 }
716               
717               xfree (ct);
718             }
719         }
720       else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME"))
721         {
722           /* This is an S/MIME opaque encrypted or signed message.
723              Check what it really is.  */
724           char *ct, *smtype;
725
726           ct = mapi_get_message_content_type (message, NULL, &smtype);
727           if (!ct)
728             log_debug ("%s:%s: message has no content type", 
729                        SRCNAME, __func__);
730           else
731             {
732               log_debug ("%s:%s: content type is '%s'", 
733                          SRCNAME, __func__, ct);
734               if (smtype)
735                 {
736                   log_debug ("%s:%s:   smime-type is '%s'", 
737                              SRCNAME, __func__, smtype);
738               
739                   if (!strcmp (ct, "application/pkcs7-mime")
740                       || !strcmp (ct, "application/x-pkcs7-mime"))
741                     {
742                       if (!strcmp (smtype, "signed-data"))
743                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
744                       else if (!strcmp (smtype, "enveloped-data"))
745                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
746                     }
747                   xfree (smtype);
748                 }
749               xfree (ct);
750             }
751           if (!newvalue)
752             newvalue = xstrdup ("IPM.Note.GpgOL");
753         }
754       else if (opt.enable_smime
755                && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.'))
756         {
757           /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional
758              but the previous condition has already taken care of
759              this).  Note that we can't just insert a new part and
760              keep the SMIME; we need to change the SMIME part of the
761              class name so that Outlook does not process it as an
762              SMIME message. */
763           newvalue = (char*)xmalloc (strlen (s) + 1);
764           strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
765         }
766       else if (opt.enable_smime && sync_override && have_override
767                && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
768         {
769           /* In case the original message class is not yet an GpgOL
770              class we set it here.  This is needed to convince Outlook
771              not to do any special processing for IPM.Note.SMIME etc.  */
772           LPSPropValue propval2 = NULL;
773
774           hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
775                              &propval2);
776           if (SUCCEEDED (hr) && PROP_TYPE (propval2->ulPropTag) == PT_STRING8
777               && propval2->Value.lpszA && strcmp (propval2->Value.lpszA, s))
778             newvalue = (char*)xstrdup (s);
779           MAPIFreeBuffer (propval2);
780         }
781       else if (opt.enable_smime 
782                && (!strcmp (s, "IPM.Note.Secure.CexSig")
783                    || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
784         {
785           /* This is a CryptoEx generated signature or encrypted data. */
786           char *ct, *smtype, *proto;
787
788           ct = mapi_get_message_content_type (message, &proto, &smtype);
789           if (!ct)
790             {
791               log_debug ("%s:%s: message has no content type", 
792                          SRCNAME, __func__);
793               if (cexenc)
794                 {
795                   if (is_really_cms_encrypted (message))
796                     newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
797                   else
798                     newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
799                 }
800             }
801           else
802             {
803               log_debug ("%s:%s: content type is '%s'", 
804                          SRCNAME, __func__, ct);
805               if (smtype)
806                log_debug ("%s:%s:   smime-type is '%s'", 
807                            SRCNAME, __func__, smtype);
808               if (proto)
809                 log_debug ("%s:%s:     protocol is '%s'", 
810                            SRCNAME, __func__, proto);
811               if (smtype)
812                 {
813                   if (!strcmp (ct, "application/pkcs7-mime")
814                       || !strcmp (ct, "application/x-pkcs7-mime"))
815                     {
816                       if (!strcmp (smtype, "signed-data"))
817                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
818                       else if (!strcmp (smtype, "enveloped-data"))
819                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
820                     }
821                 }
822
823               if (!newvalue && proto)
824                 {
825                   if (!strcmp (ct, "multipart/signed")
826                       && (!strcmp (proto, "application/pkcs7-signature")
827                           || !strcmp (proto, "application/x-pkcs7-signature")))
828                     newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
829                   else if (!strcmp (ct, "multipart/signed")
830                            && (!strcmp (proto, "application/pgp-signature")))
831                     newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
832                 }
833
834               if (!newvalue && !strcmp (ct, "text/plain"))
835                 {
836                   newvalue = get_msgcls_from_pgp_lines (message);
837                 }
838
839               if (!newvalue)
840                 {
841                   if (is_really_cms_encrypted (message))
842                     newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
843                   else
844                     newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
845                 }
846
847               xfree (smtype);
848               xfree (proto);
849               xfree (ct);
850             }
851           if (!newvalue)
852             newvalue = xstrdup ("IPM.Note.GpgOL");
853         }
854     }
855   MAPIFreeBuffer (propval);
856   if (!newvalue)
857     {
858       /* We use our Sig-Status property to mark messages which passed
859          this function.  This helps us to avoid later tests.  */
860       if (!mapi_has_sig_status (message))
861         {
862           mapi_set_sig_status (message, "#");
863           need_save = 1;
864         }
865     }
866   else
867     {
868       log_debug ("%s:%s: setting message class to `%s'\n",
869                      SRCNAME, __func__, newvalue);
870       prop.ulPropTag = PR_MESSAGE_CLASS_A;
871       prop.Value.lpszA = newvalue; 
872       hr = message->SetProps (1, &prop, NULL);
873       xfree (newvalue);
874       if (hr)
875         {
876           log_error ("%s:%s: can't set message class: hr=%#lx\n",
877                      SRCNAME, __func__, hr);
878           return 0;
879         }
880       need_save = 1;
881     }
882
883   if (need_save)
884     {
885       hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
886       if (hr)
887         {
888           log_error ("%s:%s: SaveChanges() failed: hr=%#lx\n",
889                      SRCNAME, __func__, hr); 
890           return 0;
891         }
892     }
893
894   return 1;
895 }
896
897
898 /* Return the message class.  This function will never return NULL so
899    it is only useful for debugging.  Caller needs to release the
900    returned string.  */
901 char *
902 mapi_get_message_class (LPMESSAGE message)
903 {
904   HRESULT hr;
905   LPSPropValue propval = NULL;
906   char *retstr;
907
908   if (!message)
909     return xstrdup ("[No message]");
910   
911   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
912   if (FAILED (hr))
913     {
914       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
915                  SRCNAME, __func__, hr);
916       return xstrdup (hr == MAPI_E_NOT_FOUND?
917                         "[No message class property]":
918                         "[Error getting message class property]");
919     }
920
921   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
922     retstr = xstrdup (propval->Value.lpszA);
923   else
924     retstr = xstrdup ("[Invalid message class property]");
925     
926   MAPIFreeBuffer (propval);
927   return retstr;
928 }
929
930
931
932 /* Return the message type.  This function knows only about our own
933    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
934    no special support for.  */
935 msgtype_t
936 mapi_get_message_type (LPMESSAGE message)
937 {
938   HRESULT hr;
939   ULONG tag;
940   LPSPropValue propval = NULL;
941   msgtype_t msgtype = MSGTYPE_UNKNOWN;
942
943   if (!message)
944     return msgtype; 
945
946   if (get_gpgolmsgclass_tag (message, &tag) )
947     return msgtype; /* Ooops */
948
949   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
950   if (FAILED (hr))
951     {
952       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
953       if (FAILED (hr))
954         {
955           log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
956                      SRCNAME, __func__, hr);
957           return msgtype;
958         }
959     }
960   else
961     log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
962     
963   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
964     {
965       const char *s = propval->Value.lpszA;
966       if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
967         {
968           s += 14;
969           if (!*s)
970             msgtype = MSGTYPE_GPGOL;
971           else if (!strcmp (s, ".MultipartSigned"))
972             msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
973           else if (!strcmp (s, ".MultipartEncrypted"))
974             msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
975           else if (!strcmp (s, ".OpaqueSigned"))
976             msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
977           else if (!strcmp (s, ".OpaqueEncrypted"))
978             msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
979           else if (!strcmp (s, ".ClearSigned"))
980             msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
981           else if (!strcmp (s, ".PGPMessage"))
982             msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
983           else
984             log_debug ("%s:%s: message class `%s' not supported",
985                        SRCNAME, __func__, s-14);
986         }
987       else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
988         msgtype = MSGTYPE_SMIME;
989     }
990   MAPIFreeBuffer (propval);
991   return msgtype;
992 }
993
994
995 /* This function is pretty useless because IConverterSession won't
996    take attachments into account.  Need to write our own version.  */
997 int
998 mapi_to_mime (LPMESSAGE message, const char *filename)
999 {
1000   HRESULT hr;
1001   LPCONVERTERSESSION session;
1002   LPSTREAM stream;
1003
1004   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
1005                          IID_IConverterSession, (void **) &session);
1006   if (FAILED (hr))
1007     {
1008       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
1009                  SRCNAME, __func__, hr);
1010       return -1;
1011     }
1012
1013
1014   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1015                          (STGM_CREATE | STGM_READWRITE),
1016                          (char*)filename, NULL, &stream); 
1017   if (FAILED (hr)) 
1018     {
1019       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
1020                  SRCNAME, __func__, filename, hr); 
1021       hr = -1;
1022     }
1023   else
1024     {
1025       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
1026       if (FAILED (hr))
1027         {
1028           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
1029                      SRCNAME, __func__, hr);
1030           stream->Revert ();
1031           hr = -1;
1032         }
1033       else
1034         {
1035           stream->Commit (0);
1036           hr = 0;
1037         }
1038
1039       stream->Release ();
1040     }
1041
1042   session->Release ();
1043   return hr;
1044 }
1045
1046
1047 /* Return a binary property in a malloced buffer with its length stored
1048    at R_NBYTES.  Returns NULL on error.  */
1049 char *
1050 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
1051 {
1052   HRESULT hr;
1053   LPSPropValue propval = NULL;
1054   char *data;
1055
1056   *r_nbytes = 0;
1057   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
1058   if (FAILED (hr))
1059     {
1060       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1061                  SRCNAME, __func__, proptype, hr);
1062       return NULL; 
1063     }
1064   switch ( PROP_TYPE (propval->ulPropTag) )
1065     {
1066     case PT_BINARY:
1067       /* This is a binary object but we know that it must be plain
1068          ASCII due to the armored format.  */
1069       data = (char*)xmalloc (propval->Value.bin.cb + 1);
1070       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
1071       data[propval->Value.bin.cb] = 0;
1072       *r_nbytes = propval->Value.bin.cb;
1073       break;
1074       
1075     default:
1076       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1077                  SRCNAME, __func__, proptype, propval->ulPropTag);
1078       data = NULL;
1079       break;
1080     }
1081   MAPIFreeBuffer (propval);
1082   return data;
1083 }
1084
1085
1086 /* Return the attachment method for attachment OBJ.  In case of error
1087    we return 0 which happens not to be defined.  */
1088 static int
1089 get_attach_method (LPATTACH obj)
1090 {
1091   HRESULT hr;
1092   LPSPropValue propval = NULL;
1093   int method ;
1094
1095   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1096   if (FAILED (hr))
1097     {
1098       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1099                  SRCNAME, __func__, hr);
1100       return 0; 
1101     }
1102   /* We don't bother checking whether we really get a PT_LONG ulong
1103      back; if not the system is seriously damaged and we can't do
1104      further harm by returning a possible random value.  */
1105   method = propval->Value.l;
1106   MAPIFreeBuffer (propval);
1107   return method;
1108 }
1109
1110
1111
1112 /* Return the filename from the attachment as a malloced string.  The
1113    encoding we return will be UTF-8, however the MAPI docs declare
1114    that MAPI does only handle plain ANSI and thus we don't really care
1115    later on.  In fact we would need to convert the filename back to
1116    wchar and use the Unicode versions of the file API.  Returns NULL
1117    on error or if no filename is available. */
1118 static char *
1119 get_attach_filename (LPATTACH obj)
1120 {
1121   HRESULT hr;
1122   LPSPropValue propval;
1123   char *name = NULL;
1124
1125   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1126   if (FAILED(hr)) 
1127     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1128   if (FAILED(hr))
1129     {
1130       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
1131       return NULL;
1132     }
1133
1134   switch ( PROP_TYPE (propval->ulPropTag) )
1135     {
1136     case PT_UNICODE:
1137       name = wchar_to_utf8 (propval->Value.lpszW);
1138       if (!name)
1139         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1140       break;
1141       
1142     case PT_STRING8:
1143       name = xstrdup (propval->Value.lpszA);
1144       break;
1145       
1146     default:
1147       log_debug ("%s:%s: proptag=%#lx not supported\n",
1148                  SRCNAME, __func__, propval->ulPropTag);
1149       name = NULL;
1150       break;
1151     }
1152   MAPIFreeBuffer (propval);
1153   return name;
1154 }
1155
1156
1157 /* Return the content-type of the attachment OBJ or NULL if it does
1158    not exists.  Caller must free. */
1159 static char *
1160 get_attach_mime_tag (LPATTACH obj)
1161 {
1162   HRESULT hr;
1163   LPSPropValue propval = NULL;
1164   char *name;
1165
1166   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
1167   if (FAILED (hr))
1168     {
1169       if (hr != MAPI_E_NOT_FOUND)
1170         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
1171                    SRCNAME, __func__, hr);
1172       return NULL; 
1173     }
1174   switch ( PROP_TYPE (propval->ulPropTag) )
1175     {
1176     case PT_UNICODE:
1177       name = wchar_to_utf8 (propval->Value.lpszW);
1178       if (!name)
1179         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1180       break;
1181       
1182     case PT_STRING8:
1183       name = xstrdup (propval->Value.lpszA);
1184       break;
1185       
1186     default:
1187       log_debug ("%s:%s: proptag=%#lx not supported\n",
1188                  SRCNAME, __func__, propval->ulPropTag);
1189       name = NULL;
1190       break;
1191     }
1192   MAPIFreeBuffer (propval);
1193   return name;
1194 }
1195
1196
1197 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
1198    the tag of that property. */
1199 static attachtype_t
1200 get_gpgolattachtype (LPATTACH obj, ULONG tag)
1201 {
1202   HRESULT hr;
1203   LPSPropValue propval = NULL;
1204   attachtype_t retval;
1205
1206   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
1207   if (FAILED (hr))
1208     {
1209       if (hr != MAPI_E_NOT_FOUND)
1210         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
1211                    SRCNAME, __func__, hr);
1212       return ATTACHTYPE_UNKNOWN; 
1213     }
1214   retval = (attachtype_t)propval->Value.l;
1215   MAPIFreeBuffer (propval);
1216   return retval;
1217 }
1218
1219
1220 /* Gather information about attachments and return a new table of
1221    attachments.  Caller must release the returned table.s The routine
1222    will return NULL in case of an error or if no attachments are
1223    available.  With FAST set only some information gets collected. */
1224 mapi_attach_item_t *
1225 mapi_create_attach_table (LPMESSAGE message, int fast)
1226 {    
1227   HRESULT hr;
1228   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
1229   LPMAPITABLE mapitable;
1230   LPSRowSet   mapirows;
1231   mapi_attach_item_t *table; 
1232   unsigned int pos, n_attach;
1233   ULONG moss_tag;
1234
1235   if (get_gpgolattachtype_tag (message, &moss_tag) )
1236     return NULL;
1237
1238   /* Open the attachment table.  */
1239   hr = message->GetAttachmentTable (0, &mapitable);
1240   if (FAILED (hr))
1241     {
1242       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
1243                  SRCNAME, __func__, hr);
1244       return NULL;
1245     }
1246       
1247   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
1248                        NULL, NULL, 0, &mapirows);
1249   if (FAILED (hr))
1250     {
1251       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
1252                  SRCNAME, __func__, hr);
1253       mapitable->Release ();
1254       return NULL;
1255     }
1256   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
1257
1258   log_debug ("%s:%s: message has %u attachments\n",
1259              SRCNAME, __func__, n_attach);
1260   if (!n_attach)
1261     {
1262       FreeProws (mapirows);
1263       mapitable->Release ();
1264       return NULL;
1265     }
1266
1267   /* Allocate our own table.  */
1268   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
1269   for (pos=0; pos < n_attach; pos++) 
1270     {
1271       LPATTACH att;
1272
1273       if (mapirows->aRow[pos].cValues < 1)
1274         {
1275           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
1276           table[pos].mapipos = -1;
1277           continue;
1278         }
1279       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
1280         {
1281           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
1282           table[pos].mapipos = -1;
1283           continue;
1284         }
1285       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
1286
1287       hr = message->OpenAttach (table[pos].mapipos, NULL,
1288                                 MAPI_BEST_ACCESS, &att);        
1289       if (FAILED (hr))
1290         {
1291           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
1292                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
1293           table[pos].mapipos = -1;
1294           continue;
1295         }
1296
1297       table[pos].method = get_attach_method (att);
1298       table[pos].filename = fast? NULL : get_attach_filename (att);
1299       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
1300       if (table[pos].content_type)
1301         {
1302           char *p = strchr (table[pos].content_type, ';');
1303           if (p)
1304             {
1305               *p++ = 0;
1306               trim_trailing_spaces (table[pos].content_type);
1307               while (strchr (" \t\r\n", *p))
1308                 p++;
1309               trim_trailing_spaces (p);
1310               table[pos].content_type_parms = p;
1311             }
1312         }
1313       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
1314       att->Release ();
1315     }
1316   table[0].private_mapitable = mapitable;
1317   FreeProws (mapirows);
1318   table[pos].end_of_table = 1;
1319   mapitable = NULL;
1320
1321   if (fast)
1322     {
1323       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
1324                  SRCNAME, __func__);
1325     }
1326   else
1327     {
1328       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
1329       for (pos=0; !table[pos].end_of_table; pos++)
1330         {
1331           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
1332                      table[pos].mapipos,
1333                      table[pos].attach_type,
1334                      table[pos].filename, table[pos].content_type,
1335                      table[pos].content_type_parms);
1336         }
1337     }
1338
1339   return table;
1340 }
1341
1342
1343 /* Release a table as created by mapi_create_attach_table. */
1344 void
1345 mapi_release_attach_table (mapi_attach_item_t *table)
1346 {
1347   unsigned int pos;
1348   LPMAPITABLE mapitable;
1349
1350   if (!table)
1351     return;
1352
1353   mapitable = (LPMAPITABLE)table[0].private_mapitable;
1354   if (mapitable)
1355     mapitable->Release ();
1356   for (pos=0; !table[pos].end_of_table; pos++)
1357     {
1358       xfree (table[pos].filename);
1359       xfree (table[pos].content_type);
1360     }
1361   xfree (table);
1362 }
1363
1364
1365 /* Return an attachment as a new IStream object.  Returns NULL on
1366    failure.  If R_ATATCH is not NULL the actual attachment will not be
1367    released by stored at that address; the caller needs to release it
1368    in this case.  */
1369 LPSTREAM
1370 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
1371                            LPATTACH *r_attach)
1372 {
1373   HRESULT hr;
1374   LPATTACH att;
1375   LPSTREAM stream;
1376
1377   if (r_attach)
1378     *r_attach = NULL;
1379
1380   if (!item || item->end_of_table || item->mapipos == -1)
1381     return NULL;
1382
1383   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1384   if (FAILED (hr))
1385     {
1386       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1387                  SRCNAME, __func__, item->mapipos, hr);
1388       return NULL;
1389     }
1390   if (item->method != ATTACH_BY_VALUE)
1391     {
1392       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1393       att->Release ();
1394       return NULL;
1395     }
1396
1397   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1398                           0, 0, (LPUNKNOWN*) &stream);
1399   if (FAILED (hr))
1400     {
1401       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1402                  SRCNAME, __func__, hr);
1403       att->Release ();
1404       return NULL;
1405     }
1406
1407   if (r_attach)
1408     *r_attach = att;
1409   else
1410     att->Release ();
1411
1412   return stream;
1413 }
1414
1415
1416 /* Return a malloced buffer with the content of the attachment. If
1417    R_NBYTES is not NULL the number of bytes will get stored there.
1418    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
1419    NULL on error.  If UNPROTECT is set and the appropriate crypto
1420    attribute is available, the function returns the unprotected
1421    version of the atatchment. */
1422 static char *
1423 attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect, 
1424                   int *r_was_protected)
1425 {
1426   HRESULT hr;
1427   LPSTREAM stream;
1428   STATSTG statInfo;
1429   ULONG nread;
1430   char *buffer;
1431   symenc_t symenc = NULL;
1432
1433   if (r_was_protected)
1434     *r_was_protected = 0;
1435
1436   if (unprotect)
1437     {
1438       ULONG tag;
1439       char *iv;
1440       size_t ivlen;
1441
1442       if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag) 
1443           && (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
1444         {
1445           symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
1446           xfree (iv);
1447           if (!symenc)
1448             log_error ("%s:%s: can't open encryption context", 
1449                        SRCNAME, __func__);
1450         }
1451     }
1452   
1453
1454   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1455                           0, 0, (LPUNKNOWN*) &stream);
1456   if (FAILED (hr))
1457     {
1458       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1459                  SRCNAME, __func__, hr);
1460       return NULL;
1461     }
1462
1463   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
1464   if ( hr != S_OK )
1465     {
1466       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
1467       stream->Release ();
1468       return NULL;
1469     }
1470       
1471   /* Allocate one byte more so that we can terminate the string.  */
1472   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
1473
1474   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
1475   if ( hr != S_OK )
1476     {
1477       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
1478       xfree (buffer);
1479       stream->Release ();
1480       return NULL;
1481     }
1482   if (nread != statInfo.cbSize.QuadPart)
1483     {
1484       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
1485       xfree (buffer);
1486       buffer = NULL;
1487     }
1488   stream->Release ();
1489
1490   if (buffer && symenc)
1491     {
1492       symenc_cfb_decrypt (symenc, buffer, buffer, nread);
1493       if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
1494         {
1495           xfree (buffer);
1496           buffer = native_to_utf8 
1497             (_("[The content of this message is not visible because it has "
1498                "been decrypted by another Outlook session.  Use the "
1499                "\"decrypt/verify\" command to make it visible]"));
1500           nread = strlen (buffer);
1501         }
1502       else
1503         {
1504           memmove (buffer, buffer+16, nread-16);
1505           nread -= 16;
1506           if (r_was_protected)
1507             *r_was_protected = 1;
1508         }
1509     }
1510
1511   /* Make sure that the buffer is a C string.  */
1512   if (buffer)
1513     buffer[nread] = 0;
1514
1515   symenc_close (symenc);
1516   if (r_nbytes)
1517     *r_nbytes = nread;
1518   return buffer;
1519 }
1520
1521
1522
1523 /* Return an attachment as a malloced buffer.  The size of the buffer
1524    will be stored at R_NBYTES.  Returns NULL on failure. */
1525 char *
1526 mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes)
1527 {
1528   HRESULT hr;
1529   LPATTACH att;
1530   char *buffer;
1531
1532   if (!item || item->end_of_table || item->mapipos == -1)
1533     return NULL;
1534
1535   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1536   if (FAILED (hr))
1537     {
1538       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1539                  SRCNAME, __func__, item->mapipos, hr);
1540       return NULL;
1541     }
1542   if (item->method != ATTACH_BY_VALUE)
1543     {
1544       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1545       att->Release ();
1546       return NULL;
1547     }
1548
1549   buffer = attach_to_buffer (att, r_nbytes, 0, NULL);
1550   att->Release ();
1551
1552   return buffer;
1553 }
1554
1555
1556 /* Mark this attachment as the orginal MOSS message.  We set a custom
1557    property as well as the hidden hidden flag.  */
1558 int 
1559 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
1560 {
1561   int retval = -1;
1562   HRESULT hr;
1563   LPATTACH att;
1564   SPropValue prop;
1565
1566   if (!item || item->end_of_table || item->mapipos == -1)
1567     return -1;
1568
1569   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1570   if (FAILED (hr))
1571     {
1572       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1573                  SRCNAME, __func__, item->mapipos, hr);
1574       return -1;
1575     }
1576
1577   if (FAILED (hr)) 
1578     {
1579       log_error ("%s:%s: can't map %s property: hr=%#lx\n",
1580                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1581       goto leave;
1582     }
1583     
1584   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
1585     goto leave;
1586   prop.Value.l = ATTACHTYPE_MOSS;
1587   hr = HrSetOneProp (att, &prop);       
1588   if (hr)
1589     {
1590       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1591                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1592       return false;
1593     }
1594
1595   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
1596   prop.Value.b = TRUE;
1597   hr = HrSetOneProp (att, &prop);
1598   if (hr)
1599     {
1600       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
1601                  SRCNAME, __func__, hr); 
1602       goto leave;
1603     }
1604   
1605
1606   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
1607   if (hr)
1608     {
1609       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
1610                  SRCNAME, __func__, hr); 
1611       goto leave;
1612     }
1613   
1614   retval = 0;
1615     
1616  leave:
1617   att->Release ();
1618   return retval;
1619 }
1620
1621
1622 /* If the hidden property has not been set on ATTACH, set it and save
1623    the changes. */
1624 int 
1625 mapi_set_attach_hidden (LPATTACH attach)
1626 {
1627   int retval = -1;
1628   HRESULT hr;
1629   LPSPropValue propval;
1630   SPropValue prop;
1631
1632   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
1633   if (SUCCEEDED (hr) 
1634       && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
1635       && propval->Value.b)
1636     return 0;/* Already set to hidden. */
1637
1638   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
1639   prop.Value.b = TRUE;
1640   hr = HrSetOneProp (attach, &prop);
1641   if (hr)
1642     {
1643       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
1644                  SRCNAME, __func__, hr); 
1645       goto leave;
1646     }
1647   
1648   hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
1649   if (hr)
1650     {
1651       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
1652                  SRCNAME, __func__, hr); 
1653       goto leave;
1654     }
1655   
1656   retval = 0;
1657     
1658  leave:
1659   return retval;
1660 }
1661
1662
1663
1664 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
1665 int
1666 mapi_has_sig_status (LPMESSAGE msg)
1667 {
1668   HRESULT hr;
1669   LPSPropValue propval = NULL;
1670   ULONG tag;
1671   int yes;
1672
1673   if (get_gpgolsigstatus_tag (msg, &tag) )
1674     return 0; /* Error:  Assume No.  */
1675   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1676   if (FAILED (hr))
1677     return 0; /* No.  */  
1678   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1679     yes = 1;
1680   else
1681     yes = 0;
1682
1683   MAPIFreeBuffer (propval);
1684   return yes;
1685 }
1686
1687
1688 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
1689    is not set to unchecked.  */
1690 int
1691 mapi_test_sig_status (LPMESSAGE msg)
1692 {
1693   HRESULT hr;
1694   LPSPropValue propval = NULL;
1695   ULONG tag;
1696   int yes;
1697
1698   if (get_gpgolsigstatus_tag (msg, &tag) )
1699     return 0; /* Error:  Assume No.  */
1700   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1701   if (FAILED (hr))
1702     return 0; /* No.  */  
1703
1704   /* We return False if we have an unknown signature status (?) or the
1705      message has been setn by us and not yet checked (@).  */
1706   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1707     yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
1708                                      || !strcmp (propval->Value.lpszA, "@")));
1709   else
1710     yes = 0;
1711
1712   MAPIFreeBuffer (propval);
1713   return yes;
1714 }
1715
1716
1717 /* Return the signature status as an allocated string.  Will never
1718    return NULL.  */
1719 char *
1720 mapi_get_sig_status (LPMESSAGE msg)
1721 {
1722   HRESULT hr;
1723   LPSPropValue propval = NULL;
1724   ULONG tag;
1725   char *retstr;
1726
1727   if (get_gpgolsigstatus_tag (msg, &tag) )
1728     return xstrdup ("[Error getting tag for sig status]");
1729   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1730   if (FAILED (hr))
1731     return xstrdup ("");
1732   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1733     retstr = xstrdup (propval->Value.lpszA);
1734   else
1735     retstr = xstrdup ("[Sig status has an invalid type]");
1736
1737   MAPIFreeBuffer (propval);
1738   return retstr;
1739 }
1740
1741
1742
1743
1744 /* Set the signature status property to STATUS_STRING.  There are a
1745    few special values:
1746
1747      "#" The message is not of interest to us.
1748      "@" The message has been created and signed or encrypted by us.
1749      "?" The signature status has not been checked.
1750      "!" The signature verified okay 
1751      "~" The signature was not fully verified.
1752      "-" The signature is bad
1753
1754    Note that this function does not call SaveChanges.  */
1755 int 
1756 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
1757 {
1758   HRESULT hr;
1759   SPropValue prop;
1760
1761   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
1762     return -1;
1763   prop.Value.lpszA = xstrdup (status_string);
1764   hr = HrSetOneProp (message, &prop);   
1765   xfree (prop.Value.lpszA);
1766   if (hr)
1767     {
1768       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1769                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
1770       return -1;
1771     }
1772
1773   return 0;
1774 }
1775
1776
1777 /* When sending a message we need to fake the message class so that OL
1778    processes it according to our needs.  However, if we later try to
1779    get the message class from the sent message, OL still has the SMIME
1780    message class and tries to hide this by trying to decrypt the
1781    message and return the message class from the plaintext.  To
1782    mitigate the problem we define our own msg class override
1783    property.  */
1784 int 
1785 mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
1786 {
1787   HRESULT hr;
1788   SPropValue prop;
1789
1790   if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
1791     return -1;
1792   prop.Value.lpszA = xstrdup (name);
1793   hr = HrSetOneProp (message, &prop);   
1794   xfree (prop.Value.lpszA);
1795   if (hr)
1796     {
1797       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1798                  SRCNAME, __func__, "GpgOL Msg Class", hr); 
1799       return -1;
1800     }
1801
1802   return 0;
1803 }
1804
1805
1806 /* Return the charset as assigned by GpgOL to an attachment.  This may
1807    return NULL it is has not been assigned or is the standard
1808    (UTF-8). */
1809 char *
1810 mapi_get_gpgol_charset (LPMESSAGE obj)
1811 {
1812   HRESULT hr;
1813   LPSPropValue propval = NULL;
1814   ULONG tag;
1815   char *retstr;
1816
1817   if (get_gpgolcharset_tag (obj, &tag) )
1818     return NULL; /* Error.  */
1819   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
1820   if (FAILED (hr))
1821     return NULL;
1822   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1823     {
1824       if (!strcmp (propval->Value.lpszA, "utf-8"))
1825         retstr = NULL;
1826       else
1827         retstr = xstrdup (propval->Value.lpszA);
1828     }
1829   else
1830     retstr = NULL;
1831
1832   MAPIFreeBuffer (propval);
1833   return retstr;
1834 }
1835
1836
1837 /* Set the GpgOl charset t an asstachment. 
1838    Note that this function does not call SaveChanges.  */
1839 int 
1840 mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
1841 {
1842   HRESULT hr;
1843   SPropValue prop;
1844   char *p;
1845
1846   /* Note that we lowercase the value and cut it to a max of 32
1847      characters.  The latter is required to make sure that
1848      HrSetOneProp will always work.  */
1849   if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
1850     return -1;
1851   prop.Value.lpszA = xstrdup (charset);
1852   for (p=prop.Value.lpszA; *p; p++)
1853     *p = tolower (*(unsigned char*)p);
1854   if (strlen (prop.Value.lpszA) > 32)
1855     prop.Value.lpszA[32] = 0;
1856   hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);   
1857   xfree (prop.Value.lpszA);
1858   if (hr)
1859     {
1860       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1861                  SRCNAME, __func__, "GpgOL Charset", hr); 
1862       return -1;
1863     }
1864
1865   return 0;
1866 }
1867
1868
1869 /* Return the MIME info as an allocated string.  Will never return
1870    NULL.  */
1871 char *
1872 mapi_get_mime_info (LPMESSAGE msg)
1873 {
1874   HRESULT hr;
1875   LPSPropValue propval = NULL;
1876   ULONG tag;
1877   char *retstr;
1878
1879   if (get_gpgolmimeinfo_tag (msg, &tag) )
1880     return xstrdup ("[Error getting tag for MIME info]");
1881   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1882   if (FAILED (hr))
1883     return xstrdup ("");
1884   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1885     retstr = xstrdup (propval->Value.lpszA);
1886   else
1887     retstr = xstrdup ("[MIME info has an invalid type]");
1888
1889   MAPIFreeBuffer (propval);
1890   return retstr;
1891 }
1892
1893
1894
1895
1896 /* Helper for mapi_get_msg_content_type() */
1897 static int
1898 get_message_content_type_cb (void *dummy_arg,
1899                              rfc822parse_event_t event, rfc822parse_t msg)
1900 {
1901   if (event == RFC822PARSE_T2BODY)
1902     return 42; /* Hack to stop the parsing after having read the
1903                   outer headers. */
1904   return 0;
1905 }
1906
1907
1908 /* Return Content-Type of the current message.  This one is taken
1909    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
1910    string with the protocol parameter will be stored at this address,
1911    if no protocol is given NULL will be stored.  If R_SMTYPE is not
1912    NULL a string with the smime-type parameter will be stored there.
1913    Caller must release all returned strings.  */
1914 char *
1915 mapi_get_message_content_type (LPMESSAGE message,
1916                                char **r_protocol, char **r_smtype)
1917 {
1918   HRESULT hr;
1919   LPSPropValue propval = NULL;
1920   rfc822parse_t msg;
1921   const char *header_lines, *s;
1922   rfc822parse_field_t ctx;
1923   size_t length;
1924   char *retstr = NULL;
1925   
1926   if (r_protocol)
1927     *r_protocol = NULL;
1928   if (r_smtype)
1929     *r_smtype = NULL;
1930
1931   hr = HrGetOneProp ((LPMAPIPROP)message,
1932                      PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
1933   if (FAILED (hr))
1934     {
1935       log_error ("%s:%s: error getting the headers lines: hr=%#lx",
1936                  SRCNAME, __func__, hr);
1937       return NULL; 
1938     }
1939   if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
1940     {
1941       /* As per rfc822, header lines must be plain ascii, so no need
1942          to cope withy unicode etc. */
1943       log_error ("%s:%s: proptag=%#lx not supported\n",
1944                  SRCNAME, __func__, propval->ulPropTag);
1945       MAPIFreeBuffer (propval);
1946       return NULL;
1947     }
1948   header_lines = propval->Value.lpszA;
1949
1950   /* Read the headers into an rfc822 object. */
1951   msg = rfc822parse_open (get_message_content_type_cb, NULL);
1952   if (!msg)
1953     {
1954       log_error ("%s:%s: rfc822parse_open failed\n", SRCNAME, __func__);
1955       MAPIFreeBuffer (propval);
1956       return NULL;
1957     }
1958   
1959   while ((s = strchr (header_lines, '\n')))
1960     {
1961       length = (s - header_lines);
1962       if (length && s[-1] == '\r')
1963         length--;
1964       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
1965       header_lines = s+1;
1966     }
1967   
1968   /* Parse the content-type field. */
1969   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
1970   if (ctx)
1971     {
1972       const char *s1, *s2;
1973       s1 = rfc822parse_query_media_type (ctx, &s2);
1974       if (s1)
1975         {
1976           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
1977           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
1978
1979           if (r_protocol)
1980             {
1981               s = rfc822parse_query_parameter (ctx, "protocol", 0);
1982               if (s)
1983                 *r_protocol = xstrdup (s);
1984             }
1985           if (r_smtype)
1986             {
1987               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
1988               if (s)
1989                 *r_smtype = xstrdup (s);
1990             }
1991         }
1992       rfc822parse_release_field (ctx);
1993     }
1994
1995   rfc822parse_close (msg);
1996   MAPIFreeBuffer (propval);
1997   return retstr;
1998 }
1999
2000
2001 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
2002    This indicates that there sghould be no PR_BODY tag.  */
2003 int
2004 mapi_has_last_decrypted (LPMESSAGE message)
2005 {
2006   HRESULT hr;
2007   LPSPropValue propval = NULL;
2008   ULONG tag;
2009   int yes = 0;
2010   
2011   if (get_gpgollastdecrypted_tag (message, &tag) )
2012     return 0; /* No.  */
2013   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
2014   if (FAILED (hr))
2015     return 0; /* No.  */  
2016   
2017   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
2018     yes = 1;
2019
2020   MAPIFreeBuffer (propval);
2021   return yes;
2022 }
2023
2024
2025 /* Returns True if MESSAGE has a GpgOL Last Decrypted property and
2026    that matches the current session. */
2027 int
2028 mapi_test_last_decrypted (LPMESSAGE message)
2029 {
2030   HRESULT hr;
2031   LPSPropValue propval = NULL;
2032   ULONG tag;
2033   int yes = 0;
2034
2035   if (get_gpgollastdecrypted_tag (message, &tag) )
2036     goto leave; /* No.  */
2037   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
2038   if (FAILED (hr))
2039     goto leave; /* No.  */  
2040
2041   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
2042       && propval->Value.bin.cb == 8
2043       && !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
2044     yes = 1;
2045
2046   MAPIFreeBuffer (propval);
2047  leave:
2048   log_debug ("%s:%s: message decrypted during this session: %s\n",
2049              SRCNAME, __func__, yes?"yes":"no");
2050   return yes;
2051 }
2052
2053
2054
2055 /* Helper for mapi_get_gpgol_body_attachment.  */
2056 static int
2057 has_gpgol_body_name (LPATTACH obj)
2058 {
2059   HRESULT hr;
2060   LPSPropValue propval;
2061   int yes = 0;
2062
2063   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
2064   if (FAILED(hr))
2065     return 0;
2066
2067   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
2068     {
2069       if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt"))
2070         yes = 1;
2071       else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm"))
2072         yes = 2;
2073     }
2074   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2075     {
2076       if (!strcmp (propval->Value.lpszA, "gpgol000.txt"))
2077         yes = 1;
2078       else if (!strcmp (propval->Value.lpszA, "gpgol000.htm"))
2079         yes = 2;
2080     }
2081   MAPIFreeBuffer (propval);
2082   return yes;
2083 }
2084
2085 /* Helper to check whether the file name of OBJ is "smime.p7m".
2086    Returns on true if so.  */
2087 static int
2088 has_smime_filename (LPATTACH obj)
2089 {
2090   HRESULT hr;
2091   LPSPropValue propval;
2092   int yes = 0;
2093
2094   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
2095   if (FAILED(hr))
2096     return 0;
2097
2098   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
2099     {
2100       if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
2101         yes = 1;
2102     }
2103   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2104     {
2105       if (!strcmp (propval->Value.lpszA, "smime.p7m"))
2106         yes = 1;
2107     }
2108   MAPIFreeBuffer (propval);
2109   return yes;
2110 }
2111
2112
2113 /* Return the content of the body attachment of MESSAGE.  The body
2114    attachment is a hidden attachment created by us for later display.
2115    If R_NBYTES is not NULL the number of bytes in the returned buffer
2116    is stored there.  If R_ISHTML is not NULL a flag indicating whether
2117    the HTML is html formatted is stored there.  If R_PROTECTED is not
2118    NULL a flag indicating whether the message was protected is stored
2119    there.  If no body attachment can be found or on any other error an
2120    error codes is returned and NULL is stored at R_BODY.  Caller must
2121    free the returned string.  If NULL is passed for R_BODY, the
2122    function will only test whether a body attachment is available and
2123    return an error code if not.  R_IS_HTML and R_PROTECTED are not
2124    defined in this case.  */
2125 int
2126 mapi_get_gpgol_body_attachment (LPMESSAGE message, 
2127                                 char **r_body, size_t *r_nbytes, 
2128                                 int *r_ishtml, int *r_protected)
2129 {    
2130   HRESULT hr;
2131   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
2132   LPMAPITABLE mapitable;
2133   LPSRowSet   mapirows;
2134   unsigned int pos, n_attach;
2135   ULONG moss_tag;
2136   char *body = NULL;
2137   int bodytype;
2138   int found = 0;
2139
2140   if (r_body)
2141     *r_body = NULL;
2142   if (r_ishtml)
2143     *r_ishtml = 0;
2144   if (r_protected)
2145     *r_protected = 0;
2146
2147   if (get_gpgolattachtype_tag (message, &moss_tag) )
2148     return -1;
2149
2150   hr = message->GetAttachmentTable (0, &mapitable);
2151   if (FAILED (hr))
2152     {
2153       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
2154                  SRCNAME, __func__, hr);
2155       return -1;
2156     }
2157       
2158   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
2159                        NULL, NULL, 0, &mapirows);
2160   if (FAILED (hr))
2161     {
2162       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
2163                  SRCNAME, __func__, hr);
2164       mapitable->Release ();
2165       return -1;
2166     }
2167   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
2168   if (!n_attach)
2169     {
2170       FreeProws (mapirows);
2171       mapitable->Release ();
2172       log_debug ("%s:%s: No attachments at all", SRCNAME, __func__);
2173       return -1;
2174     }
2175   log_debug ("%s:%s: message has %u attachments\n",
2176              SRCNAME, __func__, n_attach);
2177
2178   for (pos=0; pos < n_attach; pos++) 
2179     {
2180       LPATTACH att;
2181
2182       if (mapirows->aRow[pos].cValues < 1)
2183         {
2184           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
2185           continue;
2186         }
2187       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
2188         {
2189           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
2190           continue;
2191         }
2192       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
2193                                 NULL, MAPI_BEST_ACCESS, &att);  
2194       if (FAILED (hr))
2195         {
2196           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
2197                      SRCNAME, __func__, pos, 
2198                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
2199           continue;
2200         }
2201       if ((bodytype=has_gpgol_body_name (att))
2202            && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
2203         {
2204           found = 1;
2205           if (r_body)
2206             {
2207               char *charset;
2208               
2209               if (get_attach_method (att) == ATTACH_BY_VALUE)
2210                 body = attach_to_buffer (att, r_nbytes, 1, r_protected);
2211               if (body && (charset = mapi_get_gpgol_charset ((LPMESSAGE)att)))
2212                 {
2213                   /* We only support transcoding from Latin-1 for now.  */
2214                   if (strcmp (charset, "iso-8859-1") 
2215                       && !strcmp (charset, "latin-1"))
2216                     log_debug ("%s:%s: Using Latin-1 instead of %s",
2217                                SRCNAME, __func__, charset);
2218                   xfree (charset);
2219                   charset = latin1_to_utf8 (body);
2220                   xfree (body);
2221                   body = charset;
2222                 }
2223             }
2224           att->Release ();
2225           if (r_ishtml)
2226             *r_ishtml = (bodytype == 2);
2227           break;
2228         }
2229       att->Release ();
2230     }
2231   FreeProws (mapirows);
2232   mapitable->Release ();
2233   if (!found)
2234     {
2235       log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__);
2236       if (r_body)
2237         *r_body = native_to_utf8 
2238           (_("[The content of this message is not visible"
2239              " due to an processing error in GpgOL.]"));
2240       return -1;
2241     }
2242
2243   if (r_body)
2244     *r_body = body;
2245   else
2246     xfree (body);  /* (Should not happen.)  */
2247   return 0;
2248 }
2249