Add SentOnBehalfOfName to ignored prop changes
[gpgol.git] / src / mailitem-events.cpp
1 /* mailitem-events.h - Event handling for mails.
2  * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik
3  * Software engineering by Intevation GmbH
4  *
5  * This file is part of GpgOL.
6  *
7  * GpgOL is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * GpgOL is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "config.h"
22 #include "common.h"
23 #include "eventsink.h"
24 #include "eventsinks.h"
25 #include "mymapi.h"
26 #include "mymapitags.h"
27 #include "oomhelp.h"
28 #include "ocidl.h"
29 #include "windowmessages.h"
30 #include "mail.h"
31 #include "mapihelp.h"
32 #include "gpgoladdin.h"
33 #include "wks-helper.h"
34
35 #undef _
36 #define _(a) utf8_gettext (a)
37
38 const wchar_t *prop_blacklist[] = {
39   L"Body",
40   L"HTMLBody",
41   L"To", /* Somehow this is done when a mail is opened */
42   L"CC", /* Ditto */
43   L"BCC", /* Ditto */
44   L"Categories",
45   L"UnRead",
46   L"OutlookVersion",
47   L"OutlookInternalVersion",
48   L"ReceivedTime",
49   L"InternetCodepage",
50   L"ConversationIndex",
51   L"Subject",
52   L"SentOnBehalfOfName",
53   NULL };
54
55 typedef enum
56   {
57     AfterWrite = 0xFC8D,
58     AttachmentAdd = 0xF00B,
59     AttachmentRead = 0xF00C,
60     AttachmentRemove = 0xFBAE,
61     BeforeAttachmentAdd = 0xFBB0,
62     BeforeAttachmentPreview = 0xFBAF,
63     BeforeAttachmentRead = 0xFBAB,
64     BeforeAttachmentSave = 0xF00D,
65     BeforeAttachmentWriteToTempFile = 0xFBB2,
66     BeforeAutoSave = 0xFC02,
67     BeforeCheckNames = 0xF00A,
68     BeforeDelete = 0xFA75,
69     BeforeRead = 0xFC8C,
70     Close = 0xF004,
71     CustomAction = 0xF006,
72     CustomPropertyChange = 0xF008,
73     Forward = 0xF468,
74     Open = 0xF003,
75     PropertyChange = 0xF009,
76     Read = 0xF001,
77     ReadComplete = 0xFC8F,
78     Reply = 0xF466,
79     ReplyAll = 0xF467,
80     Send = 0xF005,
81     Unload = 0xFBAD,
82     Write = 0xF002
83   } MailEvent;
84
85 /* Mail Item Events */
86 BEGIN_EVENT_SINK(MailItemEvents, IDispatch)
87 /* We are still in the class declaration */
88
89 private:
90   Mail * m_mail; /* The mail object related to this mailitem */
91 };
92
93 MailItemEvents::MailItemEvents() :
94     m_object(NULL),
95     m_pCP(NULL),
96     m_cookie(0),
97     m_ref(1),
98     m_mail(NULL)
99 {
100 }
101
102 MailItemEvents::~MailItemEvents()
103 {
104   if (m_pCP)
105     m_pCP->Unadvise(m_cookie);
106   if (m_object)
107     gpgol_release (m_object);
108 }
109
110 static bool propchangeWarnShown = false;
111 static bool attachRemoveWarnShown = false;
112 static bool addinsLogged = false;
113
114 static DWORD WINAPI
115 do_delayed_locate (LPVOID arg)
116 {
117   Sleep(100);
118   do_in_ui_thread (RECIPIENT_ADDED, arg);
119   return 0;
120 }
121
122 /* The main Invoke function. The return value of this
123    function does not appear to have any effect on outlook
124    although I have read in an example somewhere that you
125    should return S_OK so that outlook continues to handle
126    the event I have not yet seen any effect by returning
127    error values here and no MSDN documentation about the
128    return values.
129 */
130 EVENT_SINK_INVOKE(MailItemEvents)
131 {
132   USE_INVOKE_ARGS
133   TSTART;
134   if (!m_mail)
135     {
136       m_mail = Mail::getMailForItem (m_object);
137       if (!m_mail)
138         {
139           log_error ("%s:%s: mail event without mail object known. Bug.",
140                      SRCNAME, __func__);
141           TRETURN S_OK;
142         }
143     }
144
145   bool is_reply = false;
146   switch(dispid)
147     {
148       case BeforeAutoSave:
149         {
150           log_oom ("%s:%s: BeforeAutoSave : %p",
151                    SRCNAME, __func__, m_mail);
152           if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
153               !m_mail->isDraftEncrypt())
154             {
155               log_debug ("%s:%s: Draft encryption for autosave starting now.",
156                          SRCNAME, __func__);
157               m_mail->setIsDraftEncrypt (true);
158               m_mail->prepareCrypto_o ();
159             }
160           TRETURN S_OK;
161         }
162       case Open:
163         {
164           log_oom ("%s:%s: Open : %p",
165                          SRCNAME, __func__, m_mail);
166           int draft_flags = 0;
167           if (!opt.encrypt_default && !opt.sign_default)
168             {
169               TRETURN S_OK;
170             }
171           LPMESSAGE message = get_oom_base_message (m_object);
172           if (!message)
173             {
174               log_error ("%s:%s: Failed to get message.",
175                          SRCNAME, __func__);
176               TBREAK;
177             }
178           if (opt.encrypt_default)
179             {
180               draft_flags = 1;
181             }
182           if (opt.sign_default)
183             {
184               draft_flags += 2;
185             }
186           set_gpgol_draft_info_flags (message, draft_flags);
187           gpgol_release (message);
188           TBREAK;
189         }
190       case BeforeRead:
191         {
192           log_oom ("%s:%s: BeforeRead : %p",
193                          SRCNAME, __func__, m_mail);
194           if (GpgolAddin::get_instance ()->isShutdown())
195             {
196               log_debug ("%s:%s: Ignoring read after shutdown.",
197                          SRCNAME, __func__);
198               TBREAK;
199             }
200
201           if (m_mail->preProcessMessage_m ())
202             {
203               log_error ("%s:%s: Pre process message failed.",
204                          SRCNAME, __func__);
205             }
206           TBREAK;
207         }
208       case Read:
209         {
210           log_oom ("%s:%s: Read : %p",
211                          SRCNAME, __func__, m_mail);
212           if (!addinsLogged)
213             {
214               // We do it here as this nearly always comes and we want to remove
215               // as much as possible from the startup time.
216               log_addins ();
217               addinsLogged = true;
218             }
219           if (!m_mail->isCryptoMail ())
220             {
221               log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.",
222                          SRCNAME, __func__, m_mail);
223               /* Ensure that no wrong sigstatus is shown */
224               CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
225                                         NULL));
226               TBREAK;
227             }
228           if (m_mail->setUUID_o ())
229             {
230               log_debug ("%s:%s: Failed to set uuid.",
231                          SRCNAME, __func__);
232               delete m_mail; /* deletes this, too */
233               TRETURN S_OK;
234             }
235           if (m_mail->decryptVerify_o ())
236             {
237               log_error ("%s:%s: Decrypt message failed.",
238                          SRCNAME, __func__);
239             }
240           if (!opt.enable_smime && m_mail->isSMIME_m ())
241             {
242               /* We want to save the mail when it's an smime mail and smime
243                  is disabled to revert it. */
244               log_debug ("%s:%s: S/MIME mail but S/MIME is disabled."
245                          " Need save.",
246                          SRCNAME, __func__);
247               m_mail->setNeedsSave (true);
248             }
249           TBREAK;
250         }
251       case PropertyChange:
252         {
253           if (!parms || parms->cArgs != 1 ||
254               parms->rgvarg[0].vt != VT_BSTR ||
255               !parms->rgvarg[0].bstrVal)
256             {
257               log_error ("%s:%s: Unexpected params.",
258                          SRCNAME, __func__);
259               TBREAK;
260             }
261           const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
262           if (!m_mail->isCryptoMail ())
263             {
264               if (m_mail->hasOverrideMimeData())
265                 {
266                   /* This is a mail created by us. Ignore propchanges. */
267                   TBREAK;
268                 }
269               if (!wcscmp (prop_name, L"To") /* ||
270                   !wcscmp (prop_name, L"BCC") ||
271                   !wcscmp (prop_name, L"CC")
272                   Testing shows that Outlook always sends these three in a row
273                   */)
274                 {
275                   if (opt.autosecure || (m_mail->needs_crypto_m () & 1))
276                     {
277                       /* XXX Racy race. This is a fix for crashes
278                          that happend if a resolved recipient is copied an pasted.
279                          If we then access the recipients object in the Property
280                          Change event we crash. Thus we do the delay dance. */
281                       HANDLE thread = CreateThread (NULL, 0, do_delayed_locate,
282                                                     (LPVOID) m_mail, 0,
283                                                     NULL);
284                       CloseHandle(thread);
285                     }
286                 }
287               TBREAK;
288             }
289           for (const wchar_t **cur = prop_blacklist; *cur; cur++)
290             {
291               if (!wcscmp (prop_name, *cur))
292                 {
293                   log_oom ("%s:%s: Message %p propchange: %ls discarded.",
294                            SRCNAME, __func__, m_object, prop_name);
295                   TRETURN S_OK;
296                 }
297             }
298           log_oom ("%s:%s: Message %p propchange: %ls.",
299                    SRCNAME, __func__, m_object, prop_name);
300
301           if (!wcscmp (prop_name, L"SendUsingAccount"))
302             {
303               bool sent = get_oom_bool (m_object, "Sent");
304               if (sent)
305                 {
306                   log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ",
307                              SRCNAME, __func__, m_object);
308                   TRETURN S_OK;
309                 }
310               log_debug ("%s:%s: Message %p looks like send again.",
311                         SRCNAME, __func__, m_object);
312               m_mail->setIsSendAgain (true);
313               TRETURN S_OK;
314             }
315
316           /* We have tried several scenarios to handle propery changes.
317              Only save the property in MAPI and call MAPI SaveChanges
318              worked and did not leak plaintext but this caused outlook
319              still to break the attachments of PGP/MIME Mails into two
320              attachments and add them as winmail.dat so other clients
321              are broken.
322
323              Alternatively reverting the mail, saving the property and
324              then decrypt again also worked a bit but there were some
325              weird side effects and breakages. But this has the usual
326              problem of a revert that the mail is created by outlook and
327              e.g. multipart/signed signatures from most MUA's are broken.
328
329              Some things to try out might be the close approach and then
330              another open or a selection change. But for now we just warn.
331
332              As a workardound a user should make property changes when
333              the mail was not read by us. */
334           if (propchangeWarnShown)
335             {
336               TRETURN S_OK;
337             }
338
339           wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet"));
340           char *fmt;
341           gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n"
342                                   "Property changes are not yet handled for crypto messages.\n\n"
343                                   "To workaround this limitation please change the property when the "
344                                   "message is not open in any window and not selected in the "
345                                   "messagelist.\n\nFor example by right clicking but not selecting the message.\n"),
346                           wchar_to_utf8(prop_name));
347           memdbg_alloc (fmt);
348           wchar_t *msg = utf8_to_wchar (fmt);
349           xfree (fmt);
350           MessageBoxW (get_active_hwnd(), msg, title,
351                        MB_ICONINFORMATION | MB_OK);
352           xfree (msg);
353           xfree (title);
354           propchangeWarnShown = true;
355           TRETURN S_OK;
356         }
357       case CustomPropertyChange:
358         {
359           log_oom ("%s:%s: CustomPropertyChange : %p",
360                          SRCNAME, __func__, m_mail);
361           /* TODO */
362           TBREAK;
363         }
364       case Send:
365         {
366           /* This is the only event where we can cancel the send of a
367              mailitem. But it is too early for us to encrypt as the MAPI
368              structures are not yet filled. Crypto based on the
369              Outlook Object Model data did not work as the messages
370              were only sent out empty. See 2b376a48 for a try of
371              this.
372
373              This is why we store send_seen and invoke a save which
374              may result in an error but only after triggering all the
375              behavior we need -> filling mapi structures and invoking the
376              AfterWrite handler where we encrypt.
377
378              If this encryption is successful and we pass the send
379              as then the encrypted data is sent.
380            */
381           log_oom ("%s:%s: Send : %p",
382                          SRCNAME, __func__, m_mail);
383           if (!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
384             {
385              log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p",
386                         SRCNAME, __func__, m_mail, m_object);
387              TBREAK;
388             }
389
390           if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
391            {
392              log_debug ("%s:%s: Uncancellable send event.",
393                         SRCNAME, __func__);
394              TBREAK;
395            }
396
397           if (m_mail->cryptState () == Mail::NoCryptMail &&
398               m_mail->needs_crypto_m ())
399             {
400               log_debug ("%s:%s: Send event for crypto mail %p saving and starting.",
401                          SRCNAME, __func__, m_mail);
402
403               m_mail->prepareCrypto_o ();
404
405               // Save the Mail
406               invoke_oom_method (m_object, "Save", NULL);
407
408               if (!m_mail->isAsyncCryptDisabled ())
409                 {
410                   // The afterwrite in the save should have triggered
411                   // the encryption. We cancel send for our asyncness.
412                   // Cancel send
413                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
414                   TBREAK;
415                 }
416               else
417                 {
418                   if (m_mail->cryptState () == Mail::NoCryptMail)
419                     {
420                       // Crypto failed or was canceled
421                       log_debug ("%s:%s: Message %p mail %p cancelling send - "
422                                  "Crypto failed or canceled.",
423                                  SRCNAME, __func__, m_object, m_mail);
424                       *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
425                       TBREAK;
426                     }
427                   // For inline response we can't trigger send programatically
428                   // so we do the encryption in sync.
429                   if (m_mail->cryptState () == Mail::NeedsUpdateInOOM)
430                     {
431                       m_mail->updateCryptOOM_o ();
432                     }
433                   if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
434                     {
435                       m_mail->setCryptState (Mail::WantsSendMIME);
436                     }
437                   if (m_mail->getDoPGPInline () && m_mail->cryptState () != Mail::WantsSendInline)
438                     {
439                       log_debug ("%s:%s: Message %p mail %p cancelling send - "
440                                  "Invalid state.",
441                                  SRCNAME, __func__, m_object, m_mail);
442                       gpgol_bug (m_mail->getWindow (),
443                                  ERR_INLINE_BODY_INV_STATE);
444                       *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
445                       TBREAK;
446                     }
447                 }
448             }
449
450           if (m_mail->cryptState () == Mail::WantsSendInline)
451             {
452               if (!m_mail->hasCryptedOrEmptyBody_o ())
453                 {
454                   log_debug ("%s:%s: Message %p mail %p cancelling send - "
455                              "not encrypted or not empty body detected.",
456                              SRCNAME, __func__, m_object, m_mail);
457                   gpgol_bug (m_mail->getWindow (),
458                              ERR_WANTS_SEND_INLINE_BODY);
459                   m_mail->setCryptState (Mail::NoCryptMail);
460                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
461                   TBREAK;
462                 }
463               log_debug ("%s:%s: Passing send event for no-mime message %p.",
464                          SRCNAME, __func__, m_object);
465               WKSHelper::instance()->allow_notify (1000);
466               TBREAK;
467             }
468
469           if (m_mail->cryptState () == Mail::WantsSendMIME)
470             {
471               if (!m_mail->hasCryptedOrEmptyBody_o ())
472                 {
473 /* The safety checks here trigger too often. Somehow for some
474    users the body is not empty after the encryption but when
475    it is sent it is still sent with the crypto content because
476    the encrypted MIME Structure is used because it is
477    correct in MAPI land.
478
479    For safety reasons enabling the checks might be better but
480    until we figure out why for some users the body replacement
481    does not work we have to disable them. Otherwise GpgOL
482    is unusuable for such users. GnuPG-Bug-Id: T3875
483 */
484 #define DISABLE_SAFTEY_CHECKS
485 #ifndef DISABLE_SAFTEY_CHECKS
486                   gpgol_bug (m_mail->getWindow (),
487                              ERR_WANTS_SEND_MIME_BODY);
488                   log_debug ("%s:%s: Message %p mail %p cancelling send mime - "
489                              "not encrypted or not empty body detected.",
490                              SRCNAME, __func__, m_object, m_mail);
491                   m_mail->setCryptState (Mail::NoCryptMail);
492                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
493                   TBREAK;
494 #else
495                   log_debug ("%s:%s: Message %p mail %p - "
496                              "not encrypted or not empty body detected - MIME.",
497                              SRCNAME, __func__, m_object, m_mail);
498 #endif
499                 }
500               /* Now we adress T3656 if Outlooks internal S/MIME is somehow
501                * mixed in (even if it is enabled and then disabled) it might
502                * cause strange behavior in that it sends the plain message
503                * and not the encrypted message. Tests have shown that we can
504                * bypass that by calling submit message on our base
505                * message.
506                *
507                * We do this conditionally as our other way of using OOM
508                * to send is proven to work and we don't want to mess
509                * with it.
510                */
511               // Get the Message class.
512               HRESULT hr;
513               LPSPropValue propval = NULL;
514
515               // It's important we use the _not_ base message here.
516               LPMESSAGE message = get_oom_message (m_object);
517               hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
518               gpgol_release (message);
519               if (FAILED (hr))
520                 {
521                   log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
522                              SRCNAME, __func__, hr);
523                   gpgol_release (message);
524                   TBREAK;
525                 }
526               if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL"))
527                 {
528                   // Does not have a message class by us.
529                   log_debug ("%s:%s: Message %p - No GpgOL Message class after encryption. cls is: '%s'",
530                              SRCNAME, __func__, m_object, propval->Value.lpszA);
531                   log_debug ("%s:%s: Message %p - Activating T3656 Workaround",
532                              SRCNAME, __func__, m_object);
533                   message = get_oom_base_message (m_object);
534                   if (message)
535                     {
536                       // It's important we use the _base_ message here.
537                       mapi_save_changes (message, FORCE_SAVE);
538                       message->SubmitMessage(0);
539                       gpgol_release (message);
540                       // Close the composer and trigger unloads
541                       CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0,
542                                                 NULL));
543                     }
544                   else
545                     {
546                       gpgol_bug (nullptr,
547                                  ERR_GET_BASE_MSG_FAILED);
548                     }
549                   // Cancel send
550                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
551                 }
552               MAPIFreeBuffer (propval);
553               if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE)
554                 {
555                   TBREAK;
556                 }
557               log_debug ("%s:%s: Passing send event for mime-encrypted message %p.",
558                          SRCNAME, __func__, m_object);
559               WKSHelper::instance()->allow_notify (1000);
560               TBREAK;
561             }
562           else
563             {
564               log_debug ("%s:%s: Message %p cancelling send - "
565                          "crypto or second save failed.",
566                          SRCNAME, __func__, m_object);
567               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
568             }
569           TRETURN S_OK;
570         }
571       case Write:
572         {
573           log_oom ("%s:%s: Write : %p",
574                          SRCNAME, __func__, m_mail);
575           /* This is a bit strange. We sometimes get multiple write events
576              without a read in between. When we access the message in
577              the second event it fails and if we cancel the event outlook
578              crashes. So we have keep the m_needs_wipe state variable
579              to keep track of that. */
580           if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
581            {
582              /* This happens in the weird case */
583              log_debug ("%s:%s: Uncancellable write event.",
584                         SRCNAME, __func__);
585              TBREAK;
586            }
587
588           if (m_mail->passWrite())
589             {
590               log_debug ("%s:%s: Passing write because passNextWrite was set for %p",
591                          SRCNAME, __func__, m_mail);
592               TBREAK;
593             }
594
595           if (m_mail->isCryptoMail () && !m_mail->needsSave ())
596             {
597               Mail *last_mail = Mail::getLastMail ();
598               if (Mail::isValidPtr (last_mail))
599                 {
600                   /* We want to identify here if there was a mail created that
601                      should receive the contents of this mail. For this we check
602                      for a write in the same loop as a mail creation.
603                      Now when switching from one mail to another this is also what
604                      happens. The new mail is loaded and the old mail is written.
605                      To distinguish the two we check that the new mail does not have
606                      an entryID, a Subject and No Size. Maybe just size or entryID
607                      would be enough but better save then sorry.
608
609                      Security consideration: Worst case we pass the write here but
610                      an unload follows before we get the scheduled revert. This
611                      would leak plaintext. But does not happen in our tests.
612
613                      Similarly if we crash or Outlook is closed before we see this
614                      revert. But as we immediately revert after the write this should
615                      also not happen. */
616                   const std::string lastSubject = last_mail->getSubject_o ();
617                   char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
618                   int lastSize = get_oom_int (last_mail->item (), "Size");
619                   std::string lastEntryStr;
620                   if (lastEntryID)
621                     {
622                       lastEntryStr = lastEntryID;
623                       xfree (lastEntryID);
624                     }
625
626                   if (!lastSize && !lastEntryStr.size () && !lastSubject.size ())
627                     {
628                       log_debug ("%s:%s: Write in the same loop as empty load."
629                                  " Pass but schedule revert.",
630                                  SRCNAME, __func__);
631
632                       /* This might be a forward. So don't invalidate yet. */
633
634                       // Mail::clearLastMail ();
635
636                       do_in_ui_thread_async (REVERT_MAIL, m_mail);
637                       TRETURN S_OK;
638                     }
639                 }
640               /* We cancel the write event to stop outlook from excessively
641                  syncing our changes.
642                  if smime support is disabled and we still have an smime
643                  mail we also don't want to cancel the write event
644                  to enable reverting this mails.
645                  */
646               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
647               log_debug ("%s:%s: Canceling write event.",
648                          SRCNAME, __func__);
649               TRETURN S_OK;
650             }
651
652           if (m_mail->isCryptoMail () && m_mail->needsSave () &&
653               m_mail->revert_o ())
654             {
655               /* An error cleaning the mail should not happen normally.
656                  But just in case there is an error we cancel the
657                  write here. */
658               log_debug ("%s:%s: Failed to remove plaintext.",
659                          SRCNAME, __func__);
660               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
661             }
662
663           if (!m_mail->isCryptoMail () && m_mail->is_forwarded_crypto_mail () &&
664               !m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
665             {
666               /* We are sure now that while this is a forward of an encrypted
667                * mail that the forward should not be signed or encrypted. So
668                * it's not constructed by us. We need to remove our attachments
669                * though so that they are not included in the forward. */
670               log_debug ("%s:%s: Writing unencrypted forward of crypt mail. "
671                          "Removing attachments. mail: %p item: %p",
672                          SRCNAME, __func__, m_mail, m_object);
673               if (m_mail->removeOurAttachments_o ())
674                 {
675                   // Worst case we forward some encrypted data here not
676                   // a security problem, so let it pass.
677                   log_error ("%s:%s: Failed to remove our attachments.",
678                              SRCNAME, __func__);
679                 }
680               /* Remove marker because we did this now. */
681               m_mail->setIsForwardedCryptoMail (false);
682             }
683
684           if (m_mail->isDraftEncrypt () &&
685               m_mail->cryptState () != Mail::NeedsFirstAfterWrite &&
686               m_mail->cryptState () != Mail::NeedsSecondAfterWrite)
687             {
688               log_debug ("%s:%s: Canceling write because draft encrypt is on"
689                          " progress.",
690                          SRCNAME, __func__);
691               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
692               TRETURN S_OK;
693             }
694
695           if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
696               !m_mail->isDraftEncrypt())
697             {
698               log_debug ("%s:%s: Draft encryption starting now.",
699                          SRCNAME, __func__);
700               m_mail->setIsDraftEncrypt (true);
701               m_mail->prepareCrypto_o ();
702             }
703
704           log_debug ("%s:%s: Passing write event. %i %i",
705                      SRCNAME, __func__, m_mail->isDraftEncrypt(), m_mail->cryptState());
706           m_mail->setNeedsSave (false);
707           TBREAK;
708         }
709       case AfterWrite:
710         {
711           log_oom ("%s:%s: AfterWrite : %p",
712                          SRCNAME, __func__, m_mail);
713           if (m_mail->cryptState () == Mail::NeedsFirstAfterWrite)
714             {
715               /* Seen the first after write. Advance the state */
716               m_mail->setCryptState (Mail::NeedsActualCrypt);
717               if (m_mail->encryptSignStart_o ())
718                 {
719                   log_debug ("%s:%s: Encrypt sign start failed.",
720                              SRCNAME, __func__);
721                   m_mail->setCryptState (Mail::NoCryptMail);
722                 }
723               TRETURN S_OK;
724             }
725           if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
726             {
727               m_mail->setCryptState (Mail::NeedsUpdateInMAPI);
728               m_mail->updateCryptMAPI_m ();
729               log_debug ("%s:%s: Second after write done.",
730                          SRCNAME, __func__);
731               TRETURN S_OK;
732             }
733           TBREAK;
734         }
735       case Close:
736         {
737           log_oom ("%s:%s: Close : %p",
738                          SRCNAME, __func__, m_mail);
739           if (m_mail->isCryptoMail ())
740             {
741               /* Close. This happens when an Opened mail is closed.
742                  To prevent the question of wether or not to save the changes
743                  (Which would save the decrypted data without an event to
744                  prevent it) we cancel the close and then either close it
745                  with discard changes or revert / save it.
746                  Contrary to documentation we can invoke close from
747                  close.
748                  */
749               if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
750                 {
751                   /* This happens in the weird case */
752                   log_debug ("%s:%s: Uncancellable close event.",
753                              SRCNAME, __func__);
754                   TBREAK;
755                 }
756               if (m_mail->getCloseTriggered ())
757                 {
758                   /* Our close with discard changes, pass through */
759                   m_mail->setCloseTriggered (false);
760                   TRETURN S_OK;
761                 }
762               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
763               log_oom ("%s:%s: Canceling close event.",
764                              SRCNAME, __func__);
765               if (Mail::close(m_mail))
766                 {
767                   log_debug ("%s:%s: Close request failed.",
768                              SRCNAME, __func__);
769                 }
770             }
771           TRETURN S_OK;
772         }
773       case Unload:
774         {
775           log_oom ("%s:%s: Unload : %p",
776                          SRCNAME, __func__, m_mail);
777           log_debug ("%s:%s: Removing Mail for message: %p.",
778                      SRCNAME, __func__, m_object);
779           delete m_mail;
780           log_oom ("%s:%s: deletion done",
781                          SRCNAME, __func__);
782           memdbg_dump ();
783           TRETURN S_OK;
784         }
785       case ReplyAll:
786       case Reply:
787           is_reply = true;
788           /* fall through */
789       case Forward:
790         {
791           log_oom ("%s:%s: %s : %p",
792                          SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail);
793           int draft_flags = 0;
794           if (opt.encrypt_default)
795             {
796               draft_flags = 1;
797             }
798           if (opt.sign_default)
799             {
800               draft_flags += 2;
801             }
802           bool is_crypto_mail = m_mail->isCryptoMail ();
803
804           /* If it is a crypto mail and the settings should not be taken
805            * from the crypto mail and always encrypt / sign is on. Or
806            * If it is not a crypto mail and we have automaticalls sign_encrypt. */
807           if ((is_crypto_mail && !opt.reply_crypt && draft_flags) ||
808               (!is_crypto_mail && draft_flags))
809             {
810               /* Check if we can use the dispval */
811                 if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) &&
812                     parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF))
813                 {
814                   LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
815                   if (msg)
816                     {
817                       set_gpgol_draft_info_flags (msg, draft_flags);
818                       gpgol_release (msg);
819                     }
820                   else
821                     {
822                       log_error ("%s:%s: Failed to get base message.",
823                                  SRCNAME, __func__);
824                     }
825                 }
826               else
827                 {
828                   log_error ("%s:%s: Unexpected parameters.",
829                              SRCNAME, __func__);
830                 }
831             }
832
833           if (!is_crypto_mail)
834             {
835               /* Replys to non crypto mails do not interest us anymore. */
836               TBREAK;
837             }
838
839           Mail *last_mail = Mail::getLastMail ();
840           if (Mail::isValidPtr (last_mail))
841             {
842               /* We want to identify here if there was a mail created that
843                  should receive the contents of this mail. For this we check
844                  for a forward in the same loop as a mail creation.
845
846                  We need to do it this complicated and can't just use
847                  get_mail_for_item because the mailitem pointer we get here
848                  is a different one then the one with which the mail was loaded.
849               */
850               char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
851               int lastSize = get_oom_int (last_mail->item (), "Size");
852               std::string lastEntryStr;
853               if (lastEntryID)
854                 {
855                   lastEntryStr = lastEntryID;
856                   xfree (lastEntryID);
857                 }
858
859               if (!lastSize && !lastEntryStr.size ())
860                 {
861                   if (!is_reply)
862                     {
863                       log_debug ("%s:%s: Forward in the same loop as empty "
864                                  "load Marking %p (item %p) as forwarded.",
865                                  SRCNAME, __func__, last_mail,
866                                  last_mail->item ());
867
868                       last_mail->setIsForwardedCryptoMail (true);
869                     }
870                   else
871                     {
872                       log_debug ("%s:%s: Reply in the same loop as empty "
873                                  "load Marking %p (item %p) as reply.",
874                                  SRCNAME, __func__, last_mail,
875                                  last_mail->item ());
876                     }
877                   if (m_mail->isBlockHTML ())
878                     {
879                       std::string buf;
880                       /** TRANSLATORS: Part of a warning dialog that disallows
881                         reply and forward with contents */
882                       buf = is_reply ? _("You are replying to an unsigned S/MIME "
883                                          "email.") :
884                                        _("You are forwarding an unsigned S/MIME "
885                                          "email.");
886                       buf +="\n\n";
887                       buf += _("In this version of S/MIME an attacker could "
888                                "use the missing signature to have you "
889                                "decrypt contents from a different, otherwise "
890                                "completely unrelated email and place it in the "
891                                "quote so they can get hold of it.\n"
892                                "This is why we only allow quoting to be done manually.");
893                       buf += "\n\n";
894                       buf += _("Please copy the relevant contents and insert "
895                                "them into the new email.");
896
897                       gpgol_message_box (get_active_hwnd (), buf.c_str(),
898                                          _("GpgOL"), MB_OK);
899
900                       do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000);
901                     }
902                 }
903               // We can now invalidate the last mail
904               Mail::clearLastMail ();
905             }
906
907           log_oom ("%s:%s: Reply Forward ReplyAll: %p",
908                          SRCNAME, __func__, m_mail);
909           if (!opt.reply_crypt)
910             {
911               TBREAK;
912             }
913           int crypto_flags = 0;
914           if (!(crypto_flags = m_mail->getCryptoFlags ()))
915             {
916               TBREAK;
917             }
918           if (parms->cArgs != 2 || parms->rgvarg[1].vt != (VT_DISPATCH) ||
919               parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
920             {
921               /* This happens in the weird case */
922               log_debug ("%s:%s: Unexpected args %i %x %x named: %i",
923                          SRCNAME, __func__, parms->cArgs, parms->rgvarg[0].vt, parms->rgvarg[1].vt,
924                          parms->cNamedArgs);
925               TBREAK;
926             }
927           LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
928           if (!msg)
929             {
930               log_debug ("%s:%s: Failed to get base message",
931                          SRCNAME, __func__);
932               TBREAK;
933             }
934           set_gpgol_draft_info_flags (msg, crypto_flags);
935           gpgol_release (msg);
936           TBREAK;
937         }
938       case AttachmentRemove:
939         {
940           log_oom ("%s:%s: AttachmentRemove: %p",
941                          SRCNAME, __func__, m_mail);
942           if (!m_mail->isCryptoMail () || attachRemoveWarnShown ||
943               m_mail->attachmentRemoveWarningDisabled ())
944             {
945               TRETURN S_OK;
946             }
947           gpgol_message_box (get_active_hwnd (),
948                              _("Attachments are part of the crypto message.\nThey "
949                                "can't be permanently removed and will be shown again the next "
950                                "time this message is opened."),
951                              _("Sorry, that's not possible, yet"), MB_OK);
952           attachRemoveWarnShown = true;
953           TRETURN S_OK;
954         }
955
956       default:
957         log_oom ("%s:%s: Message:%p Unhandled Event: %lx \n",
958                        SRCNAME, __func__, m_object, dispid);
959     }
960   TRETURN S_OK;
961 }
962 END_EVENT_SINK(MailItemEvents, IID_MailItemEvents)