d39cd2bde4719a8f2d89307e904bf6a312c1354a
[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
344     }
345   return DefWindowProc(hWnd, message, wParam, lParam);
346 }
347
348 HWND
349 create_responder_window ()
350 {
351   TSTART;
352   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
353   char cls_name[cls_name_len];
354   if (g_responder_window)
355     {
356       TRETURN g_responder_window;
357     }
358   /* Create Window wants a mutable string as the first parameter */
359   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
360
361   WNDCLASS windowClass;
362   windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS;
363   windowClass.lpfnWndProc = gpgol_window_proc;
364   windowClass.cbClsExtra = 0;
365   windowClass.cbWndExtra = 0;
366   windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL);
367   windowClass.hIcon = 0;
368   windowClass.hCursor = 0;
369   windowClass.hbrBackground = 0;
370   windowClass.lpszMenuName  = 0;
371   windowClass.lpszClassName = cls_name;
372   RegisterClass(&windowClass);
373   g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0,
374                                      0, 0, 0, (HMENU) 0,
375                                      (HINSTANCE) GetModuleHandle(NULL), 0);
376   TRETURN g_responder_window;
377 }
378
379 static int
380 send_msg_to_ui_thread (wm_ctx_t *ctx)
381 {
382   TSTART;
383   size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
384   char cls_name[cls_name_len];
385   snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
386
387   HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
388   if (!responder)
389   {
390     log_error ("%s:%s: Failed to find responder window.",
391                SRCNAME, __func__);
392     TRETURN -1;
393   }
394   SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
395   TRETURN 0;
396 }
397
398 int
399 do_in_ui_thread (gpgol_wmsg_type type, void *data)
400 {
401   TSTART;
402   wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
403   ctx.wmsg_type = type;
404   ctx.data = data;
405
406   log_debug ("%s:%s: Sending message of type %i",
407              SRCNAME, __func__, type);
408
409   if (send_msg_to_ui_thread (&ctx))
410     {
411       TRETURN -1;
412     }
413   TRETURN ctx.err;
414 }
415
416 static DWORD WINAPI
417 do_async (LPVOID arg)
418 {
419   TSTART;
420   wm_ctx_t *ctx = (wm_ctx_t*) arg;
421   log_debug ("%s:%s: Do async with type %i after %i ms",
422              SRCNAME, __func__, ctx ? ctx->wmsg_type : -1,
423              ctx->delay);
424   if (ctx->delay)
425     {
426       Sleep (ctx->delay);
427     }
428   send_msg_to_ui_thread (ctx);
429   xfree (ctx);
430   TRETURN 0;
431 }
432
433 void
434 do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
435 {
436   TSTART;
437   wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t));
438   ctx->wmsg_type = type;
439   ctx->data = data;
440   ctx->delay = delay;
441
442   CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
443   TRETURN;
444 }
445
446 LRESULT CALLBACK
447 gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
448 {
449 /* Once we are in the close events we don't have enough
450    control to revert all our changes so we have to do it
451    with this nice little hack by catching the WM_CLOSE message
452    before it reaches outlook. */
453   LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam;
454
455   /* What we do here is that we catch all WM_CLOSE messages that
456      get to Outlook. Then we check if the last open Explorer
457      is the target of the close. In set case we start our shutdown
458      routine before we pass the WM_CLOSE to outlook */
459   switch (cwp->message)
460     {
461       case WM_CLOSE:
462       {
463         HWND lastChild = NULL;
464         log_debug ("%s:%s: Got WM_CLOSE",
465                    SRCNAME, __func__);
466         if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application())
467           {
468             TRACEPOINT;
469             TBREAK;
470           }
471         LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(),
472                                                "Explorers");
473
474         if (!explorers)
475           {
476             log_error ("%s:%s: No explorers object",
477                        SRCNAME, __func__);
478             TBREAK;
479           }
480         int count = get_oom_int (explorers, "Count");
481
482         if (count != 1)
483           {
484             log_debug ("%s:%s: More then one explorer. Not shutting down.",
485                        SRCNAME, __func__);
486             gpgol_release (explorers);
487             TBREAK;
488           }
489
490         LPDISPATCH explorer = get_oom_object (explorers, "Item(1)");
491         gpgol_release (explorers);
492
493         if (!explorer)
494           {
495             TRACEPOINT;
496             TBREAK;
497           }
498
499         /* Casting to LPOLEWINDOW and calling GetWindow
500            succeeded in Outlook 2016 but always TRETURNed
501            the number 1. So we need this hack. */
502         char *caption = get_oom_string (explorer, "Caption");
503         gpgol_release (explorer);
504         if (!caption)
505           {
506             log_debug ("%s:%s: No caption.",
507                        SRCNAME, __func__);
508             TBREAK;
509           }
510         /* rctrl_renwnd32 is the window class of outlook. */
511         HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
512                                   caption);
513         xfree (caption);
514         lastChild = hwnd;
515         if (hwnd == cwp->hwnd)
516           {
517             log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
518                        "Shutting down.",
519                        SRCNAME, __func__);
520             GpgolAddin::get_instance ()->shutdown();
521             TBREAK;
522           }
523         TBREAK;
524       }
525      case WM_SYSCOMMAND:
526         /*
527          This comes to often and when we are closed from the icon
528          we also get WM_CLOSE
529        if (cwp->wParam == SC_CLOSE)
530         {
531           log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
532                      SRCNAME, __func__);
533           GpgolAddin::get_instance ()->shutdown();
534         } */
535        break;
536      default:
537 //       log_debug ("WM: %x", (unsigned int) cwp->message);
538        break;
539     }
540   return CallNextHookEx (NULL, code, wParam, lParam);
541 }
542
543 /* Create the message hook for outlook's windowmessages
544    we are especially interested in WM_QUIT to do cleanups
545    and prevent the "Item has changed" question. */
546 HHOOK
547 create_message_hook()
548 {
549   TSTART;
550   TRETURN SetWindowsHookEx (WH_CALLWNDPROC,
551                            gpgol_hook,
552                            NULL,
553                            GetCurrentThreadId());
554 }
555
556 GPGRT_LOCK_DEFINE (invalidate_lock);
557
558 static bool invalidation_in_progress;
559
560 DWORD WINAPI
561 delayed_invalidate_ui (LPVOID minsleep)
562 {
563   TSTART;
564   if (invalidation_in_progress)
565     {
566       log_debug ("%s:%s: Invalidation canceled as it is in progress.",
567                  SRCNAME, __func__);
568       TRETURN 0;
569     }
570   TRACEPOINT;
571   invalidation_in_progress = true;
572   gpgol_lock(&invalidate_lock);
573
574   int sleep_ms = (intptr_t)minsleep;
575   Sleep (sleep_ms);
576   int i = 0;
577   while (invalidation_blocked)
578     {
579       i++;
580       if (i % 10 == 0)
581         {
582           log_debug ("%s:%s: Waiting for invalidation.",
583                      SRCNAME, __func__);
584         }
585
586       Sleep (100);
587       /* Do we need an abort statement here? */
588     }
589   do_in_ui_thread (INVALIDATE_UI, nullptr);
590   TRACEPOINT;
591   invalidation_in_progress = false;
592   gpgol_unlock(&invalidate_lock);
593   TRETURN 0;
594 }
595
596 DWORD WINAPI
597 close_mail (LPVOID mail)
598 {
599   TSTART;
600   do_in_ui_thread (CLOSE, mail);
601   TRETURN 0;
602 }
603
604 void
605 blockInv()
606 {
607   TSTART;
608   invalidation_blocked++;
609   log_oom ("%s:%s: Invalidation block count %i",
610                  SRCNAME, __func__, invalidation_blocked);
611   TRETURN;
612 }
613
614 void
615 unblockInv()
616 {
617   TSTART;
618   invalidation_blocked--;
619   log_oom ("%s:%s: Invalidation block count %i",
620                  SRCNAME, __func__, invalidation_blocked);
621
622   if (invalidation_blocked < 0)
623     {
624       log_error ("%s:%s: Invalidation block mismatch",
625                  SRCNAME, __func__);
626       invalidation_blocked = 0;
627     }
628   TRETURN;
629 }