Add lock tracing
[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       wm_ctx_t *ctx = (wm_ctx_t *) lParam;
47       log_debug ("%s:%s: Recieved user msg: %i",
48                  SRCNAME, __func__, ctx->wmsg_type);
49       switch (ctx->wmsg_type)
50         {
51           case (PARSING_DONE):
52             {
53               auto mail = (Mail*) ctx->data;
54               if (!Mail::isValidPtr (mail))
55                 {
56                   log_debug ("%s:%s: Parsing done for mail which is gone.",
57                              SRCNAME, __func__);
58                   TBREAK;
59                 }
60               mail->parsing_done();
61               TBREAK;
62             }
63           case (RECIPIENT_ADDED):
64             {
65               auto mail = (Mail*) ctx->data;
66               if (!Mail::isValidPtr (mail))
67                 {
68                   log_debug ("%s:%s: Recipient add for mail which is gone.",
69                              SRCNAME, __func__);
70                   TBREAK;
71                 }
72               mail->locateKeys_o ();
73               TBREAK;
74             }
75           case (REVERT_MAIL):
76             {
77               auto mail = (Mail*) ctx->data;
78               if (!Mail::isValidPtr (mail))
79                 {
80                   log_debug ("%s:%s: Revert mail for mail which is gone.",
81                              SRCNAME, __func__);
82                   TBREAK;
83                 }
84
85               mail->setNeedsSave (true);
86               /* Some magic here. Accessing any existing inline body cements
87                  it. Otherwise updating the body through the revert also changes
88                  the body of a inline mail. */
89               char *inlineBody = get_inline_body ();
90               xfree (inlineBody);
91
92               // Does the revert.
93               log_debug ("%s:%s: Revert mail. Invoking save.",
94                          SRCNAME, __func__);
95               invoke_oom_method (mail->item (), "Save", NULL);
96               log_debug ("%s:%s: Revert mail. Save done. Updating body..",
97                          SRCNAME, __func__);
98               mail->updateBody_o ();
99               log_debug ("%s:%s: Revert mail done.",
100                          SRCNAME, __func__);
101               TBREAK;
102             }
103           case (INVALIDATE_UI):
104             {
105               if (!invalidation_blocked)
106                 {
107                   log_debug ("%s:%s: Invalidating UI",
108                              SRCNAME, __func__);
109                   gpgoladdin_invalidate_ui();
110                   log_debug ("%s:%s: Invalidation done",
111                              SRCNAME, __func__);
112                 }
113               else
114                 {
115                   log_debug ("%s:%s: Received invalidation msg while blocked."
116                              " Ignoring it",
117                              SRCNAME, __func__);
118                 }
119               TBREAK;
120             }
121           case (INVALIDATE_LAST_MAIL):
122             {
123               log_debug ("%s:%s: clearing last mail",
124                          SRCNAME, __func__);
125               Mail::clearLastMail ();
126               TBREAK;
127             }
128           case (CLOSE):
129             {
130               auto mail = (Mail*) ctx->data;
131               if (!Mail::isValidPtr (mail))
132                 {
133                   log_debug ("%s:%s: Close for mail which is gone.",
134                              SRCNAME, __func__);
135                   TBREAK;
136                 }
137               mail->refCurrentItem();
138               Mail::closeInspector_o (mail);
139               TRACEPOINT;
140               Mail::close (mail);
141               log_debug ("%s:%s: Close finished.",
142                          SRCNAME, __func__);
143               mail->releaseCurrentItem();
144               TBREAK;
145             }
146           case (CRYPTO_DONE):
147             {
148               auto mail = (Mail*) ctx->data;
149               if (!Mail::isValidPtr (mail))
150                 {
151                   log_debug ("%s:%s: Crypto done for mail which is gone.",
152                              SRCNAME, __func__);
153                   TBREAK;
154                 }
155               // modify the mail.
156               if (mail->cryptState () == Mail::NeedsUpdateInOOM)
157                 {
158                   // Save the Mail
159                   log_debug ("%s:%s: Crypto done for %p updating oom.",
160                              SRCNAME, __func__, mail);
161                   mail->updateCryptOOM_o ();
162                 }
163               if (mail->cryptState () == Mail::NeedsSecondAfterWrite)
164                 {
165                   invoke_oom_method (mail->item (), "Save", NULL);
166                   log_debug ("%s:%s: Second save done for %p Invoking second send.",
167                              SRCNAME, __func__, mail);
168                 }
169               // Finaly this should pass.
170               if (invoke_oom_method (mail->item (), "Send", NULL))
171                 {
172                   log_error ("%s:%s: Send failed for %p. "
173                              "Trying SubmitMessage instead.\n"
174                              "This will likely crash.",
175                              SRCNAME, __func__, mail);
176
177                   /* What we do here is similar to the T3656 workaround
178                      in mailitem-events.cpp. In our tests this works but
179                      is unstable. So we only use it as a very very last
180                      resort. */
181                   auto mail_message = get_oom_base_message (mail->item());
182                   if (!mail_message)
183                     {
184                       gpgol_bug (mail->getWindow (),
185                                  ERR_GET_BASE_MSG_FAILED);
186                       TBREAK;
187                     }
188                   // It's important we use the _base_ message here.
189                   mapi_save_changes (mail_message, FORCE_SAVE);
190                   HRESULT hr = mail_message->SubmitMessage(0);
191                   gpgol_release (mail_message);
192
193                   if (hr == S_OK)
194                     {
195                       do_in_ui_thread_async (CLOSE, (LPVOID) mail);
196                     }
197                   else
198                     {
199                       log_error ("%s:%s: SubmitMessage Failed hr=0x%lx.",
200                                  SRCNAME, __func__, hr);
201                       gpgol_bug (mail->getWindow (),
202                                  ERR_SEND_FALLBACK_FAILED);
203                     }
204                 }
205               else
206                 {
207                   mail->releaseCurrentItem ();
208                 }
209               log_debug ("%s:%s:  Send for %p completed.",
210                          SRCNAME, __func__, mail);
211               TBREAK;
212             }
213           case (BRING_TO_FRONT):
214             {
215               HWND wnd = get_active_hwnd ();
216               if (wnd)
217                 {
218                   log_debug ("%s:%s: Bringing window %p to front.",
219                              SRCNAME, __func__, wnd);
220                   bring_to_front (wnd);
221                 }
222               else
223                 {
224                   log_debug ("%s:%s: No active window found for bring to front.",
225                              SRCNAME, __func__);
226                 }
227               TBREAK;
228             }
229           case (WKS_NOTIFY):
230             {
231               WKSHelper::instance ()->notify ((const char *) ctx->data);
232               xfree (ctx->data);
233               TBREAK;
234             }
235           case (CLEAR_REPLY_FORWARD):
236             {
237               auto mail = (Mail*) ctx->data;
238               if (!Mail::isValidPtr (mail))
239                 {
240                   log_debug ("%s:%s: Clear reply forward for mail which is gone.",
241                              SRCNAME, __func__);
242                   TBREAK;
243                 }
244               mail->wipe_o (true);
245               mail->removeAllAttachments_o ();
246               TBREAK;
247             }
248           case (DO_AUTO_SECURE):
249             {
250               auto mail = (Mail*) ctx->data;
251               if (!Mail::isValidPtr (mail))
252                 {
253                   log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
254                              SRCNAME, __func__);
255                   TBREAK;
256                 }
257               mail->setDoAutosecure_m (true);
258               TBREAK;
259             }
260           case (DONT_AUTO_SECURE):
261             {
262               auto mail = (Mail*) ctx->data;
263               if (!Mail::isValidPtr (mail))
264                 {
265                   log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
266                              SRCNAME, __func__);
267                   TBREAK;
268                 }
269               mail->setDoAutosecure_m (false);
270               TBREAK;
271             }
272           case (CONFIG_KEY_DONE):
273             {
274               log_debug ("%s:%s: Key configuration done.",
275                          SRCNAME, __func__);
276
277               Addressbook::update_key_o (ctx->data);
278               TBREAK;
279             }
280           default:
281             log_debug ("%s:%s: Unknown msg %x",
282                        SRCNAME, __func__, ctx->wmsg_type);
283         }
284         TRETURN 0;
285     }
286   return DefWindowProc(hWnd, message, wParam, lParam);
287 }
288
289 HWND
290 create_responder_window ()
291 {
292   TSTART;
293   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
294   char cls_name[cls_name_len];
295   if (g_responder_window)
296     {
297       TRETURN g_responder_window;
298     }
299   /* Create Window wants a mutable string as the first parameter */
300   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
301
302   WNDCLASS windowClass;
303   windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
304   windowClass.lpfnWndProc = gpgol_window_proc;
305   windowClass.cbClsExtra = 0;
306   windowClass.cbWndExtra = 0;
307   windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL);
308   windowClass.hIcon = 0;
309   windowClass.hCursor = 0;
310   windowClass.hbrBackground = 0;
311   windowClass.lpszMenuName  = 0;
312   windowClass.lpszClassName = cls_name;
313   RegisterClass(&windowClass);
314   g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0,
315                                      0, 0, 0, (HMENU) 0,
316                                      (HINSTANCE) GetModuleHandle(NULL), 0);
317   TRETURN g_responder_window;
318 }
319
320 static int
321 send_msg_to_ui_thread (wm_ctx_t *ctx)
322 {
323   TSTART;
324   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
325   char cls_name[cls_name_len];
326   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
327
328   HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
329   if (!responder)
330   {
331     log_error ("%s:%s: Failed to find responder window.",
332                SRCNAME, __func__);
333     TRETURN -1;
334   }
335   SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
336   TRETURN 0;
337 }
338
339 int
340 do_in_ui_thread (gpgol_wmsg_type type, void *data)
341 {
342   TSTART;
343   wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
344   ctx.wmsg_type = type;
345   ctx.data = data;
346
347   log_debug ("%s:%s: Sending message of type %i",
348              SRCNAME, __func__, type);
349
350   if (send_msg_to_ui_thread (&ctx))
351     {
352       TRETURN -1;
353     }
354   TRETURN ctx.err;
355 }
356
357 static DWORD WINAPI
358 do_async (LPVOID arg)
359 {
360   TSTART;
361   wm_ctx_t *ctx = (wm_ctx_t*) arg;
362   log_debug ("%s:%s: Do async with type %i after %i ms",
363              SRCNAME, __func__, ctx ? ctx->wmsg_type : -1,
364              ctx->delay);
365   if (ctx->delay)
366     {
367       Sleep (ctx->delay);
368     }
369   send_msg_to_ui_thread (ctx);
370   xfree (ctx);
371   TRETURN 0;
372 }
373
374 void
375 do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
376 {
377   TSTART;
378   wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t));
379   ctx->wmsg_type = type;
380   ctx->data = data;
381   ctx->delay = delay;
382
383   CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
384   TRETURN;
385 }
386
387 LRESULT CALLBACK
388 gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
389 {
390 /* Once we are in the close events we don't have enough
391    control to revert all our changes so we have to do it
392    with this nice little hack by catching the WM_CLOSE message
393    before it reaches outlook. */
394   LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam;
395
396   /* What we do here is that we catch all WM_CLOSE messages that
397      get to Outlook. Then we check if the last open Explorer
398      is the target of the close. In set case we start our shutdown
399      routine before we pass the WM_CLOSE to outlook */
400   switch (cwp->message)
401     {
402       case WM_CLOSE:
403       {
404         HWND lastChild = NULL;
405         log_debug ("%s:%s: Got WM_CLOSE",
406                    SRCNAME, __func__);
407         if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application())
408           {
409             TRACEPOINT;
410             TBREAK;
411           }
412         LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(),
413                                                "Explorers");
414
415         if (!explorers)
416           {
417             log_error ("%s:%s: No explorers object",
418                        SRCNAME, __func__);
419             TBREAK;
420           }
421         int count = get_oom_int (explorers, "Count");
422
423         if (count != 1)
424           {
425             log_debug ("%s:%s: More then one explorer. Not shutting down.",
426                        SRCNAME, __func__);
427             gpgol_release (explorers);
428             TBREAK;
429           }
430
431         LPDISPATCH explorer = get_oom_object (explorers, "Item(1)");
432         gpgol_release (explorers);
433
434         if (!explorer)
435           {
436             TRACEPOINT;
437             TBREAK;
438           }
439
440         /* Casting to LPOLEWINDOW and calling GetWindow
441            succeeded in Outlook 2016 but always TRETURNed
442            the number 1. So we need this hack. */
443         char *caption = get_oom_string (explorer, "Caption");
444         gpgol_release (explorer);
445         if (!caption)
446           {
447             log_debug ("%s:%s: No caption.",
448                        SRCNAME, __func__);
449             TBREAK;
450           }
451         /* rctrl_renwnd32 is the window class of outlook. */
452         HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
453                                   caption);
454         xfree (caption);
455         lastChild = hwnd;
456         if (hwnd == cwp->hwnd)
457           {
458             log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
459                        "Shutting down.",
460                        SRCNAME, __func__);
461             GpgolAddin::get_instance ()->shutdown();
462             TBREAK;
463           }
464         TBREAK;
465       }
466      case WM_SYSCOMMAND:
467         /*
468          This comes to often and when we are closed from the icon
469          we also get WM_CLOSE
470        if (cwp->wParam == SC_CLOSE)
471         {
472           log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
473                      SRCNAME, __func__);
474           GpgolAddin::get_instance ()->shutdown();
475         } */
476        break;
477      default:
478 //       log_debug ("WM: %x", (unsigned int) cwp->message);
479        break;
480     }
481   return CallNextHookEx (NULL, code, wParam, lParam);
482 }
483
484 /* Create the message hook for outlook's windowmessages
485    we are especially interested in WM_QUIT to do cleanups
486    and prevent the "Item has changed" question. */
487 HHOOK
488 create_message_hook()
489 {
490   TSTART;
491   TRETURN SetWindowsHookEx (WH_CALLWNDPROC,
492                            gpgol_hook,
493                            NULL,
494                            GetCurrentThreadId());
495 }
496
497 GPGRT_LOCK_DEFINE (invalidate_lock);
498
499 static bool invalidation_in_progress;
500
501 DWORD WINAPI
502 delayed_invalidate_ui (LPVOID minsleep)
503 {
504   TSTART;
505   if (invalidation_in_progress)
506     {
507       log_debug ("%s:%s: Invalidation canceled as it is in progress.",
508                  SRCNAME, __func__);
509       TRETURN 0;
510     }
511   TRACEPOINT;
512   invalidation_in_progress = true;
513   gpgol_lock(&invalidate_lock);
514
515   int sleep_ms = (intptr_t)minsleep;
516   Sleep (sleep_ms);
517   int i = 0;
518   while (invalidation_blocked)
519     {
520       i++;
521       if (i % 10 == 0)
522         {
523           log_debug ("%s:%s: Waiting for invalidation.",
524                      SRCNAME, __func__);
525         }
526
527       Sleep (100);
528       /* Do we need an abort statement here? */
529     }
530   do_in_ui_thread (INVALIDATE_UI, nullptr);
531   TRACEPOINT;
532   invalidation_in_progress = false;
533   gpgol_unlock(&invalidate_lock);
534   TRETURN 0;
535 }
536
537 DWORD WINAPI
538 close_mail (LPVOID mail)
539 {
540   TSTART;
541   do_in_ui_thread (CLOSE, mail);
542   TRETURN 0;
543 }
544
545 void
546 blockInv()
547 {
548   TSTART;
549   invalidation_blocked++;
550   log_oom ("%s:%s: Invalidation block count %i",
551                  SRCNAME, __func__, invalidation_blocked);
552   TRETURN;
553 }
554
555 void
556 unblockInv()
557 {
558   TSTART;
559   invalidation_blocked--;
560   log_oom ("%s:%s: Invalidation block count %i",
561                  SRCNAME, __func__, invalidation_blocked);
562
563   if (invalidation_blocked < 0)
564     {
565       log_error ("%s:%s: Invalidation block mismatch",
566                  SRCNAME, __func__);
567       invalidation_blocked = 0;
568     }
569   TRETURN;
570 }