strdup -> xstrdup
[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 "mapihelp.h"
32 #include "parsetlv.h"
33 #include "oomhelp.h"
34
35 #include <string>
36
37 #ifndef CRYPT_E_STREAM_INSUFFICIENT_DATA
38 #define CRYPT_E_STREAM_INSUFFICIENT_DATA 0x80091011
39 #endif
40 #ifndef CRYPT_E_ASN1_BADTAG
41 #define CRYPT_E_ASN1_BADTAG 0x8009310B
42 #endif
43
44
45 static int get_attach_method (LPATTACH obj);
46 static int has_smime_filename (LPATTACH obj);
47 static char *get_attach_mime_tag (LPATTACH obj);
48
49
50 \f
51
52 /* Print a MAPI property to the log stream. */
53 void
54 log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
55 {
56   HRESULT hr;
57   LPSPropValue propval = NULL;
58   size_t keylen;
59   void *key;
60   char *buf;
61
62   if (!message)
63     return; /* No message: Nop. */
64
65   hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval);
66   if (FAILED (hr))
67     {
68       log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n",
69                  SRCNAME, __func__, propname, hr);
70       return;
71     }
72     
73   switch ( PROP_TYPE (propval->ulPropTag) )
74     {
75     case PT_BINARY:
76       keylen = propval->Value.bin.cb;
77       key = propval->Value.bin.lpb;
78       log_hexdump (key, keylen, "%s: %20s=", __func__, propname);
79       break;
80
81     case PT_UNICODE:
82       buf = wchar_to_utf8 (propval->Value.lpszW);
83       if (!buf)
84         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
85       else
86         log_debug ("%s: %20s=`%s'", __func__, propname, buf);
87       xfree (buf);
88       break;
89       
90     case PT_STRING8:
91       log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA);
92       break;
93
94     case PT_LONG:
95       log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l);
96       break;
97
98     default:
99       log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n",
100                  SRCNAME, __func__, propname,
101                  PROP_TYPE (propval->ulPropTag) );
102       return;
103     }
104   MAPIFreeBuffer (propval);
105 }
106
107
108 /* Helper to create a named property. */
109 static ULONG 
110 create_gpgol_tag (LPMESSAGE message, const wchar_t *name, const char *func)
111 {
112   HRESULT hr;
113   LPSPropTagArray proparr = NULL;
114   MAPINAMEID mnid, *pmnid;
115   wchar_t *propname = wcsdup (name);
116   /* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties.  */
117   GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06,
118                                             0x1b, 0x03, 0x10, 0x04}};
119   ULONG result;
120   
121   memset (&mnid, 0, sizeof mnid);
122   mnid.lpguid = &guid;
123   mnid.ulKind = MNID_STRING;
124   mnid.Kind.lpwstrName = propname;
125   pmnid = &mnid;
126   hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
127   xfree (propname);
128   if (FAILED (hr))
129     proparr = NULL;
130   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
131     {
132       log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
133                  SRCNAME, func, hr); 
134       result = 0;
135     }
136   else
137     result = (proparr->aulPropTag[0] & 0xFFFF0000);
138   if (proparr)
139     MAPIFreeBuffer (proparr);
140     
141   return result;
142 }
143
144
145 /* Return the property tag for GpgOL Msg Class. */
146 int 
147 get_gpgolmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
148 {
149   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Msg Class", __func__)))
150     return -1;
151   *r_tag |= PT_STRING8;
152   return 0;
153 }
154
155 /* Return the property tag for GpgOL Old Msg Class.  The Old Msg Class
156    saves the message class as seen before we changed it the first
157    time. */
158 int 
159 get_gpgololdmsgclass_tag (LPMESSAGE message, ULONG *r_tag)
160 {
161   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Old Msg Class", __func__)))
162     return -1;
163   *r_tag |= PT_STRING8;
164   return 0;
165 }
166
167
168 /* Return the property tag for GpgOL Attach Type. */
169 int 
170 get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag)
171 {
172   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__)))
173     return -1;
174   *r_tag |= PT_LONG;
175   return 0;
176 }
177
178
179 /* Return the property tag for GpgOL Sig Status. */
180 int 
181 get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag)
182 {
183   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__)))
184     return -1;
185   *r_tag |= PT_STRING8;
186   return 0;
187 }
188
189
190 /* Return the property tag for GpgOL Protect IV. */
191 int 
192 get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag)
193 {
194   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__)))
195     return -1;
196   *r_tag |= PT_BINARY;
197   return 0;
198 }
199
200 /* Return the property tag for GpgOL Last Decrypted. */
201 int 
202 get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag)
203 {
204   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__)))
205     return -1;
206   *r_tag |= PT_BINARY;
207   return 0;
208 }
209
210
211 /* Return the property tag for GpgOL MIME structure. */
212 int 
213 get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag)
214 {
215   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__)))
216     return -1;
217   *r_tag |= PT_STRING8;
218   return 0;
219 }
220
221
222 /* Return the property tag for GpgOL Charset. */
223 int 
224 get_gpgolcharset_tag (LPMESSAGE message, ULONG *r_tag)
225 {
226   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Charset", __func__)))
227     return -1;
228   *r_tag |= PT_STRING8;
229   return 0;
230 }
231
232
233 /* Return the property tag for GpgOL Draft Info.  */
234 int 
235 get_gpgoldraftinfo_tag (LPMESSAGE message, ULONG *r_tag)
236 {
237   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Draft Info", __func__)))
238     return -1;
239   *r_tag |= PT_STRING8;
240   return 0;
241 }
242
243
244 /* Return the tag of the Internet Charset Body property which seems to
245    hold the PR_BODY as received and thus before charset
246    conversion.  */
247 int
248 get_internetcharsetbody_tag (LPMESSAGE message, ULONG *r_tag)
249 {
250   HRESULT hr;
251   LPSPropTagArray proparr = NULL;
252   MAPINAMEID mnid, *pmnid;      
253   /* {4E3A7680-B77A-11D0-9DA5-00C04FD65685} */
254   GUID guid = {0x4E3A7680, 0xB77A, 0x11D0, {0x9D, 0xA5, 0x00, 0xC0,
255                                             0x4F, 0xD6, 0x56, 0x85}};
256   wchar_t propname[] = L"Internet Charset Body";
257   int result;
258
259   memset (&mnid, 0, sizeof mnid);
260   mnid.lpguid = &guid;
261   mnid.ulKind = MNID_STRING;
262   mnid.Kind.lpwstrName = propname;
263   pmnid = &mnid;
264   hr = message->GetIDsFromNames (1, &pmnid, 0, &proparr);
265   if (FAILED (hr))
266     proparr = NULL;
267   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
268     {
269       log_debug ("%s:%s: can't get the Internet Charset Body property:"
270                  " hr=%#lx\n", SRCNAME, __func__, hr); 
271       result = -1;
272     }
273   else
274     {
275       result = 0;
276       *r_tag = ((proparr->aulPropTag[0] & 0xFFFF0000) | PT_BINARY);
277     }
278
279   if (proparr)
280     MAPIFreeBuffer (proparr);
281   
282   return result;
283 }
284
285 /* Return the property tag for GpgOL UUID Info.  */
286 static int
287 get_gpgoluid_tag (LPMESSAGE message, ULONG *r_tag)
288 {
289   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL UID", __func__)))
290     return -1;
291   *r_tag |= PT_UNICODE;
292   return 0;
293 }
294
295 char *
296 mapi_get_uid (LPMESSAGE msg)
297 {
298   /* If the UUID is not in OOM maybe we find it in mapi. */
299   if (!msg)
300     {
301       log_error ("%s:%s: Called without message",
302                  SRCNAME, __func__);
303       return NULL;
304     }
305   ULONG tag;
306   if (get_gpgoluid_tag (msg, &tag))
307     {
308       log_debug ("%s:%s: Failed to get tag for '%p'",
309                  SRCNAME, __func__, msg);
310       return NULL;
311     }
312   LPSPropValue propval = NULL;
313   HRESULT hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
314   if (hr)
315     {
316       log_debug ("%s:%s: Failed to get prop for '%p'",
317                  SRCNAME, __func__, msg);
318       return NULL;
319     }
320   char *ret = NULL;
321   if (PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
322     {
323       ret = wchar_to_utf8 (propval->Value.lpszW);
324       log_debug ("%s:%s: Fund uuid in MAPI for %p",
325                  SRCNAME, __func__, msg);
326     }
327   else if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
328     {
329       ret = xstrdup (propval->Value.lpszA);
330       log_debug ("%s:%s: Fund uuid in MAPI for %p",
331                  SRCNAME, __func__, msg);
332     }
333   MAPIFreeBuffer (propval);
334   return ret;
335 }
336
337
338 /* A Wrapper around the SaveChanges method.  This function should be
339    called indirect through the mapi_save_changes macro.  Returns 0 on
340    success. */
341 int
342 mapi_do_save_changes (LPMESSAGE message, ULONG flags, int only_del_body,
343                       const char *dbg_file, const char *dbg_func)
344 {
345   HRESULT hr;
346   SPropTagArray proparray;
347   int any = 0;
348   
349   if (mapi_has_last_decrypted (message))
350     {
351       proparray.cValues = 1;
352       proparray.aulPropTag[0] = PR_BODY;
353       hr = message->DeleteProps (&proparray, NULL);
354       if (hr)
355         log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed",
356                        log_srcname (dbg_file), dbg_func);
357       else
358         any = 1;
359
360       proparray.cValues = 1;
361       proparray.aulPropTag[0] = PR_BODY_HTML;
362       hr = message->DeleteProps (&proparray, NULL);
363       if (hr)
364         log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed",
365                        log_srcname (dbg_file), dbg_func);
366       else
367         any = 1;
368     }
369
370   if (!only_del_body || any)
371     {
372       int i;
373       for (i = 0, hr = 0; hr && i < 10; i++)
374         {
375           hr = message->SaveChanges (flags);
376           if (hr)
377             {
378               log_debug ("%s:%s: Failed try to save.",
379                          SRCNAME, __func__);
380               Sleep (1000);
381             }
382         }
383       if (hr)
384         {
385           log_error ("%s:%s: SaveChanges(%lu) failed: hr=%#lx\n",
386                      log_srcname (dbg_file), dbg_func,
387                      (unsigned long)flags, hr); 
388           return -1;
389         }
390     }
391   
392   return 0;
393 }
394
395
396 /* Set an arbitary header in the message MSG with NAME to the value
397    VAL. */
398 int
399 mapi_set_header (LPMESSAGE msg, const char *name, const char *val)
400 {  
401   HRESULT hr;
402   LPSPropTagArray pProps = NULL;
403   SPropValue pv;
404   MAPINAMEID mnid, *pmnid;      
405   /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
406   GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
407                                             0x00, 0x00, 0x00, 0x46} };
408   int result;
409
410   if (!msg)
411     return -1;
412
413   memset (&mnid, 0, sizeof mnid);
414   mnid.lpguid = &guid;
415   mnid.ulKind = MNID_STRING;
416   mnid.Kind.lpwstrName = utf8_to_wchar (name);
417   pmnid = &mnid;
418   hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
419   xfree (mnid.Kind.lpwstrName);
420   if (FAILED (hr)) 
421     {
422       pProps = NULL;
423       log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
424                  SRCNAME, __func__, name, hr); 
425       result = -1;
426     }
427   else
428     {
429       pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
430       pv.Value.lpszA = (char *)val;
431       hr = HrSetOneProp(msg, &pv);      
432       if (hr)
433         {
434           log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
435                      SRCNAME, __func__, name, hr); 
436           result = -1;
437         }
438       else
439         result = 0;
440     }
441
442   if (pProps)
443     MAPIFreeBuffer (pProps);
444
445   return result;
446 }
447
448
449 /* Return the headers as ASCII string. Returns empty
450    string on failure. */
451 std::string
452 mapi_get_header (LPMESSAGE message)
453 {
454   HRESULT hr;
455   LPSTREAM stream;
456   ULONG bRead;
457   std::string ret;
458
459   if (!message)
460     return ret;
461
462   hr = gpgol_openProperty (message, PR_TRANSPORT_MESSAGE_HEADERS_A, &IID_IStream, 0, 0,
463                               (LPUNKNOWN*)&stream);
464   if (hr)
465     {
466       log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
467       return ret;
468     }
469
470   char buf[8192];
471   while ((hr = stream->Read (buf, 8192, &bRead)) == S_OK ||
472          hr == S_FALSE)
473     {
474       if (!bRead)
475         {
476           // EOF
477           break;
478         }
479       ret += std::string (buf, bRead);
480     }
481   gpgol_release (stream);
482   return ret;
483 }
484
485
486 /* Return the body as a new IStream object.  Returns NULL on failure.
487    The stream returns the body as an ASCII stream (Use mapi_get_body
488    for an UTF-8 value).  */
489 LPSTREAM
490 mapi_get_body_as_stream (LPMESSAGE message)
491 {
492   HRESULT hr;
493   ULONG tag;
494   LPSTREAM stream;
495
496   if (!message)
497     return NULL;
498
499   if (!get_internetcharsetbody_tag (message, &tag) )
500     {
501       /* The store knows about the Internet Charset Body property,
502          thus try to get the body from this property if it exists.  */
503
504       hr = gpgol_openProperty (message, tag, &IID_IStream, 0, 0,
505                                   (LPUNKNOWN*)&stream);
506       if (!hr)
507         return stream;
508
509       log_debug ("%s:%s: OpenProperty tag=%lx failed: hr=%#lx",
510                  SRCNAME, __func__, tag, hr);
511     }
512
513   /* We try to get it as an ASCII body.  If this fails we would either
514      need to implement some kind of stream filter to translated to
515      utf-8 or read everyting into a memory buffer and [provide an
516      istream from that memory buffer.  */
517   hr = gpgol_openProperty (message, PR_BODY_A, &IID_IStream, 0, 0,
518                               (LPUNKNOWN*)&stream);
519   if (hr)
520     {
521       log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
522       return NULL;
523     }
524
525   return stream;
526 }
527
528
529
530 /* Return the body of the message in an allocated buffer.  The buffer
531    is guaranteed to be Nul terminated.  The actual length (ie. the
532    strlen()) will be stored at R_NBYTES.  The body will be returned in
533    UTF-8 encoding. Returns NULL if no body is available.  */
534 char *
535 mapi_get_body (LPMESSAGE message, size_t *r_nbytes)
536 {
537   HRESULT hr;
538   LPSPropValue lpspvFEID = NULL;
539   LPSTREAM stream;
540   STATSTG statInfo;
541   ULONG nread;
542   char *body = NULL;
543
544   if (r_nbytes)
545     *r_nbytes = 0;
546   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
547   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
548     { 
549       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
550         {
551         case PT_UNICODE:
552           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
553           if (!body)
554             log_debug ("%s: error converting to utf8\n", __func__);
555           break;
556           
557         case PT_STRING8:
558           body = xstrdup (lpspvFEID->Value.lpszA);
559           break;
560           
561         default:
562           log_debug ("%s: proptag=0x%08lx not supported\n",
563                      __func__, lpspvFEID->ulPropTag);
564           break;
565         }
566       MAPIFreeBuffer (lpspvFEID);
567     }
568   else /* Message is large; use an IStream to read it.  */
569     {
570       hr = gpgol_openProperty (message, PR_BODY, &IID_IStream, 0, 0,
571                                   (LPUNKNOWN*)&stream);
572       if (hr)
573         {
574           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
575                      SRCNAME, __func__, hr);
576           return NULL;
577         }
578       
579       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
580       if (hr)
581         {
582           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
583           gpgol_release (stream);
584           return NULL;
585         }
586       
587       /* Fixme: We might want to read only the first 1k to decide
588          whether this is actually an OpenPGP message and only then
589          continue reading.  */
590       body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
591       hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
592       if (hr)
593         {
594           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
595           xfree (body);
596           gpgol_release (stream);
597           return NULL;
598         }
599       body[nread] = 0;
600       body[nread+1] = 0;
601       if (nread != statInfo.cbSize.QuadPart)
602         {
603           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
604           xfree (body);
605           gpgol_release (stream);
606           return NULL;
607         }
608       gpgol_release (stream);
609       
610       {
611         char *tmp;
612         tmp = wchar_to_utf8 ((wchar_t*)body);
613         if (!tmp)
614           log_debug ("%s: error converting to utf8\n", __func__);
615         else
616           {
617             xfree (body);
618             body = tmp;
619           }
620       }
621     }
622
623   if (r_nbytes)
624     *r_nbytes = strlen (body);
625   return body;
626 }
627
628
629
630 /* Look at the body of the MESSAGE and try to figure out whether this
631    is a supported PGP message.  Returns the new message class or NULL
632    if it does not look like a PGP message.
633
634    If r_nobody is not null it is set to true if no body was found.
635    */
636 static char *
637 get_msgcls_from_pgp_lines (LPMESSAGE message, bool *r_nobody = nullptr)
638 {
639   HRESULT hr;
640   LPSTREAM stream;
641   STATSTG statInfo;
642   ULONG nread;
643   size_t nbytes;
644   char *body = NULL;
645   char *p;
646   char *msgcls = NULL;
647   int is_wchar = 0;
648
649   if (r_nobody)
650     {
651       *r_nobody = false;
652     }
653
654   stream = mapi_get_body_as_stream (message);
655   if (!stream)
656     {
657       log_debug ("%s:%s: Failed to get body ASCII stream.",
658                  SRCNAME, __func__);
659       hr = gpgol_openProperty (message, PR_BODY_W, &IID_IStream, 0, 0,
660                                   (LPUNKNOWN*)&stream);
661       if (hr)
662         {
663           log_error ("%s:%s: Failed to get  w_body stream. : hr=%#lx",
664                      SRCNAME, __func__, hr);
665           if (r_nobody)
666             {
667               *r_nobody = true;
668             }
669           return NULL;
670         }
671       else
672         {
673           is_wchar = 1;
674         }
675     }
676
677   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
678   if (hr)
679     {
680       log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
681       gpgol_release (stream);
682       return NULL;
683     }
684
685   /* We read only the first 1k to decide whether this is actually an
686      OpenPGP armored message .  */
687   nbytes = (size_t)statInfo.cbSize.QuadPart;
688   if (nbytes > 1024*2)
689     nbytes = 1024*2;
690   body = (char*)xmalloc (nbytes + 2);
691   hr = stream->Read (body, nbytes, &nread);
692   if (hr)
693     {
694       log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
695       xfree (body);
696       gpgol_release (stream);
697       return NULL;
698     }
699   body[nread] = 0;
700   body[nread+1] = 0;
701   if (nread != nbytes)
702     {
703       log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
704
705       xfree (body);
706       gpgol_release (stream);
707       return NULL;
708     }
709   gpgol_release (stream);
710
711   if (is_wchar)
712     {
713       char *tmp;
714       tmp = wchar_to_utf8 ((wchar_t*)body);
715       if (!tmp)
716         log_debug ("%s: error converting to utf8\n", __func__);
717       else
718         {
719           xfree (body);
720           body = tmp;
721         }
722     }
723
724
725   /* The first ~1k of the body of the message is now available in the
726      utf-8 string BODY.  Walk over it to figure out its type.  */
727   for (p=body; p && *p; p = ((p=strchr (p+1, '\n')) ? (p+1) : NULL))
728     {
729       if (!strncmp (p, "-----BEGIN PGP ", 15))
730         {
731           /* Enabling clearsigned detection for Outlook 2010 and later
732              would result in data loss as the signature is not reverted. */
733           if (!strncmp (p+15, "SIGNED MESSAGE-----", 19)
734               && trailing_ws_p (p+15+19))
735             msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned");
736           else if (!strncmp (p+15, "MESSAGE-----", 12)
737                    && trailing_ws_p (p+15+12))
738             msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage");
739           break;
740         }
741       else if (!trailing_ws_p (p))
742         {
743           /* We have text before the message. In that case we need
744              to break because some bad MUA's like Outlook do not insert
745              quote characters before a replied to message. In that case
746              the reply to an inline Mail from an Outlook without GpgOL
747              enabled could cause the behavior that we would detect
748              the original message.
749           */
750           log_debug ("%s:%s: Detected non whitespace %c before a PGP Marker",
751                      SRCNAME, __func__, *p);
752           break;
753         }
754     }
755
756
757   xfree (body);
758   return msgcls;
759 }
760
761
762 /* Check whether the message is really a CMS encrypted message.  
763    We check here whether the message is really encrypted by looking at
764    the object identifier inside the CMS data.  Returns:
765     -1 := Unknown message type,
766      0 := The message is signed,
767      1 := The message is encrypted.
768
769    This function is required for two reasons: 
770
771    1. Due to a bug in CryptoEx which sometimes assignes the *.CexEnc
772       message class to signed messages and only updates the message
773       class after accessing them.  Thus in old stores there may be a
774       lot of *.CexEnc message which are actually just signed.
775  
776    2. If the smime-type parameter is missing we need another way to
777       decide whether to decrypt or to verify.
778
779    3. Some messages lack a PR_TRANSPORT_MESSAGE_HEADERS and thus it is
780       not possible to deduce the message type from the mail headers.
781       This function may be used to identify the message anyway.
782  */
783 static int
784 is_really_cms_encrypted (LPMESSAGE message)
785 {    
786   HRESULT hr;
787   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
788   LPMAPITABLE mapitable;
789   LPSRowSet   mapirows;
790   unsigned int pos, n_attach;
791   int result = -1; /* Unknown.  */
792   LPATTACH att = NULL;
793   LPSTREAM stream = NULL;
794   char buffer[24];  /* 24 bytes are more than enough to peek at.
795                        Cf. ksba_cms_identify() from the libksba
796                        package.  */
797   const char *p;
798   ULONG nread;
799   size_t n;
800   tlvinfo_t ti;
801
802   hr = message->GetAttachmentTable (0, &mapitable);
803   if (FAILED (hr))
804     {
805       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
806                  SRCNAME, __func__, hr);
807       return -1;
808     }
809       
810   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
811                        NULL, NULL, 0, &mapirows);
812   if (FAILED (hr))
813     {
814       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
815                  SRCNAME, __func__, hr);
816       gpgol_release (mapitable);
817       return -1;
818     }
819   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
820   if (n_attach != 1)
821     {
822       FreeProws (mapirows);
823       gpgol_release (mapitable);
824       log_debug ("%s:%s: not just one attachment", SRCNAME, __func__);
825       return -1;
826     }
827   pos = 0;
828
829   if (mapirows->aRow[pos].cValues < 1)
830     {
831       log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
832       goto leave;
833     }
834   if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
835     {
836       log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
837       goto leave;
838     }
839   hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
840                             NULL, MAPI_BEST_ACCESS, &att);
841   memdbg_addRef (att);
842   if (FAILED (hr))
843     {
844       log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
845                  SRCNAME, __func__, pos, 
846                  mapirows->aRow[pos].lpProps[0].Value.l, hr);
847       goto leave;
848     }
849   if (!has_smime_filename (att))
850     {
851       log_debug ("%s:%s: no smime filename", SRCNAME, __func__);
852       goto leave;
853     }
854   if (get_attach_method (att) != ATTACH_BY_VALUE)
855     {
856       log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
857       goto leave;
858     }
859   
860   hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
861                           0, 0, (LPUNKNOWN*) &stream);
862   if (FAILED (hr))
863     {
864       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
865                  SRCNAME, __func__, hr);
866       goto leave;
867     }
868
869   hr = stream->Read (buffer, sizeof buffer, &nread);
870   if ( hr != S_OK )
871     {
872       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
873       goto leave;
874     }
875   if (nread < sizeof buffer)
876     {
877       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
878       goto leave;
879     }
880
881   p = buffer;
882   n = nread;
883   if (parse_tlv (&p, &n, &ti))
884     goto leave;
885   if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_SEQUENCE
886         && ti.is_cons) )
887     goto leave;
888   if (parse_tlv (&p, &n, &ti))
889     goto leave;
890   if (!(ti.cls == ASN1_CLASS_UNIVERSAL && ti.tag == ASN1_TAG_OBJECT_ID
891         && !ti.is_cons && ti.length) || ti.length > n)
892     goto leave;
893   /* Now is this enveloped data (1.2.840.113549.1.7.3)
894                  or signed data (1.2.840.113549.1.7.2) ? */
895   if (ti.length == 9)
896     {
897       if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
898         result = 1; /* Encrypted.  */
899       else if (!memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x02", 9))
900         result = 0; /* Signed.  */
901     }
902   
903  leave:
904   if (stream)
905     gpgol_release (stream);
906   if (att)
907     gpgol_release (att);
908   FreeProws (mapirows);
909   gpgol_release (mapitable);
910   return result;
911 }
912
913
914
915 /* Return the content-type of the first and only attachment of MESSAGE
916    or NULL if it does not exists.  Caller must free. */
917 static char *
918 get_first_attach_mime_tag (LPMESSAGE message)
919 {    
920   HRESULT hr;
921   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
922   LPMAPITABLE mapitable;
923   LPSRowSet   mapirows;
924   unsigned int pos, n_attach;
925   LPATTACH att = NULL;
926   char *result = NULL;
927
928   hr = message->GetAttachmentTable (0, &mapitable);
929   if (FAILED (hr))
930     {
931       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
932                  SRCNAME, __func__, hr);
933       return NULL;
934     }
935       
936   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
937                        NULL, NULL, 0, &mapirows);
938   if (FAILED (hr))
939     {
940       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
941                  SRCNAME, __func__, hr);
942       gpgol_release (mapitable);
943       return NULL;
944     }
945   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
946   if (n_attach < 1)
947     {
948       FreeProws (mapirows);
949       gpgol_release (mapitable);
950       log_debug ("%s:%s: less then one attachment", SRCNAME, __func__);
951       return NULL;
952     }
953   pos = 0;
954
955   if (mapirows->aRow[pos].cValues < 1)
956     {
957       log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
958       goto leave;
959     }
960   if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
961     {
962       log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
963       goto leave;
964     }
965   hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
966                             NULL, MAPI_BEST_ACCESS, &att);      
967   memdbg_addRef (att);
968   if (FAILED (hr))
969     {
970       log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
971                  SRCNAME, __func__, pos, 
972                  mapirows->aRow[pos].lpProps[0].Value.l, hr);
973       goto leave;
974     }
975
976   /* Note: We do not expect a filename.  */
977
978   if (get_attach_method (att) != ATTACH_BY_VALUE)
979     {
980       log_debug ("%s:%s: wrong attach method", SRCNAME, __func__);
981       goto leave;
982     }
983
984   result = get_attach_mime_tag (att);
985   
986  leave:
987   if (att)
988     gpgol_release (att);
989   FreeProws (mapirows);
990   gpgol_release (mapitable);
991   return result;
992 }
993
994
995 /* Look at the first attachment's content type to determine the
996    messageclass. */
997 static char *
998 get_msgcls_from_first_attachment (LPMESSAGE message)
999 {
1000   char *ret = nullptr;
1001   char *attach_mime = get_first_attach_mime_tag (message);
1002   if (!attach_mime)
1003     {
1004       return nullptr;
1005     }
1006   if (!strcmp (attach_mime, "application/pgp-encrypted"))
1007     {
1008       ret = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
1009       xfree (attach_mime);
1010     }
1011   else if (!strcmp (attach_mime, "application/pgp-signature"))
1012     {
1013       ret = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1014       xfree (attach_mime);
1015     }
1016   return ret;
1017 }
1018
1019 /* Helper for mapi_change_message_class.  Returns the new message
1020    class as an allocated string.
1021
1022    Most message today are of the message class "IPM.Note".  However a
1023    PGP/MIME encrypted message also has this class.  We need to see
1024    whether we can detect such a mail right here and change the message
1025    class accordingly. */
1026 static char *
1027 change_message_class_ipm_note (LPMESSAGE message)
1028 {
1029   char *newvalue = NULL;
1030   char *ct, *proto;
1031
1032   ct = mapi_get_message_content_type (message, &proto, NULL);
1033   log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__,
1034              ct ? ct : "null");
1035   if (ct && proto)
1036     {
1037       log_debug ("%s:%s:     protocol is '%s'", SRCNAME, __func__, proto);
1038
1039       if (!strcmp (ct, "multipart/encrypted")
1040           && !strcmp (proto, "application/pgp-encrypted"))
1041         {
1042           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
1043         }
1044       else if (!strcmp (ct, "multipart/signed")
1045                && !strcmp (proto, "application/pgp-signature"))
1046         {
1047           /* Sometimes we receive a PGP/MIME signed message with a
1048              class IPM.Note.  */
1049           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1050         }
1051       else if (!strcmp (ct, "multipart/mixed")
1052                && !strcmp (proto, "application/pgp-encrypted"))
1053         {
1054           /* This case happens if an encrypted mail is moved
1055              by outlook local filter rules.
1056           */
1057           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
1058         }
1059       xfree (proto);
1060     }
1061   else if (ct && !strcmp (ct, "application/ms-tnef"))
1062     {
1063       /* ms-tnef can either be inline PGP or PGP/MIME. First check
1064          for inline and then look at the attachments if they look
1065          like PGP /MIME .*/
1066       newvalue = get_msgcls_from_pgp_lines (message);
1067       if (!newvalue)
1068         {
1069           /* So no PGP Inline. Lets look at the attachment. */
1070           newvalue = get_msgcls_from_first_attachment (message);
1071         }
1072     }
1073   else if (!ct || !strcmp (ct, "text/plain") ||
1074            !strcmp (ct, "multipart/mixed") ||
1075            !strcmp (ct, "multipart/alternative") ||
1076            !strcmp (ct, "multipart/related") ||
1077            !strcmp (ct, "text/html"))
1078     {
1079       bool has_no_body = false;
1080       /* It is quite common to have a multipart/mixed or alternative
1081          mail with separate encrypted PGP parts.  Look at the body to
1082          decide.  */
1083       newvalue = get_msgcls_from_pgp_lines (message, &has_no_body);
1084
1085       if (!newvalue && has_no_body && ct && !strcmp (ct, "multipart/mixed"))
1086         {
1087           /* This is uncommon. But some Exchanges might break a PGP/MIME mail
1088              this way. Let's take a look at the attachments. Maybe it's
1089              a PGP/MIME mail. */
1090           log_debug ("%s:%s: Multipart mixed without body found. Looking at attachments.",
1091                      SRCNAME, __func__);
1092           newvalue = get_msgcls_from_first_attachment (message);
1093         }
1094     }
1095
1096   xfree (ct);
1097
1098   return newvalue;
1099 }
1100
1101 /* Helper for mapi_change_message_class.  Returns the new message
1102    class as an allocated string.
1103
1104    This function is used for the message class "IPM.Note.SMIME".  It
1105    indicates an S/MIME opaque encrypted or signed message.  This may
1106    also be an PGP/MIME mail. */
1107 static char *
1108 change_message_class_ipm_note_smime (LPMESSAGE message)
1109 {
1110   char *newvalue = NULL;
1111   char *ct, *proto, *smtype;
1112   
1113   ct = mapi_get_message_content_type (message, &proto, &smtype);
1114   if (ct)
1115     {
1116       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1117       if (proto
1118           && !strcmp (ct, "multipart/signed")
1119           && !strcmp (proto, "application/pgp-signature"))
1120         {
1121           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1122         }
1123       else if (ct && !strcmp (ct, "application/ms-tnef"))
1124         {
1125           /* So no PGP Inline. Lets look at the attachment. */
1126           char *attach_mime = get_first_attach_mime_tag (message);
1127           if (!attach_mime)
1128             {
1129               xfree (ct);
1130               xfree (proto);
1131               return nullptr;
1132             }
1133           if (!strcmp (attach_mime, "multipart/signed"))
1134             {
1135               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1136               xfree (attach_mime);
1137             }
1138         }
1139       else if (!opt.enable_smime)
1140         ; /* S/MIME not enabled; thus no further checks.  */
1141       else if (smtype)
1142         {
1143           log_debug ("%s:%s:   smime-type is '%s'", SRCNAME, __func__, smtype);
1144           
1145           if (!strcmp (ct, "application/pkcs7-mime")
1146               || !strcmp (ct, "application/x-pkcs7-mime"))
1147             {
1148               if (!strcmp (smtype, "signed-data"))
1149                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1150               else if (!strcmp (smtype, "enveloped-data"))
1151                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1152             }
1153         }
1154       else
1155         {
1156           /* No smime type.  The filename parameter is often not
1157              reliable, thus we better look into the message to see if
1158              it is encrypted and assume an opaque signed one if this
1159              is not the case.  */
1160           switch (is_really_cms_encrypted (message))
1161             {
1162             case 0:
1163               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1164               break;
1165             case 1:
1166               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1167               break;
1168             }
1169
1170         }
1171       xfree (smtype);
1172       xfree (proto);
1173       xfree (ct);
1174     }
1175   else
1176     {
1177       log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1178
1179       /* CryptoEx (or the Toltec Connector) create messages without
1180          the transport headers property and thus we don't know the
1181          content type.  We try to detect the message type anyway by
1182          looking into the first and only attachments.  */
1183       switch (is_really_cms_encrypted (message))
1184         {
1185         case 0:
1186           newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1187           break;
1188         case 1:
1189           newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1190           break;
1191         default: /* Unknown.  */
1192           break;
1193         }
1194     }
1195
1196   /* If we did not found anything but let's change the class anyway.  */
1197   if (!newvalue && opt.enable_smime)
1198     newvalue = xstrdup ("IPM.Note.GpgOL");
1199
1200   return newvalue;
1201 }
1202
1203 /* Helper for mapi_change_message_class.  Returns the new message
1204    class as an allocated string.
1205
1206    This function is used for the message class
1207    "IPM.Note.SMIME.MultipartSigned".  This is an S/MIME message class
1208    but smime support is not enabled.  We need to check whether this is
1209    actually a PGP/MIME message.  */
1210 static char *
1211 change_message_class_ipm_note_smime_multipartsigned (LPMESSAGE message)
1212 {
1213   char *newvalue = NULL;
1214   char *ct, *proto;
1215
1216   ct = mapi_get_message_content_type (message, &proto, NULL);
1217   if (ct)
1218     {
1219       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1220       if (proto 
1221           && !strcmp (ct, "multipart/signed")
1222           && !strcmp (proto, "application/pgp-signature"))
1223         {
1224           newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1225         }
1226       else if (!strcmp (ct, "wks.confirmation.mail"))
1227         {
1228           newvalue = xstrdup ("IPM.Note.GpgOL.WKSConfirmation");
1229         }
1230       else if (ct && !strcmp (ct, "application/ms-tnef"))
1231         {
1232           /* So no PGP Inline. Lets look at the attachment. */
1233           char *attach_mime = get_first_attach_mime_tag (message);
1234           if (!attach_mime)
1235             {
1236               xfree (ct);
1237               xfree (proto);
1238               return nullptr;
1239             }
1240           if (!strcmp (attach_mime, "multipart/signed"))
1241             {
1242               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1243               xfree (attach_mime);
1244             }
1245         }
1246       xfree (proto);
1247       xfree (ct);
1248     }
1249   else
1250     log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1251   
1252   return newvalue;
1253 }
1254
1255 /* Helper for mapi_change_message_class.  Returns the new message
1256    class as an allocated string.
1257
1258    This function is used for the message classes
1259    "IPM.Note.Secure.CexSig" and "IPM.Note.Secure.Cexenc" (in the
1260    latter case IS_CEXSIG is true).  These are CryptoEx generated
1261    signature or encryption messages.  */
1262 static char *
1263 change_message_class_ipm_note_secure_cex (LPMESSAGE message, int is_cexenc)
1264 {
1265   char *newvalue = NULL;
1266   char *ct, *smtype, *proto;
1267   
1268   ct = mapi_get_message_content_type (message, &proto, &smtype);
1269   if (ct)
1270     {
1271       log_debug ("%s:%s: content type is '%s'", SRCNAME, __func__, ct);
1272       if (smtype)
1273         log_debug ("%s:%s:   smime-type is '%s'", SRCNAME, __func__, smtype);
1274       if (proto)
1275         log_debug ("%s:%s:     protocol is '%s'", SRCNAME, __func__, proto);
1276
1277       if (smtype)
1278         {
1279           if (!strcmp (ct, "application/pkcs7-mime")
1280               || !strcmp (ct, "application/x-pkcs7-mime"))
1281             {
1282               if (!strcmp (smtype, "signed-data"))
1283                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1284               else if (!strcmp (smtype, "enveloped-data"))
1285                 newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1286             }
1287         }
1288
1289       if (!newvalue && proto)
1290         {
1291           if (!strcmp (ct, "multipart/signed")
1292               && (!strcmp (proto, "application/pkcs7-signature")
1293                   || !strcmp (proto, "application/x-pkcs7-signature")))
1294             {
1295               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1296             }
1297           else if (!strcmp (ct, "multipart/signed")
1298                    && (!strcmp (proto, "application/pgp-signature")))
1299             {
1300               newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1301             }
1302         }
1303       
1304       if (!newvalue && (!strcmp (ct, "text/plain") ||
1305                         !strcmp (ct, "multipart/alternative") ||
1306                         !strcmp (ct, "multipart/mixed")))
1307         {
1308           newvalue = get_msgcls_from_pgp_lines (message);
1309         }
1310       
1311       if (!newvalue)
1312         {
1313           switch (is_really_cms_encrypted (message))
1314             {
1315             case 0:
1316               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1317               break;
1318             case 1:
1319               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1320               break;
1321             }
1322         }
1323       
1324       xfree (smtype);
1325       xfree (proto);
1326       xfree (ct);
1327     }
1328   else
1329     {
1330       log_debug ("%s:%s: message has no content type", SRCNAME, __func__);
1331       if (is_cexenc)
1332         {
1333           switch (is_really_cms_encrypted (message))
1334             {
1335             case 0:
1336               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
1337               break;
1338             case 1:
1339               newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
1340               break;
1341             }
1342         }
1343       else
1344         {
1345           char *mimetag;
1346
1347           mimetag = get_first_attach_mime_tag (message);
1348           if (mimetag && !strcmp (mimetag, "multipart/signed"))
1349             newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
1350           xfree (mimetag);
1351         }
1352
1353       if (!newvalue)
1354         {
1355           newvalue = get_msgcls_from_pgp_lines (message);
1356         }
1357     }
1358
1359   if (!newvalue)
1360     newvalue = xstrdup ("IPM.Note.GpgOL");
1361
1362   return newvalue;
1363 }
1364
1365 static msgtype_t
1366 string_to_type (const char *s)
1367 {
1368   if (!s || strlen (s) < 14)
1369     {
1370       return MSGTYPE_UNKNOWN;
1371     }
1372   if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
1373     {
1374       s += 14;
1375       if (!*s)
1376         return MSGTYPE_GPGOL;
1377       else if (!strcmp (s, ".MultipartSigned"))
1378         return MSGTYPE_GPGOL_MULTIPART_SIGNED;
1379       else if (!strcmp (s, ".MultipartEncrypted"))
1380         return MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
1381       else if (!strcmp (s, ".OpaqueSigned"))
1382         return MSGTYPE_GPGOL_OPAQUE_SIGNED;
1383       else if (!strcmp (s, ".OpaqueEncrypted"))
1384         return MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
1385       else if (!strcmp (s, ".ClearSigned"))
1386         return MSGTYPE_GPGOL_CLEAR_SIGNED;
1387       else if (!strcmp (s, ".PGPMessage"))
1388         return MSGTYPE_GPGOL_PGP_MESSAGE;
1389       else if (!strcmp (s, ".WKSConfirmation"))
1390         return MSGTYPE_GPGOL_WKS_CONFIRMATION;
1391       else
1392         log_debug ("%s:%s: message class `%s' not supported",
1393                    SRCNAME, __func__, s-14);
1394     }
1395   else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
1396     return MSGTYPE_SMIME;
1397   return MSGTYPE_UNKNOWN;
1398 }
1399
1400
1401 /* This function checks whether MESSAGE requires processing by us and
1402    adjusts the message class to our own.  By passing true for
1403    SYNC_OVERRIDE the actual MAPI message class will be updated to our
1404    own message class overide.  Return true if the message was
1405    changed. */
1406 int
1407 mapi_change_message_class (LPMESSAGE message, int sync_override,
1408                            msgtype_t *r_type)
1409 {
1410   HRESULT hr;
1411   ULONG tag;
1412   SPropValue prop;
1413   LPSPropValue propval = NULL;
1414   char *newvalue = NULL;
1415   int need_save = 0;
1416   int have_override = 0;
1417
1418   if (!message)
1419     return 0; /* No message: Nop. */
1420
1421   if (get_gpgolmsgclass_tag (message, &tag) )
1422     return 0; /* Ooops. */
1423
1424   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1425   if (FAILED (hr))
1426     {
1427       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1428       if (FAILED (hr))
1429         {
1430           log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1431                      SRCNAME, __func__, hr);
1432           return 0;
1433         }
1434     }
1435   else
1436     {
1437       have_override = 1;
1438       log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1439     }
1440     
1441   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1442     {
1443       const char *s = propval->Value.lpszA;
1444       int cexenc = 0;
1445       
1446       log_debug ("%s:%s: checking message class `%s'", 
1447                        SRCNAME, __func__, s);
1448       if (!strcmp (s, "IPM.Note"))
1449         {
1450           newvalue = change_message_class_ipm_note (message);
1451         }
1452       else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME"))
1453         {
1454           newvalue = change_message_class_ipm_note_smime (message);
1455         }
1456       else if (opt.enable_smime
1457                && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.'))
1458         {
1459           /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional
1460              but the previous condition has already taken care of
1461              this).  Note that we can't just insert a new part and
1462              keep the SMIME; we need to change the SMIME part of the
1463              class name so that Outlook does not process it as an
1464              SMIME message. */
1465
1466           char *tmp = change_message_class_ipm_note_smime_multipartsigned
1467             (message);
1468           /* This case happens even for PGP/MIME mails but that is ok
1469              as we later fiddle out the protocol. But we have to
1470              check if this is a WKS Mail now so that we can do the
1471              special handling for that. */
1472           if (tmp && !strcmp (tmp, "IPM.Note.GpgOL.WKSConfirmation"))
1473             {
1474               newvalue = tmp;
1475             }
1476           else
1477             {
1478               xfree (tmp);
1479               newvalue = (char*)xmalloc (strlen (s) + 1);
1480               strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
1481             }
1482         }
1483       else if (!strcmp (s, "IPM.Note.SMIME.MultipartSigned"))
1484         {
1485           /* This is an S/MIME message class but smime support is not
1486              enabled.  We need to check whether this is actually a
1487              PGP/MIME message.  */
1488           newvalue = change_message_class_ipm_note_smime_multipartsigned
1489             (message);
1490         }
1491       else if (sync_override && have_override
1492                && !strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14]||s[14] =='.'))
1493         {
1494           /* In case the original message class is not yet an GpgOL
1495              class we set it here.  This is needed to convince Outlook
1496              not to do any special processing for IPM.Note.SMIME etc.  */
1497           LPSPropValue propval2 = NULL;
1498
1499           hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A,
1500                              &propval2);
1501           if (!SUCCEEDED (hr))
1502             {
1503               log_debug ("%s:%s: Failed to get PR_MESSAGE_CLASS_A property.",
1504                          SRCNAME, __func__);
1505             }
1506           else if (PROP_TYPE (propval2->ulPropTag) != PT_STRING8)
1507             {
1508               log_debug ("%s:%s: PR_MESSAGE_CLASS_A is not string.",
1509                          SRCNAME, __func__);
1510             }
1511           else if (!propval2->Value.lpszA)
1512             {
1513               log_debug ("%s:%s: PR_MESSAGE_CLASS_A is null.",
1514                          SRCNAME, __func__);
1515             }
1516           else if (!strcmp (propval2->Value.lpszA, s))
1517             {
1518               log_debug ("%s:%s: PR_MESSAGE_CLASS_A is already the same.",
1519                          SRCNAME, __func__);
1520             }
1521           else
1522             {
1523               newvalue = (char*)xstrdup (s);
1524             }
1525           MAPIFreeBuffer (propval2);
1526         }
1527       else if (opt.enable_smime 
1528                && (!strcmp (s, "IPM.Note.Secure.CexSig")
1529                    || (cexenc = !strcmp (s, "IPM.Note.Secure.CexEnc"))))
1530         {
1531           newvalue = change_message_class_ipm_note_secure_cex
1532             (message, cexenc);
1533         }
1534       if (r_type && !newvalue)
1535         {
1536           *r_type = string_to_type (s);
1537         }
1538     }
1539
1540   if (!newvalue)
1541     {
1542       /* We use our Sig-Status property to mark messages which passed
1543          this function.  This helps us to avoid later tests.  */
1544       if (!mapi_has_sig_status (message))
1545         {
1546           mapi_set_sig_status (message, "#");
1547           need_save = 1;
1548         }
1549     }
1550   else
1551     {
1552       if (r_type)
1553         {
1554           *r_type = string_to_type (newvalue);
1555         }
1556       /* Save old message class if not yet done.  (The second
1557          condition is just a failsafe check). */
1558       if (!get_gpgololdmsgclass_tag (message, &tag)
1559           && PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1560         {
1561           LPSPropValue propval2 = NULL;
1562
1563           hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval2);
1564           if (!FAILED (hr))
1565             MAPIFreeBuffer (propval2);
1566           else
1567             {
1568               /* No such property - save it.  */
1569               log_debug ("%s:%s: saving old message class\n",
1570                          SRCNAME, __func__);
1571               prop.ulPropTag = tag;
1572               prop.Value.lpszA = propval->Value.lpszA; 
1573               hr = message->SetProps (1, &prop, NULL);
1574               if (hr)
1575                 {
1576                   log_error ("%s:%s: can't save old message class: hr=%#lx\n",
1577                              SRCNAME, __func__, hr);
1578                   MAPIFreeBuffer (propval);
1579                   return 0;
1580                 }
1581               need_save = 1;
1582             }
1583         }
1584       
1585       /* Change message class.  */
1586       log_debug ("%s:%s: setting message class to `%s'\n",
1587                  SRCNAME, __func__, newvalue);
1588       prop.ulPropTag = PR_MESSAGE_CLASS_A;
1589       prop.Value.lpszA = newvalue; 
1590       hr = message->SetProps (1, &prop, NULL);
1591       xfree (newvalue);
1592       if (hr)
1593         {
1594           log_error ("%s:%s: can't set message class: hr=%#lx\n",
1595                      SRCNAME, __func__, hr);
1596           MAPIFreeBuffer (propval);
1597           return 0;
1598         }
1599       need_save = 1;
1600     }
1601
1602   MAPIFreeBuffer (propval);
1603
1604   if (need_save)
1605     {
1606       if (mapi_save_changes (message, KEEP_OPEN_READWRITE|FORCE_SAVE))
1607         return 0;
1608     }
1609
1610   return 1;
1611 }
1612
1613
1614 /* Return the message class.  This function will never return NULL so
1615    it is mostly useful for debugging.  Caller needs to release the
1616    returned string.  */
1617 char *
1618 mapi_get_message_class (LPMESSAGE message)
1619 {
1620   HRESULT hr;
1621   LPSPropValue propval = NULL;
1622   char *retstr;
1623
1624   if (!message)
1625     return xstrdup ("[No message]");
1626   
1627   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1628   if (FAILED (hr))
1629     {
1630       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1631                  SRCNAME, __func__, hr);
1632       return xstrdup (hr == MAPI_E_NOT_FOUND?
1633                         "[No message class property]":
1634                         "[Error getting message class property]");
1635     }
1636
1637   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1638     retstr = xstrdup (propval->Value.lpszA);
1639   else
1640     retstr = xstrdup ("[Invalid message class property]");
1641     
1642   MAPIFreeBuffer (propval);
1643   return retstr;
1644 }
1645
1646 /* Return the old message class.  This function returns NULL if no old
1647    message class has been saved.  Caller needs to release the returned
1648    string.  */
1649 char *
1650 mapi_get_old_message_class (LPMESSAGE message)
1651 {
1652   HRESULT hr;
1653   ULONG tag;
1654   LPSPropValue propval = NULL;
1655   char *retstr;
1656
1657   if (!message)
1658     return NULL;
1659   
1660   if (get_gpgololdmsgclass_tag (message, &tag))
1661     return NULL;
1662
1663   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1664   if (FAILED (hr))
1665     {
1666       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
1667                  SRCNAME, __func__, hr);
1668       return NULL;
1669     }
1670
1671   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1672     retstr = xstrdup (propval->Value.lpszA);
1673   else
1674     retstr = NULL;
1675     
1676   MAPIFreeBuffer (propval);
1677   return retstr;
1678 }
1679
1680
1681
1682 /* Return the sender of the message.  According to the specs this is
1683    an UTF-8 string; we rely on that the UI server handles
1684    internationalized domain names.  */ 
1685 char *
1686 mapi_get_sender (LPMESSAGE message)
1687 {
1688   HRESULT hr;
1689   LPSPropValue propval = NULL;
1690   char *buf;
1691   char *p0, *p;
1692   
1693   if (!message)
1694     return NULL; /* No message: Nop. */
1695
1696   hr = HrGetOneProp ((LPMAPIPROP)message, PR_PRIMARY_SEND_ACCT, &propval);
1697   if (FAILED (hr))
1698     {
1699       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1700                  SRCNAME, __func__, hr);
1701       return NULL;
1702     }
1703     
1704   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1705     {
1706       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1707                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1708       MAPIFreeBuffer (propval);
1709       return NULL;
1710     }
1711   
1712   buf = wchar_to_utf8 (propval->Value.lpszW);
1713   MAPIFreeBuffer (propval);
1714   if (!buf)
1715     {
1716       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1717       return NULL;
1718     }
1719   /* The PR_PRIMARY_SEND_ACCT property seems to be divided into fields
1720      using Ctrl-A as delimiter.  The first field looks like the ascii
1721      formatted number of fields to follow, the second field like the
1722      email account and the third seems to be a textual description of
1723      that account.  We return the second field. */
1724   p = strchr (buf, '\x01');
1725   if (!p)
1726     {
1727       log_error ("%s:%s: unknown format of the value `%s'\n",
1728                  SRCNAME, __func__, buf);
1729       xfree (buf);
1730       return NULL;
1731     }
1732   for (p0=buf, p++; *p && *p != '\x01';)
1733     *p0++ = *p++;
1734   *p0 = 0;
1735
1736   /* When using an Exchange account this is an X.509 address and not
1737      an SMTP address.  We try to detect this here and extract only the
1738      CN RDN.  Note that there are two CNs.  This is just a simple
1739      approach and not a real parser.  A better way to do this would be
1740      to ask MAPI to resolve the X.500 name to an SMTP name.  */
1741   if (strstr (buf, "/o=") && strstr (buf, "/ou=") &&
1742       (p = strstr (buf, "/cn=Recipients")) && (p = strstr (p+1, "/cn=")))
1743     {
1744       log_debug ("%s:%s: orig address is `%s'\n", SRCNAME, __func__, buf);
1745       memmove (buf, p+4, strlen (p+4)+1);
1746       if (!strchr (buf, '@'))
1747         {
1748           /* Some Exchange accounts return only the accoutn name and
1749              no rfc821 mail address.  Kleopatra chokes on that, thus
1750              we append a domain name.  Thisis a bad hack.  */
1751           char *newbuf = (char *)xmalloc (strlen (buf) + 6 + 1);
1752           strcpy (stpcpy (newbuf, buf), "@local");
1753           xfree (buf);
1754           buf = newbuf;
1755         }
1756       
1757     }
1758   log_debug ("%s:%s: address is `%s'\n", SRCNAME, __func__, buf);
1759   return buf;
1760 }
1761
1762 static char *
1763 resolve_ex_from_address (LPMESSAGE message)
1764 {
1765   HRESULT hr;
1766   char *sender_entryid;
1767   size_t entryidlen;
1768   LPMAPISESSION session;
1769   ULONG utype;
1770   LPUNKNOWN user;
1771   LPSPropValue propval = NULL;
1772   char *buf;
1773
1774   if (g_ol_version_major < 14)
1775     {
1776       log_debug ("%s:%s: Not implemented for Ol < 14", SRCNAME, __func__);
1777       return NULL;
1778     }
1779
1780   sender_entryid = mapi_get_binary_prop (message, PR_SENDER_ENTRYID,
1781                                          &entryidlen);
1782   if (!sender_entryid)
1783     {
1784       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1785       return NULL;
1786     }
1787
1788   session = get_oom_mapi_session ();
1789
1790   if (!session)
1791     {
1792       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1793       xfree (sender_entryid);
1794       return NULL;
1795     }
1796
1797   hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1798                            &IID_IMailUser,
1799                            MAPI_BEST_ACCESS | MAPI_CACHE_ONLY,
1800                            &utype, (IUnknown**)&user);
1801   if (FAILED (hr))
1802     {
1803       log_debug ("%s:%s: Failed to open cached entry. Fallback to uncached.",
1804                  SRCNAME, __func__);
1805       hr = session->OpenEntry (entryidlen,  (LPENTRYID)sender_entryid,
1806                                &IID_IMailUser,
1807                                MAPI_BEST_ACCESS,
1808                                &utype, (IUnknown**)&user);
1809     }
1810   gpgol_release (session);
1811
1812   if (FAILED (hr))
1813     {
1814       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1815       return NULL;
1816     }
1817
1818   hr = HrGetOneProp ((LPMAPIPROP)user, PR_SMTP_ADDRESS_W, &propval);
1819   if (FAILED (hr))
1820     {
1821       log_error ("%s:%s: Error: %i", SRCNAME, __func__, __LINE__);
1822       return NULL;
1823     }
1824
1825   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE)
1826     {
1827       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1828                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1829       MAPIFreeBuffer (propval);
1830       return NULL;
1831     }
1832   buf = wchar_to_utf8 (propval->Value.lpszW);
1833   MAPIFreeBuffer (propval);
1834
1835   return buf;
1836 }
1837
1838 /* Return the from address of the message as a malloced UTF-8 string.
1839    Returns NULL if that address is not available.  */
1840 char *
1841 mapi_get_from_address (LPMESSAGE message)
1842 {
1843   HRESULT hr;
1844   LPSPropValue propval = NULL;
1845   char *buf;
1846   ULONG try_props[3] = {PidTagSenderSmtpAddress_W,
1847                         PR_SENT_REPRESENTING_SMTP_ADDRESS_W,
1848                         PR_SENDER_EMAIL_ADDRESS_W};
1849
1850   if (!message)
1851     return xstrdup ("[no message]"); /* Ooops.  */
1852
1853   for (int i = 0; i < 3; i++)
1854     {
1855       /* We try to get different properties first as they contain
1856          the SMTP address of the sender. EMAIL address can be
1857          some LDAP stuff for exchange. */
1858       hr = HrGetOneProp ((LPMAPIPROP)message, try_props[i],
1859                          &propval);
1860       if (!FAILED (hr))
1861         {
1862           break;
1863         }
1864     }
1865    /* This is the last result that should always work but not necessarily
1866       contain an SMTP Address. */
1867   if (FAILED (hr))
1868     {
1869       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1870                  SRCNAME, __func__, hr);
1871       return NULL;
1872     }
1873
1874   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1875     {
1876       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1877                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1878       MAPIFreeBuffer (propval);
1879       return NULL;
1880     }
1881   
1882   buf = wchar_to_utf8 (propval->Value.lpszW);
1883   MAPIFreeBuffer (propval);
1884   if (!buf)
1885     {
1886       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1887       return NULL;
1888     }
1889
1890   if (strstr (buf, "/o="))
1891     {
1892       char *buf2;
1893       /* If both SMTP Address properties are not set
1894          we need to fallback to resolve the address
1895          through the address book */
1896       log_debug ("%s:%s: resolving exchange address.",
1897                  SRCNAME, __func__);
1898       buf2 = resolve_ex_from_address (message);
1899       if (buf2)
1900         {
1901           xfree (buf);
1902           return buf2;
1903         }
1904     }
1905
1906   return buf;
1907 }
1908
1909
1910 /* Return the subject of the message as a malloced UTF-8 string.
1911    Returns a replacement string if a subject is missing.  */
1912 char *
1913 mapi_get_subject (LPMESSAGE message)
1914 {
1915   HRESULT hr;
1916   LPSPropValue propval = NULL;
1917   char *buf;
1918   
1919   if (!message)
1920     return xstrdup ("[no message]"); /* Ooops.  */
1921
1922   hr = HrGetOneProp ((LPMAPIPROP)message, PR_SUBJECT_W, &propval);
1923   if (FAILED (hr))
1924     {
1925       log_debug ("%s:%s: HrGetOneProp failed: hr=%#lx\n",
1926                  SRCNAME, __func__, hr);
1927       return xstrdup (_("[no subject]"));
1928     }
1929     
1930   if (PROP_TYPE (propval->ulPropTag) != PT_UNICODE) 
1931     {
1932       log_debug ("%s:%s: HrGetOneProp returns invalid type %lu\n",
1933                  SRCNAME, __func__, PROP_TYPE (propval->ulPropTag) );
1934       MAPIFreeBuffer (propval);
1935       return xstrdup (_("[no subject]"));
1936     }
1937   
1938   buf = wchar_to_utf8 (propval->Value.lpszW);
1939   MAPIFreeBuffer (propval);
1940   if (!buf)
1941     {
1942       log_error ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
1943       return xstrdup (_("[no subject]"));
1944     }
1945
1946   return buf;
1947 }
1948
1949
1950
1951
1952 /* Return the message type.  This function knows only about our own
1953    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
1954    no special support for.  */
1955 msgtype_t
1956 mapi_get_message_type (LPMESSAGE message)
1957 {
1958   HRESULT hr;
1959   ULONG tag;
1960   LPSPropValue propval = NULL;
1961   msgtype_t msgtype = MSGTYPE_UNKNOWN;
1962
1963   if (!message)
1964     return msgtype; 
1965
1966   if (get_gpgolmsgclass_tag (message, &tag) )
1967     return msgtype; /* Ooops */
1968
1969   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1970   if (FAILED (hr))
1971     {
1972       hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
1973       if (FAILED (hr))
1974         {
1975           log_error ("%s:%s: HrGetOneProp(PR_MESSAGE_CLASS) failed: hr=%#lx\n",
1976                      SRCNAME, __func__, hr);
1977           return msgtype;
1978         }
1979     }
1980   else
1981     log_debug ("%s:%s: have override message class\n", SRCNAME, __func__);
1982
1983   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
1984     {
1985       msgtype = string_to_type (propval->Value.lpszA);
1986     }
1987   MAPIFreeBuffer (propval);
1988   return msgtype;
1989 }
1990
1991
1992 /* This function is pretty useless because IConverterSession won't
1993    take attachments into account.  Need to write our own version.  */
1994 int
1995 mapi_to_mime (LPMESSAGE message, const char *filename)
1996 {
1997   HRESULT hr;
1998   LPCONVERTERSESSION session;
1999   LPSTREAM stream;
2000
2001   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
2002                          IID_IConverterSession, (void **) &session);
2003   if (FAILED (hr))
2004     {
2005       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
2006                  SRCNAME, __func__, hr);
2007       return -1;
2008     }
2009
2010
2011   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
2012                          (STGM_CREATE | STGM_READWRITE),
2013                          (char*)filename, NULL, &stream); 
2014   if (FAILED (hr)) 
2015     {
2016       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
2017                  SRCNAME, __func__, filename, hr); 
2018       hr = -1;
2019     }
2020   else
2021     {
2022       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
2023       if (FAILED (hr))
2024         {
2025           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
2026                      SRCNAME, __func__, hr);
2027           stream->Revert ();
2028           hr = -1;
2029         }
2030       else
2031         {
2032           stream->Commit (0);
2033           hr = 0;
2034         }
2035
2036       gpgol_release (stream);
2037     }
2038
2039   gpgol_release (session);
2040   return hr;
2041 }
2042
2043
2044 /* Return a binary property in a malloced buffer with its length stored
2045    at R_NBYTES.  Returns NULL on error.  */
2046 char *
2047 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
2048 {
2049   HRESULT hr;
2050   LPSPropValue propval = NULL;
2051   char *data;
2052
2053   *r_nbytes = 0;
2054   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
2055   if (FAILED (hr))
2056     {
2057       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
2058                  SRCNAME, __func__, proptype, hr);
2059       return NULL; 
2060     }
2061   switch ( PROP_TYPE (propval->ulPropTag) )
2062     {
2063     case PT_BINARY:
2064       /* This is a binary object but we know that it must be plain
2065          ASCII due to the armored format.  */
2066       data = (char*)xmalloc (propval->Value.bin.cb + 1);
2067       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
2068       data[propval->Value.bin.cb] = 0;
2069       *r_nbytes = propval->Value.bin.cb;
2070       break;
2071       
2072     default:
2073       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
2074                  SRCNAME, __func__, proptype, propval->ulPropTag);
2075       data = NULL;
2076       break;
2077     }
2078   MAPIFreeBuffer (propval);
2079   return data;
2080 }
2081
2082 /* Return an integer property at R_VALUE.  On error the function
2083    returns -1 and sets R_VALUE to 0, on success 0 is returned.  */
2084 int
2085 mapi_get_int_prop (LPMAPIPROP object, ULONG proptype, LONG *r_value)
2086 {
2087   int rc = -1;
2088   HRESULT hr;
2089   LPSPropValue propval = NULL;
2090
2091   *r_value = 0;
2092   hr = HrGetOneProp (object, proptype, &propval);
2093   if (FAILED (hr))
2094     {
2095       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
2096                  SRCNAME, __func__, proptype, hr);
2097       return -1; 
2098     }
2099   switch ( PROP_TYPE (propval->ulPropTag) )
2100     {
2101     case PT_LONG:
2102       *r_value = propval->Value.l;
2103       rc = 0;
2104       
2105       break;
2106       
2107     default:
2108       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
2109                  SRCNAME, __func__, proptype, propval->ulPropTag);
2110       break;
2111     }
2112   MAPIFreeBuffer (propval);
2113   return rc;
2114 }
2115
2116
2117 /* Return the attachment method for attachment OBJ.  In case of error
2118    we return 0 which happens not to be defined.  */
2119 static int
2120 get_attach_method (LPATTACH obj)
2121 {
2122   HRESULT hr;
2123   LPSPropValue propval = NULL;
2124   int method ;
2125
2126   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
2127   if (FAILED (hr))
2128     {
2129       log_error ("%s:%s: error getting attachment method: hr=%#lx",
2130                  SRCNAME, __func__, hr);
2131       return 0; 
2132     }
2133   /* We don't bother checking whether we really get a PT_LONG ulong
2134      back; if not the system is seriously damaged and we can't do
2135      further harm by returning a possible random value.  */
2136   method = propval->Value.l;
2137   MAPIFreeBuffer (propval);
2138   return method;
2139 }
2140
2141
2142
2143 /* Return the filename from the attachment as a malloced string.  The
2144    encoding we return will be UTF-8, however the MAPI docs declare
2145    that MAPI does only handle plain ANSI and thus we don't really care
2146    later on.  In fact we would need to convert the filename back to
2147    wchar and use the Unicode versions of the file API.  Returns NULL
2148    on error or if no filename is available. */
2149 static char *
2150 get_attach_filename (LPATTACH obj)
2151 {
2152   HRESULT hr;
2153   LPSPropValue propval;
2154   char *name = NULL;
2155
2156   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
2157   if (FAILED(hr)) 
2158     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
2159   if (FAILED(hr))
2160     {
2161       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
2162       return NULL;
2163     }
2164
2165   switch ( PROP_TYPE (propval->ulPropTag) )
2166     {
2167     case PT_UNICODE:
2168       name = wchar_to_utf8 (propval->Value.lpszW);
2169       if (!name)
2170         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2171       break;
2172       
2173     case PT_STRING8:
2174       name = xstrdup (propval->Value.lpszA);
2175       break;
2176       
2177     default:
2178       log_debug ("%s:%s: proptag=%#lx not supported\n",
2179                  SRCNAME, __func__, propval->ulPropTag);
2180       name = NULL;
2181       break;
2182     }
2183   MAPIFreeBuffer (propval);
2184   return name;
2185 }
2186
2187 /* Return the content-id of the attachment OBJ or NULL if it does
2188    not exists.  Caller must free. */
2189 static char *
2190 get_attach_content_id (LPATTACH obj)
2191 {
2192   HRESULT hr;
2193   LPSPropValue propval = NULL;
2194   char *name;
2195
2196   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_CONTENT_ID, &propval);
2197   if (FAILED (hr))
2198     {
2199       if (hr != MAPI_E_NOT_FOUND)
2200         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2201                    SRCNAME, __func__, hr);
2202       return NULL;
2203     }
2204   switch ( PROP_TYPE (propval->ulPropTag) )
2205     {
2206     case PT_UNICODE:
2207       name = wchar_to_utf8 (propval->Value.lpszW);
2208       if (!name)
2209         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2210       break;
2211
2212     case PT_STRING8:
2213       name = xstrdup (propval->Value.lpszA);
2214       break;
2215
2216     default:
2217       log_debug ("%s:%s: proptag=%#lx not supported\n",
2218                  SRCNAME, __func__, propval->ulPropTag);
2219       name = NULL;
2220       break;
2221     }
2222   MAPIFreeBuffer (propval);
2223   return name;
2224 }
2225
2226 /* Return the content-type of the attachment OBJ or NULL if it does
2227    not exists.  Caller must free. */
2228 static char *
2229 get_attach_mime_tag (LPATTACH obj)
2230 {
2231   HRESULT hr;
2232   LPSPropValue propval = NULL;
2233   char *name;
2234
2235   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
2236   if (FAILED (hr))
2237     {
2238       if (hr != MAPI_E_NOT_FOUND)
2239         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
2240                    SRCNAME, __func__, hr);
2241       return NULL; 
2242     }
2243   switch ( PROP_TYPE (propval->ulPropTag) )
2244     {
2245     case PT_UNICODE:
2246       name = wchar_to_utf8 (propval->Value.lpszW);
2247       if (!name)
2248         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
2249       break;
2250       
2251     case PT_STRING8:
2252       name = xstrdup (propval->Value.lpszA);
2253       break;
2254       
2255     default:
2256       log_debug ("%s:%s: proptag=%#lx not supported\n",
2257                  SRCNAME, __func__, propval->ulPropTag);
2258       name = NULL;
2259       break;
2260     }
2261   MAPIFreeBuffer (propval);
2262   return name;
2263 }
2264
2265
2266 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
2267    the tag of that property. */
2268 attachtype_t
2269 get_gpgolattachtype (LPATTACH obj, ULONG tag)
2270 {
2271   HRESULT hr;
2272   LPSPropValue propval = NULL;
2273   attachtype_t retval;
2274
2275   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2276   if (FAILED (hr))
2277     {
2278       if (hr != MAPI_E_NOT_FOUND)
2279         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
2280                    SRCNAME, __func__, hr);
2281       return ATTACHTYPE_UNKNOWN; 
2282     }
2283   retval = (attachtype_t)propval->Value.l;
2284   MAPIFreeBuffer (propval);
2285   return retval;
2286 }
2287
2288
2289 /* Gather information about attachments and return a new table of
2290    attachments.  Caller must release the returned table.s The routine
2291    will return NULL in case of an error or if no attachments are
2292    available.  With FAST set only some information gets collected. */
2293 mapi_attach_item_t *
2294 mapi_create_attach_table (LPMESSAGE message, int fast)
2295 {    
2296   HRESULT hr;
2297   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
2298   LPMAPITABLE mapitable;
2299   LPSRowSet   mapirows;
2300   mapi_attach_item_t *table; 
2301   unsigned int pos, n_attach;
2302   ULONG moss_tag;
2303
2304   if (get_gpgolattachtype_tag (message, &moss_tag) )
2305     return NULL;
2306
2307   /* Open the attachment table.  */
2308   hr = message->GetAttachmentTable (0, &mapitable);
2309   memdbg_addRef (mapitable);
2310   if (FAILED (hr))
2311     {
2312       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
2313                  SRCNAME, __func__, hr);
2314       return NULL;
2315     }
2316       
2317   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
2318                        NULL, NULL, 0, &mapirows);
2319   if (FAILED (hr))
2320     {
2321       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
2322                  SRCNAME, __func__, hr);
2323       gpgol_release (mapitable);
2324       return NULL;
2325     }
2326   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
2327
2328   log_debug ("%s:%s: message has %u attachments\n",
2329              SRCNAME, __func__, n_attach);
2330   if (!n_attach)
2331     {
2332       FreeProws (mapirows);
2333       gpgol_release (mapitable);
2334       return NULL;
2335     }
2336
2337   /* Allocate our own table.  */
2338   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
2339   for (pos=0; pos < n_attach; pos++) 
2340     {
2341       LPATTACH att;
2342
2343       if (mapirows->aRow[pos].cValues < 1)
2344         {
2345           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
2346           table[pos].mapipos = -1;
2347           continue;
2348         }
2349       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
2350         {
2351           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
2352           table[pos].mapipos = -1;
2353           continue;
2354         }
2355       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
2356
2357       hr = message->OpenAttach (table[pos].mapipos, NULL,
2358                                 MAPI_BEST_ACCESS, &att);
2359       memdbg_addRef (att);
2360       if (FAILED (hr))
2361         {
2362           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
2363                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
2364           table[pos].mapipos = -1;
2365           continue;
2366         }
2367
2368       table[pos].method = get_attach_method (att);
2369       table[pos].filename = fast? NULL : get_attach_filename (att);
2370       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
2371       table[pos].content_id = fast? NULL : get_attach_content_id (att);
2372       if (table[pos].content_type)
2373         {
2374           char *p = strchr (table[pos].content_type, ';');
2375           if (p)
2376             {
2377               *p++ = 0;
2378               trim_trailing_spaces (table[pos].content_type);
2379               while (strchr (" \t\r\n", *p))
2380                 p++;
2381               trim_trailing_spaces (p);
2382               table[pos].content_type_parms = p;
2383             }
2384         }
2385       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
2386       gpgol_release (att);
2387     }
2388   table[0].private_mapitable = mapitable;
2389   FreeProws (mapirows);
2390   table[pos].end_of_table = 1;
2391   mapitable = NULL;
2392
2393   if (fast)
2394     {
2395       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
2396                  SRCNAME, __func__);
2397     }
2398   else
2399     {
2400       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
2401       for (pos=0; !table[pos].end_of_table; pos++)
2402         {
2403           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
2404                      table[pos].mapipos,
2405                      table[pos].attach_type,
2406                      table[pos].filename, table[pos].content_type,
2407                      table[pos].content_type_parms);
2408         }
2409     }
2410
2411   return table;
2412 }
2413
2414
2415 /* Release a table as created by mapi_create_attach_table. */
2416 void
2417 mapi_release_attach_table (mapi_attach_item_t *table)
2418 {
2419   unsigned int pos;
2420   LPMAPITABLE mapitable;
2421
2422   if (!table)
2423     return;
2424
2425   mapitable = (LPMAPITABLE)table[0].private_mapitable;
2426   if (mapitable)
2427     gpgol_release (mapitable);
2428   for (pos=0; !table[pos].end_of_table; pos++)
2429     {
2430       xfree (table[pos].filename);
2431       xfree (table[pos].content_type);
2432       xfree (table[pos].content_id);
2433     }
2434   xfree (table);
2435 }
2436
2437
2438 /* Return an attachment as a new IStream object.  Returns NULL on
2439    failure.  If R_ATTACH is not NULL the actual attachment will not be
2440    released but stored at that address; the caller needs to release it
2441    in this case.  */
2442 LPSTREAM
2443 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item,
2444                            LPATTACH *r_attach)
2445 {
2446   HRESULT hr;
2447   LPATTACH att;
2448   LPSTREAM stream;
2449
2450   if (r_attach)
2451     *r_attach = NULL;
2452
2453   if (!item || item->end_of_table || item->mapipos == -1)
2454     return NULL;
2455
2456   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2457   memdbg_addRef (att);
2458   if (FAILED (hr))
2459     {
2460       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2461                  SRCNAME, __func__, item->mapipos, hr);
2462       return NULL;
2463     }
2464   if (item->method != ATTACH_BY_VALUE)
2465     {
2466       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2467       gpgol_release (att);
2468       return NULL;
2469     }
2470
2471   hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
2472                           0, 0, (LPUNKNOWN*) &stream);
2473   if (FAILED (hr))
2474     {
2475       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2476                  SRCNAME, __func__, hr);
2477       gpgol_release (att);
2478       return NULL;
2479     }
2480
2481   if (r_attach)
2482     *r_attach = att;
2483   else
2484     gpgol_release (att);
2485
2486   return stream;
2487 }
2488
2489
2490 /* Return a malloced buffer with the content of the attachment. If
2491    R_NBYTES is not NULL the number of bytes will get stored there.
2492    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
2493    NULL on error.  If UNPROTECT is set and the appropriate crypto
2494    attribute is available, the function returns the unprotected
2495    version of the atatchment. */
2496 static char *
2497 attach_to_buffer (LPATTACH att, size_t *r_nbytes)
2498 {
2499   HRESULT hr;
2500   LPSTREAM stream;
2501   STATSTG statInfo;
2502   ULONG nread;
2503   char *buffer;
2504
2505   hr = gpgol_openProperty (att, PR_ATTACH_DATA_BIN, &IID_IStream,
2506                           0, 0, (LPUNKNOWN*) &stream);
2507   if (FAILED (hr))
2508     {
2509       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
2510                  SRCNAME, __func__, hr);
2511       return NULL;
2512     }
2513
2514   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
2515   if ( hr != S_OK )
2516     {
2517       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
2518       gpgol_release (stream);
2519       return NULL;
2520     }
2521
2522   /* Allocate one byte more so that we can terminate the string.  */
2523   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
2524
2525   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
2526   if ( hr != S_OK )
2527     {
2528       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
2529       xfree (buffer);
2530       gpgol_release (stream);
2531       return NULL;
2532     }
2533   if (nread != statInfo.cbSize.QuadPart)
2534     {
2535       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
2536       xfree (buffer);
2537       buffer = NULL;
2538     }
2539   gpgol_release (stream);
2540
2541   /* Make sure that the buffer is a C string.  */
2542   if (buffer)
2543     buffer[nread] = 0;
2544
2545   if (r_nbytes)
2546     *r_nbytes = nread;
2547   return buffer;
2548 }
2549
2550
2551
2552 /* Return an attachment as a malloced buffer.  The size of the buffer
2553    will be stored at R_NBYTES.  If unprotect is true, the atatchment
2554    will be unprotected.  Returns NULL on failure. */
2555 char *
2556 mapi_get_attach (LPMESSAGE message,
2557                  mapi_attach_item_t *item, size_t *r_nbytes)
2558 {
2559   HRESULT hr;
2560   LPATTACH att;
2561   char *buffer;
2562
2563   if (!item || item->end_of_table || item->mapipos == -1)
2564     return NULL;
2565
2566   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2567   memdbg_addRef (att);
2568   if (FAILED (hr))
2569     {
2570       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2571                  SRCNAME, __func__, item->mapipos, hr);
2572       return NULL;
2573     }
2574   if (item->method != ATTACH_BY_VALUE)
2575     {
2576       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
2577       gpgol_release (att);
2578       return NULL;
2579     }
2580
2581   buffer = attach_to_buffer (att, r_nbytes);
2582   gpgol_release (att);
2583
2584   return buffer;
2585 }
2586
2587
2588 /* Mark this attachment as the original MOSS message.  We set a custom
2589    property as well as the hidden flag.  */
2590 int 
2591 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
2592 {
2593   int retval = -1;
2594   HRESULT hr;
2595   LPATTACH att;
2596   SPropValue prop;
2597
2598   if (!item || item->end_of_table || item->mapipos == -1)
2599     return -1;
2600
2601   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
2602   memdbg_addRef (att);
2603   if (FAILED (hr))
2604     {
2605       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
2606                  SRCNAME, __func__, item->mapipos, hr);
2607       return -1;
2608     }
2609
2610   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
2611     goto leave;
2612   prop.Value.l = ATTACHTYPE_MOSS;
2613   hr = HrSetOneProp (att, &prop);       
2614   if (hr)
2615     {
2616       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2617                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
2618       return false;
2619     }
2620
2621   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2622   prop.Value.b = TRUE;
2623   hr = HrSetOneProp (att, &prop);
2624   if (hr)
2625     {
2626       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2627                  SRCNAME, __func__, hr); 
2628       goto leave;
2629     }
2630   
2631
2632   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
2633   if (hr)
2634     {
2635       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2636                  SRCNAME, __func__, hr); 
2637       goto leave;
2638     }
2639   
2640   retval = 0;
2641     
2642  leave:
2643   gpgol_release (att);
2644   return retval;
2645 }
2646
2647
2648 /* If the hidden property has not been set on ATTACH, set it and save
2649    the changes. */
2650 int 
2651 mapi_set_attach_hidden (LPATTACH attach)
2652 {
2653   int retval = -1;
2654   HRESULT hr;
2655   LPSPropValue propval;
2656   SPropValue prop;
2657
2658   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2659   if (SUCCEEDED (hr) 
2660       && PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN
2661       && propval->Value.b)
2662     return 0;/* Already set to hidden. */
2663
2664   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
2665   prop.Value.b = TRUE;
2666   hr = HrSetOneProp (attach, &prop);
2667   if (hr)
2668     {
2669       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
2670                  SRCNAME, __func__, hr); 
2671       goto leave;
2672     }
2673   
2674   hr = attach->SaveChanges (KEEP_OPEN_READWRITE);
2675   if (hr)
2676     {
2677       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
2678                  SRCNAME, __func__, hr); 
2679       goto leave;
2680     }
2681   
2682   retval = 0;
2683     
2684  leave:
2685   return retval;
2686 }
2687
2688
2689 /* Returns true if ATTACH has the hidden flag set to true.  */
2690 int
2691 mapi_test_attach_hidden (LPATTACH attach)
2692 {
2693   HRESULT hr;
2694   LPSPropValue propval = NULL;
2695   int result = 0;
2696   
2697   hr = HrGetOneProp ((LPMAPIPROP)attach, PR_ATTACHMENT_HIDDEN, &propval);
2698   if (FAILED (hr))
2699     return result; /* No.  */  
2700   
2701   if (PROP_TYPE (propval->ulPropTag) == PT_BOOLEAN && propval->Value.b)
2702     result = 1; /* Yes.  */
2703
2704   MAPIFreeBuffer (propval);
2705   return result;
2706 }
2707
2708
2709
2710
2711 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
2712 int
2713 mapi_has_sig_status (LPMESSAGE msg)
2714 {
2715   HRESULT hr;
2716   LPSPropValue propval = NULL;
2717   ULONG tag;
2718   int yes;
2719
2720   if (get_gpgolsigstatus_tag (msg, &tag) )
2721     return 0; /* Error:  Assume No.  */
2722   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2723   if (FAILED (hr))
2724     return 0; /* No.  */  
2725   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2726     yes = 1;
2727   else
2728     yes = 0;
2729
2730   MAPIFreeBuffer (propval);
2731   return yes;
2732 }
2733
2734
2735 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
2736    is not set to unchecked.  */
2737 int
2738 mapi_test_sig_status (LPMESSAGE msg)
2739 {
2740   HRESULT hr;
2741   LPSPropValue propval = NULL;
2742   ULONG tag;
2743   int yes;
2744
2745   if (get_gpgolsigstatus_tag (msg, &tag) )
2746     return 0; /* Error:  Assume No.  */
2747   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2748   if (FAILED (hr))
2749     return 0; /* No.  */  
2750
2751   /* We return False if we have an unknown signature status (?) or the
2752      message has been sent by us and not yet checked (@).  */
2753   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2754     yes = !(propval->Value.lpszA && (!strcmp (propval->Value.lpszA, "?")
2755                                      || !strcmp (propval->Value.lpszA, "@")));
2756   else
2757     yes = 0;
2758
2759   MAPIFreeBuffer (propval);
2760   return yes;
2761 }
2762
2763
2764 /* Return the signature status as an allocated string.  Will never
2765    return NULL.  */
2766 char *
2767 mapi_get_sig_status (LPMESSAGE msg)
2768 {
2769   HRESULT hr;
2770   LPSPropValue propval = NULL;
2771   ULONG tag;
2772   char *retstr;
2773
2774   if (get_gpgolsigstatus_tag (msg, &tag) )
2775     return xstrdup ("[Error getting tag for sig status]");
2776   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2777   if (FAILED (hr))
2778     return xstrdup ("");
2779   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2780     retstr = xstrdup (propval->Value.lpszA);
2781   else
2782     retstr = xstrdup ("[Sig status has an invalid type]");
2783
2784   MAPIFreeBuffer (propval);
2785   return retstr;
2786 }
2787
2788
2789
2790
2791 /* Set the signature status property to STATUS_STRING.  There are a
2792    few special values:
2793
2794      "#" The message is not of interest to us.
2795      "@" The message has been created and signed or encrypted by us.
2796      "?" The signature status has not been checked.
2797      "!" The signature verified okay 
2798      "~" The signature was not fully verified.
2799      "-" The signature is bad
2800
2801    Note that this function does not call SaveChanges.  */
2802 int 
2803 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
2804 {
2805   HRESULT hr;
2806   SPropValue prop;
2807
2808   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
2809     return -1;
2810   prop.Value.lpszA = xstrdup (status_string);
2811   hr = HrSetOneProp (message, &prop);   
2812   xfree (prop.Value.lpszA);
2813   if (hr)
2814     {
2815       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2816                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
2817       return -1;
2818     }
2819
2820   return 0;
2821 }
2822
2823
2824 /* When sending a message we need to fake the message class so that OL
2825    processes it according to our needs.  However, if we later try to
2826    get the message class from the sent message, OL still has the SMIME
2827    message class and tries to hide this by trying to decrypt the
2828    message and return the message class from the plaintext.  To
2829    mitigate the problem we define our own msg class override
2830    property.  */
2831 int 
2832 mapi_set_gpgol_msg_class (LPMESSAGE message, const char *name)
2833 {
2834   HRESULT hr;
2835   SPropValue prop;
2836
2837   if (get_gpgolmsgclass_tag (message, &prop.ulPropTag) )
2838     return -1;
2839   prop.Value.lpszA = xstrdup (name);
2840   hr = HrSetOneProp (message, &prop);   
2841   xfree (prop.Value.lpszA);
2842   if (hr)
2843     {
2844       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2845                  SRCNAME, __func__, "GpgOL Msg Class", hr); 
2846       return -1;
2847     }
2848
2849   return 0;
2850 }
2851
2852
2853 /* Return the charset as assigned by GpgOL to an attachment.  This may
2854    return NULL it is has not been assigned or is the standard
2855    (UTF-8). */
2856 char *
2857 mapi_get_gpgol_charset (LPMESSAGE obj)
2858 {
2859   HRESULT hr;
2860   LPSPropValue propval = NULL;
2861   ULONG tag;
2862   char *retstr;
2863
2864   if (get_gpgolcharset_tag (obj, &tag) )
2865     return NULL; /* Error.  */
2866   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
2867   if (FAILED (hr))
2868     return NULL;
2869   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2870     {
2871       if (!strcmp (propval->Value.lpszA, "utf-8"))
2872         retstr = NULL;
2873       else
2874         retstr = xstrdup (propval->Value.lpszA);
2875     }
2876   else
2877     retstr = NULL;
2878
2879   MAPIFreeBuffer (propval);
2880   return retstr;
2881 }
2882
2883
2884 /* Set the GpgOl charset property to an attachment. 
2885    Note that this function does not call SaveChanges.  */
2886 int 
2887 mapi_set_gpgol_charset (LPMESSAGE obj, const char *charset)
2888 {
2889   HRESULT hr;
2890   SPropValue prop;
2891   char *p;
2892
2893   /* Note that we lowercase the value and cut it to a max of 32
2894      characters.  The latter is required to make sure that
2895      HrSetOneProp will always work.  */
2896   if (get_gpgolcharset_tag (obj, &prop.ulPropTag) )
2897     return -1;
2898   prop.Value.lpszA = xstrdup (charset);
2899   for (p=prop.Value.lpszA; *p; p++)
2900     *p = tolower (*(unsigned char*)p);
2901   if (strlen (prop.Value.lpszA) > 32)
2902     prop.Value.lpszA[32] = 0;
2903   hr = HrSetOneProp ((LPMAPIPROP)obj, &prop);   
2904   xfree (prop.Value.lpszA);
2905   if (hr)
2906     {
2907       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
2908                  SRCNAME, __func__, "GpgOL Charset", hr); 
2909       return -1;
2910     }
2911
2912   return 0;
2913 }
2914
2915
2916
2917 /* Return GpgOL's draft info string as an allocated string.  If no
2918    draft info is available, NULL is returned.  */
2919 char *
2920 mapi_get_gpgol_draft_info (LPMESSAGE msg)
2921 {
2922   HRESULT hr;
2923   LPSPropValue propval = NULL;
2924   ULONG tag;
2925   char *retstr;
2926
2927   if (get_gpgoldraftinfo_tag (msg, &tag) )
2928     return NULL;
2929   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
2930   if (FAILED (hr))
2931     return NULL;
2932   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
2933     retstr = xstrdup (propval->Value.lpszA);
2934   else
2935     retstr = NULL;
2936
2937   MAPIFreeBuffer (propval);
2938   return retstr;
2939 }
2940
2941
2942 /* Set GpgOL's draft info string to STRING.  This string is defined as:
2943
2944    Character 1:  'E' = encrypt selected,
2945                  'e' = encrypt not selected.
2946                  '-' = don't care
2947    Character 2:  'S' = sign selected,
2948                  's' = sign not selected.
2949                  '-' = don't care
2950    Character 3:  'A' = Auto protocol 
2951                  'P' = OpenPGP protocol
2952                  'X' = S/MIME protocol
2953                  '-' = don't care
2954                  
2955    If string is NULL, the property will get deleted.
2956
2957    Note that this function does not call SaveChanges.  */
2958 int 
2959 mapi_set_gpgol_draft_info (LPMESSAGE message, const char *string)
2960 {
2961   HRESULT hr;
2962   SPropValue prop;
2963   SPropTagArray proparray;
2964
2965   if (get_gpgoldraftinfo_tag (message, &prop.ulPropTag) )
2966     return -1;
2967   if (string)
2968     {
2969       prop.Value.lpszA = xstrdup (string);
2970       hr = HrSetOneProp (message, &prop);       
2971       xfree (prop.Value.lpszA);
2972     }
2973   else
2974     {
2975       proparray.cValues = 1;
2976       proparray.aulPropTag[0] = prop.ulPropTag;
2977       hr = message->DeleteProps (&proparray, NULL);
2978     }
2979   if (hr)
2980     {
2981       log_error ("%s:%s: can't %s %s property: hr=%#lx\n",
2982                  SRCNAME, __func__, string?"set":"delete",
2983                  "GpgOL Draft Info", hr); 
2984       return -1;
2985     }
2986
2987   return 0;
2988 }
2989
2990
2991 /* Return the MIME info as an allocated string.  Will never return
2992    NULL.  */
2993 char *
2994 mapi_get_mime_info (LPMESSAGE msg)
2995 {
2996   HRESULT hr;
2997   LPSPropValue propval = NULL;
2998   ULONG tag;
2999   char *retstr;
3000
3001   if (get_gpgolmimeinfo_tag (msg, &tag) )
3002     return xstrdup ("[Error getting tag for MIME info]");
3003   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
3004   if (FAILED (hr))
3005     return xstrdup ("");
3006   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3007     retstr = xstrdup (propval->Value.lpszA);
3008   else
3009     retstr = xstrdup ("[MIME info has an invalid type]");
3010
3011   MAPIFreeBuffer (propval);
3012   return retstr;
3013 }
3014
3015
3016 /* Helper around mapi_get_gpgol_draft_info to avoid
3017    the string handling.
3018    Return values are:
3019    0 -> Do nothing
3020    1 -> Encrypt
3021    2 -> Sign
3022    3 -> Encrypt & Sign*/
3023 int
3024 get_gpgol_draft_info_flags (LPMESSAGE message)
3025 {
3026   char *buf = mapi_get_gpgol_draft_info (message);
3027   int ret = 0;
3028   if (!buf)
3029     {
3030       return 0;
3031     }
3032   if (buf[0] == 'E')
3033     {
3034       ret |= 1;
3035     }
3036   if (buf[1] == 'S')
3037     {
3038       ret |= 2;
3039     }
3040   xfree (buf);
3041   return ret;
3042 }
3043
3044 /* Sets the draft info flags. Protocol is always Auto.
3045    flags should be the same as defined by
3046    get_gpgol_draft_info_flags
3047 */
3048 int
3049 set_gpgol_draft_info_flags (LPMESSAGE message, int flags)
3050 {
3051   char buf[4];
3052   buf[3] = '\0';
3053   buf[2] = 'A'; /* Protocol */
3054   buf[1] = flags & 2 ? 'S' : 's';
3055   buf[0] = flags & 1 ? 'E' : 'e';
3056
3057   return mapi_set_gpgol_draft_info (message, buf);
3058 }
3059
3060
3061 /* Helper for mapi_get_msg_content_type() */
3062 static int
3063 get_message_content_type_cb (void *dummy_arg,
3064                              rfc822parse_event_t event, rfc822parse_t msg)
3065 {
3066   (void)dummy_arg;
3067   (void)msg;
3068
3069   if (event == RFC822PARSE_T2BODY)
3070     return 42; /* Hack to stop the parsing after having read the
3071                   outer headers. */
3072   return 0;
3073 }
3074
3075
3076 /* Return Content-Type of the current message.  This one is taken
3077    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
3078    string with the protocol parameter will be stored at this address,
3079    if no protocol is given NULL will be stored.  If R_SMTYPE is not
3080    NULL a string with the smime-type parameter will be stored there.
3081    Caller must release all returned strings.  */
3082 char *
3083 mapi_get_message_content_type (LPMESSAGE message,
3084                                char **r_protocol, char **r_smtype)
3085 {
3086   rfc822parse_t msg;
3087   const char *header_lines, *s;
3088   rfc822parse_field_t ctx;
3089   size_t length;
3090   char *retstr = NULL;
3091
3092   if (r_protocol)
3093     *r_protocol = NULL;
3094   if (r_smtype)
3095     *r_smtype = NULL;
3096
3097   /* Read the headers into an rfc822 object. */
3098   msg = rfc822parse_open (get_message_content_type_cb, NULL);
3099   if (!msg)
3100     {
3101       log_error ("%s:%s: rfc822parse_open failed",
3102                  SRCNAME, __func__);
3103       return NULL;
3104     }
3105
3106   const std::string hdrStr = mapi_get_header (message);
3107   if (hdrStr.empty())
3108     {
3109
3110       log_error ("%s:%s: failed to get headers",
3111                  SRCNAME, __func__);
3112       return NULL;
3113     }
3114
3115   header_lines = hdrStr.c_str();
3116   while ((s = strchr (header_lines, '\n')))
3117     {
3118       length = (s - header_lines);
3119       if (length && s[-1] == '\r')
3120         length--;
3121
3122       if (!strncmp ("Wks-Phase: confirm", header_lines,
3123                     std::max (18, (int) length)))
3124         {
3125           log_debug ("%s:%s: detected wks confirmation mail",
3126                      SRCNAME, __func__);
3127           retstr = xstrdup ("wks.confirmation.mail");
3128           rfc822parse_close (msg);
3129           return retstr;
3130         }
3131
3132       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
3133       header_lines = s+1;
3134     }
3135
3136   /* Parse the content-type field. */
3137   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
3138   if (ctx)
3139     {
3140       const char *s1, *s2;
3141       s1 = rfc822parse_query_media_type (ctx, &s2);
3142       if (s1)
3143         {
3144           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
3145           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
3146
3147           if (r_protocol)
3148             {
3149               s = rfc822parse_query_parameter (ctx, "protocol", 0);
3150               if (s)
3151                 *r_protocol = xstrdup (s);
3152             }
3153           if (r_smtype)
3154             {
3155               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
3156               if (s)
3157                 *r_smtype = xstrdup (s);
3158             }
3159         }
3160       rfc822parse_release_field (ctx);
3161     }
3162
3163   rfc822parse_close (msg);
3164   return retstr;
3165 }
3166
3167
3168 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
3169    This indicates that there should be no PR_BODY tag.  */
3170 int
3171 mapi_has_last_decrypted (LPMESSAGE message)
3172 {
3173   HRESULT hr;
3174   LPSPropValue propval = NULL;
3175   ULONG tag;
3176   int yes = 0;
3177   
3178   if (get_gpgollastdecrypted_tag (message, &tag) )
3179     return 0; /* No.  */
3180   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
3181   if (FAILED (hr))
3182     return 0; /* No.  */  
3183   
3184   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
3185     yes = 1;
3186
3187   MAPIFreeBuffer (propval);
3188   return yes;
3189 }
3190
3191
3192 /* Helper to check whether the file name of OBJ is "smime.p7m".
3193    Returns on true if so.  */
3194 static int
3195 has_smime_filename (LPATTACH obj)
3196 {
3197   HRESULT hr;
3198   LPSPropValue propval;
3199   int yes = 0;
3200
3201   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
3202   if (FAILED(hr))
3203     {
3204       hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
3205       if (FAILED(hr))
3206         return 0;
3207     }
3208
3209   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
3210     {
3211       if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
3212         yes = 1;
3213     }
3214   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
3215     {
3216       if (!strcmp (propval->Value.lpszA, "smime.p7m"))
3217         yes = 1;
3218     }
3219   MAPIFreeBuffer (propval);
3220   return yes;
3221 }
3222
3223
3224 /* Copy the MAPI body to a PGPBODY type attachment. */
3225 int
3226 mapi_body_to_attachment (LPMESSAGE message)
3227 {
3228   HRESULT hr;
3229   LPSTREAM instream;
3230   ULONG newpos;
3231   LPATTACH newatt = NULL;
3232   SPropValue prop;
3233   LPSTREAM outstream = NULL;
3234   LPUNKNOWN punk;
3235   char body_filename[] = PGPBODYFILENAME;
3236
3237   instream = mapi_get_body_as_stream (message);
3238   if (!instream)
3239     return -1;
3240
3241   hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
3242   if (hr)
3243     {
3244       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
3245                  SRCNAME, __func__, hr);
3246       goto leave;
3247     }
3248
3249   prop.ulPropTag = PR_ATTACH_METHOD;
3250   prop.Value.ul = ATTACH_BY_VALUE;
3251   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3252   if (hr)
3253     {
3254       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
3255                  SRCNAME, __func__, hr);
3256       goto leave;
3257     }
3258
3259   /* Mark that attachment so that we know why it has been created.  */
3260   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
3261     goto leave;
3262   prop.Value.l = ATTACHTYPE_PGPBODY;
3263   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3264   if (hr)
3265     {
3266       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
3267                  SRCNAME, __func__, "GpgOL Attach Type", hr);
3268       goto leave;
3269     }
3270
3271   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
3272   prop.Value.b = TRUE;
3273   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3274   if (hr)
3275     {
3276       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
3277                  SRCNAME, __func__, hr);
3278       goto leave;
3279     }
3280
3281   prop.ulPropTag = PR_ATTACH_FILENAME_A;
3282   prop.Value.lpszA = body_filename;
3283   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
3284   if (hr)
3285     {
3286       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
3287                  SRCNAME, __func__, hr);
3288       goto leave;
3289     }
3290
3291   punk = (LPUNKNOWN)outstream;
3292   hr = gpgol_openProperty (newatt, PR_ATTACH_DATA_BIN, &IID_IStream, 0,
3293                              MAPI_CREATE|MAPI_MODIFY, &punk);
3294   if (FAILED (hr))
3295     {
3296       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
3297                  SRCNAME, __func__, hr);
3298       goto leave;
3299     }
3300   outstream = (LPSTREAM)punk;
3301
3302   /* Insert a blank line so that our mime parser skips over the mail
3303      headers.  */
3304   hr = outstream->Write ("\r\n", 2, NULL);
3305   if (hr)
3306     {
3307       log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
3308       goto leave;
3309     }
3310
3311   {
3312     ULARGE_INTEGER cb;
3313     cb.QuadPart = 0xffffffffffffffffll;
3314     hr = instream->CopyTo (outstream, cb, NULL, NULL);
3315   }
3316   if (hr)
3317     {
3318       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
3319                  SRCNAME, __func__, hr);
3320       goto leave;
3321     }
3322   hr = outstream->Commit (0);
3323   if (hr)
3324     {
3325       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
3326                  SRCNAME, __func__, hr);
3327       goto leave;
3328     }
3329   gpgol_release (outstream);
3330   outstream = NULL;
3331   hr = newatt->SaveChanges (0);
3332   if (hr)
3333     {
3334       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
3335                  SRCNAME, __func__, hr);
3336       goto leave;
3337     }
3338   gpgol_release (newatt);
3339   newatt = NULL;
3340   hr = mapi_save_changes (message, KEEP_OPEN_READWRITE);
3341
3342  leave:
3343   if (outstream)
3344     {
3345       outstream->Revert ();
3346       gpgol_release (outstream);
3347     }
3348   if (newatt)
3349     gpgol_release (newatt);
3350   gpgol_release (instream);
3351   return hr? -1:0;
3352 }
3353
3354 int
3355 mapi_mark_or_create_moss_attach (LPMESSAGE message, msgtype_t msgtype)
3356 {
3357   int i;
3358   if (msgtype == MSGTYPE_UNKNOWN ||
3359       msgtype == MSGTYPE_GPGOL)
3360     {
3361       return 0;
3362     }
3363
3364   /* First check if we already have one marked. */
3365   mapi_attach_item_t *table = mapi_create_attach_table (message, 0);
3366   int part1 = 0,
3367       part2 = 0;
3368   for (i = 0; table && !table[i].end_of_table; i++)
3369     {
3370       if (table[i].attach_type == ATTACHTYPE_PGPBODY ||
3371           table[i].attach_type == ATTACHTYPE_MOSS ||
3372           table[i].attach_type == ATTACHTYPE_MOSSTEMPL)
3373         {
3374           if (!part1)
3375             {
3376               part1 = i + 1;
3377             }
3378           else if (!part2)
3379             {
3380               /* If we have two MOSS attachments we use
3381                  the second one. */
3382               part2 = i + 1;
3383               break;
3384             }
3385         }
3386     }
3387   if (part1 || part2)
3388     {
3389       /* Found existing moss attachment */
3390       mapi_release_attach_table (table);
3391       /* Remark to ensure that it is hidden. As our revert
3392          code must unhide it so that it is not stored in winmail.dat
3393          but used as the mosstmpl. */
3394       mapi_attach_item_t *item = table - 1 + (part2 ? part2 : part1);
3395       LPATTACH att;
3396       if (message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att) != S_OK)
3397         {
3398           log_error ("%s:%s: can't open attachment at %d",
3399                      SRCNAME, __func__, item->mapipos);
3400           return -1;
3401         }
3402       memdbg_addRef (att);
3403       if (!mapi_test_attach_hidden (att))
3404         {
3405           mapi_set_attach_hidden (att);
3406         }
3407       gpgol_release (att);
3408       if (part2)
3409         return part2;
3410       return part1;
3411     }
3412
3413   if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED ||
3414       msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
3415     {
3416       /* Inline message we need to create body attachment so that we
3417          are able to restore the content. */
3418       if (mapi_body_to_attachment (message))
3419         {
3420           log_error ("%s:%s: Failed to create body attachment.",
3421                      SRCNAME, __func__);
3422           return 0;
3423         }
3424       log_debug ("%s:%s: Created body attachment. Repeating lookup.",
3425                  SRCNAME, __func__);
3426       /* The position of the MOSS attach might change depending on
3427          the attachment count of the mail. So repeat the check to get
3428          the right position. */
3429       return mapi_mark_or_create_moss_attach (message, msgtype);
3430     }
3431   if (!table)
3432     {
3433       log_debug ("%s:%s: Neither pgp inline nor an attachment table.",
3434                  SRCNAME, __func__);
3435       return 0;
3436     }
3437
3438   /* MIME Mails check for S/MIME first. */
3439   for (i = 0; !table[i].end_of_table; i++)
3440     {
3441       if (table[i].content_type
3442           && (!strcmp (table[i].content_type, "application/pkcs7-mime")
3443               || !strcmp (table[i].content_type,
3444                           "application/x-pkcs7-mime"))
3445           && table[i].filename
3446           && !strcmp (table[i].filename, "smime.p7m"))
3447         break;
3448     }
3449   if (!table[i].end_of_table)
3450     {
3451       mapi_mark_moss_attach (message, table + i);
3452       mapi_release_attach_table (table);
3453       return i + 1;
3454     }
3455
3456   /* PGP/MIME or S/MIME stuff.  */
3457   /* Multipart/encrypted message: We expect 2 attachments.
3458      The first one with the version number and the second one
3459      with the ciphertext.  As we don't know wether we are
3460      called the first time, we first try to find these
3461      attachments by looking at all attachments.  Only if this
3462      fails we identify them by their order (i.e. the first 2
3463      attachments) and mark them as part1 and part2.  */
3464   for (i = 0; !table[i].end_of_table; i++); /* Count entries */
3465   if (i >= 2)
3466     {
3467       int part1_idx = -1,
3468           part2_idx = -1;
3469       /* At least 2 attachments but none are marked.  Thus we
3470          assume that this is the first time we see this
3471          message and we will set the mark now if we see
3472          appropriate content types. */
3473       if (table[0].content_type
3474           && !strcmp (table[0].content_type,
3475                       "application/pgp-encrypted"))
3476         part1_idx = 0;
3477       if (table[1].content_type
3478           && !strcmp (table[1].content_type,
3479                       "application/octet-stream"))
3480         part2_idx = 1;
3481       if (part1_idx != -1 && part2_idx != -1)
3482         {
3483           mapi_mark_moss_attach (message, table+part1_idx);
3484           mapi_mark_moss_attach (message, table+part2_idx);
3485           mapi_release_attach_table (table);
3486           return 2;
3487         }
3488     }
3489
3490   if (!table[0].end_of_table && table[1].end_of_table)
3491     {
3492       /* No MOSS flag found in the table but there is only one
3493          attachment.  Due to the message type we know that this is
3494          the original MOSS message.  We mark this attachment as
3495          hidden, so that it won't get displayed.  We further mark
3496          it as our original MOSS attachment so that after parsing
3497          we have a mean to find it again (see above).  */
3498       mapi_mark_moss_attach (message, table + 0);
3499       mapi_release_attach_table (table);
3500       return 1;
3501     }
3502
3503    mapi_release_attach_table (table);
3504    return 0; /* No original attachment - this should not happen.  */
3505 }