c7714a7d5d7114383a606eb7aa1914c44a69791a
[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 teh sender of the message.  According to the specs this is
952    an UTF-8 string; we rely on that the UI server handles
953    internationalized domain names.  */ 
954 char *
955 mapi_get_sender (LPMESSAGE message)
956 {
957   HRESULT hr;
958   LPSPropValue propval = NULL;
959   char *buf;
960   char *p0, *p;
961   
962   if (!message)
963     return NULL; /* No message: Nop. */
964
965   hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval);
966   if (FAILED (hr))
967     {
968       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
969                  SRCNAME, __func__, hr);
970       return NULL;
971     }
972     
973   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
974     {
975       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
976                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
977       MAPIFreeBuffer (propval);
978       return NULL;
979     }
980   
981   buf = wchar_to_utf8 (propval->Value.lpszW);
982   MAPIFreeBuffer (propval);
983   if (!buf)
984     {
985       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
986       return NULL;
987     }
988   /* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields
989      using Ctrl-A as delimiter.  The first field looks like the ascii
990      formatted number of fields to follow, the second field like the
991      email account and the third seems to be a textual description of
992      that account.  We return the second field. */
993   p = strchr (buf, '\x01');
994   if (!p)
995     {
996       log_error ("%s:%s: unknown format of the value `%s'\n",
997                  SRCNAME, __func__, buf);
998       xfree (buf);
999       return NULL;
1000     }
1001   for (p0=buf, p++; *p && *p != '\x01';)
1002     *p0++ = *p++;
1003   *p0 = 0;
1004   log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf);
1005   return buf;
1006 }
1007
1008
1009
1010
1011 /* Return the message type.  This function knows only about our own
1012    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
1013    no special support for.  */
1014 msgtype_t
1015 mapi_get_message_type (LPMESSAGE message)
1016 {
1017   HRESULT hr;
1018   ULONG tag;
1019   LPSPropValue propval = NULL;
1020   msgtype_t msgtype = MSGTYPE_UNKNOWN;
1021
1022   if (!message)
1023     return msgtype; 
1024
1025   if (get_gpgolmsgclass_tag (message, &tag) )
1026     return msgtype; /* Ooops */
1027
1028   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1029   if (FAILED (hr))
1030     {
1031       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1032       if (FAILED (hr))
1033         {
1034           log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
1035                      SRCNAME, __func__, hr);
1036           return msgtype;
1037         }
1038     }
1039   else
1040     log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1041     
1042   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1043     {
1044       const char *s = propval->Value.lpszA;
1045       if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
1046         {
1047           s += 14;
1048           if (!*s)
1049             msgtype = MSGTYPE_GPGOL;
1050           else if (!strcmp (s, ".MultipartSigned"))
1051             msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
1052           else if (!strcmp (s, ".MultipartEncrypted"))
1053             msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
1054           else if (!strcmp (s, ".OpaqueSigned"))
1055             msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
1056           else if (!strcmp (s, ".OpaqueEncrypted"))
1057             msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
1058           else if (!strcmp (s, ".ClearSigned"))
1059             msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
1060           else if (!strcmp (s, ".PGPMessage"))
1061             msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
1062           else
1063             log_debug ("%s:%s: message class `%s' not supported",
1064                        SRCNAME, __func__, s-14);
1065         }
1066       else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
1067         msgtype = MSGTYPE_SMIME;
1068     }
1069   MAPIFreeBuffer (propval);
1070   return msgtype;
1071 }
1072
1073
1074 /* This function is pretty useless because IConverterSession won't
1075    take attachments into account.  Need to write our own version.  */
1076 int
1077 mapi_to_mime (LPMESSAGE message, const char *filename)
1078 {
1079   HRESULT hr;
1080   LPCONVERTERSESSION session;
1081   LPSTREAM stream;
1082
1083   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
1084                          IID_IConverterSession, (void **) &session);
1085   if (FAILED (hr))
1086     {
1087       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
1088                  SRCNAME, __func__, hr);
1089       return -1;
1090     }
1091
1092
1093   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
1094                          (STGM_CREATE | STGM_READWRITE),
1095                          (char*)filename, NULL, &stream); 
1096   if (FAILED (hr)) 
1097     {
1098       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
1099                  SRCNAME, __func__, filename, hr); 
1100       hr = -1;
1101     }
1102   else
1103     {
1104       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
1105       if (FAILED (hr))
1106         {
1107           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
1108                      SRCNAME, __func__, hr);
1109           stream->Revert ();
1110           hr = -1;
1111         }
1112       else
1113         {
1114           stream->Commit (0);
1115           hr = 0;
1116         }
1117
1118       stream->Release ();
1119     }
1120
1121   session->Release ();
1122   return hr;
1123 }
1124
1125
1126 /* Return a binary property in a malloced buffer with its length stored
1127    at R_NBYTES.  Returns NULL on error.  */
1128 char *
1129 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
1130 {
1131   HRESULT hr;
1132   LPSPropValue propval = NULL;
1133   char *data;
1134
1135   *r_nbytes = 0;
1136   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
1137   if (FAILED (hr))
1138     {
1139       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
1140                  SRCNAME, __func__, proptype, hr);
1141       return NULL; 
1142     }
1143   switch ( PROP_TYPE (propval->ulPropTag) )
1144     {
1145     case PT_BINARY:
1146       /* This is a binary object but we know that it must be plain
1147          ASCII due to the armored format.  */
1148       data = (char*)xmalloc (propval->Value.bin.cb + 1);
1149       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
1150       data[propval->Value.bin.cb] = 0;
1151       *r_nbytes = propval->Value.bin.cb;
1152       break;
1153       
1154     default:
1155       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
1156                  SRCNAME, __func__, proptype, propval->ulPropTag);
1157       data = NULL;
1158       break;
1159     }
1160   MAPIFreeBuffer (propval);
1161   return data;
1162 }
1163
1164
1165 /* Return the attachment method for attachment OBJ.  In case of error
1166    we return 0 which happens not to be defined.  */
1167 static int
1168 get_attach_method (LPATTACH obj)
1169 {
1170   HRESULT hr;
1171   LPSPropValue propval = NULL;
1172   int method ;
1173
1174   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
1175   if (FAILED (hr))
1176     {
1177       log_error ("%s:%s: error getting attachment method: hr=%#lx",
1178                  SRCNAME, __func__, hr);
1179       return 0; 
1180     }
1181   /* We don't bother checking whether we really get a PT_LONG ulong
1182      back; if not the system is seriously damaged and we can't do
1183      further harm by returning a possible random value.  */
1184   method = propval->Value.l;
1185   MAPIFreeBuffer (propval);
1186   return method;
1187 }
1188
1189
1190
1191 /* Return the filename from the attachment as a malloced string.  The
1192    encoding we return will be UTF-8, however the MAPI docs declare
1193    that MAPI does only handle plain ANSI and thus we don't really care
1194    later on.  In fact we would need to convert the filename back to
1195    wchar and use the Unicode versions of the file API.  Returns NULL
1196    on error or if no filename is available. */
1197 static char *
1198 get_attach_filename (LPATTACH obj)
1199 {
1200   HRESULT hr;
1201   LPSPropValue propval;
1202   char *name = NULL;
1203
1204   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
1205   if (FAILED(hr)) 
1206     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1207   if (FAILED(hr))
1208     {
1209       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
1210       return NULL;
1211     }
1212
1213   switch ( PROP_TYPE (propval->ulPropTag) )
1214     {
1215     case PT_UNICODE:
1216       name = wchar_to_utf8 (propval->Value.lpszW);
1217       if (!name)
1218         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1219       break;
1220       
1221     case PT_STRING8:
1222       name = xstrdup (propval->Value.lpszA);
1223       break;
1224       
1225     default:
1226       log_debug ("%s:%s: proptag=%#lx not supported\n",
1227                  SRCNAME, __func__, propval->ulPropTag);
1228       name = NULL;
1229       break;
1230     }
1231   MAPIFreeBuffer (propval);
1232   return name;
1233 }
1234
1235
1236 /* Return the content-type of the attachment OBJ or NULL if it does
1237    not exists.  Caller must free. */
1238 static char *
1239 get_attach_mime_tag (LPATTACH obj)
1240 {
1241   HRESULT hr;
1242   LPSPropValue propval = NULL;
1243   char *name;
1244
1245   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
1246   if (FAILED (hr))
1247     {
1248       if (hr != MAPI_E_NOT_FOUND)
1249         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
1250                    SRCNAME, __func__, hr);
1251       return NULL; 
1252     }
1253   switch ( PROP_TYPE (propval->ulPropTag) )
1254     {
1255     case PT_UNICODE:
1256       name = wchar_to_utf8 (propval->Value.lpszW);
1257       if (!name)
1258         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1259       break;
1260       
1261     case PT_STRING8:
1262       name = xstrdup (propval->Value.lpszA);
1263       break;
1264       
1265     default:
1266       log_debug ("%s:%s: proptag=%#lx not supported\n",
1267                  SRCNAME, __func__, propval->ulPropTag);
1268       name = NULL;
1269       break;
1270     }
1271   MAPIFreeBuffer (propval);
1272   return name;
1273 }
1274
1275
1276 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
1277    the tag of that property. */
1278 static attachtype_t
1279 get_gpgolattachtype (LPATTACH obj, ULONG tag)
1280 {
1281   HRESULT hr;
1282   LPSPropValue propval = NULL;
1283   attachtype_t retval;
1284
1285   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
1286   if (FAILED (hr))
1287     {
1288       if (hr != MAPI_E_NOT_FOUND)
1289         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
1290                    SRCNAME, __func__, hr);
1291       return ATTACHTYPE_UNKNOWN; 
1292     }
1293   retval = (attachtype_t)propval->Value.l;
1294   MAPIFreeBuffer (propval);
1295   return retval;
1296 }
1297
1298
1299 /* Gather information about attachments and return a new table of
1300    attachments.  Caller must release the returned table.s The routine
1301    will return NULL in case of an error or if no attachments are
1302    available.  With FAST set only some information gets collected. */
1303 mapi_attach_item_t *
1304 mapi_create_attach_table (LPMESSAGE message, int fast)
1305 {    
1306   HRESULT hr;
1307   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
1308   LPMAPITABLE mapitable;
1309   LPSRowSet   mapirows;
1310   mapi_attach_item_t *table; 
1311   unsigned int pos, n_attach;
1312   ULONG moss_tag;
1313
1314   if (get_gpgolattachtype_tag (message, &moss_tag) )
1315     return NULL;
1316
1317   /* Open the attachment table.  */
1318   hr = message->GetAttachmentTable (0, &mapitable);
1319   if (FAILED (hr))
1320     {
1321       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
1322                  SRCNAME, __func__, hr);
1323       return NULL;
1324     }
1325       
1326   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
1327                        NULL, NULL, 0, &mapirows);
1328   if (FAILED (hr))
1329     {
1330       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
1331                  SRCNAME, __func__, hr);
1332       mapitable->Release ();
1333       return NULL;
1334     }
1335   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
1336
1337   log_debug ("%s:%s: message has %u attachments\n",
1338              SRCNAME, __func__, n_attach);
1339   if (!n_attach)
1340     {
1341       FreeProws (mapirows);
1342       mapitable->Release ();
1343       return NULL;
1344     }
1345
1346   /* Allocate our own table.  */
1347   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
1348   for (pos=0; pos < n_attach; pos++) 
1349     {
1350       LPATTACH att;
1351
1352       if (mapirows->aRow[pos].cValues < 1)
1353         {
1354           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
1355           table[pos].mapipos = -1;
1356           continue;
1357         }
1358       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
1359         {
1360           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
1361           table[pos].mapipos = -1;
1362           continue;
1363         }
1364       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
1365
1366       hr = message->OpenAttach (table[pos].mapipos, NULL,
1367                                 MAPI_BEST_ACCESS, &att);        
1368       if (FAILED (hr))
1369         {
1370           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
1371                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
1372           table[pos].mapipos = -1;
1373           continue;
1374         }
1375
1376       table[pos].method = get_attach_method (att);
1377       table[pos].filename = fast? NULL : get_attach_filename (att);
1378       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
1379       if (table[pos].content_type)
1380         {
1381           char *p = strchr (table[pos].content_type, ';');
1382           if (p)
1383             {
1384               *p++ = 0;
1385               trim_trailing_spaces (table[pos].content_type);
1386               while (strchr (" \t\r\n", *p))
1387                 p++;
1388               trim_trailing_spaces (p);
1389               table[pos].content_type_parms = p;
1390             }
1391         }
1392       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
1393       att->Release ();
1394     }
1395   table[0].private_mapitable = mapitable;
1396   FreeProws (mapirows);
1397   table[pos].end_of_table = 1;
1398   mapitable = NULL;
1399
1400   if (fast)
1401     {
1402       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
1403                  SRCNAME, __func__);
1404     }
1405   else
1406     {
1407       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
1408       for (pos=0; !table[pos].end_of_table; pos++)
1409         {
1410           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
1411                      table[pos].mapipos,
1412                      table[pos].attach_type,
1413                      table[pos].filename, table[pos].content_type,
1414                      table[pos].content_type_parms);
1415         }
1416     }
1417
1418   return table;
1419 }
1420
1421
1422 /* Release a table as created by mapi_create_attach_table. */
1423 void
1424 mapi_release_attach_table (mapi_attach_item_t *table)
1425 {
1426   unsigned int pos;
1427   LPMAPITABLE mapitable;
1428
1429   if (!table)
1430     return;
1431
1432   mapitable = (LPMAPITABLE)table[0].private_mapitable;
1433   if (mapitable)
1434     mapitable->Release ();
1435   for (pos=0; !table[pos].end_of_table; pos++)
1436     {
1437       xfree (table[pos].filename);
1438       xfree (table[pos].content_type);
1439     }
1440   xfree (table);
1441 }
1442
1443
1444 /* Return an attachment as a new IStream object.  Returns NULL on
1445    failure.  If R_ATATCH is not NULL the actual attachment will not be
1446    released by stored at that address; the caller needs to release it
1447    in this case.  */
1448 LPSTREAM
1449 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
1450                            LPATTACH *r_attach)
1451 {
1452   HRESULT hr;
1453   LPATTACH att;
1454   LPSTREAM stream;
1455
1456   if (r_attach)
1457     *r_attach = NULL;
1458
1459   if (!item || item->end_of_table || item->mapipos == -1)
1460     return NULL;
1461
1462   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1463   if (FAILED (hr))
1464     {
1465       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1466                  SRCNAME, __func__, item->mapipos, hr);
1467       return NULL;
1468     }
1469   if (item->method != ATTACH_BY_VALUE)
1470     {
1471       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1472       att->Release ();
1473       return NULL;
1474     }
1475
1476   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1477                           0, 0, (LPUNKNOWN*) &stream);
1478   if (FAILED (hr))
1479     {
1480       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1481                  SRCNAME, __func__, hr);
1482       att->Release ();
1483       return NULL;
1484     }
1485
1486   if (r_attach)
1487     *r_attach = att;
1488   else
1489     att->Release ();
1490
1491   return stream;
1492 }
1493
1494
1495 /* Return a malloced buffer with the content of the attachment. If
1496    R_NBYTES is not NULL the number of bytes will get stored there.
1497    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
1498    NULL on error.  If UNPROTECT is set and the appropriate crypto
1499    attribute is available, the function returns the unprotected
1500    version of the atatchment. */
1501 static char *
1502 attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect, 
1503                   int *r_was_protected)
1504 {
1505   HRESULT hr;
1506   LPSTREAM stream;
1507   STATSTG statInfo;
1508   ULONG nread;
1509   char *buffer;
1510   symenc_t symenc = NULL;
1511
1512   if (r_was_protected)
1513     *r_was_protected = 0;
1514
1515   if (unprotect)
1516     {
1517       ULONG tag;
1518       char *iv;
1519       size_t ivlen;
1520
1521       if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag) 
1522           && (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
1523         {
1524           symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
1525           xfree (iv);
1526           if (!symenc)
1527             log_error ("%s:%s: can't open encryption context", 
1528                        SRCNAME, __func__);
1529         }
1530     }
1531   
1532
1533   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1534                           0, 0, (LPUNKNOWN*) &stream);
1535   if (FAILED (hr))
1536     {
1537       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1538                  SRCNAME, __func__, hr);
1539       return NULL;
1540     }
1541
1542   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
1543   if ( hr != S_OK )
1544     {
1545       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
1546       stream->Release ();
1547       return NULL;
1548     }
1549       
1550   /* Allocate one byte more so that we can terminate the string.  */
1551   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
1552
1553   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
1554   if ( hr != S_OK )
1555     {
1556       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
1557       xfree (buffer);
1558       stream->Release ();
1559       return NULL;
1560     }
1561   if (nread != statInfo.cbSize.QuadPart)
1562     {
1563       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
1564       xfree (buffer);
1565       buffer = NULL;
1566     }
1567   stream->Release ();
1568
1569   if (buffer && symenc)
1570     {
1571       symenc_cfb_decrypt (symenc, buffer, buffer, nread);
1572       if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
1573         {
1574           xfree (buffer);
1575           buffer = native_to_utf8 
1576             (_("[The content of this message is not visible because it has "
1577                "been decrypted by another Outlook session.  Use the "
1578                "\"decrypt/verify\" command to make it visible]"));
1579           nread = strlen (buffer);
1580         }
1581       else
1582         {
1583           memmove (buffer, buffer+16, nread-16);
1584           nread -= 16;
1585           if (r_was_protected)
1586             *r_was_protected = 1;
1587         }
1588     }
1589
1590   /* Make sure that the buffer is a C string.  */
1591   if (buffer)
1592     buffer[nread] = 0;
1593
1594   symenc_close (symenc);
1595   if (r_nbytes)
1596     *r_nbytes = nread;
1597   return buffer;
1598 }
1599
1600
1601
1602 /* Return an attachment as a malloced buffer.  The size of the buffer
1603    will be stored at R_NBYTES.  Returns NULL on failure. */
1604 char *
1605 mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes)
1606 {
1607   HRESULT hr;
1608   LPATTACH att;
1609   char *buffer;
1610
1611   if (!item || item->end_of_table || item->mapipos == -1)
1612     return NULL;
1613
1614   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1615   if (FAILED (hr))
1616     {
1617       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1618                  SRCNAME, __func__, item->mapipos, hr);
1619       return NULL;
1620     }
1621   if (item->method != ATTACH_BY_VALUE)
1622     {
1623       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1624       att->Release ();
1625       return NULL;
1626     }
1627
1628   buffer = attach_to_buffer (att, r_nbytes, 0, NULL);
1629   att->Release ();
1630
1631   return buffer;
1632 }
1633
1634
1635 /* Mark this attachment as the orginal MOSS message.  We set a custom
1636    property as well as the hidden hidden flag.  */
1637 int 
1638 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
1639 {
1640   int retval = -1;
1641   HRESULT hr;
1642   LPATTACH att;
1643   SPropValue prop;
1644
1645   if (!item || item->end_of_table || item->mapipos == -1)
1646     return -1;
1647
1648   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1649   if (FAILED (hr))
1650     {
1651       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1652                  SRCNAME, __func__, item->mapipos, hr);
1653       return -1;
1654     }
1655
1656   if (FAILED (hr)) 
1657     {
1658       log_error ("%s:%s: can't map %s property: hr=%#lx\n",
1659                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1660       goto leave;
1661     }
1662     
1663   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
1664     goto leave;
1665   prop.Value.l = ATTACHTYPE_MOSS;
1666   hr = HrSetOneProp (att, &prop);       
1667   if (hr)
1668     {
1669       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1670                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1671       return false;
1672     }
1673
1674   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
1675   prop.Value.b = TRUE;
1676   hr = HrSetOneProp (att, &prop);
1677   if (hr)
1678     {
1679       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
1680                  SRCNAME, __func__, hr); 
1681       goto leave;
1682     }
1683   
1684
1685   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
1686   if (hr)
1687     {
1688       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
1689                  SRCNAME, __func__, hr); 
1690       goto leave;
1691     }
1692   
1693   retval = 0;
1694     
1695  leave:
1696   att->Release ();
1697   return retval;
1698 }
1699
1700
1701 /* If the hidden property has not been set on ATTACH, set it and save
1702    the changes. */
1703 int 
1704 mapi_set_attach_hidden (LPATTACH attach)
1705 {
1706   int retval = -1;
1707   HRESULT hr;
1708   LPSPropValue propval;
1709   SPropValue prop;
1710
1711   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
1712   if (SUCCEEDED (hr) 
1713       && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
1714       && propval->Value.b)
1715     return 0;/* Already set to hidden. */
1716
1717   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
1718   prop.Value.b = TRUE;
1719   hr = HrSetOneProp (attach, &prop);
1720   if (hr)
1721     {
1722       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
1723                  SRCNAME, __func__, hr); 
1724       goto leave;
1725     }
1726   
1727   hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
1728   if (hr)
1729     {
1730       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
1731                  SRCNAME, __func__, hr); 
1732       goto leave;
1733     }
1734   
1735   retval = 0;
1736     
1737  leave:
1738   return retval;
1739 }
1740
1741
1742
1743 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
1744 int
1745 mapi_has_sig_status (LPMESSAGE msg)
1746 {
1747   HRESULT hr;
1748   LPSPropValue propval = NULL;
1749   ULONG tag;
1750   int yes;
1751
1752   if (get_gpgolsigstatus_tag (msg, &tag) )
1753     return 0; /* Error:  Assume No.  */
1754   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1755   if (FAILED (hr))
1756     return 0; /* No.  */  
1757   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1758     yes = 1;
1759   else
1760     yes = 0;
1761
1762   MAPIFreeBuffer (propval);
1763   return yes;
1764 }
1765
1766
1767 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
1768    is not set to unchecked.  */
1769 int
1770 mapi_test_sig_status (LPMESSAGE msg)
1771 {
1772   HRESULT hr;
1773   LPSPropValue propval = NULL;
1774   ULONG tag;
1775   int yes;
1776
1777   if (get_gpgolsigstatus_tag (msg, &tag) )
1778     return 0; /* Error:  Assume No.  */
1779   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1780   if (FAILED (hr))
1781     return 0; /* No.  */  
1782
1783   /* We return False if we have an unknown signature status (?) or the
1784      message has been setn by us and not yet checked (@).  */
1785   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1786     yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
1787                                      || !strcmp (propval->Value.lpszA, "@")));
1788   else
1789     yes = 0;
1790
1791   MAPIFreeBuffer (propval);
1792   return yes;
1793 }
1794
1795
1796 /* Return the signature status as an allocated string.  Will never
1797    return NULL.  */
1798 char *
1799 mapi_get_sig_status (LPMESSAGE msg)
1800 {
1801   HRESULT hr;
1802   LPSPropValue propval = NULL;
1803   ULONG tag;
1804   char *retstr;
1805
1806   if (get_gpgolsigstatus_tag (msg, &tag) )
1807     return xstrdup ("[Error getting tag for sig status]");
1808   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1809   if (FAILED (hr))
1810     return xstrdup ("");
1811   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1812     retstr = xstrdup (propval->Value.lpszA);
1813   else
1814     retstr = xstrdup ("[Sig status has an invalid type]");
1815
1816   MAPIFreeBuffer (propval);
1817   return retstr;
1818 }
1819
1820
1821
1822
1823 /* Set the signature status property to STATUS_STRING.  There are a
1824    few special values:
1825
1826      "#" The message is not of interest to us.
1827      "@" The message has been created and signed or encrypted by us.
1828      "?" The signature status has not been checked.
1829      "!" The signature verified okay 
1830      "~" The signature was not fully verified.
1831      "-" The signature is bad
1832
1833    Note that this function does not call SaveChanges.  */
1834 int 
1835 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
1836 {
1837   HRESULT hr;
1838   SPropValue prop;
1839
1840   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
1841     return -1;
1842   prop.Value.lpszA = xstrdup (status_string);
1843   hr = HrSetOneProp (message, &prop);   
1844   xfree (prop.Value.lpszA);
1845   if (hr)
1846     {
1847       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1848                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
1849       return -1;
1850     }
1851
1852   return 0;
1853 }
1854
1855
1856 /* When sending a message we need to fake the message class so that OL
1857    processes it according to our needs.  However, if we later try to
1858    get the message class from the sent message, OL still has the SMIME
1859    message class and tries to hide this by trying to decrypt the
1860    message and return the message class from the plaintext.  To
1861    mitigate the problem we define our own msg class override
1862    property.  */
1863 int 
1864 mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
1865 {
1866   HRESULT hr;
1867   SPropValue prop;
1868
1869   if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
1870     return -1;
1871   prop.Value.lpszA = xstrdup (name);
1872   hr = HrSetOneProp (message, &prop);   
1873   xfree (prop.Value.lpszA);
1874   if (hr)
1875     {
1876       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1877                  SRCNAME, __func__, "GpgOL Msg Class", hr); 
1878       return -1;
1879     }
1880
1881   return 0;
1882 }
1883
1884
1885 /* Return the charset as assigned by GpgOL to an attachment.  This may
1886    return NULL it is has not been assigned or is the standard
1887    (UTF-8). */
1888 char *
1889 mapi_get_gpgol_charset (LPMESSAGE obj)
1890 {
1891   HRESULT hr;
1892   LPSPropValue propval = NULL;
1893   ULONG tag;
1894   char *retstr;
1895
1896   if (get_gpgolcharset_tag (obj, &tag) )
1897     return NULL; /* Error.  */
1898   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
1899   if (FAILED (hr))
1900     return NULL;
1901   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1902     {
1903       if (!strcmp (propval->Value.lpszA, "utf-8"))
1904         retstr = NULL;
1905       else
1906         retstr = xstrdup (propval->Value.lpszA);
1907     }
1908   else
1909     retstr = NULL;
1910
1911   MAPIFreeBuffer (propval);
1912   return retstr;
1913 }
1914
1915
1916 /* Set the GpgOl charset t an asstachment. 
1917    Note that this function does not call SaveChanges.  */
1918 int 
1919 mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
1920 {
1921   HRESULT hr;
1922   SPropValue prop;
1923   char *p;
1924
1925   /* Note that we lowercase the value and cut it to a max of 32
1926      characters.  The latter is required to make sure that
1927      HrSetOneProp will always work.  */
1928   if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
1929     return -1;
1930   prop.Value.lpszA = xstrdup (charset);
1931   for (p=prop.Value.lpszA; *p; p++)
1932     *p = tolower (*(unsigned char*)p);
1933   if (strlen (prop.Value.lpszA) > 32)
1934     prop.Value.lpszA[32] = 0;
1935   hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);   
1936   xfree (prop.Value.lpszA);
1937   if (hr)
1938     {
1939       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1940                  SRCNAME, __func__, "GpgOL Charset", hr); 
1941       return -1;
1942     }
1943
1944   return 0;
1945 }
1946
1947
1948 /* Return the MIME info as an allocated string.  Will never return
1949    NULL.  */
1950 char *
1951 mapi_get_mime_info (LPMESSAGE msg)
1952 {
1953   HRESULT hr;
1954   LPSPropValue propval = NULL;
1955   ULONG tag;
1956   char *retstr;
1957
1958   if (get_gpgolmimeinfo_tag (msg, &tag) )
1959     return xstrdup ("[Error getting tag for MIME info]");
1960   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1961   if (FAILED (hr))
1962     return xstrdup ("");
1963   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1964     retstr = xstrdup (propval->Value.lpszA);
1965   else
1966     retstr = xstrdup ("[MIME info has an invalid type]");
1967
1968   MAPIFreeBuffer (propval);
1969   return retstr;
1970 }
1971
1972
1973
1974
1975 /* Helper for mapi_get_msg_content_type() */
1976 static int
1977 get_message_content_type_cb (void *dummy_arg,
1978                              rfc822parse_event_t event, rfc822parse_t msg)
1979 {
1980   if (event == RFC822PARSE_T2BODY)
1981     return 42; /* Hack to stop the parsing after having read the
1982                   outer headers. */
1983   return 0;
1984 }
1985
1986
1987 /* Return Content-Type of the current message.  This one is taken
1988    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
1989    string with the protocol parameter will be stored at this address,
1990    if no protocol is given NULL will be stored.  If R_SMTYPE is not
1991    NULL a string with the smime-type parameter will be stored there.
1992    Caller must release all returned strings.  */
1993 char *
1994 mapi_get_message_content_type (LPMESSAGE message,
1995                                char **r_protocol, char **r_smtype)
1996 {
1997   HRESULT hr;
1998   LPSPropValue propval = NULL;
1999   rfc822parse_t msg;
2000   const char *header_lines, *s;
2001   rfc822parse_field_t ctx;
2002   size_t length;
2003   char *retstr = NULL;
2004   
2005   if (r_protocol)
2006     *r_protocol = NULL;
2007   if (r_smtype)
2008     *r_smtype = NULL;
2009
2010   hr = HrGetOneProp ((LPMAPIPROP)message,
2011                      PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
2012   if (FAILED (hr))
2013     {
2014       log_error ("%s:%s: error getting the headers lines: hr=%#lx",
2015                  SRCNAME, __func__, hr);
2016       return NULL; 
2017     }
2018   if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
2019     {
2020       /* As per rfc822, header lines must be plain ascii, so no need
2021          to cope withy unicode etc. */
2022       log_error ("%s:%s: proptag=%#lx not supported\n",
2023                  SRCNAME, __func__, propval->ulPropTag);
2024       MAPIFreeBuffer (propval);
2025       return NULL;
2026     }
2027   header_lines = propval->Value.lpszA;
2028
2029   /* Read the headers into an rfc822 object. */
2030   msg = rfc822parse_open (get_message_content_type_cb, NULL);
2031   if (!msg)
2032     {
2033       log_error ("%s:%s: rfc822parse_open failed\n", SRCNAME, __func__);
2034       MAPIFreeBuffer (propval);
2035       return NULL;
2036     }
2037   
2038   while ((s = strchr (header_lines, '\n')))
2039     {
2040       length = (s - header_lines);
2041       if (length && s[-1] == '\r')
2042         length--;
2043       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
2044       header_lines = s+1;
2045     }
2046   
2047   /* Parse the content-type field. */
2048   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
2049   if (ctx)
2050     {
2051       const char *s1, *s2;
2052       s1 = rfc822parse_query_media_type (ctx, &s2);
2053       if (s1)
2054         {
2055           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
2056           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
2057
2058           if (r_protocol)
2059             {
2060               s = rfc822parse_query_parameter (ctx, "protocol", 0);
2061               if (s)
2062                 *r_protocol = xstrdup (s);
2063             }
2064           if (r_smtype)
2065             {
2066               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
2067               if (s)
2068                 *r_smtype = xstrdup (s);
2069             }
2070         }
2071       rfc822parse_release_field (ctx);
2072     }
2073
2074   rfc822parse_close (msg);
2075   MAPIFreeBuffer (propval);
2076   return retstr;
2077 }
2078
2079
2080 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
2081    This indicates that there sghould be no PR_BODY tag.  */
2082 int
2083 mapi_has_last_decrypted (LPMESSAGE message)
2084 {
2085   HRESULT hr;
2086   LPSPropValue propval = NULL;
2087   ULONG tag;
2088   int yes = 0;
2089   
2090   if (get_gpgollastdecrypted_tag (message, &tag) )
2091     return 0; /* No.  */
2092   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
2093   if (FAILED (hr))
2094     return 0; /* No.  */  
2095   
2096   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
2097     yes = 1;
2098
2099   MAPIFreeBuffer (propval);
2100   return yes;
2101 }
2102
2103
2104 /* Returns True if MESSAGE has a GpgOL Last Decrypted property and
2105    that matches the current session. */
2106 int
2107 mapi_test_last_decrypted (LPMESSAGE message)
2108 {
2109   HRESULT hr;
2110   LPSPropValue propval = NULL;
2111   ULONG tag;
2112   int yes = 0;
2113
2114   if (get_gpgollastdecrypted_tag (message, &tag) )
2115     goto leave; /* No.  */
2116   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
2117   if (FAILED (hr))
2118     goto leave; /* No.  */  
2119
2120   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
2121       && propval->Value.bin.cb == 8
2122       && !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
2123     yes = 1;
2124
2125   MAPIFreeBuffer (propval);
2126  leave:
2127   log_debug ("%s:%s: message decrypted during this session: %s\n",
2128              SRCNAME, __func__, yes?"yes":"no");
2129   return yes;
2130 }
2131
2132
2133
2134 /* Helper for mapi_get_gpgol_body_attachment.  */
2135 static int
2136 has_gpgol_body_name (LPATTACH obj)
2137 {
2138   HRESULT hr;
2139   LPSPropValue propval;
2140   int yes = 0;
2141
2142   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
2143   if (FAILED(hr))
2144     return 0;
2145
2146   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
2147     {
2148       if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt"))
2149         yes = 1;
2150       else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm"))
2151         yes = 2;
2152     }
2153   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2154     {
2155       if (!strcmp (propval->Value.lpszA, "gpgol000.txt"))
2156         yes = 1;
2157       else if (!strcmp (propval->Value.lpszA, "gpgol000.htm"))
2158         yes = 2;
2159     }
2160   MAPIFreeBuffer (propval);
2161   return yes;
2162 }
2163
2164 /* Helper to check whether the file name of OBJ is "smime.p7m".
2165    Returns on true if so.  */
2166 static int
2167 has_smime_filename (LPATTACH obj)
2168 {
2169   HRESULT hr;
2170   LPSPropValue propval;
2171   int yes = 0;
2172
2173   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
2174   if (FAILED(hr))
2175     return 0;
2176
2177   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
2178     {
2179       if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
2180         yes = 1;
2181     }
2182   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2183     {
2184       if (!strcmp (propval->Value.lpszA, "smime.p7m"))
2185         yes = 1;
2186     }
2187   MAPIFreeBuffer (propval);
2188   return yes;
2189 }
2190
2191
2192 /* Return the content of the body attachment of MESSAGE.  The body
2193    attachment is a hidden attachment created by us for later display.
2194    If R_NBYTES is not NULL the number of bytes in the returned buffer
2195    is stored there.  If R_ISHTML is not NULL a flag indicating whether
2196    the HTML is html formatted is stored there.  If R_PROTECTED is not
2197    NULL a flag indicating whether the message was protected is stored
2198    there.  If no body attachment can be found or on any other error an
2199    error codes is returned and NULL is stored at R_BODY.  Caller must
2200    free the returned string.  If NULL is passed for R_BODY, the
2201    function will only test whether a body attachment is available and
2202    return an error code if not.  R_IS_HTML and R_PROTECTED are not
2203    defined in this case.  */
2204 int
2205 mapi_get_gpgol_body_attachment (LPMESSAGE message, 
2206                                 char **r_body, size_t *r_nbytes, 
2207                                 int *r_ishtml, int *r_protected)
2208 {    
2209   HRESULT hr;
2210   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
2211   LPMAPITABLE mapitable;
2212   LPSRowSet   mapirows;
2213   unsigned int pos, n_attach;
2214   ULONG moss_tag;
2215   char *body = NULL;
2216   int bodytype;
2217   int found = 0;
2218
2219   if (r_body)
2220     *r_body = NULL;
2221   if (r_ishtml)
2222     *r_ishtml = 0;
2223   if (r_protected)
2224     *r_protected = 0;
2225
2226   if (get_gpgolattachtype_tag (message, &moss_tag) )
2227     return -1;
2228
2229   hr = message->GetAttachmentTable (0, &mapitable);
2230   if (FAILED (hr))
2231     {
2232       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
2233                  SRCNAME, __func__, hr);
2234       return -1;
2235     }
2236       
2237   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
2238                        NULL, NULL, 0, &mapirows);
2239   if (FAILED (hr))
2240     {
2241       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
2242                  SRCNAME, __func__, hr);
2243       mapitable->Release ();
2244       return -1;
2245     }
2246   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
2247   if (!n_attach)
2248     {
2249       FreeProws (mapirows);
2250       mapitable->Release ();
2251       log_debug ("%s:%s: No attachments at all", SRCNAME, __func__);
2252       return -1;
2253     }
2254   log_debug ("%s:%s: message has %u attachments\n",
2255              SRCNAME, __func__, n_attach);
2256
2257   for (pos=0; pos < n_attach; pos++) 
2258     {
2259       LPATTACH att;
2260
2261       if (mapirows->aRow[pos].cValues < 1)
2262         {
2263           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
2264           continue;
2265         }
2266       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
2267         {
2268           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
2269           continue;
2270         }
2271       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
2272                                 NULL, MAPI_BEST_ACCESS, &att);  
2273       if (FAILED (hr))
2274         {
2275           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
2276                      SRCNAME, __func__, pos, 
2277                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
2278           continue;
2279         }
2280       if ((bodytype=has_gpgol_body_name (att))
2281            && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
2282         {
2283           found = 1;
2284           if (r_body)
2285             {
2286               char *charset;
2287               
2288               if (get_attach_method (att) == ATTACH_BY_VALUE)
2289                 body = attach_to_buffer (att, r_nbytes, 1, r_protected);
2290               if (body && (charset = mapi_get_gpgol_charset ((LPMESSAGE)att)))
2291                 {
2292                   /* We only support transcoding from Latin-1 for now.  */
2293                   if (strcmp (charset, "iso-8859-1") 
2294                       && !strcmp (charset, "latin-1"))
2295                     log_debug ("%s:%s: Using Latin-1 instead of %s",
2296                                SRCNAME, __func__, charset);
2297                   xfree (charset);
2298                   charset = latin1_to_utf8 (body);
2299                   xfree (body);
2300                   body = charset;
2301                 }
2302             }
2303           att->Release ();
2304           if (r_ishtml)
2305             *r_ishtml = (bodytype == 2);
2306           break;
2307         }
2308       att->Release ();
2309     }
2310   FreeProws (mapirows);
2311   mapitable->Release ();
2312   if (!found)
2313     {
2314       log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__);
2315       if (r_body)
2316         *r_body = native_to_utf8 
2317           (_("[The content of this message is not visible"
2318              " due to an processing error in GpgOL.]"));
2319       return -1;
2320     }
2321
2322   if (r_body)
2323     *r_body = body;
2324   else
2325     xfree (body);  /* (Should not happen.)  */
2326   return 0;
2327 }
2328