Concatenate body parts.
[gpgol.git] / src / mapihelp.cpp
1 /* mapihelp.cpp - Helper functions for MAPI
2  *      Copyright (C) 2005, 2007 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 <windows.h>
25
26 #include "mymapi.h"
27 #include "mymapitags.h"
28 #include "common.h"
29 #include "rfc822parse.h"
30 #include "serpent.h"
31 #include "mapihelp.h"
32
33
34 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
35                                      SRCNAME, __func__, __LINE__); \
36                         } while (0)
37
38
39 /* Print a MAPI property to the log stream. */
40 void
41 log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
42 {
43   HRESULT hr;
44   LPSPropValue propval = NULL;
45   size_t keylen;
46   void *key;
47   char *buf;
48
49   if (!message)
50     return; /* No message: Nop. */
51
52   hr = HrGetOneProp ((LPMAPIPROP)message, prop, &propval);
53   if (FAILED (hr))
54     {
55       log_debug ("%s:%s: HrGetOneProp(%s) failed: hr=%#lx\n",
56                  SRCNAME, __func__, propname, hr);
57       return;
58     }
59     
60   switch ( PROP_TYPE (propval->ulPropTag) )
61     {
62     case PT_BINARY:
63       keylen = propval->Value.bin.cb;
64       key = propval->Value.bin.lpb;
65       log_hexdump (key, keylen, "%s: %20s=", __func__, propname);
66       break;
67
68     case PT_UNICODE:
69       buf = wchar_to_utf8 (propval->Value.lpszW);
70       if (!buf)
71         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
72       else
73         log_debug ("%s: %20s=`%s'", __func__, propname, buf);
74       xfree (buf);
75       break;
76       
77     case PT_STRING8:
78       log_debug ("%s: %20s=`%s'", __func__, propname, propval->Value.lpszA);
79       break;
80
81     case PT_LONG:
82       log_debug ("%s: %20s=%ld", __func__, propname, propval->Value.l);
83       break;
84
85     default:
86       log_debug ("%s:%s: HrGetOneProp(%s) property type %lu not supported\n",
87                  SRCNAME, __func__, propname,
88                  PROP_TYPE (propval->ulPropTag) );
89       return;
90     }
91   MAPIFreeBuffer (propval);
92 }
93
94
95 /* Helper to create a named property. */
96 static ULONG 
97 create_gpgol_tag (LPMESSAGE message, wchar_t *name, const char *func)
98 {
99   HRESULT hr;
100   LPSPropTagArray proparr = NULL;
101   MAPINAMEID mnid, *pmnid;      
102   /* {31805ab8-3e92-11dc-879c-00061b031004}: GpgOL custom properties.  */
103   GUID guid = {0x31805ab8, 0x3e92, 0x11dc, {0x87, 0x9c, 0x00, 0x06,
104                                             0x1b, 0x03, 0x10, 0x04}};
105
106   memset (&mnid, 0, sizeof mnid);
107   mnid.lpguid = &guid;
108   mnid.ulKind = MNID_STRING;
109   mnid.Kind.lpwstrName = name;
110   pmnid = &mnid;
111   hr = message->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &proparr);
112   if (FAILED (hr) || !(proparr->aulPropTag[0] & 0xFFFF0000) ) 
113     {
114       log_error ("%s:%s: can't map GpgOL property: hr=%#lx\n",
115                  SRCNAME, func, hr); 
116       return 0;
117     }
118     
119   return (proparr->aulPropTag[0] & 0xFFFF0000);
120 }
121
122
123
124 /* Return the property tag for GpgOL Attach Type. */
125 int 
126 get_gpgolattachtype_tag (LPMESSAGE message, ULONG *r_tag)
127 {
128   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Attach Type", __func__)))
129     return -1;
130   *r_tag |= PT_LONG;
131   return 0;
132 }
133
134
135 /* Return the property tag for GpgOL Sig Status. */
136 int 
137 get_gpgolsigstatus_tag (LPMESSAGE message, ULONG *r_tag)
138 {
139   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Sig Status", __func__)))
140     return -1;
141   *r_tag |= PT_STRING8;
142   return 0;
143 }
144
145
146 /* Return the property tag for GpgOL Protect IV. */
147 int 
148 get_gpgolprotectiv_tag (LPMESSAGE message, ULONG *r_tag)
149 {
150   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Protect IV", __func__)))
151     return -1;
152   *r_tag |= PT_BINARY;
153   return 0;
154 }
155
156 /* Return the property tag for GpgOL Last Decrypted. */
157 int 
158 get_gpgollastdecrypted_tag (LPMESSAGE message, ULONG *r_tag)
159 {
160   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL Last Decrypted",__func__)))
161     return -1;
162   *r_tag |= PT_BINARY;
163   return 0;
164 }
165
166
167 /* Return the property tag for GpgOL MIME structure. */
168 int 
169 get_gpgolmimeinfo_tag (LPMESSAGE message, ULONG *r_tag)
170 {
171   if (!(*r_tag = create_gpgol_tag (message, L"GpgOL MIME Info", __func__)))
172     return -1;
173   *r_tag |= PT_STRING8;
174   return 0;
175 }
176
177
178
179 /* Set an arbitary header in the message MSG with NAME to the value
180    VAL. */
181 int
182 mapi_set_header (LPMESSAGE msg, const char *name, const char *val)
183 {  
184   HRESULT hr;
185   LPSPropTagArray pProps = NULL;
186   SPropValue pv;
187   MAPINAMEID mnid, *pmnid;      
188   /* {00020386-0000-0000-C000-000000000046}  ->  GUID For X-Headers */
189   GUID guid = {0x00020386, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00,
190                                             0x00, 0x00, 0x00, 0x46} };
191   
192   if (!msg)
193     return -1;
194
195   memset (&mnid, 0, sizeof mnid);
196   mnid.lpguid = &guid;
197   mnid.ulKind = MNID_STRING;
198   mnid.Kind.lpwstrName = utf8_to_wchar (name);
199   pmnid = &mnid;
200   hr = msg->GetIDsFromNames (1, &pmnid, MAPI_CREATE, &pProps);
201   xfree (mnid.Kind.lpwstrName);
202   if (FAILED (hr)) 
203     {
204       log_error ("%s:%s: can't get mapping for header `%s': hr=%#lx\n",
205                  SRCNAME, __func__, name, hr); 
206       return -1;
207     }
208     
209   pv.ulPropTag = (pProps->aulPropTag[0] & 0xFFFF0000) | PT_STRING8;
210   pv.Value.lpszA = (char *)val;
211   hr = HrSetOneProp(msg, &pv);  
212   if (hr)
213     {
214       log_error ("%s:%s: can't set header `%s': hr=%#lx\n",
215                  SRCNAME, __func__, name, hr); 
216       return -1;
217     }
218   return 0;
219 }
220
221
222
223 /* Return the body as a new IStream object.  Returns NULL on failure.
224    The stream returns the body as an ASCII stream (Use mapi_get_body
225    for an UTF-8 value).  */
226 LPSTREAM
227 mapi_get_body_as_stream (LPMESSAGE message)
228 {
229   HRESULT hr;
230   LPSTREAM stream;
231
232   if (!message)
233     return NULL;
234
235   /* We try to get it as an ASCII body.  If this fails we would either
236      need to implement some kind of stream filter to translated to
237      utf-8 or read everyting into a memory buffer and [provide an
238      istream from that memory buffer.  */
239   hr = message->OpenProperty (PR_BODY_A, &IID_IStream, 0, 0, 
240                               (LPUNKNOWN*)&stream);
241   if (hr)
242     {
243       log_debug ("%s:%s: OpenProperty failed: hr=%#lx", SRCNAME, __func__, hr);
244       return NULL;
245     }
246
247   return stream;
248 }
249
250
251
252 /* Return the body of the message in an allocated buffer.  The buffer
253    is guaranteed to be Nul terminated.  The actual length (ie. the
254    strlen()) will be stored at R_NBYTES.  The body will be returned in
255    UTF-8 encoding. Returns NULL if no body is available.  */
256 char *
257 mapi_get_body (LPMESSAGE message, size_t *r_nbytes)
258 {
259   HRESULT hr;
260   LPSPropValue lpspvFEID = NULL;
261   LPSTREAM stream;
262   STATSTG statInfo;
263   ULONG nread;
264   char *body = NULL;
265
266   if (r_nbytes)
267     *r_nbytes = 0;
268   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
269   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
270     { 
271       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
272         {
273         case PT_UNICODE:
274           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
275           if (!body)
276             log_debug ("%s: error converting to utf8\n", __func__);
277           break;
278           
279         case PT_STRING8:
280           body = xstrdup (lpspvFEID->Value.lpszA);
281           break;
282           
283         default:
284           log_debug ("%s: proptag=0x%08lx not supported\n",
285                      __func__, lpspvFEID->ulPropTag);
286           break;
287         }
288       MAPIFreeBuffer (lpspvFEID);
289     }
290   else /* Message is large; use an IStream to read it.  */
291     {
292       hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, 
293                                   (LPUNKNOWN*)&stream);
294       if (hr)
295         {
296           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
297                      SRCNAME, __func__, hr);
298           return NULL;
299         }
300       
301       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
302       if (hr)
303         {
304           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
305           stream->Release ();
306           return NULL;
307         }
308       
309       /* Fixme: We might want to read only the first 1k to decide
310          whether this is actually an OpenPGP message and only then
311          continue reading.  */
312       body = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 2);
313       hr = stream->Read (body, (size_t)statInfo.cbSize.QuadPart, &nread);
314       if (hr)
315         {
316           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
317           xfree (body);
318           stream->Release ();
319           return NULL;
320         }
321       body[nread] = 0;
322       body[nread+1] = 0;
323       if (nread != statInfo.cbSize.QuadPart)
324         {
325           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
326           xfree (body);
327           stream->Release ();
328           return NULL;
329         }
330       stream->Release ();
331       
332       {
333         char *tmp;
334         tmp = wchar_to_utf8 ((wchar_t*)body);
335         if (!tmp)
336           log_debug ("%s: error converting to utf8\n", __func__);
337         else
338           {
339             xfree (body);
340             body = tmp;
341           }
342       }
343     }
344
345   if (r_nbytes)
346     *r_nbytes = strlen (body);
347   return body;
348 }
349
350
351
352 /* Look at the body of the MESSAGE and try to figure out whether this
353    is a supported PGP message.  Returns the new message class on
354    return or NULL if not.  */
355 static char *
356 get_msgcls_from_pgp_lines (LPMESSAGE message)
357 {
358   HRESULT hr;
359   LPSPropValue lpspvFEID = NULL;
360   LPSTREAM stream;
361   STATSTG statInfo;
362   ULONG nread;
363   size_t nbytes;
364   char *body = NULL;
365   char *p;
366   char *msgcls = NULL;
367
368   hr = HrGetOneProp ((LPMAPIPROP)message, PR_BODY, &lpspvFEID);
369   if (SUCCEEDED (hr))  /* Message is small enough to be retrieved directly. */
370     { 
371       switch ( PROP_TYPE (lpspvFEID->ulPropTag) )
372         {
373         case PT_UNICODE:
374           body = wchar_to_utf8 (lpspvFEID->Value.lpszW);
375           if (!body)
376             log_debug ("%s: error converting to utf8\n", __func__);
377           break;
378           
379         case PT_STRING8:
380           body = xstrdup (lpspvFEID->Value.lpszA);
381           break;
382           
383         default:
384           log_debug ("%s: proptag=0x%08lx not supported\n",
385                      __func__, lpspvFEID->ulPropTag);
386           break;
387         }
388       MAPIFreeBuffer (lpspvFEID);
389     }
390   else /* Message is large; use an IStream to read it.  */
391     {
392       hr = message->OpenProperty (PR_BODY, &IID_IStream, 0, 0, 
393                                   (LPUNKNOWN*)&stream);
394       if (hr)
395         {
396           log_debug ("%s:%s: OpenProperty failed: hr=%#lx",
397                      SRCNAME, __func__, hr);
398           return NULL;
399         }
400       
401       hr = stream->Stat (&statInfo, STATFLAG_NONAME);
402       if (hr)
403         {
404           log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
405           stream->Release ();
406           return NULL;
407         }
408       
409       /* We read only the first 1k to decide whether this is actually
410          an OpenPGP armored message .  */
411       nbytes = (size_t)statInfo.cbSize.QuadPart;
412       if (nbytes > 1024*2)
413         nbytes = 1024*2;
414       body = (char*)xmalloc (nbytes + 2);
415       hr = stream->Read (body, nbytes, &nread);
416       if (hr)
417         {
418           log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
419           xfree (body);
420           stream->Release ();
421           return NULL;
422         }
423       body[nread] = 0;
424       body[nread+1] = 0;
425       if (nread != statInfo.cbSize.QuadPart)
426         {
427           log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
428           xfree (body);
429           stream->Release ();
430           return NULL;
431         }
432       stream->Release ();
433       
434       {
435         char *tmp;
436         tmp = wchar_to_utf8 ((wchar_t*)body);
437         if (!tmp)
438           log_debug ("%s: error converting to utf8\n", __func__);
439         else
440           {
441             xfree (body);
442             body = tmp;
443           }
444       }
445     }
446
447   /* The first ~1k of the body of the message is now availble in the
448      utf-8 string BODY.  Walk over it to figure out its type.  */
449   for (p=body; p && *p; p = (p=strchr (p+1, '\n')? (p+1):NULL))
450     if (!strncmp (p, "-----BEGIN PGP ", 15))
451       {
452         if (!strncmp (p+15, "SIGNED MESSAGE-----", 19)
453             && trailing_ws_p (p+15+19))
454           msgcls = xstrdup ("IPM.Note.GpgOL.ClearSigned");
455         else if (!strncmp (p+15, "MESSAGE-----", 12)
456                  && trailing_ws_p (p+15+12))
457           msgcls = xstrdup ("IPM.Note.GpgOL.PGPMessage");
458         break;
459       }
460     else if (!trailing_ws_p (p))
461       break;  /* Text before the PGP message - don't take this as a
462                  proper message.  */
463          
464   xfree (body);
465   return msgcls;
466 }
467
468
469
470 /* This function checks whether MESSAGE requires processing by us and
471    adjusts the message class to our own.  Return true if the message
472    was changed. */
473 int
474 mapi_change_message_class (LPMESSAGE message)
475 {
476   HRESULT hr;
477   SPropValue prop;
478   LPSPropValue propval = NULL;
479   char *newvalue = NULL;
480   int need_save = 0;
481
482   if (!message)
483     return 0; /* No message: Nop. */
484
485   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
486   if (FAILED (hr))
487     {
488       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
489                  SRCNAME, __func__, hr);
490       return 0;
491     }
492     
493   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
494     {
495       const char *s = propval->Value.lpszA;
496
497       if (!strcmp (s, "IPM.Note"))
498         {
499           /* Most message today are of this type.  However a PGP/MIME
500              encrypted message also has this class here.  We need
501              to see whether we can detect such a mail right here and
502              change the message class accordingly. */
503           char *ct, *proto;
504
505           ct = mapi_get_message_content_type (message, &proto, NULL);
506           if (!ct)
507             log_debug ("%s:%s: message has no content type", 
508                        SRCNAME, __func__);
509           else
510             {
511               log_debug ("%s:%s: content type is '%s'", 
512                          SRCNAME, __func__, ct);
513               if (proto)
514                 {
515                   log_debug ("%s:%s:     protocol is '%s'", 
516                              SRCNAME, __func__, proto);
517               
518                   if (!strcmp (ct, "multipart/encrypted")
519                       && !strcmp (proto, "application/pgp-encrypted"))
520                     {
521                       newvalue = xstrdup ("IPM.Note.GpgOL.MultipartEncrypted");
522                     }
523                   else if (!strcmp (ct, "multipart/signed")
524                            && !strcmp (proto, "application/pgp-signature"))
525                     {
526                       /* Sometimes we receive a PGP/MIME signed
527                          message with a class IPM.Note.  */
528                       newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
529                     }
530                   xfree (proto);
531                 }
532               else if (!strcmp (ct, "text/plain"))
533                 {
534                   newvalue = get_msgcls_from_pgp_lines (message);
535                 }
536               
537               xfree (ct);
538             }
539         }
540       else if (opt.enable_smime && !strcmp (s, "IPM.Note.SMIME"))
541         {
542           /* This is an S/MIME opaque encrypted or signed message.
543              Check what it really is.  */
544           char *ct, *smtype;
545
546           ct = mapi_get_message_content_type (message, NULL, &smtype);
547           if (!ct)
548             log_debug ("%s:%s: message has no content type", 
549                        SRCNAME, __func__);
550           else
551             {
552               log_debug ("%s:%s: content type is '%s'", 
553                          SRCNAME, __func__, ct);
554               if (smtype)
555                 {
556                   log_debug ("%s:%s:   smime-type is '%s'", 
557                              SRCNAME, __func__, smtype);
558               
559                   if (!strcmp (ct, "application/pkcs7-mime")
560                       || !strcmp (ct, "application/x-pkcs7-mime"))
561                     {
562                       if (!strcmp (smtype, "signed-data"))
563                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
564                       else if (!strcmp (smtype, "enveloped-data"))
565                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
566                     }
567                   xfree (smtype);
568                 }
569               xfree (ct);
570             }
571           if (!newvalue)
572             newvalue = xstrdup ("IPM.Note.GpgOL");
573         }
574       else if (opt.enable_smime
575                && !strncmp (s, "IPM.Note.SMIME", 14) && (!s[14]||s[14] =='.'))
576         {
577           /* This is "IPM.Note.SMIME.foo" (where ".foo" is optional
578              but the previous condition has already taken care of
579              this).  Note that we can't just insert a new part and
580              keep the SMIME; we need to change the SMIME part of the
581              class name so that Outlook does not process it as an
582              SMIME message. */
583           newvalue = (char*)xmalloc (strlen (s) + 1);
584           strcpy (stpcpy (newvalue, "IPM.Note.GpgOL"), s+14);
585         }
586       else if (opt.enable_smime && !strcmp (s, "IPM.Note.Secure.CexSig"))
587         {
588           /* This is a CryptoEx generated signature. */
589           char *ct, *smtype;
590
591           ct = mapi_get_message_content_type (message, NULL, &smtype);
592           if (!ct)
593             log_debug ("%s:%s: message has no content type", 
594                        SRCNAME, __func__);
595           else
596             {
597               log_debug ("%s:%s: content type is '%s'", 
598                          SRCNAME, __func__, ct);
599               if (smtype)
600                 {
601                   log_debug ("%s:%s:   smime-type is '%s'", 
602                              SRCNAME, __func__, smtype);
603               
604                   if (!strcmp (ct, "application/pkcs7-mime")
605                       || !strcmp (ct, "application/x-pkcs7-mime"))
606                     {
607                       if (!strcmp (smtype, "signed-data"))
608                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
609                       else if (!strcmp (smtype, "enveloped-data"))
610                         newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
611                     }
612                   else if (!strcmp (ct, "application/pkcs7-signature"))
613                     {
614                       newvalue = xstrdup ("IPM.Note.GpgOL.MultipartSigned");
615                     }
616                   xfree (smtype);
617                 }
618               xfree (ct);
619             }
620           if (!newvalue)
621             newvalue = xstrdup ("IPM.Note.GpgOL");
622         }
623     }
624   MAPIFreeBuffer (propval);
625   if (!newvalue)
626     {
627       /* We use our Sig-Status property to mark messages which passed
628          this function.  This helps us to avoid later tests.  */
629       if (!mapi_has_sig_status (message))
630         {
631           mapi_set_sig_status (message, "#");
632           need_save = 1;
633         }
634     }
635   else
636     {
637       prop.ulPropTag = PR_MESSAGE_CLASS_A;
638       prop.Value.lpszA = newvalue; 
639       hr = message->SetProps (1, &prop, NULL);
640       xfree (newvalue);
641       if (hr)
642         {
643           log_error ("%s:%s: can't set message class: hr=%#lx\n",
644                      SRCNAME, __func__, hr);
645           return 0;
646         }
647       need_save = 1;
648     }
649
650   if (need_save)
651     {
652       hr = message->SaveChanges (KEEP_OPEN_READWRITE|FORCE_SAVE);
653       if (hr)
654         {
655           log_error ("%s:%s: SaveChanges() failed: hr=%#lx\n",
656                      SRCNAME, __func__, hr); 
657           return 0;
658         }
659     }
660
661   return 1;
662 }
663
664
665 /* Return the message class.  This function will never return NULL so
666    it is onlyuseful for debugging.  Caller needs to releasse the
667    returned string.  */
668 char *
669 mapi_get_message_class (LPMESSAGE message)
670 {
671   HRESULT hr;
672   LPSPropValue propval = NULL;
673   char *retstr;
674
675   if (!message)
676     return xstrdup ("[No message]");
677   
678   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
679   if (FAILED (hr))
680     {
681       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
682                  SRCNAME, __func__, hr);
683       return xstrdup (hr == MAPI_E_NOT_FOUND?
684                         "[No message class property]":
685                         "[Error getting message class property]");
686     }
687
688   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
689     retstr = xstrdup (propval->Value.lpszA);
690   else
691     retstr = xstrdup ("[Invalid message class property]");
692     
693   MAPIFreeBuffer (propval);
694   return retstr;
695 }
696
697
698
699 /* Return the message type.  This function knows only about our own
700    message types.  Returns MSGTYPE_UNKNOWN for any MESSAGE we have
701    no special support for.  */
702 msgtype_t
703 mapi_get_message_type (LPMESSAGE message)
704 {
705   HRESULT hr;
706   LPSPropValue propval = NULL;
707   msgtype_t msgtype = MSGTYPE_UNKNOWN;
708
709   if (!message)
710     return msgtype; 
711
712   hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
713   if (FAILED (hr))
714     {
715       log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
716                  SRCNAME, __func__, hr);
717       return msgtype;
718     }
719     
720   if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8 )
721     {
722       const char *s = propval->Value.lpszA;
723       if (!strncmp (s, "IPM.Note.GpgOL", 14) && (!s[14] || s[14] =='.'))
724         {
725           s += 14;
726           if (!*s)
727             msgtype = MSGTYPE_GPGOL;
728           else if (!strcmp (s, ".MultipartSigned"))
729             msgtype = MSGTYPE_GPGOL_MULTIPART_SIGNED;
730           else if (!strcmp (s, ".MultipartEncrypted"))
731             msgtype = MSGTYPE_GPGOL_MULTIPART_ENCRYPTED;
732           else if (!strcmp (s, ".OpaqueSigned"))
733             msgtype = MSGTYPE_GPGOL_OPAQUE_SIGNED;
734           else if (!strcmp (s, ".OpaqueEncrypted"))
735             msgtype = MSGTYPE_GPGOL_OPAQUE_ENCRYPTED;
736           else if (!strcmp (s, ".ClearSigned"))
737             msgtype = MSGTYPE_GPGOL_CLEAR_SIGNED;
738           else if (!strcmp (s, ".PGPMessage"))
739             msgtype = MSGTYPE_GPGOL_PGP_MESSAGE;
740           else
741             log_debug ("%s:%s: message class `%s' not supported",
742                        SRCNAME, __func__, s-14);
743         }
744       else if (!strncmp (s, "IPM.Note.SMIME", 14) && (!s[14] || s[14] =='.'))
745         msgtype = MSGTYPE_SMIME;
746     }
747   MAPIFreeBuffer (propval);
748   return msgtype;
749 }
750
751
752 /* This function is pretty useless because IConverterSession won't
753    take attachments into account.  Need to write our own version.  */
754 int
755 mapi_to_mime (LPMESSAGE message, const char *filename)
756 {
757   HRESULT hr;
758   LPCONVERTERSESSION session;
759   LPSTREAM stream;
760
761   hr = CoCreateInstance (CLSID_IConverterSession, NULL, CLSCTX_INPROC_SERVER,
762                          IID_IConverterSession, (void **) &session);
763   if (FAILED (hr))
764     {
765       log_error ("%s:%s: can't create new IConverterSession object: hr=%#lx",
766                  SRCNAME, __func__, hr);
767       return -1;
768     }
769
770
771   hr = OpenStreamOnFile (MAPIAllocateBuffer, MAPIFreeBuffer,
772                          (STGM_CREATE | STGM_READWRITE),
773                          (char*)filename, NULL, &stream); 
774   if (FAILED (hr)) 
775     {
776       log_error ("%s:%s: can't create file `%s': hr=%#lx\n",
777                  SRCNAME, __func__, filename, hr); 
778       hr = -1;
779     }
780   else
781     {
782       hr = session->MAPIToMIMEStm (message, stream, CCSF_SMTP);
783       if (FAILED (hr))
784         {
785           log_error ("%s:%s: MAPIToMIMEStm failed: hr=%#lx",
786                      SRCNAME, __func__, hr);
787           stream->Revert ();
788           hr = -1;
789         }
790       else
791         {
792           stream->Commit (0);
793           hr = 0;
794         }
795
796       stream->Release ();
797     }
798
799   session->Release ();
800   return hr;
801 }
802
803
804 /* Return a binary property in a malloced buffer with its length stored
805    at R_NBYTES.  Returns NULL on error.  */
806 char *
807 mapi_get_binary_prop (LPMESSAGE message, ULONG proptype, size_t *r_nbytes)
808 {
809   HRESULT hr;
810   LPSPropValue propval = NULL;
811   char *data;
812
813   *r_nbytes = 0;
814   hr = HrGetOneProp ((LPMAPIPROP)message, proptype, &propval);
815   if (FAILED (hr))
816     {
817       log_error ("%s:%s: error getting property %#lx: hr=%#lx",
818                  SRCNAME, __func__, proptype, hr);
819       return NULL; 
820     }
821   switch ( PROP_TYPE (propval->ulPropTag) )
822     {
823     case PT_BINARY:
824       /* This is a binary object but we know that it must be plain
825          ASCII due to the armored format.  */
826       data = (char*)xmalloc (propval->Value.bin.cb + 1);
827       memcpy (data, propval->Value.bin.lpb, propval->Value.bin.cb);
828       data[propval->Value.bin.cb] = 0;
829       *r_nbytes = propval->Value.bin.cb;
830       break;
831       
832     default:
833       log_debug ("%s:%s: requested property %#lx has unknown tag %#lx\n",
834                  SRCNAME, __func__, proptype, propval->ulPropTag);
835       data = NULL;
836       break;
837     }
838   MAPIFreeBuffer (propval);
839   return data;
840 }
841
842
843 /* Return the attachment method for attachment OBJ.  In case of error
844    we return 0 which happens not to be defined.  */
845 static int
846 get_attach_method (LPATTACH obj)
847 {
848   HRESULT hr;
849   LPSPropValue propval = NULL;
850   int method ;
851
852   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_METHOD, &propval);
853   if (FAILED (hr))
854     {
855       log_error ("%s:%s: error getting attachment method: hr=%#lx",
856                  SRCNAME, __func__, hr);
857       return 0; 
858     }
859   /* We don't bother checking whether we really get a PT_LONG ulong
860      back; if not the system is seriously damaged and we can't do
861      further harm by returning a possible random value.  */
862   method = propval->Value.l;
863   MAPIFreeBuffer (propval);
864   return method;
865 }
866
867
868
869 /* Return the filename from the attachment as a malloced string.  The
870    encoding we return will be UTF-8, however the MAPI docs declare
871    that MAPI does only handle plain ANSI and thus we don't really care
872    later on.  In fact we would need to convert the filename back to
873    wchar and use the Unicode versions of the file API.  Returns NULL
874    on error or if no filename is available. */
875 static char *
876 get_attach_filename (LPATTACH obj)
877 {
878   HRESULT hr;
879   LPSPropValue propval;
880   char *name = NULL;
881
882   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_LONG_FILENAME, &propval);
883   if (FAILED(hr)) 
884     hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
885   if (FAILED(hr))
886     {
887       log_debug ("%s:%s: no filename property found", SRCNAME, __func__);
888       return NULL;
889     }
890
891   switch ( PROP_TYPE (propval->ulPropTag) )
892     {
893     case PT_UNICODE:
894       name = wchar_to_utf8 (propval->Value.lpszW);
895       if (!name)
896         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
897       break;
898       
899     case PT_STRING8:
900       name = xstrdup (propval->Value.lpszA);
901       break;
902       
903     default:
904       log_debug ("%s:%s: proptag=%#lx not supported\n",
905                  SRCNAME, __func__, propval->ulPropTag);
906       name = NULL;
907       break;
908     }
909   MAPIFreeBuffer (propval);
910   return name;
911 }
912
913
914 /* Return the content-type of the attachment OBJ or NULL if it does
915    not exists.  Caller must free. */
916 static char *
917 get_attach_mime_tag (LPATTACH obj)
918 {
919   HRESULT hr;
920   LPSPropValue propval = NULL;
921   char *name;
922
923   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_MIME_TAG_A, &propval);
924   if (FAILED (hr))
925     {
926       if (hr != MAPI_E_NOT_FOUND)
927         log_error ("%s:%s: error getting attachment's MIME tag: hr=%#lx",
928                    SRCNAME, __func__, hr);
929       return NULL; 
930     }
931   switch ( PROP_TYPE (propval->ulPropTag) )
932     {
933     case PT_UNICODE:
934       name = wchar_to_utf8 (propval->Value.lpszW);
935       if (!name)
936         log_debug ("%s:%s: error converting to utf8\n", SRCNAME, __func__);
937       break;
938       
939     case PT_STRING8:
940       name = xstrdup (propval->Value.lpszA);
941       break;
942       
943     default:
944       log_debug ("%s:%s: proptag=%#lx not supported\n",
945                  SRCNAME, __func__, propval->ulPropTag);
946       name = NULL;
947       break;
948     }
949   MAPIFreeBuffer (propval);
950   return name;
951 }
952
953
954 /* Return the GpgOL Attach Type for attachment OBJ.  Tag needs to be
955    the tag of that property. */
956 static attachtype_t
957 get_gpgolattachtype (LPATTACH obj, ULONG tag)
958 {
959   HRESULT hr;
960   LPSPropValue propval = NULL;
961   attachtype_t retval;
962
963   hr = HrGetOneProp ((LPMAPIPROP)obj, tag, &propval);
964   if (FAILED (hr))
965     {
966       if (hr != MAPI_E_NOT_FOUND)
967         log_error ("%s:%s: error getting GpgOL Attach Type: hr=%#lx",
968                    SRCNAME, __func__, hr);
969       return ATTACHTYPE_UNKNOWN; 
970     }
971   retval = (attachtype_t)propval->Value.l;
972   MAPIFreeBuffer (propval);
973   return retval;
974 }
975
976
977 /* Gather information about attachments and return a new table of
978    attachments.  Caller must release the returned table.s The routine
979    will return NULL in case of an error or if no attachments are
980    available.  With FAST set only some information gets collected. */
981 mapi_attach_item_t *
982 mapi_create_attach_table (LPMESSAGE message, int fast)
983 {    
984   HRESULT hr;
985   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
986   LPMAPITABLE mapitable;
987   LPSRowSet   mapirows;
988   mapi_attach_item_t *table; 
989   unsigned int pos, n_attach;
990   ULONG moss_tag;
991
992   if (get_gpgolattachtype_tag (message, &moss_tag) )
993     return NULL;
994
995   /* Open the attachment table.  */
996   hr = message->GetAttachmentTable (0, &mapitable);
997   if (FAILED (hr))
998     {
999       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
1000                  SRCNAME, __func__, hr);
1001       return NULL;
1002     }
1003       
1004   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
1005                        NULL, NULL, 0, &mapirows);
1006   if (FAILED (hr))
1007     {
1008       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
1009                  SRCNAME, __func__, hr);
1010       mapitable->Release ();
1011       return NULL;
1012     }
1013   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
1014
1015   log_debug ("%s:%s: message has %u attachments\n",
1016              SRCNAME, __func__, n_attach);
1017   if (!n_attach)
1018     {
1019       FreeProws (mapirows);
1020       mapitable->Release ();
1021       return NULL;
1022     }
1023
1024   /* Allocate our own table.  */
1025   table = (mapi_attach_item_t *)xcalloc (n_attach+1, sizeof *table);
1026   for (pos=0; pos < n_attach; pos++) 
1027     {
1028       LPATTACH att;
1029
1030       if (mapirows->aRow[pos].cValues < 1)
1031         {
1032           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
1033           table[pos].mapipos = -1;
1034           continue;
1035         }
1036       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
1037         {
1038           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
1039           table[pos].mapipos = -1;
1040           continue;
1041         }
1042       table[pos].mapipos = mapirows->aRow[pos].lpProps[0].Value.l;
1043
1044       hr = message->OpenAttach (table[pos].mapipos, NULL,
1045                                 MAPI_BEST_ACCESS, &att);        
1046       if (FAILED (hr))
1047         {
1048           log_error ("%s:%s: can't open attachment %d (%d): hr=%#lx",
1049                      SRCNAME, __func__, pos, table[pos].mapipos, hr);
1050           table[pos].mapipos = -1;
1051           continue;
1052         }
1053
1054       table[pos].method = get_attach_method (att);
1055       table[pos].filename = fast? NULL : get_attach_filename (att);
1056       table[pos].content_type = fast? NULL : get_attach_mime_tag (att);
1057       if (table[pos].content_type)
1058         {
1059           char *p = strchr (table[pos].content_type, ';');
1060           if (p)
1061             {
1062               *p++ = 0;
1063               trim_trailing_spaces (table[pos].content_type);
1064               while (strchr (" \t\r\n", *p))
1065                 p++;
1066               trim_trailing_spaces (p);
1067               table[pos].content_type_parms = p;
1068             }
1069         }
1070       table[pos].attach_type = get_gpgolattachtype (att, moss_tag);
1071       att->Release ();
1072     }
1073   table[0].private_mapitable = mapitable;
1074   FreeProws (mapirows);
1075   table[pos].end_of_table = 1;
1076   mapitable = NULL;
1077
1078   if (fast)
1079     {
1080       log_debug ("%s:%s: attachment info: not shown due to fast flag\n",
1081                  SRCNAME, __func__);
1082     }
1083   else
1084     {
1085       log_debug ("%s:%s: attachment info:\n", SRCNAME, __func__);
1086       for (pos=0; !table[pos].end_of_table; pos++)
1087         {
1088           log_debug ("\t%d mt=%d fname=`%s' ct=`%s' ct_parms=`%s'\n",
1089                      table[pos].mapipos,
1090                      table[pos].attach_type,
1091                      table[pos].filename, table[pos].content_type,
1092                      table[pos].content_type_parms);
1093         }
1094     }
1095
1096   return table;
1097 }
1098
1099
1100 /* Release a table as created by mapi_create_attach_table. */
1101 void
1102 mapi_release_attach_table (mapi_attach_item_t *table)
1103 {
1104   unsigned int pos;
1105   LPMAPITABLE mapitable;
1106
1107   if (!table)
1108     return;
1109
1110   mapitable = (LPMAPITABLE)table[0].private_mapitable;
1111   if (mapitable)
1112     mapitable->Release ();
1113   for (pos=0; !table[pos].end_of_table; pos++)
1114     {
1115       xfree (table[pos].filename);
1116       xfree (table[pos].content_type);
1117     }
1118   xfree (table);
1119 }
1120
1121
1122 /* Return an attachment as a new IStream object.  Returns NULL on
1123    failure. */
1124 LPSTREAM
1125 mapi_get_attach_as_stream (LPMESSAGE message, mapi_attach_item_t *item)
1126 {
1127   HRESULT hr;
1128   LPATTACH att;
1129   LPSTREAM stream;
1130
1131   if (!item || item->end_of_table || item->mapipos == -1)
1132     return NULL;
1133
1134   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1135   if (FAILED (hr))
1136     {
1137       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1138                  SRCNAME, __func__, item->mapipos, hr);
1139       return NULL;
1140     }
1141   if (item->method != ATTACH_BY_VALUE)
1142     {
1143       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1144       att->Release ();
1145       return NULL;
1146     }
1147
1148   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1149                           0, 0, (LPUNKNOWN*) &stream);
1150   if (FAILED (hr))
1151     {
1152       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1153                  SRCNAME, __func__, hr);
1154       att->Release ();
1155       return NULL;
1156     }
1157
1158   att->Release ();
1159
1160   return stream;
1161 }
1162
1163
1164 /* Return a malloced buffer with the content of the attachment. If
1165    R_NBYTES is not NULL the number of bytes will get stored there.
1166    ATT must have an attachment method of ATTACH_BY_VALUE.  Returns
1167    NULL on error.  If UNPROTECT is set and the appropriate crypto
1168    attribute is available, the function returns the unprotected
1169    version of the atatchment. */
1170 static char *
1171 attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect, 
1172                   int *r_was_protected)
1173 {
1174   HRESULT hr;
1175   LPSTREAM stream;
1176   STATSTG statInfo;
1177   ULONG nread;
1178   char *buffer;
1179   symenc_t symenc = NULL;
1180
1181   if (r_was_protected)
1182     *r_was_protected = 0;
1183
1184   if (unprotect)
1185     {
1186       ULONG tag;
1187       char *iv;
1188       size_t ivlen;
1189
1190       if (!get_gpgolprotectiv_tag ((LPMESSAGE)att, &tag) 
1191           && (iv = mapi_get_binary_prop ((LPMESSAGE)att, tag, &ivlen)))
1192         {
1193           symenc = symenc_open (get_128bit_session_key (), 16, iv, ivlen);
1194           xfree (iv);
1195           if (!symenc)
1196             log_error ("%s:%s: can't open encryption context", 
1197                        SRCNAME, __func__);
1198         }
1199     }
1200   
1201
1202   hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
1203                           0, 0, (LPUNKNOWN*) &stream);
1204   if (FAILED (hr))
1205     {
1206       log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
1207                  SRCNAME, __func__, hr);
1208       return NULL;
1209     }
1210
1211   hr = stream->Stat (&statInfo, STATFLAG_NONAME);
1212   if ( hr != S_OK )
1213     {
1214       log_error ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
1215       stream->Release ();
1216       return NULL;
1217     }
1218       
1219   /* Allocate one byte more so that we can terminate the string.  */
1220   buffer = (char*)xmalloc ((size_t)statInfo.cbSize.QuadPart + 1);
1221
1222   hr = stream->Read (buffer, (size_t)statInfo.cbSize.QuadPart, &nread);
1223   if ( hr != S_OK )
1224     {
1225       log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
1226       xfree (buffer);
1227       stream->Release ();
1228       return NULL;
1229     }
1230   if (nread != statInfo.cbSize.QuadPart)
1231     {
1232       log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
1233       xfree (buffer);
1234       buffer = NULL;
1235     }
1236   stream->Release ();
1237
1238   if (buffer && symenc)
1239     {
1240       symenc_cfb_decrypt (symenc, buffer, buffer, nread);
1241       if (nread < 16 || memcmp (buffer, "GpgOL attachment", 16))
1242         {
1243           xfree (buffer);
1244           buffer = native_to_utf8 
1245             (_("[The content of this message is not visible because it has "
1246                "been decrypted by another Outlook session.  Use the "
1247                "\"decrypt/verify\" command to make it visible]"));
1248           nread = strlen (buffer);
1249         }
1250       else
1251         {
1252           memmove (buffer, buffer+16, nread-16);
1253           nread -= 16;
1254           if (r_was_protected)
1255             *r_was_protected = 1;
1256         }
1257     }
1258
1259   /* Make sure that the buffer is a C string.  */
1260   if (buffer)
1261     buffer[nread] = 0;
1262
1263   symenc_close (symenc);
1264   if (r_nbytes)
1265     *r_nbytes = nread;
1266   return buffer;
1267 }
1268
1269
1270
1271 /* Return an attachment as a malloced buffer; The size of the buffer
1272    will be stored at R_NBYTES.  Returns NULL on failure. */
1273 char *
1274 mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes)
1275 {
1276   HRESULT hr;
1277   LPATTACH att;
1278   char *buffer;
1279
1280   if (!item || item->end_of_table || item->mapipos == -1)
1281     return NULL;
1282
1283   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1284   if (FAILED (hr))
1285     {
1286       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1287                  SRCNAME, __func__, item->mapipos, hr);
1288       return NULL;
1289     }
1290   if (item->method != ATTACH_BY_VALUE)
1291     {
1292       log_error ("%s:%s: attachment: method not supported", SRCNAME, __func__);
1293       att->Release ();
1294       return NULL;
1295     }
1296
1297   buffer = attach_to_buffer (att, r_nbytes, 0, NULL);
1298   att->Release ();
1299
1300   return buffer;
1301 }
1302
1303
1304 /* Mark this attachment as the orginal MOSS message.  We set a custom
1305    property as weel ast the hidden hidden flag on ot..  */
1306 int 
1307 mapi_mark_moss_attach (LPMESSAGE message, mapi_attach_item_t *item)
1308 {
1309   int retval = -1;
1310   HRESULT hr;
1311   LPATTACH att;
1312   SPropValue prop;
1313
1314   if (!item || item->end_of_table || item->mapipos == -1)
1315     return -1;
1316
1317   hr = message->OpenAttach (item->mapipos, NULL, MAPI_BEST_ACCESS, &att);
1318   if (FAILED (hr))
1319     {
1320       log_error ("%s:%s: can't open attachment at %d: hr=%#lx",
1321                  SRCNAME, __func__, item->mapipos, hr);
1322       return -1;
1323     }
1324
1325   if (FAILED (hr)) 
1326     {
1327       log_error ("%s:%s: can't map %s property: hr=%#lx\n",
1328                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1329       goto leave;
1330     }
1331     
1332   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
1333     goto leave;
1334   prop.Value.l = ATTACHTYPE_MOSS;
1335   hr = HrSetOneProp (att, &prop);       
1336   if (hr)
1337     {
1338       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1339                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
1340       return false;
1341     }
1342
1343   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
1344   prop.Value.b = TRUE;
1345   hr = HrSetOneProp (att, &prop);
1346   if (hr)
1347     {
1348       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
1349                  SRCNAME, __func__, hr); 
1350       goto leave;
1351     }
1352   
1353
1354   hr = att->SaveChanges (KEEP_OPEN_READWRITE);
1355   if (hr)
1356     {
1357       log_error ("%s:%s: SaveChanges(attachment) failed: hr=%#lx\n",
1358                  SRCNAME, __func__, hr); 
1359       goto leave;
1360     }
1361   
1362   retval = 0;
1363     
1364  leave:
1365   att->Release ();
1366   return retval;
1367 }
1368
1369
1370
1371 /* Returns True if MESSAGE has the GpgOL Sig Status property.  */
1372 int
1373 mapi_has_sig_status (LPMESSAGE msg)
1374 {
1375   HRESULT hr;
1376   LPSPropValue propval = NULL;
1377   ULONG tag;
1378   int yes;
1379
1380   if (get_gpgolsigstatus_tag (msg, &tag) )
1381     return 0; /* Error:  Assume No.  */
1382   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1383   if (FAILED (hr))
1384     return 0; /* No.  */  
1385   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1386     yes = 1;
1387   else
1388     yes = 0;
1389
1390   MAPIFreeBuffer (propval);
1391   return yes;
1392 }
1393
1394
1395 /* Returns True if MESSAGE has a GpgOL Sig Status property and that it
1396    is not set to unchecked.  */
1397 int
1398 mapi_test_sig_status (LPMESSAGE msg)
1399 {
1400   HRESULT hr;
1401   LPSPropValue propval = NULL;
1402   ULONG tag;
1403   int yes;
1404
1405   if (get_gpgolsigstatus_tag (msg, &tag) )
1406     return 0; /* Error:  Assume No.  */
1407   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1408   if (FAILED (hr))
1409     return 0; /* No.  */  
1410   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1411     yes = !(propval->Value.lpszA && !strcmp (propval->Value.lpszA, "?"));
1412   else
1413     yes = 0;
1414
1415   MAPIFreeBuffer (propval);
1416   return yes;
1417 }
1418
1419
1420 /* Return the signature status as an allocated string.  Will never
1421    return NULL.  */
1422 char *
1423 mapi_get_sig_status (LPMESSAGE msg)
1424 {
1425   HRESULT hr;
1426   LPSPropValue propval = NULL;
1427   ULONG tag;
1428   char *retstr;
1429
1430   if (get_gpgolsigstatus_tag (msg, &tag) )
1431     return xstrdup ("[Error getting tag for sig status]");
1432   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1433   if (FAILED (hr))
1434     return xstrdup ("");
1435   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1436     retstr = xstrdup (propval->Value.lpszA);
1437   else
1438     retstr = xstrdup ("[Sig status has an invalid type]");
1439
1440   MAPIFreeBuffer (propval);
1441   return retstr;
1442 }
1443
1444
1445
1446
1447 /* Set the signature status property to STATUS_STRING.  There are a
1448    few special values:
1449
1450      "#" The message is not of interest to us.
1451      "@" The message has been created and signed or encrypted by us.
1452      "?" The signature status has not been checked.
1453      "!" The signature verified okay 
1454      "~" The signature was not fully verified.
1455      "-" The signature is bad
1456
1457    Note that this function does not call SaveChanges.  */
1458 int 
1459 mapi_set_sig_status (LPMESSAGE message, const char *status_string)
1460 {
1461   HRESULT hr;
1462   SPropValue prop;
1463
1464   if (get_gpgolsigstatus_tag (message, &prop.ulPropTag) )
1465     return -1;
1466   prop.Value.lpszA = xstrdup (status_string);
1467   hr = HrSetOneProp (message, &prop);   
1468   xfree (prop.Value.lpszA);
1469   if (hr)
1470     {
1471       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
1472                  SRCNAME, __func__, "GpgOL Sig Status", hr); 
1473       return -1;
1474     }
1475
1476   return 0;
1477 }
1478
1479
1480 /* Return the MIME info as an allocated string.  Will never return
1481    NULL.  */
1482 char *
1483 mapi_get_mime_info (LPMESSAGE msg)
1484 {
1485   HRESULT hr;
1486   LPSPropValue propval = NULL;
1487   ULONG tag;
1488   char *retstr;
1489
1490   if (get_gpgolmimeinfo_tag (msg, &tag) )
1491     return xstrdup ("[Error getting tag for MIME info]");
1492   hr = HrGetOneProp ((LPMAPIPROP)msg, tag, &propval);
1493   if (FAILED (hr))
1494     return xstrdup ("");
1495   if (PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1496     retstr = xstrdup (propval->Value.lpszA);
1497   else
1498     retstr = xstrdup ("[MIME info has an invalid type]");
1499
1500   MAPIFreeBuffer (propval);
1501   return retstr;
1502 }
1503
1504
1505
1506
1507 /* Helper for mapi_get_msg_content_type() */
1508 static int
1509 get_message_content_type_cb (void *dummy_arg,
1510                              rfc822parse_event_t event, rfc822parse_t msg)
1511 {
1512   if (event == RFC822PARSE_T2BODY)
1513     return 42; /* Hack to stop the parsing after having read the
1514                   outher headers. */
1515   return 0;
1516 }
1517
1518
1519 /* Return Content-Type of the current message.  This one is taken
1520    directly from the rfc822 header.  If R_PROTOCOL is not NULL a
1521    string with the protocol parameter will be stored at this address,
1522    if no protocol is given NULL will be stored.  If R_SMTYPE is not
1523    NULL a string with the smime-type parameter will be stored there.
1524    Caller must release all returned strings.  */
1525 char *
1526 mapi_get_message_content_type (LPMESSAGE message,
1527                                char **r_protocol, char **r_smtype)
1528 {
1529   HRESULT hr;
1530   LPSPropValue propval = NULL;
1531   rfc822parse_t msg;
1532   const char *header_lines, *s;
1533   rfc822parse_field_t ctx;
1534   size_t length;
1535   char *retstr = NULL;
1536   
1537   if (r_protocol)
1538     *r_protocol = NULL;
1539   if (r_smtype)
1540     *r_smtype = NULL;
1541
1542   hr = HrGetOneProp ((LPMAPIPROP)message,
1543                      PR_TRANSPORT_MESSAGE_HEADERS_A, &propval);
1544   if (FAILED (hr))
1545     {
1546       log_error ("%s:%s: error getting the headers lines: hr=%#lx",
1547                  SRCNAME, __func__, hr);
1548       return NULL; 
1549     }
1550   if (PROP_TYPE (propval->ulPropTag) != PT_STRING8)
1551     {
1552       /* As per rfc822, header lines must be plain ascii, so no need
1553          to cope withy unicode etc. */
1554       log_error ("%s:%s: proptag=%#lx not supported\n",
1555                  SRCNAME, __func__, propval->ulPropTag);
1556       MAPIFreeBuffer (propval);
1557       return NULL;
1558     }
1559   header_lines = propval->Value.lpszA;
1560
1561   /* Read the headers into an rfc822 object. */
1562   msg = rfc822parse_open (get_message_content_type_cb, NULL);
1563   if (!msg)
1564     {
1565       log_error ("%s:%s: rfc822parse_open failed\n", SRCNAME, __func__);
1566       MAPIFreeBuffer (propval);
1567       return NULL;
1568     }
1569   
1570   while ((s = strchr (header_lines, '\n')))
1571     {
1572       length = (s - header_lines);
1573       if (length && s[-1] == '\r')
1574         length--;
1575       rfc822parse_insert (msg, (const unsigned char*)header_lines, length);
1576       header_lines = s+1;
1577     }
1578   
1579   /* Parse the content-type field. */
1580   ctx = rfc822parse_parse_field (msg, "Content-Type", -1);
1581   if (ctx)
1582     {
1583       const char *s1, *s2;
1584       s1 = rfc822parse_query_media_type (ctx, &s2);
1585       if (s1)
1586         {
1587           retstr = (char*)xmalloc (strlen (s1) + 1 + strlen (s2) + 1);
1588           strcpy (stpcpy (stpcpy (retstr, s1), "/"), s2);
1589
1590           if (r_protocol)
1591             {
1592               s = rfc822parse_query_parameter (ctx, "protocol", 0);
1593               if (s)
1594                 *r_protocol = xstrdup (s);
1595             }
1596           if (r_smtype)
1597             {
1598               s = rfc822parse_query_parameter (ctx, "smime-type", 0);
1599               if (s)
1600                 *r_smtype = xstrdup (s);
1601             }
1602         }
1603       rfc822parse_release_field (ctx);
1604     }
1605
1606   rfc822parse_close (msg);
1607   MAPIFreeBuffer (propval);
1608   return retstr;
1609 }
1610
1611
1612 /* Returns True if MESSAGE has a GpgOL Last Decrypted property with any value.
1613    This indicates that there sghould be no PR_BODY tag.  */
1614 int
1615 mapi_has_last_decrypted (LPMESSAGE message)
1616 {
1617   HRESULT hr;
1618   LPSPropValue propval = NULL;
1619   ULONG tag;
1620   int yes = 0;
1621   
1622   if (get_gpgollastdecrypted_tag (message, &tag) )
1623     return 0; /* No.  */
1624   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1625   if (FAILED (hr))
1626     return 0; /* No.  */  
1627   
1628   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY)
1629     yes = 1;
1630
1631   MAPIFreeBuffer (propval);
1632   return yes;
1633 }
1634
1635
1636 /* Returns True if MESSAGE has a GpgOL Last Decrypted property and
1637    that matches the curren sessiobn. */
1638 int
1639 mapi_test_last_decrypted (LPMESSAGE message)
1640 {
1641   HRESULT hr;
1642   LPSPropValue propval = NULL;
1643   ULONG tag;
1644   int yes = 0;
1645
1646   if (get_gpgollastdecrypted_tag (message, &tag) )
1647     return 0; /* No.  */
1648   hr = HrGetOneProp ((LPMAPIPROP)message, tag, &propval);
1649   if (FAILED (hr))
1650     return 0; /* No.  */  
1651
1652   if (PROP_TYPE (propval->ulPropTag) == PT_BINARY
1653       && propval->Value.bin.cb == 8
1654       && !memcmp (propval->Value.bin.lpb, get_64bit_session_marker (), 8) )
1655     yes = 1;
1656
1657   MAPIFreeBuffer (propval);
1658   return yes;
1659 }
1660
1661
1662
1663 /* Helper for mapi_get_gpgol_body_attachment.  */
1664 static int
1665 has_gpgol_body_name (LPATTACH obj)
1666 {
1667   HRESULT hr;
1668   LPSPropValue propval;
1669   int yes = 0;
1670
1671   hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
1672   if (FAILED(hr))
1673     return 0;
1674
1675   if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
1676     {
1677       if (!wcscmp (propval->Value.lpszW, L"gpgol000.txt"))
1678         yes = 1;
1679       else if (!wcscmp (propval->Value.lpszW, L"gpgol000.htm"))
1680         yes = 2;
1681     }
1682   else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
1683     {
1684       if (!strcmp (propval->Value.lpszA, "gpgol000.txt"))
1685         yes = 1;
1686       else if (!strcmp (propval->Value.lpszA, "gpgol000.htm"))
1687         yes = 2;
1688     }
1689   MAPIFreeBuffer (propval);
1690   return yes;
1691 }
1692
1693
1694 /* Return the content of the body attachment of MESSAGE.  The body
1695    attachment is a hidden attachment created by us for later display.
1696    If R_NBYTES is not NULL the number of bytes in the returned buffer
1697    is stored there.  If R_ISHTML is not NULL a flag indicating whether
1698    the HTML is html formatted is stored there.  If R_PROTECTED is not
1699    NULL a flag indicating whether the message was protected is stored
1700    there.  If no body attachment can be found or on any other error
1701    NULL is returned.  Caller must free the returned string. */
1702 char *
1703 mapi_get_gpgol_body_attachment (LPMESSAGE message, size_t *r_nbytes, 
1704                                 int *r_ishtml, int *r_protected)
1705 {    
1706   HRESULT hr;
1707   SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
1708   LPMAPITABLE mapitable;
1709   LPSRowSet   mapirows;
1710   unsigned int pos, n_attach;
1711   ULONG moss_tag;
1712   char *body = NULL;
1713   int bodytype;
1714
1715   if (r_ishtml)
1716     *r_ishtml = 0;
1717   if (r_protected)
1718     *r_protected = 0;
1719
1720   if (get_gpgolattachtype_tag (message, &moss_tag) )
1721     return NULL;
1722
1723   hr = message->GetAttachmentTable (0, &mapitable);
1724   if (FAILED (hr))
1725     {
1726       log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
1727                  SRCNAME, __func__, hr);
1728       return NULL;
1729     }
1730       
1731   hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
1732                        NULL, NULL, 0, &mapirows);
1733   if (FAILED (hr))
1734     {
1735       log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
1736                  SRCNAME, __func__, hr);
1737       mapitable->Release ();
1738       return NULL;
1739     }
1740   n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
1741   if (!n_attach)
1742     {
1743       FreeProws (mapirows);
1744       mapitable->Release ();
1745       log_debug ("%s:%s: No attachments at all", SRCNAME, __func__);
1746       return NULL;
1747     }
1748   log_debug ("%s:%s: message has %u attachments\n",
1749              SRCNAME, __func__, n_attach);
1750
1751   for (pos=0; pos < n_attach; pos++) 
1752     {
1753       LPATTACH att;
1754
1755       if (mapirows->aRow[pos].cValues < 1)
1756         {
1757           log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
1758           continue;
1759         }
1760       if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
1761         {
1762           log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
1763           continue;
1764         }
1765       hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
1766                                 NULL, MAPI_BEST_ACCESS, &att);  
1767       if (FAILED (hr))
1768         {
1769           log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
1770                      SRCNAME, __func__, pos, 
1771                      mapirows->aRow[pos].lpProps[0].Value.l, hr);
1772           continue;
1773         }
1774       if ((bodytype=has_gpgol_body_name (att))
1775            && get_gpgolattachtype (att, moss_tag) == ATTACHTYPE_FROMMOSS)
1776         {
1777           if (get_attach_method (att) == ATTACH_BY_VALUE)
1778             body = attach_to_buffer (att, r_nbytes, 1, r_protected);
1779           att->Release ();
1780           if (r_ishtml)
1781             *r_ishtml = (bodytype == 2);
1782           break;
1783         }
1784       att->Release ();
1785     }
1786   FreeProws (mapirows);
1787   mapitable->Release ();
1788   if (!body)
1789     {
1790       log_error ("%s:%s: no suitable body attachment found", SRCNAME,__func__);
1791       body = native_to_utf8 (_("[The content of this message is not visible"
1792                                " due to an processing error in GpgOL.]"));
1793     }
1794   
1795   return body;
1796 }
1797