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