7993f31290adaa9b537ab6616af0f53de6d291a1
[gpgol.git] / src / message.cpp
1 /* message.cpp - Functions for message handling
2  *      Copyright (C) 2006, 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 #include <windows.h>
24
25 #include "mymapi.h"
26 #include "mymapitags.h"
27 #include "myexchext.h"
28 #include "common.h"
29 #include "mapihelp.h"
30 #include "mimeparser.h"
31 #include "mimemaker.h"
32 #include "display.h"
33 #include "message.h"
34
35 #define TRACEPOINT() do { log_debug ("%s:%s:%d: tracepoint\n", \
36                                      SRCNAME, __func__, __LINE__); \
37                         } while (0)
38
39
40 static void 
41 ul_release (LPVOID punk, const char *func)
42 {
43   ULONG res;
44   
45   if (!punk)
46     return;
47   res = UlRelease (punk);
48   if (opt.enable_debug & DBG_MEMORY)
49     log_debug ("%s:%s: UlRelease(%p) had %lu references\n", 
50                SRCNAME, func, punk, res);
51 }
52
53
54 /* A helper function used by OnRead and OnOpen to dispatch the
55    message.  Returns true if the message has been processed.  */
56 bool
57 message_incoming_handler (LPMESSAGE message, HWND hwnd)
58 {
59   bool retval = false;
60   msgtype_t msgtype;
61   int pass = 0;
62
63  retry:
64   pass++;
65   msgtype = mapi_get_message_type (message);
66   switch (msgtype)
67     {
68     case MSGTYPE_UNKNOWN: 
69       /* If this message has never passed our change message class
70          code it won't have an unknown msgtype _and_ no sig status
71          flag.  Thus we look at the message class now and change it if
72          required.  It won't get displayed correctly right away but a
73          latter decrypt command or when viewed a second time all has
74          been set.  Note that we should have similar code for some
75          message classes in GpgolUserEvents:OnSelectionChange; but
76          tehre are a couiple of problems.  */
77       if (pass == 1 && !mapi_has_sig_status (message))
78         {
79           log_debug ("%s:%s: message class not yet checked - doing now\n",
80                      SRCNAME, __func__);
81           if (mapi_change_message_class (message, 0))
82             goto retry;
83         }
84       break;
85     case MSGTYPE_SMIME:
86       if (pass == 1 && opt.enable_smime)
87         {
88           log_debug ("%s:%s: message class not checked with smime enabled "
89                      "- doing now\n", SRCNAME, __func__);
90           if (mapi_change_message_class (message, 0))
91             goto retry;
92         }
93       break;
94     case MSGTYPE_GPGOL:
95       log_debug ("%s:%s: ignoring unknown message of original SMIME class\n",
96                  SRCNAME, __func__);
97       break;
98     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
99       log_debug ("%s:%s: processing multipart signed message\n", 
100                  SRCNAME, __func__);
101       retval = true;
102       message_verify (message, msgtype, 0, hwnd);
103       break;
104     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
105       log_debug ("%s:%s: processing multipart encrypted message\n",
106                  SRCNAME, __func__);
107       retval = true;
108       message_decrypt (message, msgtype, 0, hwnd);
109       break;
110     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
111       log_debug ("%s:%s: processing opaque signed message\n", 
112                  SRCNAME, __func__);
113       retval = true;
114       message_verify (message, msgtype, 0, hwnd);
115       break;
116     case MSGTYPE_GPGOL_CLEAR_SIGNED:
117       log_debug ("%s:%s: processing clear signed pgp message\n", 
118                  SRCNAME, __func__);
119       retval = true;
120       message_verify (message, msgtype, 0, hwnd);
121       break;
122     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
123       log_debug ("%s:%s: processing opaque encrypted message\n",
124                  SRCNAME, __func__);
125       retval = true;
126       message_decrypt (message, msgtype, 0, hwnd);
127       break;
128     case MSGTYPE_GPGOL_PGP_MESSAGE:
129       log_debug ("%s:%s: processing pgp message\n", SRCNAME, __func__);
130       retval = true;
131       message_decrypt (message, msgtype, 0, hwnd);
132       break;
133     }
134
135   return retval;
136 }
137
138
139 /* Common Code used by OnReadComplete and OnOpenComplete to display a
140    modified message.   Returns true if the message was encrypted.  */
141 bool
142 message_display_handler (LPEXCHEXTCALLBACK eecb, HWND hwnd)
143 {
144   int err;
145   HRESULT hr;
146   LPMESSAGE message = NULL;
147   LPMDB mdb = NULL;
148   int ishtml, wasprotected = false;
149   char *body;
150   
151   hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
152   if (SUCCEEDED (hr))
153     {
154       /* (old: If the message was protected we don't allow a fallback to the
155          OOM display methods.)  Now: As it is difficult to find the
156          actual window we now use the OOM display always.  */
157       err = mapi_get_gpgol_body_attachment (message, &body, NULL, 
158                                             &ishtml, &wasprotected);
159       if (!err && body)
160         update_display (hwnd, /*wasprotected? NULL:*/ eecb, ishtml, body);
161       else
162         update_display (hwnd, NULL, 0, 
163                         _("[Crypto operation failed - "
164                           "can't show the body of the message]"));
165       xfree (body);
166   
167       /*  put_outlook_property (eecb, "EncryptedStatus", "MyStatus"); */
168     }
169   else
170     log_debug_w32 (hr, "%s:%s: error getting message", SRCNAME, __func__);
171
172   ul_release (message, __func__);
173   ul_release (mdb, __func__);
174
175   return !!wasprotected;
176 }
177
178
179 /* Helper for message_wipe_body_cruft.  */
180 static void
181 do_wipe_body (LPMESSAGE message)
182 {
183   HRESULT hr;
184   SPropTagArray proparray;
185   int anyokay = 0;
186   
187   proparray.cValues = 1;
188   proparray.aulPropTag[0] = PR_BODY;
189   hr = message->DeleteProps (&proparray, NULL);
190   if (hr)
191     log_debug_w32 (hr, "%s:%s: deleting PR_BODY failed", SRCNAME, __func__);
192   else
193     anyokay++;
194             
195   proparray.cValues = 1;
196   proparray.aulPropTag[0] = PR_BODY_HTML;
197   message->DeleteProps (&proparray, NULL);
198   if (hr)
199     log_debug_w32 (hr, "%s:%s: deleting PR_BODY_HTML failed", 
200                    SRCNAME, __func__);
201   else
202     anyokay++;
203   
204   if (anyokay)
205     {
206       hr = message->SaveChanges (KEEP_OPEN_READWRITE);
207       if (hr)
208         log_error_w32 (hr, "%s:%s: SaveChanges failed", SRCNAME, __func__); 
209       else
210         log_debug ("%s:%s: SaveChanges succeded; body cruft removed",
211                    SRCNAME, __func__); 
212     }
213 }
214
215
216 /* If the current message is an encrypted one remove the body
217    properties which might have come up due to OL internal
218    syncronization and a failing olDiscard feature.  */
219 void
220 message_wipe_body_cruft (LPEXCHEXTCALLBACK eecb)
221 {
222   
223   HRESULT hr;
224   LPMESSAGE message = NULL;
225   LPMDB mdb = NULL;
226       
227   log_debug ("%s:%s: enter", SRCNAME, __func__);
228   hr = eecb->GetObject (&mdb, (LPMAPIPROP *)&message);
229   if (SUCCEEDED (hr))
230     {
231       switch (mapi_get_message_type (message))
232         {
233         case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
234         case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
235           {
236             if (mapi_has_last_decrypted (message))
237               do_wipe_body (message);
238             else
239               log_debug_w32 (hr, "%s:%s: "
240                              "error getting message decryption status", 
241                              SRCNAME, __func__);
242           }
243           break;
244
245         case MSGTYPE_GPGOL_PGP_MESSAGE:
246           {
247             /* In general we can't delete the body of a message if it
248                is an inline PGP encrypted message because the body
249                holds the ciphertext.  However, while decrypting, we
250                take a copy of the body and work on that in future; if
251                this has been done we can delete the body.  */
252             mapi_attach_item_t *table;
253             int found = 0;
254             int tblidx;
255
256             table = mapi_create_attach_table (message, 0);
257             if (table)
258               {
259                 for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
260                   if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
261                       && table[tblidx].filename 
262                       && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
263                     {
264                       found = 1;
265                       break;
266                     }
267               }
268             mapi_release_attach_table (table);
269             if (found)
270               do_wipe_body (message);
271           }
272           break;
273
274         default: 
275           break;
276         }
277       
278       ul_release (message, __func__);
279       ul_release (mdb, __func__);
280     }
281 }
282
283
284
285 /* Display some information about MESSAGE.  */
286 void
287 message_show_info (LPMESSAGE message, HWND hwnd)
288 {
289   char *msgcls = mapi_get_message_class (message);
290   char *sigstat = mapi_get_sig_status (message);
291   char *mimeinfo = mapi_get_mime_info (message);
292   size_t buflen;
293   char *buffer;
294
295   buflen = strlen (msgcls) + strlen (sigstat) + strlen (mimeinfo) + 200;
296   buffer = (char*)xmalloc (buflen+1);
297   snprintf (buffer, buflen, 
298             _("Signature status: %s\n"
299               "Message class ..: %s\n"
300               "MIME structure .:\n"
301               "%s"), 
302             sigstat,
303             msgcls,
304             mimeinfo);
305   
306   MessageBox (hwnd, buffer, _("GpgOL - Message Information"),
307               MB_ICONINFORMATION|MB_OK);
308   xfree (buffer);
309   xfree (mimeinfo);
310   xfree (sigstat);
311   xfree (msgcls);
312 }
313
314
315
316 \f
317 /* Convert the clear signed message from INPUT into a PGP/MIME signed
318    message and return it in a new allocated buffer.  OUTPUTLEN
319    received the valid length of that buffer; the buffer is guarnateed
320    to be Nul terminated.  */
321 static char *
322 pgp_mime_from_clearsigned (LPSTREAM input, size_t *outputlen)
323 {
324   HRESULT hr;
325   STATSTG statinfo;
326   ULONG nread;
327   char *body = NULL;
328   char *p, *p0, *dest, *mark;
329   char boundary[BOUNDARYSIZE+1];
330   char top_header[200 + 2*BOUNDARYSIZE]; 
331   char sig_header[100 + BOUNDARYSIZE]; 
332   char end_header[10 + BOUNDARYSIZE];
333   size_t reserved_space;
334   int state;
335
336   *outputlen = 0;
337
338   /* Note that our parser does not make use of the micalg parameter.  */
339   generate_boundary (boundary);
340   snprintf (top_header, sizeof top_header,
341             "MIME-Version: 1.0\r\n"
342             "Content-Type: multipart/signed; boundary=\"%s\";\r\n"
343             "              protocol=\"application/pgp-signature\"\r\n"
344             "\r\n"
345             "--%s\r\n", boundary, boundary);
346   snprintf (sig_header, sizeof sig_header,
347             "--%s\r\n"
348             "Content-Type: application/pgp-signature\r\n"
349             "\r\n", boundary);
350   snprintf (end_header, sizeof end_header,
351             "\r\n"
352             "--%s--\r\n", boundary);
353   reserved_space = (strlen (top_header) + strlen (sig_header) 
354                     + strlen (end_header)+ 100);
355
356   hr = input->Stat (&statinfo, STATFLAG_NONAME);
357   if (hr)
358     {
359       log_debug ("%s:%s: Stat failed: hr=%#lx", SRCNAME, __func__, hr);
360       return NULL;
361     }
362       
363   body = (char*)xmalloc (reserved_space
364                          + (size_t)statinfo.cbSize.QuadPart + 2);
365   hr = input->Read (body+reserved_space,
366                     (size_t)statinfo.cbSize.QuadPart, &nread);
367   if (hr)
368     {
369       log_debug ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
370       xfree (body);
371       return NULL;
372     }
373   body[reserved_space + nread] = 0;
374   body[reserved_space + nread+1] = 0;  /* Just in case this is
375                                           accidently an wchar_t. */
376   if (nread != statinfo.cbSize.QuadPart)
377     {
378       log_debug ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
379       xfree (body);
380       return NULL;
381     }
382
383   /* We do in place conversion. */
384   state = 0;
385   dest = NULL;
386   for (p=body+reserved_space; p && *p; p = (p=strchr (p+1, '\n'))? (p+1):NULL)
387     {
388       if (!state && !strncmp (p, "-----BEGIN PGP SIGNED MESSAGE-----", 34)
389           && trailing_ws_p (p+34) )
390         {
391           dest = stpcpy (body, top_header);
392           state = 1;
393         }
394       else if (state == 1)
395         {
396           /* Wait for an empty line.  */
397           if (trailing_ws_p (p))
398             state = 2;
399         }
400       else if (state == 2 && strncmp (p, "-----", 5))
401         {
402           /* Copy signed data. */
403           p0 = p;
404           if (*p == '-' && p[1] == ' ')
405             p +=2;  /* Remove escaping.  */
406           mark = NULL;
407           while (*p && *p != '\n')
408             {
409               if (*p == ' ' || *p == '\t' || *p == '\r')
410                 mark = p;
411               else
412                 mark = NULL;
413               *dest++ = *p++;
414             }
415           if (mark)
416             *mark =0; /* Remove trailing white space.  */
417           if (*p == '\n')
418             {
419               if (p[-1] == '\r')
420                 *dest++ = '\r';
421               *dest++ = '\n';
422             }
423           if (p > p0)
424             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
425         }
426       else if (state == 2)
427         {
428           /* Armor line encountered.  */
429           p0 = p;
430           if (strncmp (p, "-----BEGIN PGP SIGNATURE-----", 29)
431               || !trailing_ws_p (p+29) )
432             fprintf (stderr,"Invalid clear signed message\n");
433           state = 3;
434           dest = stpcpy (dest, sig_header);
435         
436           while (*p && *p != '\n')
437             *dest++ = *p++;
438           if (*p == '\n')
439             {
440               if (p[-1] == '\r')
441                 *dest++ = '\r';
442               *dest++ = '\n';
443             }
444           if (p > p0)
445             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
446         }
447       else if (state == 3 && strncmp (p, "-----", 5))
448         {
449           /* Copy signature.  */
450           p0 = p;
451           while (*p && *p != '\n')
452             *dest++ = *p++;
453           if (*p == '\n')
454             {
455               if (p[-1] == '\r')
456                 *dest++ = '\r';
457               *dest++ = '\n';
458             }
459           if (p > p0)
460             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
461         }
462       else if (state == 3)
463         {
464           /* Armor line encountered.  */
465           p0 = p;
466           if (strncmp (p, "-----END PGP SIGNATURE-----", 27)
467               || !trailing_ws_p (p+27) )
468             fprintf (stderr,"Invalid clear signed message (no end)\n");
469           while (*p && *p != '\n')
470             *dest++ = *p++;
471           if (*p == '\n')
472             {
473               if (p[-1] == '\r')
474                 *dest++ = '\r';
475               *dest++ = '\n';
476             }
477           dest = stpcpy (dest, end_header);
478           if (p > p0)
479             p--; /* Adjust so that the strchr (p+1, '\n') can work. */
480           break; /* Ah well, we can stop here.  */
481         }
482     }
483   if (!dest)
484     {
485       xfree (body);
486       return NULL;
487     }
488   *dest = 0;
489   *outputlen = strlen (body);
490
491   return body;
492 }
493
494
495 /* Verify MESSAGE and update the attachments as required.  MSGTYPE
496    should be the type of the message so that the fucntion can decide
497    what to do.  With FORCE set the verification is done regardlessless
498    of a cached signature result. */
499 int
500 message_verify (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
501 {
502   HRESULT hr;
503   mapi_attach_item_t *table = NULL;
504   LPSTREAM opaquestream = NULL;
505   int moss_idx = -1;
506   int i;
507   char *inbuf = NULL;
508   size_t inbuflen = 0;
509   protocol_t protocol = PROTOCOL_UNKNOWN;
510   int err;
511
512   switch (msgtype)
513     {
514     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
515     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
516     case MSGTYPE_GPGOL_CLEAR_SIGNED:
517       break;
518     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
519     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
520     case MSGTYPE_GPGOL_PGP_MESSAGE:
521       log_debug ("%s:%s: message of type %d not expected",
522                  SRCNAME, __func__, msgtype);
523       return -1; /* Should not be called for such a message.  */
524     case MSGTYPE_UNKNOWN:
525     case MSGTYPE_SMIME:
526     case MSGTYPE_GPGOL:
527       log_debug ("%s:%s: message of type %d ignored",
528                  SRCNAME, __func__, msgtype);
529       return 0; /* Nothing to do.  */
530     }
531   
532   /* If a verification is forced, we set the cached signature status
533      first to "?" to mark that no verification has yet happened.  If a
534      verification status has been set and the body attachment is
535      available we don't do a verification again.  The need to check
536      for the body attachment is to avoid problems if that attachment
537      has accidently be deleted. */
538   if (force)
539     mapi_set_sig_status (message, "?");
540   else if (mapi_test_sig_status (message) 
541            && !mapi_get_gpgol_body_attachment (message, NULL,NULL,NULL,NULL))
542     return 0; /* Already checked that message.  */
543
544   if (msgtype == MSGTYPE_GPGOL_CLEAR_SIGNED)
545     {
546       /* PGP's clear signed messages are special: All is contained in
547          the body and thus there is no requirement for an
548          attachment.  */
549       LPSTREAM rawstream;
550       
551       rawstream = mapi_get_body_as_stream (message);
552       if (!rawstream)
553         return -1;
554       
555       inbuf = pgp_mime_from_clearsigned (rawstream, &inbuflen);
556       rawstream->Release ();
557       if (!inbuf)
558         return -1;
559       protocol = PROTOCOL_OPENPGP;
560     }
561   else if (msgtype == MSGTYPE_GPGOL_OPAQUE_SIGNED)
562     {
563       /* S/MIME opaque signed message: The data is expected to be in
564          an attachment.  */
565       table = mapi_create_attach_table (message, 0);
566       if (!table)
567         return -1; /* No attachment - this should not happen.  */
568
569       for (i=0; !table[i].end_of_table; i++)
570         if (table[i].content_type               
571             && (!strcmp (table[i].content_type, "application/pkcs7-mime")
572                 || !strcmp (table[i].content_type,
573                             "application/x-pkcs7-mime"))
574             && table[i].filename
575             && !strcmp (table[i].filename, "smime.p7m"))
576           break;
577       if (table[i].end_of_table)
578         {
579           log_debug ("%s:%s: attachment for opaque signed S/MIME not found",
580                      SRCNAME, __func__);
581           mapi_release_attach_table (table);
582           return -1;
583         }
584
585       opaquestream = mapi_get_attach_as_stream (message, table+i, NULL);
586       if (!opaquestream)
587         {
588           mapi_release_attach_table (table);
589           return -1; /* Problem getting the attachment.  */
590         }
591       protocol = PROTOCOL_SMIME;
592     }
593   else
594     {
595       /* PGP/MIME or S/MIME stuff.  */
596       table = mapi_create_attach_table (message, 0);
597       if (!table)
598         return -1; /* No attachment - this should not happen.  */
599
600       for (i=0; !table[i].end_of_table; i++)
601         if (table[i].attach_type == ATTACHTYPE_MOSS)
602           {
603             moss_idx = i;
604             break;
605           }
606       if (moss_idx == -1 && !table[0].end_of_table && table[1].end_of_table)
607         {
608           /* No MOSS flag found in the table but there is only one
609              attachment.  Due to the message type we know that this is
610              the original MOSS message.  We mark this attachment as
611              hidden, so that it won't get displayed.  We further mark
612              it as our original MOSS attachment so that after parsing
613              we have a mean to find it again (see above).  */ 
614           moss_idx = 0;
615           mapi_mark_moss_attach (message, table+0);
616         }
617       
618       if (moss_idx == -1)
619         {
620           mapi_release_attach_table (table);
621           return -1; /* No original attachment - this should not happen.  */
622         }
623
624       inbuf = mapi_get_attach (message, table+0, &inbuflen);
625       if (!inbuf)
626         {
627           mapi_release_attach_table (table);
628           return -1; /* Problem getting the attachment.  */
629         }
630     }
631
632   if (opaquestream)
633     err = mime_verify_opaque (protocol, opaquestream, message, hwnd, 0);
634   else
635     err = mime_verify (protocol, inbuf, inbuflen, message, hwnd, 0);
636   log_debug ("mime_verify%s returned %d", opaquestream? "_opaque":"", err);
637   if (err)
638     {
639       char buf[200];
640       
641       snprintf (buf, sizeof buf, "Verify failed (%s)", gpg_strerror (err));
642       MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
643     }
644   if (opaquestream)
645     opaquestream->Release ();
646   xfree (inbuf);
647                     
648   if (err)
649     {
650       char buf[200];
651       snprintf (buf, sizeof buf, "- %s", gpg_strerror (err));
652       mapi_set_sig_status (message, gpg_strerror (err));
653     }
654   else
655     mapi_set_sig_status (message, "! Good signature");
656
657   hr = message->SaveChanges (KEEP_OPEN_READWRITE);
658   if (hr)
659     log_error_w32 (hr, "%s:%s: SaveChanges failed",
660                    SRCNAME, __func__); 
661
662
663   mapi_release_attach_table (table);
664   return 0;
665 }
666
667
668 /* Copy the MAPI body to a PGPBODY type attachment. */
669 static int
670 pgp_body_to_attachment (LPMESSAGE message)
671 {
672   HRESULT hr;
673   LPSTREAM instream;
674   ULONG newpos;
675   LPATTACH newatt = NULL;
676   SPropValue prop;
677   LPSTREAM outstream = NULL;
678   LPUNKNOWN punk;
679
680   instream = mapi_get_body_as_stream (message);
681   if (!instream)
682     return -1;
683   
684   hr = message->CreateAttach (NULL, 0, &newpos, &newatt);
685   if (hr)
686     {
687       log_error ("%s:%s: can't create attachment: hr=%#lx\n",
688                  SRCNAME, __func__, hr); 
689       goto leave;
690     }
691
692   prop.ulPropTag = PR_ATTACH_METHOD;
693   prop.Value.ul = ATTACH_BY_VALUE;
694   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
695   if (hr)
696     {
697       log_error ("%s:%s: can't set attach method: hr=%#lx\n",
698                  SRCNAME, __func__, hr); 
699       goto leave;
700     }
701
702   /* Mark that attachment so that we know why it has been created.  */
703   if (get_gpgolattachtype_tag (message, &prop.ulPropTag) )
704     goto leave;
705   prop.Value.l = ATTACHTYPE_PGPBODY;
706   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);        
707   if (hr)
708     {
709       log_error ("%s:%s: can't set %s property: hr=%#lx\n",
710                  SRCNAME, __func__, "GpgOL Attach Type", hr); 
711       goto leave;
712     }
713
714   prop.ulPropTag = PR_ATTACHMENT_HIDDEN;
715   prop.Value.b = TRUE;
716   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
717   if (hr)
718     {
719       log_error ("%s:%s: can't set hidden attach flag: hr=%#lx\n",
720                  SRCNAME, __func__, hr); 
721       goto leave;
722     }
723
724   prop.ulPropTag = PR_ATTACH_FILENAME_A;
725   prop.Value.lpszA = PGPBODYFILENAME;
726   hr = HrSetOneProp ((LPMAPIPROP)newatt, &prop);
727   if (hr)
728     {
729       log_error ("%s:%s: can't set attach filename: hr=%#lx\n",
730                  SRCNAME, __func__, hr); 
731       goto leave;
732     }
733
734   punk = (LPUNKNOWN)outstream;
735   hr = newatt->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 0,
736                              MAPI_CREATE|MAPI_MODIFY, &punk);
737   if (FAILED (hr)) 
738     {
739       log_error ("%s:%s: can't create output stream: hr=%#lx\n",
740                  SRCNAME, __func__, hr); 
741       goto leave;
742     }
743   outstream = (LPSTREAM)punk;
744
745   /* Insert a blank line so that our mime parser skips over the mail
746      headers.  */
747   hr = outstream->Write ("\r\n", 2, NULL);
748   if (hr)
749     {
750       log_error ("%s:%s: Write failed: hr=%#lx", SRCNAME, __func__, hr);
751       goto leave;
752     }
753
754   {
755     ULARGE_INTEGER cb;
756     cb.QuadPart = 0xffffffffffffffffll;
757     hr = instream->CopyTo (outstream, cb, NULL, NULL);
758   }
759   if (hr)
760     {
761       log_error ("%s:%s: can't copy streams: hr=%#lx\n",
762                  SRCNAME, __func__, hr); 
763       goto leave;
764     }
765   hr = outstream->Commit (0);
766   if (hr)
767     {
768       log_error ("%s:%s: Commiting output stream failed: hr=%#lx",
769                  SRCNAME, __func__, hr);
770       goto leave;
771     }
772   outstream->Release ();
773   outstream = NULL;
774   hr = newatt->SaveChanges (0);
775   if (hr)
776     {
777       log_error ("%s:%s: SaveChanges of the attachment failed: hr=%#lx\n",
778                  SRCNAME, __func__, hr); 
779       goto leave;
780     }
781   newatt->Release ();
782   newatt = NULL;
783   hr = message->SaveChanges (KEEP_OPEN_READWRITE);
784   if (hr)
785     log_error ("%s:%s: SaveChanges failed: hr=%#lx\n", SRCNAME, __func__, hr); 
786
787  leave:
788   if (outstream)
789     {
790       outstream->Revert ();
791       outstream->Release ();
792     }
793   if (newatt)
794     newatt->Release ();
795   instream->Release ();
796   return hr? -1:0;
797 }
798
799
800 /* Decrypt MESSAGE, check signature and update the attachments as
801    required.  MSGTYPE should be the type of the message so that the
802    function can decide what to do.  With FORCE set the decryption is
803    done regardless whether it has already been done.  */
804 int
805 message_decrypt (LPMESSAGE message, msgtype_t msgtype, int force, HWND hwnd)
806 {
807   mapi_attach_item_t *table = NULL;
808   int part1_idx, part2_idx;
809   int tblidx;
810   int retval = -1;
811   LPSTREAM cipherstream;
812   gpg_error_t err;
813   int is_opaque = 0;
814   protocol_t protocol;
815   LPATTACH saved_attach = NULL;
816   int need_saved_attach = 0;
817   int need_rfc822_parser = 0;
818
819   switch (msgtype)
820     {
821     case MSGTYPE_UNKNOWN:
822     case MSGTYPE_SMIME:
823     case MSGTYPE_GPGOL:
824     case MSGTYPE_GPGOL_OPAQUE_SIGNED:
825     case MSGTYPE_GPGOL_MULTIPART_SIGNED:
826     case MSGTYPE_GPGOL_CLEAR_SIGNED:
827       return -1; /* Should not have been called for this.  */
828     case MSGTYPE_GPGOL_MULTIPART_ENCRYPTED:
829       break;
830     case MSGTYPE_GPGOL_OPAQUE_ENCRYPTED:
831       is_opaque = 1;
832       break;
833     case MSGTYPE_GPGOL_PGP_MESSAGE:
834       break;
835     }
836   
837   if (!force && mapi_test_last_decrypted (message))
838     return 0; /* Already decrypted this message once during this
839                  session.  No need to do it again. */
840
841   if (msgtype == MSGTYPE_GPGOL_PGP_MESSAGE)
842     {
843       /* PGP messages are special: All is contained in the body and
844          thus there would be no requirement for an attachment.
845          However, due to problems with Outlook overwriting the body of
846          the message after decryption, we need to save the body away
847          before decrypting it.  We then always look for that original
848          body atatchment and create one if it does not exist.  */
849       part1_idx = -1;
850       table = mapi_create_attach_table (message, 0);
851       if (!table)
852         ;
853       else
854         {
855           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
856             if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
857                 && table[tblidx].filename 
858                 && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
859               {
860                 part1_idx = tblidx;
861                 break;
862               }
863         }
864       if (part1_idx == -1)
865         {
866           mapi_release_attach_table (table);
867           if (pgp_body_to_attachment (message))
868             table = NULL;
869           else
870             table = mapi_create_attach_table (message, 0);
871           if (table)
872             {
873               for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
874                 if (table[tblidx].attach_type == ATTACHTYPE_PGPBODY
875                     && table[tblidx].filename 
876                     && !strcmp (table[tblidx].filename, PGPBODYFILENAME))
877                   {
878                     part1_idx = tblidx;
879                     break;
880                   }
881             }
882         }
883       if (!table || part1_idx == -1)
884         {
885           log_debug ("%s:%s: problem copying the PGP inline encrypted message",
886                      SRCNAME, __func__);
887           goto leave;
888         }
889       cipherstream = mapi_get_attach_as_stream (message, table+part1_idx,
890                                                 NULL);
891       if (!cipherstream)
892         goto leave; /* Problem getting the attachment.  */
893       protocol = PROTOCOL_OPENPGP;
894       need_rfc822_parser = 1;
895     }
896   else
897     {
898       /* PGP/MIME or S/MIME stuff.  */
899       table = mapi_create_attach_table (message, 0);
900       if (!table)
901         goto leave; /* No attachment - this should not happen.  */
902
903       if (is_opaque)
904         {
905           /* S/MIME opaque encrypted message: We expect one
906              attachment.  As we don't know wether we are called the
907              first time, we first try to find this attachment by
908              looking at all attachments.  Only if this fails we
909              identify it by its order.  */
910           part2_idx = -1;
911           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
912             if (table[tblidx].attach_type == ATTACHTYPE_MOSSTEMPL)
913               {
914                 /* This attachment has been generated by us in the
915                    course of sending a new message.  The content will
916                    be multipart/signed because we used this to trick
917                    out OL.  We stop here and use this part for further
918                    processing.  */
919                 part2_idx = tblidx;
920                 need_rfc822_parser = 1;
921                 break;
922               }
923             else if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
924               {
925                 if (part2_idx == -1 && table[tblidx].content_type 
926                     && (!strcmp (table[tblidx].content_type,
927                                  "application/pkcs7-mime")
928                         || !strcmp (table[tblidx].content_type,
929                                     "application/x-pkcs7-mime")))
930                   part2_idx = tblidx;
931               }
932           if (part2_idx == -1 && tblidx >= 1)
933             {
934               /* We have attachments but none are marked.  Thus we
935                  assume that this is the first time we see this
936                  message and we will set the mark now if we see
937                  appropriate content types. */
938               if (table[0].content_type               
939                   && (!strcmp (table[0].content_type, "application/pkcs7-mime")
940                       || !strcmp (table[0].content_type,
941                                   "application/x-pkcs7-mime")))
942                 part2_idx = 0;
943               if (part2_idx != -1)
944                 mapi_mark_moss_attach (message, table+part2_idx);
945             }
946           if (part2_idx == -1)
947             {
948               log_debug ("%s:%s: this is not an S/MIME encrypted message",
949                          SRCNAME, __func__);
950               goto leave;
951             }
952           protocol = PROTOCOL_SMIME;
953         }
954       else 
955         {
956           /* Multipart/encrypted message: We expect 2 attachments.
957              The first one with the version number and the second one
958              with the ciphertext.  As we don't know wether we are
959              called the first time, we first try to find these
960              attachments by looking at all attachments.  Only if this
961              fails we identify them by their order (i.e. the first 2
962              attachments) and mark them as part1 and part2.  */
963           part1_idx = part2_idx = -1;
964           for (tblidx=0; !table[tblidx].end_of_table; tblidx++)
965             if (table[tblidx].attach_type == ATTACHTYPE_MOSS)
966               {
967                 if (part1_idx == -1 && table[tblidx].content_type 
968                     && !strcmp (table[tblidx].content_type,
969                                 "application/pgp-encrypted"))
970                   part1_idx = tblidx;
971                 else if (part2_idx == -1 && table[tblidx].content_type 
972                          && !strcmp (table[tblidx].content_type,
973                                      "application/octet-stream"))
974                   part2_idx = tblidx;
975               }
976           if (part1_idx == -1 && part2_idx == -1 && tblidx >= 2)
977             {
978               /* At least 2 attachments but none are marked.  Thus we
979                  assume that this is the first time we see this
980                  message and we will set the mark now if we see
981                  appropriate content types. */
982               if (table[0].content_type               
983                   && !strcmp (table[0].content_type,
984                               "application/pgp-encrypted"))
985                 part1_idx = 0;
986               if (table[1].content_type             
987                   && !strcmp (table[1].content_type, 
988                               "application/octet-stream"))
989                 part2_idx = 1;
990               if (part1_idx != -1 && part2_idx != -1)
991                 {
992                   mapi_mark_moss_attach (message, table+part1_idx);
993                   mapi_mark_moss_attach (message, table+part2_idx);
994                 }
995             }
996
997
998           if (part1_idx == -1 || part2_idx == -1 
999               && !table[0].end_of_table && table[1].end_of_table
1000               && table[0].attach_type == ATTACHTYPE_MOSS
1001               && table[0].filename 
1002               && !strcmp (table[0].filename, MIMEATTACHFILENAME))
1003             {
1004               /* This is likely a PGP/MIME created by us.  Due to the
1005                  way we created that message, the MAPI derived content
1006                  type is wrong and there is only one attachment
1007                  (gpgolXXX.dat).  We simply assume that it is PGP/MIME
1008                  encrypted and pass it on to the mime parser.  We also
1009                  keep the attachment open so that we can later set it
1010                  to hidden if not yet done.  I can't remember whether
1011                  it is possible to set the hidden attribute when
1012                  creating the message - probably not.  Thus we take
1013                  care of it here. */
1014               log_debug ("%s:%s: "
1015                          "assuming self-created PGP/MIME encrypted message",
1016                          SRCNAME, __func__);
1017               part2_idx = 0;
1018               need_saved_attach = 1;
1019             }
1020           else if (part1_idx == -1 || part2_idx == -1)
1021             {
1022               log_debug ("%s:%s: this is not a PGP/MIME encrypted message",
1023                          SRCNAME, __func__);
1024               goto leave;
1025             }
1026           protocol = PROTOCOL_OPENPGP;
1027         }
1028       
1029       cipherstream = mapi_get_attach_as_stream (message, table+part2_idx,
1030                                                 need_saved_attach? 
1031                                                 &saved_attach : NULL );
1032       if (!cipherstream)
1033         goto leave; /* Problem getting the attachment.  */
1034     }
1035
1036   err = mime_decrypt (protocol, cipherstream, message, 
1037                       need_rfc822_parser, hwnd, 0);
1038   log_debug ("mime_decrypt returned %d (%s)", err, gpg_strerror (err));
1039   if (err)
1040     {
1041       char buf[200];
1042       
1043       snprintf (buf, sizeof buf, "Decryption failed (%s)", gpg_strerror (err));
1044       MessageBox (NULL, buf, "GpgOL", MB_ICONINFORMATION|MB_OK);
1045     }
1046   else
1047     {
1048       if (saved_attach)
1049         mapi_set_attach_hidden (saved_attach);
1050     }
1051   cipherstream->Release ();
1052   retval = 0;
1053
1054
1055  leave:
1056   if (saved_attach)
1057     saved_attach->Release ();
1058   mapi_release_attach_table (table);
1059   return retval;
1060 }
1061
1062
1063 \f
1064
1065 /* Return an array of strings with the recipients of the message. On
1066    success a malloced array is returned containing allocated strings
1067    for each recipient.  The end of the array is marked by NULL.
1068    Caller is responsible for releasing the array.  On failure NULL is
1069    returned.  */
1070 static char **
1071 get_recipients (LPMESSAGE message)
1072 {
1073   static SizedSPropTagArray (1L, PropRecipientNum) = {1L, {PR_EMAIL_ADDRESS}};
1074   HRESULT hr;
1075   LPMAPITABLE lpRecipientTable = NULL;
1076   LPSRowSet lpRecipientRows = NULL;
1077   unsigned int rowidx;
1078   LPSPropValue row;
1079   char **rset;
1080   int rsetidx;
1081
1082
1083   if (!message)
1084     return NULL;
1085
1086   hr = message->GetRecipientTable (0, &lpRecipientTable);
1087   if (FAILED (hr)) 
1088     {
1089       log_debug_w32 (-1, "%s:%s: GetRecipientTable failed", SRCNAME, __func__);
1090       return NULL;
1091     }
1092
1093   hr = HrQueryAllRows (lpRecipientTable, (LPSPropTagArray) &PropRecipientNum,
1094                        NULL, NULL, 0L, &lpRecipientRows);
1095   if (FAILED (hr)) 
1096     {
1097       log_debug_w32 (-1, "%s:%s: HrQueryAllRows failed", SRCNAME, __func__);
1098       if (lpRecipientTable)
1099         lpRecipientTable->Release();
1100       return NULL;
1101     }
1102
1103   rset = (char**)xcalloc (lpRecipientRows->cRows+1, sizeof *rset);
1104
1105   for (rowidx=0, rsetidx=0; rowidx < lpRecipientRows->cRows; rowidx++)
1106     {
1107       if (!lpRecipientRows->aRow[rowidx].cValues)
1108         continue;
1109       row = lpRecipientRows->aRow[rowidx].lpProps;
1110
1111       switch (PROP_TYPE (row->ulPropTag))
1112         {
1113         case PT_UNICODE:
1114           if ((rset[rsetidx] = wchar_to_utf8 (row->Value.lpszW)))
1115             rsetidx++;
1116           else
1117             log_debug ("%s:%s: error converting recipient to utf8\n",
1118                        SRCNAME, __func__);
1119           break;
1120       
1121         case PT_STRING8: /* Assume ASCII. */
1122           rset[rsetidx++] = xstrdup (row->Value.lpszA);
1123           break;
1124           
1125         default:
1126           log_debug ("%s:%s: proptag=0x%08lx not supported\n",
1127                      SRCNAME, __func__, row->ulPropTag);
1128           break;
1129         }
1130     }
1131
1132   if (lpRecipientTable)
1133     lpRecipientTable->Release();
1134   if (lpRecipientRows)
1135     FreeProws(lpRecipientRows); 
1136   
1137   log_debug ("%s:%s: got %d recipients:\n", SRCNAME, __func__, rsetidx);
1138   for (rsetidx=0; rset[rsetidx]; rsetidx++)
1139     log_debug ("%s:%s: \t`%s'\n", SRCNAME, __func__, rset[rsetidx]);
1140
1141   return rset;
1142 }
1143
1144
1145 static void
1146 release_recipient_array (char **recipients)
1147 {
1148   int idx;
1149
1150   if (recipients)
1151     {
1152       for (idx=0; recipients[idx]; idx++)
1153         xfree (recipients[idx]);
1154       xfree (recipients);
1155     }
1156 }
1157
1158
1159
1160 static int
1161 sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd, int signflag)
1162 {
1163   gpg_error_t err;
1164   char **recipients;
1165
1166   recipients = get_recipients (message);
1167   if (!recipients || !recipients[0])
1168     {
1169       MessageBox (hwnd, _("No recipients to encrypt to are given"),
1170                   "GpgOL", MB_ICONERROR|MB_OK);
1171
1172       err = gpg_error (GPG_ERR_GENERAL);
1173     }
1174   else
1175     {
1176       if (signflag)
1177         err = mime_sign_encrypt (message, hwnd, protocol, recipients);
1178       else
1179         err = mime_encrypt (message, hwnd, protocol, recipients);
1180       if (err)
1181         {
1182           char buf[200];
1183           
1184           snprintf (buf, sizeof buf,
1185                     _("Encryption failed (%s)"), gpg_strerror (err));
1186           MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
1187         }
1188     }
1189   release_recipient_array (recipients);
1190   return err;
1191 }
1192
1193
1194 /* Sign the MESSAGE.  */
1195 int 
1196 message_sign (LPMESSAGE message, protocol_t protocol, HWND hwnd)
1197 {
1198   gpg_error_t err;
1199
1200   err = mime_sign (message, hwnd, protocol);
1201   if (err)
1202     {
1203       char buf[200];
1204       
1205       snprintf (buf, sizeof buf,
1206                 _("Signing failed (%s)"), gpg_strerror (err));
1207       MessageBox (hwnd, buf, "GpgOL", MB_ICONERROR|MB_OK);
1208     }
1209   return err;
1210 }
1211
1212
1213
1214 /* Encrypt the MESSAGE.  */
1215 int 
1216 message_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd)
1217 {
1218   return sign_encrypt (message, protocol, hwnd, 0);
1219 }
1220
1221
1222 /* Sign+Encrypt the MESSAGE.  */
1223 int 
1224 message_sign_encrypt (LPMESSAGE message, protocol_t protocol, HWND hwnd)
1225 {
1226   return sign_encrypt (message, protocol, hwnd, 1);
1227 }
1228
1229
1230