Explicitly note when close is passed for drafs
[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 (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
153            {
154              /* This happens in the weird case */
155              log_debug ("%s:%s: Uncancellable BeforeAutoSave.",
156                         SRCNAME, __func__);
157              TBREAK;
158            }
159
160           if (m_mail->isCryptoMail() && !m_mail->decryptedSuccessfully ())
161             {
162               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
163               log_debug ("%s:%s: Autosave for not successfuly decrypted mail."
164                          "Cancel it.",
165                          SRCNAME, __func__);
166               TBREAK;
167             }
168
169           if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
170               !m_mail->isDraftEncrypt())
171             {
172               log_debug ("%s:%s: Draft encryption for autosave starting now.",
173                          SRCNAME, __func__);
174               m_mail->setIsDraftEncrypt (true);
175               m_mail->prepareCrypto_o ();
176             }
177           TRETURN S_OK;
178         }
179       case Open:
180         {
181           log_oom ("%s:%s: Open : %p",
182                          SRCNAME, __func__, m_mail);
183           int draft_flags = 0;
184           if (!opt.encrypt_default && !opt.sign_default)
185             {
186               TRETURN S_OK;
187             }
188           LPMESSAGE message = get_oom_base_message (m_object);
189           if (!message)
190             {
191               log_error ("%s:%s: Failed to get message.",
192                          SRCNAME, __func__);
193               TBREAK;
194             }
195           if (opt.encrypt_default)
196             {
197               draft_flags = 1;
198             }
199           if (opt.sign_default)
200             {
201               draft_flags += 2;
202             }
203           set_gpgol_draft_info_flags (message, draft_flags);
204           gpgol_release (message);
205           TBREAK;
206         }
207       case BeforeRead:
208         {
209           log_oom ("%s:%s: BeforeRead : %p",
210                          SRCNAME, __func__, m_mail);
211           if (GpgolAddin::get_instance ()->isShutdown())
212             {
213               log_debug ("%s:%s: Ignoring read after shutdown.",
214                          SRCNAME, __func__);
215               TBREAK;
216             }
217
218           if (m_mail->preProcessMessage_m ())
219             {
220               log_error ("%s:%s: Pre process message failed.",
221                          SRCNAME, __func__);
222             }
223           TBREAK;
224         }
225       case Read:
226         {
227           log_oom ("%s:%s: Read : %p",
228                          SRCNAME, __func__, m_mail);
229           if (!addinsLogged)
230             {
231               // We do it here as this nearly always comes and we want to remove
232               // as much as possible from the startup time.
233               log_addins ();
234               addinsLogged = true;
235             }
236           if (!m_mail->isCryptoMail ())
237             {
238               log_debug ("%s:%s: Non crypto mail %p opened. Updating sigstatus.",
239                          SRCNAME, __func__, m_mail);
240               /* Ensure that no wrong sigstatus is shown */
241               CloseHandle(CreateThread (NULL, 0, delayed_invalidate_ui, (LPVOID) 300, 0,
242                                         NULL));
243               TBREAK;
244             }
245           if (m_mail->setUUID_o ())
246             {
247               log_debug ("%s:%s: Failed to set uuid.",
248                          SRCNAME, __func__);
249               delete m_mail; /* deletes this, too */
250               TRETURN S_OK;
251             }
252           if (m_mail->decryptVerify_o ())
253             {
254               log_error ("%s:%s: Decrypt message failed.",
255                          SRCNAME, __func__);
256             }
257           if (!opt.enable_smime && m_mail->isSMIME_m ())
258             {
259               /* We want to save the mail when it's an smime mail and smime
260                  is disabled to revert it. */
261               log_debug ("%s:%s: S/MIME mail but S/MIME is disabled."
262                          " Need save.",
263                          SRCNAME, __func__);
264               m_mail->setNeedsSave (true);
265             }
266           TBREAK;
267         }
268       case PropertyChange:
269         {
270           if (!parms || parms->cArgs != 1 ||
271               parms->rgvarg[0].vt != VT_BSTR ||
272               !parms->rgvarg[0].bstrVal)
273             {
274               log_error ("%s:%s: Unexpected params.",
275                          SRCNAME, __func__);
276               TBREAK;
277             }
278           const wchar_t *prop_name = parms->rgvarg[0].bstrVal;
279           if (!m_mail->isCryptoMail ())
280             {
281               if (m_mail->hasOverrideMimeData())
282                 {
283                   /* This is a mail created by us. Ignore propchanges. */
284                   TBREAK;
285                 }
286               if (!wcscmp (prop_name, L"To") /* ||
287                   !wcscmp (prop_name, L"BCC") ||
288                   !wcscmp (prop_name, L"CC")
289                   Testing shows that Outlook always sends these three in a row
290                   */)
291                 {
292                   if (opt.autosecure || (m_mail->needs_crypto_m () & 1))
293                     {
294                       /* XXX Racy race. This is a fix for crashes
295                          that happend if a resolved recipient is copied an pasted.
296                          If we then access the recipients object in the Property
297                          Change event we crash. Thus we do the delay dance. */
298                       HANDLE thread = CreateThread (NULL, 0, do_delayed_locate,
299                                                     (LPVOID) m_mail, 0,
300                                                     NULL);
301                       CloseHandle(thread);
302                     }
303                 }
304               TBREAK;
305             }
306           for (const wchar_t **cur = prop_blacklist; *cur; cur++)
307             {
308               if (!wcscmp (prop_name, *cur))
309                 {
310                   log_oom ("%s:%s: Message %p propchange: %ls discarded.",
311                            SRCNAME, __func__, m_object, prop_name);
312                   TRETURN S_OK;
313                 }
314             }
315           log_oom ("%s:%s: Message %p propchange: %ls.",
316                    SRCNAME, __func__, m_object, prop_name);
317
318           if (!wcscmp (prop_name, L"SendUsingAccount"))
319             {
320               bool sent = get_oom_bool (m_object, "Sent");
321               if (sent)
322                 {
323                   log_debug ("%s:%s: Ignoring SendUsingAccount change for sent %p ",
324                              SRCNAME, __func__, m_object);
325                   TRETURN S_OK;
326                 }
327               log_debug ("%s:%s: Message %p looks like send again.",
328                         SRCNAME, __func__, m_object);
329               m_mail->setIsSendAgain (true);
330               TRETURN S_OK;
331             }
332           if (is_draft_mail (m_object))
333             {
334               log_oom ("%s:%s: Change allowed for draft",
335                        SRCNAME, __func__);
336               TRETURN S_OK;
337             }
338
339           /* We have tried several scenarios to handle propery changes.
340              Only save the property in MAPI and call MAPI SaveChanges
341              worked and did not leak plaintext but this caused outlook
342              still to break the attachments of PGP/MIME Mails into two
343              attachments and add them as winmail.dat so other clients
344              are broken.
345
346              Alternatively reverting the mail, saving the property and
347              then decrypt again also worked a bit but there were some
348              weird side effects and breakages. But this has the usual
349              problem of a revert that the mail is created by outlook and
350              e.g. multipart/signed signatures from most MUA's are broken.
351
352              Some things to try out might be the close approach and then
353              another open or a selection change. But for now we just warn.
354
355              As a workardound a user should make property changes when
356              the mail was not read by us. */
357           if (propchangeWarnShown)
358             {
359               TRETURN S_OK;
360             }
361
362           wchar_t *title = utf8_to_wchar (_("Sorry, that's not possible, yet"));
363           char *fmt;
364           gpgrt_asprintf (&fmt, _("GpgOL has prevented the change to the \"%s\" property.\n"
365                                   "Property changes are not yet handled for crypto messages.\n\n"
366                                   "To workaround this limitation please change the property when the "
367                                   "message is not open in any window and not selected in the "
368                                   "messagelist.\n\nFor example by right clicking but not selecting the message.\n"),
369                           wchar_to_utf8(prop_name));
370           memdbg_alloc (fmt);
371           wchar_t *msg = utf8_to_wchar (fmt);
372           xfree (fmt);
373           MessageBoxW (get_active_hwnd(), msg, title,
374                        MB_ICONINFORMATION | MB_OK);
375           xfree (msg);
376           xfree (title);
377           propchangeWarnShown = true;
378           TRETURN S_OK;
379         }
380       case CustomPropertyChange:
381         {
382           log_oom ("%s:%s: CustomPropertyChange : %p",
383                          SRCNAME, __func__, m_mail);
384           /* TODO */
385           TBREAK;
386         }
387       case Send:
388         {
389           /* This is the only event where we can cancel the send of a
390              mailitem. But it is too early for us to encrypt as the MAPI
391              structures are not yet filled. Crypto based on the
392              Outlook Object Model data did not work as the messages
393              were only sent out empty. See 2b376a48 for a try of
394              this.
395
396              This is why we store send_seen and invoke a save which
397              may result in an error but only after triggering all the
398              behavior we need -> filling mapi structures and invoking the
399              AfterWrite handler where we encrypt.
400
401              If this encryption is successful and we pass the send
402              as then the encrypted data is sent.
403            */
404           log_oom ("%s:%s: Send : %p",
405                          SRCNAME, __func__, m_mail);
406           if (!m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
407             {
408               if (m_mail->isCryptoMail ())
409                 {
410                   log_debug ("%s:%s: Want to send crypto mail without crypto. "
411                              "Decrypting.", SRCNAME, __func__);
412                   m_mail->decryptPermanently_o ();
413                   TBREAK;
414                 }
415               log_debug ("%s:%s: No crypto neccessary. Passing send for %p obj %p",
416                          SRCNAME, __func__, m_mail, m_object);
417               TBREAK;
418             }
419
420           if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
421            {
422              log_debug ("%s:%s: Uncancellable send event.",
423                         SRCNAME, __func__);
424              TBREAK;
425            }
426
427           if (m_mail->cryptState () == Mail::NoCryptMail &&
428               m_mail->needs_crypto_m ())
429             {
430               log_debug ("%s:%s: Send event for crypto mail %p saving and starting.",
431                          SRCNAME, __func__, m_mail);
432
433               m_mail->prepareCrypto_o ();
434
435               // Save the Mail
436               invoke_oom_method (m_object, "Save", NULL);
437
438               if (!m_mail->isAsyncCryptDisabled ())
439                 {
440                   // The afterwrite in the save should have triggered
441                   // the encryption. We cancel send for our asyncness.
442                   // Cancel send
443                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
444                   TBREAK;
445                 }
446               else
447                 {
448                   if (m_mail->cryptState () == Mail::NoCryptMail)
449                     {
450                       // Crypto failed or was canceled
451                       log_debug ("%s:%s: Message %p mail %p cancelling send - "
452                                  "Crypto failed or canceled.",
453                                  SRCNAME, __func__, m_object, m_mail);
454                       *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
455                       TBREAK;
456                     }
457                   // For inline response we can't trigger send programatically
458                   // so we do the encryption in sync.
459                   if (m_mail->cryptState () == Mail::NeedsUpdateInOOM)
460                     {
461                       m_mail->updateCryptOOM_o ();
462                     }
463                   if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
464                     {
465                       m_mail->setCryptState (Mail::WantsSendMIME);
466                     }
467                   if (m_mail->getDoPGPInline () && m_mail->cryptState () != Mail::WantsSendInline)
468                     {
469                       log_debug ("%s:%s: Message %p mail %p cancelling send - "
470                                  "Invalid state.",
471                                  SRCNAME, __func__, m_object, m_mail);
472                       gpgol_bug (m_mail->getWindow (),
473                                  ERR_INLINE_BODY_INV_STATE);
474                       *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
475                       TBREAK;
476                     }
477                 }
478             }
479
480           if (m_mail->cryptState () == Mail::WantsSendInline)
481             {
482               if (!m_mail->hasCryptedOrEmptyBody_o ())
483                 {
484                   log_debug ("%s:%s: Message %p mail %p cancelling send - "
485                              "not encrypted or not empty body detected.",
486                              SRCNAME, __func__, m_object, m_mail);
487                   gpgol_bug (m_mail->getWindow (),
488                              ERR_WANTS_SEND_INLINE_BODY);
489                   m_mail->setCryptState (Mail::NoCryptMail);
490                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
491                   TBREAK;
492                 }
493               log_debug ("%s:%s: Passing send event for no-mime message %p.",
494                          SRCNAME, __func__, m_object);
495               WKSHelper::instance()->allow_notify (1000);
496               TBREAK;
497             }
498
499           if (m_mail->cryptState () == Mail::WantsSendMIME)
500             {
501               if (!m_mail->hasCryptedOrEmptyBody_o ())
502                 {
503 /* The safety checks here trigger too often. Somehow for some
504    users the body is not empty after the encryption but when
505    it is sent it is still sent with the crypto content because
506    the encrypted MIME Structure is used because it is
507    correct in MAPI land.
508
509    For safety reasons enabling the checks might be better but
510    until we figure out why for some users the body replacement
511    does not work we have to disable them. Otherwise GpgOL
512    is unusuable for such users. GnuPG-Bug-Id: T3875
513 */
514 #define DISABLE_SAFTEY_CHECKS
515 #ifndef DISABLE_SAFTEY_CHECKS
516                   gpgol_bug (m_mail->getWindow (),
517                              ERR_WANTS_SEND_MIME_BODY);
518                   log_debug ("%s:%s: Message %p mail %p cancelling send mime - "
519                              "not encrypted or not empty body detected.",
520                              SRCNAME, __func__, m_object, m_mail);
521                   m_mail->setCryptState (Mail::NoCryptMail);
522                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
523                   TBREAK;
524 #else
525                   log_debug ("%s:%s: Message %p mail %p - "
526                              "not encrypted or not empty body detected - MIME.",
527                              SRCNAME, __func__, m_object, m_mail);
528 #endif
529                 }
530               /* Now we adress T3656 if Outlooks internal S/MIME is somehow
531                * mixed in (even if it is enabled and then disabled) it might
532                * cause strange behavior in that it sends the plain message
533                * and not the encrypted message. Tests have shown that we can
534                * bypass that by calling submit message on our base
535                * message.
536                *
537                * We do this conditionally as our other way of using OOM
538                * to send is proven to work and we don't want to mess
539                * with it.
540                */
541               // Get the Message class.
542               HRESULT hr;
543               LPSPropValue propval = NULL;
544
545               // It's important we use the _not_ base message here.
546               LPMESSAGE message = get_oom_message (m_object);
547               hr = HrGetOneProp ((LPMAPIPROP)message, PR_MESSAGE_CLASS_A, &propval);
548               gpgol_release (message);
549               if (FAILED (hr))
550                 {
551                   log_error ("%s:%s: HrGetOneProp() failed: hr=%#lx\n",
552                              SRCNAME, __func__, hr);
553                   gpgol_release (message);
554                   TBREAK;
555                 }
556               if (propval->Value.lpszA && !strstr (propval->Value.lpszA, "GpgOL"))
557                 {
558                   // Does not have a message class by us.
559                   log_debug ("%s:%s: Message %p - No GpgOL Message class after encryption. cls is: '%s'",
560                              SRCNAME, __func__, m_object, propval->Value.lpszA);
561                   log_debug ("%s:%s: Message %p - Activating T3656 Workaround",
562                              SRCNAME, __func__, m_object);
563                   message = get_oom_base_message (m_object);
564                   if (message)
565                     {
566                       // It's important we use the _base_ message here.
567                       mapi_save_changes (message, FORCE_SAVE);
568                       message->SubmitMessage(0);
569                       gpgol_release (message);
570                       // Close the composer and trigger unloads
571                       CloseHandle(CreateThread (NULL, 0, close_mail, (LPVOID) m_mail, 0,
572                                                 NULL));
573                     }
574                   else
575                     {
576                       gpgol_bug (nullptr,
577                                  ERR_GET_BASE_MSG_FAILED);
578                     }
579                   // Cancel send
580                   *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
581                 }
582               MAPIFreeBuffer (propval);
583               if (*(parms->rgvarg[0].pboolVal) == VARIANT_TRUE)
584                 {
585                   TBREAK;
586                 }
587               log_debug ("%s:%s: Passing send event for mime-encrypted message %p.",
588                          SRCNAME, __func__, m_object);
589               WKSHelper::instance()->allow_notify (1000);
590               TBREAK;
591             }
592           else
593             {
594               log_debug ("%s:%s: Message %p cancelling send - "
595                          "crypto or second save failed.",
596                          SRCNAME, __func__, m_object);
597               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
598             }
599           TRETURN S_OK;
600         }
601       case Write:
602         {
603           log_oom ("%s:%s: Write : %p",
604                          SRCNAME, __func__, m_mail);
605           /* This is a bit strange. We sometimes get multiple write events
606              without a read in between. When we access the message in
607              the second event it fails and if we cancel the event outlook
608              crashes. So we have keep the m_needs_wipe state variable
609              to keep track of that. */
610           if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
611            {
612              /* This happens in the weird case */
613              log_debug ("%s:%s: Uncancellable write event.",
614                         SRCNAME, __func__);
615              TBREAK;
616            }
617
618           if (m_mail->passWrite())
619             {
620               log_debug ("%s:%s: Passing write because passNextWrite was set for %p",
621                          SRCNAME, __func__, m_mail);
622               TBREAK;
623             }
624
625           if (m_mail->isCryptoMail () && !m_mail->needsSave ())
626             {
627               if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
628                   is_draft_mail (m_object) && m_mail->decryptedSuccessfully ())
629                 {
630                   if (m_mail->cryptState () == Mail::NeedsFirstAfterWrite ||
631                       m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
632                     {
633                       log_debug ("%s:%s: re-encryption in progress. Passing.",
634                                  SRCNAME, __func__);
635                       TBREAK;
636                     }
637                   /* This is the case for a modified draft */
638                   log_debug ("%s:%s: Draft re-encryption starting now.",
639                              SRCNAME, __func__);
640                   m_mail->setIsDraftEncrypt (true);
641                   m_mail->prepareCrypto_o ();
642                   /* Passing write to trigger encrypt in after write */
643                   TBREAK;
644                 }
645               if (m_mail->cryptState() == Mail::WantsSendMIME)
646                 {
647                   log_debug ("%s:%s: Mail wants send mime. Passing.",
648                              SRCNAME, __func__);
649                   TBREAK;
650                 }
651
652               Mail *last_mail = Mail::getLastMail ();
653               if (Mail::isValidPtr (last_mail))
654                 {
655                   /* We want to identify here if there was a mail created that
656                      should receive the contents of this mail. For this we check
657                      for a write in the same loop as a mail creation.
658                      Now when switching from one mail to another this is also what
659                      happens. The new mail is loaded and the old mail is written.
660                      To distinguish the two we check that the new mail does not have
661                      an entryID, a Subject and No Size. Maybe just size or entryID
662                      would be enough but better save then sorry.
663
664                      Security consideration: Worst case we pass the write here but
665                      an unload follows before we get the scheduled revert. This
666                      would leak plaintext. But does not happen in our tests.
667
668                      Similarly if we crash or Outlook is closed before we see this
669                      revert. But as we immediately revert after the write this should
670                      also not happen. */
671                   const std::string lastSubject = last_mail->getSubject_o ();
672                   char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
673                   int lastSize = get_oom_int (last_mail->item (), "Size");
674                   std::string lastEntryStr;
675                   if (lastEntryID)
676                     {
677                       lastEntryStr = lastEntryID;
678                       xfree (lastEntryID);
679                     }
680
681                   if (!lastSize && !lastEntryStr.size () && !lastSubject.size ())
682                     {
683                       log_debug ("%s:%s: Write in the same loop as empty load."
684                                  " Pass but schedule revert.",
685                                  SRCNAME, __func__);
686
687                       /* This might be a forward. So don't invalidate yet. */
688
689                       // Mail::clearLastMail ();
690
691                       do_in_ui_thread_async (REVERT_MAIL, m_mail);
692                       TRETURN S_OK;
693                     }
694                 }
695               /* We cancel the write event to stop outlook from excessively
696                  syncing our changes.
697                  if smime support is disabled and we still have an smime
698                  mail we also don't want to cancel the write event
699                  to enable reverting this mails.
700                  */
701               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
702               log_debug ("%s:%s: Canceling write event.",
703                          SRCNAME, __func__);
704               TRETURN S_OK;
705             }
706
707           if (m_mail->isCryptoMail () && m_mail->needsSave () &&
708               m_mail->revert_o ())
709             {
710               /* An error cleaning the mail should not happen normally.
711                  But just in case there is an error we cancel the
712                  write here. */
713               log_debug ("%s:%s: Failed to remove plaintext. Canceling.",
714                          SRCNAME, __func__);
715               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
716               TRETURN S_OK;
717             }
718
719           if (!m_mail->isCryptoMail () && m_mail->is_forwarded_crypto_mail () &&
720               !m_mail->needs_crypto_m () && m_mail->cryptState () == Mail::NoCryptMail)
721             {
722               /* We are sure now that while this is a forward of an encrypted
723                * mail that the forward should not be signed or encrypted. So
724                * it's not constructed by us. We need to remove our attachments
725                * though so that they are not included in the forward. */
726               log_debug ("%s:%s: Writing unencrypted forward of crypt mail. "
727                          "Removing attachments. mail: %p item: %p",
728                          SRCNAME, __func__, m_mail, m_object);
729               if (m_mail->removeOurAttachments_o ())
730                 {
731                   // Worst case we forward some encrypted data here not
732                   // a security problem, so let it pass.
733                   log_error ("%s:%s: Failed to remove our attachments.",
734                              SRCNAME, __func__);
735                 }
736               /* Remove marker because we did this now. */
737               m_mail->setIsForwardedCryptoMail (false);
738             }
739
740           if (m_mail->isDraftEncrypt () &&
741               m_mail->cryptState () != Mail::NeedsFirstAfterWrite &&
742               m_mail->cryptState () != Mail::NeedsSecondAfterWrite)
743             {
744               log_debug ("%s:%s: Canceling write because draft encrypt is in"
745                          " progress.",
746                          SRCNAME, __func__);
747               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
748               TRETURN S_OK;
749             }
750
751           if (opt.draft_key && (m_mail->needs_crypto_m () & 1) &&
752               !m_mail->isDraftEncrypt())
753             {
754               log_debug ("%s:%s: Draft encryption starting now.",
755                          SRCNAME, __func__);
756               m_mail->setIsDraftEncrypt (true);
757               m_mail->prepareCrypto_o ();
758             }
759
760           log_debug ("%s:%s: Passing write event. %i %i",
761                      SRCNAME, __func__, m_mail->isDraftEncrypt(), m_mail->cryptState());
762           m_mail->setNeedsSave (false);
763           TBREAK;
764         }
765       case AfterWrite:
766         {
767           log_oom ("%s:%s: AfterWrite : %p",
768                          SRCNAME, __func__, m_mail);
769           if (m_mail->cryptState () == Mail::NeedsFirstAfterWrite)
770             {
771               /* Seen the first after write. Advance the state */
772               m_mail->setCryptState (Mail::NeedsActualCrypt);
773               if (m_mail->encryptSignStart_o ())
774                 {
775                   log_debug ("%s:%s: Encrypt sign start failed.",
776                              SRCNAME, __func__);
777                   m_mail->setCryptState (Mail::NoCryptMail);
778                 }
779               TRETURN S_OK;
780             }
781           if (m_mail->cryptState () == Mail::NeedsSecondAfterWrite)
782             {
783               m_mail->setCryptState (Mail::NeedsUpdateInMAPI);
784               m_mail->updateCryptMAPI_m ();
785               log_debug ("%s:%s: Second after write done.",
786                          SRCNAME, __func__);
787               TRETURN S_OK;
788             }
789           TBREAK;
790         }
791       case Close:
792         {
793           log_oom ("%s:%s: Close : %p",
794                          SRCNAME, __func__, m_mail);
795           if (m_mail->isCryptoMail ())
796             {
797               if (is_draft_mail (m_object))
798                 {
799                   /* In that case we want to ask the question to avoid data loss
800                     */
801                   log_oom ("%s:%s: Passing close because of draft status: %p",
802                            SRCNAME, __func__, m_mail);
803                 }
804               /* Close. This happens when an Opened mail is closed.
805                  To prevent the question of wether or not to save the changes
806                  (Which would save the decrypted data without an event to
807                  prevent it) we cancel the close and then either close it
808                  with discard changes or revert / save it.
809                  Contrary to documentation we can invoke close from
810                  close.
811                  */
812               if (parms->cArgs != 1 || parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
813                 {
814                   /* This happens in the weird case */
815                   log_debug ("%s:%s: Uncancellable close event.",
816                              SRCNAME, __func__);
817                   TBREAK;
818                 }
819               if (m_mail->getCloseTriggered ())
820                 {
821                   /* Our close with discard changes, pass through */
822                   m_mail->setCloseTriggered (false);
823                   TRETURN S_OK;
824                 }
825               *(parms->rgvarg[0].pboolVal) = VARIANT_TRUE;
826               log_oom ("%s:%s: Canceling close event.",
827                              SRCNAME, __func__);
828               if (Mail::close(m_mail))
829                 {
830                   log_debug ("%s:%s: Close request failed.",
831                              SRCNAME, __func__);
832                 }
833             }
834           TRETURN S_OK;
835         }
836       case Unload:
837         {
838           log_oom ("%s:%s: Unload : %p",
839                          SRCNAME, __func__, m_mail);
840           log_debug ("%s:%s: Removing Mail for message: %p.",
841                      SRCNAME, __func__, m_object);
842           delete m_mail;
843           log_oom ("%s:%s: deletion done",
844                          SRCNAME, __func__);
845           memdbg_dump ();
846           TRETURN S_OK;
847         }
848       case ReplyAll:
849       case Reply:
850           is_reply = true;
851           /* fall through */
852       case Forward:
853         {
854           log_oom ("%s:%s: %s : %p",
855                          SRCNAME, __func__, is_reply ? "reply" : "forward", m_mail);
856           int draft_flags = 0;
857           if (opt.encrypt_default)
858             {
859               draft_flags = 1;
860             }
861           if (opt.sign_default)
862             {
863               draft_flags += 2;
864             }
865           bool is_crypto_mail = m_mail->isCryptoMail ();
866
867           /* If it is a crypto mail and the settings should not be taken
868            * from the crypto mail and always encrypt / sign is on. Or
869            * If it is not a crypto mail and we have automaticalls sign_encrypt. */
870           if ((is_crypto_mail && !opt.reply_crypt && draft_flags) ||
871               (!is_crypto_mail && draft_flags))
872             {
873               /* Check if we can use the dispval */
874                 if (parms->cArgs == 2 && parms->rgvarg[1].vt == (VT_DISPATCH) &&
875                     parms->rgvarg[0].vt == (VT_BOOL | VT_BYREF))
876                 {
877                   LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
878                   if (msg)
879                     {
880                       set_gpgol_draft_info_flags (msg, draft_flags);
881                       gpgol_release (msg);
882                     }
883                   else
884                     {
885                       log_error ("%s:%s: Failed to get base message.",
886                                  SRCNAME, __func__);
887                     }
888                 }
889               else
890                 {
891                   log_error ("%s:%s: Unexpected parameters.",
892                              SRCNAME, __func__);
893                 }
894             }
895
896           if (!is_crypto_mail)
897             {
898               /* Replys to non crypto mails do not interest us anymore. */
899               TBREAK;
900             }
901
902           Mail *last_mail = Mail::getLastMail ();
903           if (Mail::isValidPtr (last_mail))
904             {
905               /* We want to identify here if there was a mail created that
906                  should receive the contents of this mail. For this we check
907                  for a forward in the same loop as a mail creation.
908
909                  We need to do it this complicated and can't just use
910                  get_mail_for_item because the mailitem pointer we get here
911                  is a different one then the one with which the mail was loaded.
912               */
913               char *lastEntryID = get_oom_string (last_mail->item (), "EntryID");
914               int lastSize = get_oom_int (last_mail->item (), "Size");
915               std::string lastEntryStr;
916               if (lastEntryID)
917                 {
918                   lastEntryStr = lastEntryID;
919                   xfree (lastEntryID);
920                 }
921
922               if (!lastSize && !lastEntryStr.size ())
923                 {
924                   if (!is_reply)
925                     {
926                       log_debug ("%s:%s: Forward in the same loop as empty "
927                                  "load Marking %p (item %p) as forwarded.",
928                                  SRCNAME, __func__, last_mail,
929                                  last_mail->item ());
930
931                       last_mail->setIsForwardedCryptoMail (true);
932                     }
933                   else
934                     {
935                       log_debug ("%s:%s: Reply in the same loop as empty "
936                                  "load Marking %p (item %p) as reply.",
937                                  SRCNAME, __func__, last_mail,
938                                  last_mail->item ());
939                     }
940                   if (m_mail->isBlockHTML ())
941                     {
942                       std::string buf;
943                       /** TRANSLATORS: Part of a warning dialog that disallows
944                         reply and forward with contents */
945                       buf = is_reply ? _("You are replying to an unsigned S/MIME "
946                                          "email.") :
947                                        _("You are forwarding an unsigned S/MIME "
948                                          "email.");
949                       buf +="\n\n";
950                       buf += _("In this version of S/MIME an attacker could "
951                                "use the missing signature to have you "
952                                "decrypt contents from a different, otherwise "
953                                "completely unrelated email and place it in the "
954                                "quote so they can get hold of it.\n"
955                                "This is why we only allow quoting to be done manually.");
956                       buf += "\n\n";
957                       buf += _("Please copy the relevant contents and insert "
958                                "them into the new email.");
959
960                       gpgol_message_box (get_active_hwnd (), buf.c_str(),
961                                          _("GpgOL"), MB_OK);
962
963                       do_in_ui_thread_async (CLEAR_REPLY_FORWARD, last_mail, 1000);
964                     }
965                 }
966               // We can now invalidate the last mail
967               Mail::clearLastMail ();
968             }
969
970           log_oom ("%s:%s: Reply Forward ReplyAll: %p",
971                          SRCNAME, __func__, m_mail);
972           if (!opt.reply_crypt)
973             {
974               TBREAK;
975             }
976           int crypto_flags = 0;
977           if (!(crypto_flags = m_mail->getCryptoFlags ()))
978             {
979               TBREAK;
980             }
981           if (parms->cArgs != 2 || parms->rgvarg[1].vt != (VT_DISPATCH) ||
982               parms->rgvarg[0].vt != (VT_BOOL | VT_BYREF))
983             {
984               /* This happens in the weird case */
985               log_debug ("%s:%s: Unexpected args %i %x %x named: %i",
986                          SRCNAME, __func__, parms->cArgs, parms->rgvarg[0].vt, parms->rgvarg[1].vt,
987                          parms->cNamedArgs);
988               TBREAK;
989             }
990           LPMESSAGE msg = get_oom_base_message (parms->rgvarg[1].pdispVal);
991           if (!msg)
992             {
993               log_debug ("%s:%s: Failed to get base message",
994                          SRCNAME, __func__);
995               TBREAK;
996             }
997           set_gpgol_draft_info_flags (msg, crypto_flags);
998           gpgol_release (msg);
999           TBREAK;
1000         }
1001       case AttachmentRemove:
1002         {
1003           log_oom ("%s:%s: AttachmentRemove: %p",
1004                          SRCNAME, __func__, m_mail);
1005           if (!m_mail->isCryptoMail () || attachRemoveWarnShown ||
1006               m_mail->attachmentRemoveWarningDisabled ())
1007             {
1008               TRETURN S_OK;
1009             }
1010           gpgol_message_box (get_active_hwnd (),
1011                              _("Attachments are part of the crypto message.\nThey "
1012                                "can't be permanently removed and will be shown again the next "
1013                                "time this message is opened."),
1014                              _("Sorry, that's not possible, yet"), MB_OK);
1015           attachRemoveWarnShown = true;
1016           TRETURN S_OK;
1017         }
1018
1019       default:
1020         log_oom ("%s:%s: Message:%p Unhandled Event: %lx \n",
1021                        SRCNAME, __func__, m_object, dispid);
1022     }
1023   TRETURN S_OK;
1024 }
1025 END_EVENT_SINK(MailItemEvents, IID_MailItemEvents)