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