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