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