Add explicit decrypt as WindowMessage handler
[gpgol.git] / src / windowmessages.cpp
1 /* @file windowmessages.h
2  * @brief Helper class to work with the windowmessage handler thread.
3  *
4  * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
5  * Software engineering by Intevation GmbH
6  *
7  * This file is part of GpgOL.
8  *
9  * GpgOL is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * GpgOL is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22 #include "windowmessages.h"
23
24 #include "common.h"
25 #include "oomhelp.h"
26 #include "mail.h"
27 #include "gpgoladdin.h"
28 #include "wks-helper.h"
29 #include "addressbook.h"
30
31 #include <stdio.h>
32
33 #define RESPONDER_CLASS_NAME "GpgOLResponder"
34
35 /* Singleton window */
36 static HWND g_responder_window = NULL;
37 static int invalidation_blocked = 0;
38
39 LONG_PTR WINAPI
40 gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
41 {
42 //  log_debug ("WMG: %x", (unsigned int) message);
43   if (message == WM_USER + 42)
44     {
45       TSTART;
46
47       if (!lParam)
48         {
49           log_error ("%s:%s: Recieved user msg without lparam",
50                      SRCNAME, __func__);
51           TRETURN DefWindowProc(hWnd, message, wParam, lParam);
52         }
53       wm_ctx_t *ctx = (wm_ctx_t *) lParam;
54       log_debug ("%s:%s: Recieved user msg: %i",
55                  SRCNAME, __func__, ctx->wmsg_type);
56       switch (ctx->wmsg_type)
57         {
58           case (PARSING_DONE):
59             {
60               auto mail = (Mail*) ctx->data;
61               if (!Mail::isValidPtr (mail))
62                 {
63                   log_debug ("%s:%s: Parsing done for mail which is gone.",
64                              SRCNAME, __func__);
65                   TBREAK;
66                 }
67               mail->parsing_done();
68               TBREAK;
69             }
70           case (RECIPIENT_ADDED):
71             {
72               auto mail = (Mail*) ctx->data;
73               if (!Mail::isValidPtr (mail))
74                 {
75                   log_debug ("%s:%s: Recipient add for mail which is gone.",
76                              SRCNAME, __func__);
77                   TBREAK;
78                 }
79               mail->locateKeys_o ();
80               TBREAK;
81             }
82           case (REVERT_MAIL):
83             {
84               auto mail = (Mail*) ctx->data;
85               if (!Mail::isValidPtr (mail))
86                 {
87                   log_debug ("%s:%s: Revert mail for mail which is gone.",
88                              SRCNAME, __func__);
89                   TBREAK;
90                 }
91
92               mail->setNeedsSave (true);
93               /* Some magic here. Accessing any existing inline body cements
94                  it. Otherwise updating the body through the revert also changes
95                  the body of a inline mail. */
96               char *inlineBody = get_inline_body ();
97               xfree (inlineBody);
98
99               // Does the revert.
100               log_debug ("%s:%s: Revert mail. Invoking save.",
101                          SRCNAME, __func__);
102               invoke_oom_method (mail->item (), "Save", NULL);
103               log_debug ("%s:%s: Revert mail. Save done. Updating body..",
104                          SRCNAME, __func__);
105               mail->updateBody_o ();
106               log_debug ("%s:%s: Revert mail done.",
107                          SRCNAME, __func__);
108               TBREAK;
109             }
110           case (INVALIDATE_UI):
111             {
112               if (!invalidation_blocked)
113                 {
114                   log_debug ("%s:%s: Invalidating UI",
115                              SRCNAME, __func__);
116                   gpgoladdin_invalidate_ui();
117                   log_debug ("%s:%s: Invalidation done",
118                              SRCNAME, __func__);
119                 }
120               else
121                 {
122                   log_debug ("%s:%s: Received invalidation msg while blocked."
123                              " Ignoring it",
124                              SRCNAME, __func__);
125                 }
126               TBREAK;
127             }
128           case (INVALIDATE_LAST_MAIL):
129             {
130               log_debug ("%s:%s: clearing last mail",
131                          SRCNAME, __func__);
132               Mail::clearLastMail ();
133               TBREAK;
134             }
135           case (CLOSE):
136             {
137               auto mail = (Mail*) ctx->data;
138               if (!Mail::isValidPtr (mail))
139                 {
140                   log_debug ("%s:%s: Close for mail which is gone.",
141                              SRCNAME, __func__);
142                   TBREAK;
143                 }
144               mail->refCurrentItem();
145               Mail::closeInspector_o (mail);
146               TRACEPOINT;
147               Mail::close (mail);
148               log_debug ("%s:%s: Close finished.",
149                          SRCNAME, __func__);
150               mail->releaseCurrentItem();
151               TBREAK;
152             }
153           case (CRYPTO_DONE):
154             {
155               auto mail = (Mail*) ctx->data;
156               if (!Mail::isValidPtr (mail))
157                 {
158                   log_debug ("%s:%s: Crypto done for mail which is gone.",
159                              SRCNAME, __func__);
160                   TBREAK;
161                 }
162               // modify the mail.
163               if (mail->cryptState () == Mail::NeedsUpdateInOOM)
164                 {
165                   // Save the Mail
166                   log_debug ("%s:%s: Crypto done for %p updating oom.",
167                              SRCNAME, __func__, mail);
168                   mail->updateCryptOOM_o ();
169                 }
170               if (mail->cryptState () == Mail::NeedsSecondAfterWrite)
171                 {
172                   invoke_oom_method (mail->item (), "Save", NULL);
173                   log_debug ("%s:%s: Second save done for %p Invoking second send.",
174                              SRCNAME, __func__, mail);
175                 }
176               // Finaly this should pass.
177               if (invoke_oom_method (mail->item (), "Send", NULL))
178                 {
179                   log_error ("%s:%s: Send failed for %p. "
180                              "Trying SubmitMessage instead.\n"
181                              "This will likely crash.",
182                              SRCNAME, __func__, mail);
183
184                   /* What we do here is similar to the T3656 workaround
185                      in mailitem-events.cpp. In our tests this works but
186                      is unstable. So we only use it as a very very last
187                      resort. */
188                   auto mail_message = get_oom_base_message (mail->item());
189                   if (!mail_message)
190                     {
191                       gpgol_bug (mail->getWindow (),
192                                  ERR_GET_BASE_MSG_FAILED);
193                       TBREAK;
194                     }
195                   // It's important we use the _base_ message here.
196                   mapi_save_changes (mail_message, FORCE_SAVE);
197                   HRESULT hr = mail_message->SubmitMessage(0);
198                   gpgol_release (mail_message);
199
200                   if (hr == S_OK)
201                     {
202                       do_in_ui_thread_async (CLOSE, (LPVOID) mail);
203                     }
204                   else
205                     {
206                       log_error ("%s:%s: SubmitMessage Failed hr=0x%lx.",
207                                  SRCNAME, __func__, hr);
208                       gpgol_bug (mail->getWindow (),
209                                  ERR_SEND_FALLBACK_FAILED);
210                     }
211                 }
212               else
213                 {
214                   mail->releaseCurrentItem ();
215                 }
216               log_debug ("%s:%s:  Send for %p completed.",
217                          SRCNAME, __func__, mail);
218               TBREAK;
219             }
220           case (BRING_TO_FRONT):
221             {
222               HWND wnd = get_active_hwnd ();
223               if (wnd)
224                 {
225                   log_debug ("%s:%s: Bringing window %p to front.",
226                              SRCNAME, __func__, wnd);
227                   bring_to_front (wnd);
228                 }
229               else
230                 {
231                   log_debug ("%s:%s: No active window found for bring to front.",
232                              SRCNAME, __func__);
233                 }
234               TBREAK;
235             }
236           case (WKS_NOTIFY):
237             {
238               WKSHelper::instance ()->notify ((const char *) ctx->data);
239               xfree (ctx->data);
240               TBREAK;
241             }
242           case (CLEAR_REPLY_FORWARD):
243             {
244               auto mail = (Mail*) ctx->data;
245               if (!Mail::isValidPtr (mail))
246                 {
247                   log_debug ("%s:%s: Clear reply forward for mail which is gone.",
248                              SRCNAME, __func__);
249                   TBREAK;
250                 }
251               mail->wipe_o (true);
252               mail->removeAllAttachments_o ();
253               TBREAK;
254             }
255           case (DO_AUTO_SECURE):
256             {
257               auto mail = (Mail*) ctx->data;
258               if (!Mail::isValidPtr (mail))
259                 {
260                   log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
261                              SRCNAME, __func__);
262                   TBREAK;
263                 }
264               mail->setDoAutosecure_m (true);
265               TBREAK;
266             }
267           case (DONT_AUTO_SECURE):
268             {
269               auto mail = (Mail*) ctx->data;
270               if (!Mail::isValidPtr (mail))
271                 {
272                   log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
273                              SRCNAME, __func__);
274                   TBREAK;
275                 }
276               mail->setDoAutosecure_m (false);
277               TBREAK;
278             }
279           case (CONFIG_KEY_DONE):
280             {
281               log_debug ("%s:%s: Key configuration done.",
282                          SRCNAME, __func__);
283
284               Addressbook::update_key_o (ctx->data);
285               TBREAK;
286             }
287           default:
288             log_debug ("%s:%s: Unknown msg %x",
289                        SRCNAME, __func__, ctx->wmsg_type);
290         }
291         TRETURN 0;
292     }
293   else if (message == WM_USER)
294     {
295       switch (wParam)
296         {
297           case (EXT_API_CLOSE_ALL):
298             {
299               log_debug ("%s:%s: Closing all mails.",
300                          SRCNAME, __func__);
301               Mail::closeAllMails_o ();
302               TRETURN 0;
303             }
304           default:
305             log_debug ("%s:%s: Unknown external msg %i",
306                        SRCNAME, __func__, (int) wParam);
307         }
308     }
309   else if (message == WM_COPYDATA)
310     {
311       log_debug ("%s:%s Received copydata message.",
312                  SRCNAME, __func__);
313       if (!lParam)
314         {
315           STRANGEPOINT;
316           TRETURN DefWindowProc(hWnd, message, wParam, lParam);
317         }
318       const COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lParam;
319
320       if (cds->dwData == EXT_API_CLOSE)
321         {
322           log_debug ("%s:%s CopyData with external close. Payload: %.*s",
323                      SRCNAME, __func__, (int)cds->cbData, (char *) cds->lpData);
324
325           std::string uid ((char*) cds->lpData, cds->cbData);
326           auto mail = Mail::getMailForUUID (uid.c_str ());
327           if (!mail)
328             {
329               log_error ("%s:%s Failed to find mail for: %s",
330                          SRCNAME, __func__, uid.c_str() );
331               TRETURN DefWindowProc(hWnd, message, wParam, lParam);
332             }
333
334           mail->refCurrentItem ();
335           Mail::closeInspector_o (mail);
336           TRACEPOINT;
337           Mail::close (mail);
338           log_debug ("%s:%s: Close for %p uid: %s finished.",
339                      SRCNAME, __func__, mail, uid.c_str ());
340           mail->releaseCurrentItem();
341           TRETURN 0;
342         }
343       else if (cds->dwData == EXT_API_DECRYPT)
344         {
345           log_debug ("%s:%s CopyData with external decrypt. Payload: %.*s",
346                      SRCNAME, __func__, (int)cds->cbData, (char *) cds->lpData);
347
348           std::string uid ((char*) cds->lpData, cds->cbData);
349           auto mail = Mail::getMailForUUID (uid.c_str ());
350           if (!mail)
351             {
352               log_error ("%s:%s Failed to find mail for: %s",
353                          SRCNAME, __func__, uid.c_str() );
354               TRETURN DefWindowProc(hWnd, message, wParam, lParam);
355             }
356
357           log_debug ("%s:%s: Decrypting %p again.",
358                      SRCNAME, __func__, mail);
359           mail->decryptVerify_o ();
360           TRETURN 0;
361         }
362
363     }
364   return DefWindowProc(hWnd, message, wParam, lParam);
365 }
366
367 HWND
368 create_responder_window ()
369 {
370   TSTART;
371   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
372   char cls_name[cls_name_len];
373   if (g_responder_window)
374     {
375       TRETURN g_responder_window;
376     }
377   /* Create Window wants a mutable string as the first parameter */
378   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
379
380   WNDCLASS windowClass;
381   windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
382   windowClass.lpfnWndProc = gpgol_window_proc;
383   windowClass.cbClsExtra = 0;
384   windowClass.cbWndExtra = 0;
385   windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL);
386   windowClass.hIcon = 0;
387   windowClass.hCursor = 0;
388   windowClass.hbrBackground = 0;
389   windowClass.lpszMenuName  = 0;
390   windowClass.lpszClassName = cls_name;
391   RegisterClass(&windowClass);
392   g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0,
393                                      0, 0, 0, (HMENU) 0,
394                                      (HINSTANCE) GetModuleHandle(NULL), 0);
395   TRETURN g_responder_window;
396 }
397
398 static int
399 send_msg_to_ui_thread (wm_ctx_t *ctx)
400 {
401   TSTART;
402   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
403   char cls_name[cls_name_len];
404   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
405
406   HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
407   if (!responder)
408   {
409     log_error ("%s:%s: Failed to find responder window.",
410                SRCNAME, __func__);
411     TRETURN -1;
412   }
413   SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
414   TRETURN 0;
415 }
416
417 int
418 do_in_ui_thread (gpgol_wmsg_type type, void *data)
419 {
420   TSTART;
421   wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
422   ctx.wmsg_type = type;
423   ctx.data = data;
424
425   log_debug ("%s:%s: Sending message of type %i",
426              SRCNAME, __func__, type);
427
428   if (send_msg_to_ui_thread (&ctx))
429     {
430       TRETURN -1;
431     }
432   TRETURN ctx.err;
433 }
434
435 static DWORD WINAPI
436 do_async (LPVOID arg)
437 {
438   TSTART;
439   wm_ctx_t *ctx = (wm_ctx_t*) arg;
440   log_debug ("%s:%s: Do async with type %i after %i ms",
441              SRCNAME, __func__, ctx ? ctx->wmsg_type : -1,
442              ctx->delay);
443   if (ctx->delay)
444     {
445       Sleep (ctx->delay);
446     }
447   send_msg_to_ui_thread (ctx);
448   xfree (ctx);
449   TRETURN 0;
450 }
451
452 void
453 do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
454 {
455   TSTART;
456   wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t));
457   ctx->wmsg_type = type;
458   ctx->data = data;
459   ctx->delay = delay;
460
461   CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
462   TRETURN;
463 }
464
465 LRESULT CALLBACK
466 gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
467 {
468 /* Once we are in the close events we don't have enough
469    control to revert all our changes so we have to do it
470    with this nice little hack by catching the WM_CLOSE message
471    before it reaches outlook. */
472   LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam;
473
474   /* What we do here is that we catch all WM_CLOSE messages that
475      get to Outlook. Then we check if the last open Explorer
476      is the target of the close. In set case we start our shutdown
477      routine before we pass the WM_CLOSE to outlook */
478   switch (cwp->message)
479     {
480       case WM_CLOSE:
481       {
482         HWND lastChild = NULL;
483         log_debug ("%s:%s: Got WM_CLOSE",
484                    SRCNAME, __func__);
485         if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application())
486           {
487             TRACEPOINT;
488             TBREAK;
489           }
490         LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(),
491                                                "Explorers");
492
493         if (!explorers)
494           {
495             log_error ("%s:%s: No explorers object",
496                        SRCNAME, __func__);
497             TBREAK;
498           }
499         int count = get_oom_int (explorers, "Count");
500
501         if (count != 1)
502           {
503             log_debug ("%s:%s: More then one explorer. Not shutting down.",
504                        SRCNAME, __func__);
505             gpgol_release (explorers);
506             TBREAK;
507           }
508
509         LPDISPATCH explorer = get_oom_object (explorers, "Item(1)");
510         gpgol_release (explorers);
511
512         if (!explorer)
513           {
514             TRACEPOINT;
515             TBREAK;
516           }
517
518         /* Casting to LPOLEWINDOW and calling GetWindow
519            succeeded in Outlook 2016 but always TRETURNed
520            the number 1. So we need this hack. */
521         char *caption = get_oom_string (explorer, "Caption");
522         gpgol_release (explorer);
523         if (!caption)
524           {
525             log_debug ("%s:%s: No caption.",
526                        SRCNAME, __func__);
527             TBREAK;
528           }
529         /* rctrl_renwnd32 is the window class of outlook. */
530         HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
531                                   caption);
532         xfree (caption);
533         lastChild = hwnd;
534         if (hwnd == cwp->hwnd)
535           {
536             log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
537                        "Shutting down.",
538                        SRCNAME, __func__);
539             GpgolAddin::get_instance ()->shutdown();
540             TBREAK;
541           }
542         TBREAK;
543       }
544      case WM_SYSCOMMAND:
545         /*
546          This comes to often and when we are closed from the icon
547          we also get WM_CLOSE
548        if (cwp->wParam == SC_CLOSE)
549         {
550           log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
551                      SRCNAME, __func__);
552           GpgolAddin::get_instance ()->shutdown();
553         } */
554        break;
555      default:
556 //       log_debug ("WM: %x", (unsigned int) cwp->message);
557        break;
558     }
559   return CallNextHookEx (NULL, code, wParam, lParam);
560 }
561
562 /* Create the message hook for outlook's windowmessages
563    we are especially interested in WM_QUIT to do cleanups
564    and prevent the "Item has changed" question. */
565 HHOOK
566 create_message_hook()
567 {
568   TSTART;
569   TRETURN SetWindowsHookEx (WH_CALLWNDPROC,
570                            gpgol_hook,
571                            NULL,
572                            GetCurrentThreadId());
573 }
574
575 GPGRT_LOCK_DEFINE (invalidate_lock);
576
577 static bool invalidation_in_progress;
578
579 DWORD WINAPI
580 delayed_invalidate_ui (LPVOID minsleep)
581 {
582   TSTART;
583   if (invalidation_in_progress)
584     {
585       log_debug ("%s:%s: Invalidation canceled as it is in progress.",
586                  SRCNAME, __func__);
587       TRETURN 0;
588     }
589   TRACEPOINT;
590   invalidation_in_progress = true;
591   gpgol_lock(&invalidate_lock);
592
593   int sleep_ms = (intptr_t)minsleep;
594   Sleep (sleep_ms);
595   int i = 0;
596   while (invalidation_blocked)
597     {
598       i++;
599       if (i % 10 == 0)
600         {
601           log_debug ("%s:%s: Waiting for invalidation.",
602                      SRCNAME, __func__);
603         }
604
605       Sleep (100);
606       /* Do we need an abort statement here? */
607     }
608   do_in_ui_thread (INVALIDATE_UI, nullptr);
609   TRACEPOINT;
610   invalidation_in_progress = false;
611   gpgol_unlock(&invalidate_lock);
612   TRETURN 0;
613 }
614
615 DWORD WINAPI
616 close_mail (LPVOID mail)
617 {
618   TSTART;
619   do_in_ui_thread (CLOSE, mail);
620   TRETURN 0;
621 }
622
623 void
624 blockInv()
625 {
626   TSTART;
627   invalidation_blocked++;
628   log_oom ("%s:%s: Invalidation block count %i",
629                  SRCNAME, __func__, invalidation_blocked);
630   TRETURN;
631 }
632
633 void
634 unblockInv()
635 {
636   TSTART;
637   invalidation_blocked--;
638   log_oom ("%s:%s: Invalidation block count %i",
639                  SRCNAME, __func__, invalidation_blocked);
640
641   if (invalidation_blocked < 0)
642     {
643       log_error ("%s:%s: Invalidation block mismatch",
644                  SRCNAME, __func__);
645       invalidation_blocked = 0;
646     }
647   TRETURN;
648 }