1 /* @file windowmessages.h
2 * @brief Helper class to work with the windowmessage handler thread.
4 * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik
5 * Software engineering by Intevation GmbH
7 * This file is part of GpgOL.
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.
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.
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/>.
22 #include "windowmessages.h"
27 #include "gpgoladdin.h"
28 #include "wks-helper.h"
29 #include "addressbook.h"
33 #define RESPONDER_CLASS_NAME "GpgOLResponder"
35 /* Singleton window */
36 static HWND g_responder_window = NULL;
37 static int invalidation_blocked = 0;
40 gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
42 // log_debug ("WMG: %x", (unsigned int) message);
43 if (message == WM_USER + 42)
49 log_error ("%s:%s: Recieved user msg without lparam",
51 TRETURN DefWindowProc(hWnd, message, wParam, lParam);
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)
60 auto mail = (Mail*) ctx->data;
61 if (!Mail::isValidPtr (mail))
63 log_debug ("%s:%s: Parsing done for mail which is gone.",
70 case (RECIPIENT_ADDED):
72 auto mail = (Mail*) ctx->data;
73 if (!Mail::isValidPtr (mail))
75 log_debug ("%s:%s: Recipient add for mail which is gone.",
79 mail->locateKeys_o ();
84 auto mail = (Mail*) ctx->data;
85 if (!Mail::isValidPtr (mail))
87 log_debug ("%s:%s: Revert mail for mail which is gone.",
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 ();
100 log_debug ("%s:%s: Revert mail. Invoking save.",
102 invoke_oom_method (mail->item (), "Save", NULL);
103 log_debug ("%s:%s: Revert mail. Save done. Updating body..",
105 mail->updateBody_o ();
106 log_debug ("%s:%s: Revert mail done.",
110 case (INVALIDATE_UI):
112 if (!invalidation_blocked)
114 log_debug ("%s:%s: Invalidating UI",
116 gpgoladdin_invalidate_ui();
117 log_debug ("%s:%s: Invalidation done",
122 log_debug ("%s:%s: Received invalidation msg while blocked."
128 case (INVALIDATE_LAST_MAIL):
130 log_debug ("%s:%s: clearing last mail",
132 Mail::clearLastMail ();
137 auto mail = (Mail*) ctx->data;
138 if (!Mail::isValidPtr (mail))
140 log_debug ("%s:%s: Close for mail which is gone.",
144 mail->refCurrentItem();
145 Mail::closeInspector_o (mail);
148 log_debug ("%s:%s: Close finished.",
150 mail->releaseCurrentItem();
155 auto mail = (Mail*) ctx->data;
156 if (!Mail::isValidPtr (mail))
158 log_debug ("%s:%s: Crypto done for mail which is gone.",
163 if (mail->cryptState () == Mail::NeedsUpdateInOOM)
166 log_debug ("%s:%s: Crypto done for %p updating oom.",
167 SRCNAME, __func__, mail);
168 mail->updateCryptOOM_o ();
170 if (mail->cryptState () == Mail::NeedsSecondAfterWrite)
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);
176 // Finaly this should pass.
177 if (invoke_oom_method (mail->item (), "Send", NULL))
179 log_error ("%s:%s: Send failed for %p. "
180 "Trying SubmitMessage instead.\n"
181 "This will likely crash.",
182 SRCNAME, __func__, mail);
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
188 auto mail_message = get_oom_base_message (mail->item());
191 gpgol_bug (mail->getWindow (),
192 ERR_GET_BASE_MSG_FAILED);
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);
202 do_in_ui_thread_async (CLOSE, (LPVOID) mail);
206 log_error ("%s:%s: SubmitMessage Failed hr=0x%lx.",
207 SRCNAME, __func__, hr);
208 gpgol_bug (mail->getWindow (),
209 ERR_SEND_FALLBACK_FAILED);
214 mail->releaseCurrentItem ();
216 log_debug ("%s:%s: Send for %p completed.",
217 SRCNAME, __func__, mail);
220 case (BRING_TO_FRONT):
222 HWND wnd = get_active_hwnd ();
225 log_debug ("%s:%s: Bringing window %p to front.",
226 SRCNAME, __func__, wnd);
227 bring_to_front (wnd);
231 log_debug ("%s:%s: No active window found for bring to front.",
238 WKSHelper::instance ()->notify ((const char *) ctx->data);
242 case (CLEAR_REPLY_FORWARD):
244 auto mail = (Mail*) ctx->data;
245 if (!Mail::isValidPtr (mail))
247 log_debug ("%s:%s: Clear reply forward for mail which is gone.",
252 mail->removeAllAttachments_o ();
255 case (DO_AUTO_SECURE):
257 auto mail = (Mail*) ctx->data;
258 if (!Mail::isValidPtr (mail))
260 log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
264 mail->setDoAutosecure_m (true);
267 case (DONT_AUTO_SECURE):
269 auto mail = (Mail*) ctx->data;
270 if (!Mail::isValidPtr (mail))
272 log_debug ("%s:%s: DO_AUTO_SECURE for mail which is gone.",
276 mail->setDoAutosecure_m (false);
279 case (CONFIG_KEY_DONE):
281 log_debug ("%s:%s: Key configuration done.",
284 Addressbook::update_key_o (ctx->data);
288 log_debug ("%s:%s: Unknown msg %x",
289 SRCNAME, __func__, ctx->wmsg_type);
293 else if (message == WM_USER)
297 case (EXT_API_CLOSE_ALL):
299 log_debug ("%s:%s: Closing all mails.",
301 Mail::closeAllMails_o ();
305 log_debug ("%s:%s: Unknown external msg %i",
306 SRCNAME, __func__, (int) wParam);
309 else if (message == WM_COPYDATA)
311 log_debug ("%s:%s Received copydata message.",
316 TRETURN DefWindowProc(hWnd, message, wParam, lParam);
318 const COPYDATASTRUCT *cds = (COPYDATASTRUCT*)lParam;
320 if (cds->dwData == EXT_API_CLOSE)
322 log_debug ("%s:%s CopyData with external close. Payload: %.*s",
323 SRCNAME, __func__, (int)cds->cbData, (char *) cds->lpData);
325 std::string uid ((char*) cds->lpData, cds->cbData);
326 auto mail = Mail::getMailForUUID (uid.c_str ());
329 log_error ("%s:%s Failed to find mail for: %s",
330 SRCNAME, __func__, uid.c_str() );
331 TRETURN DefWindowProc(hWnd, message, wParam, lParam);
334 mail->refCurrentItem ();
335 Mail::closeInspector_o (mail);
338 log_debug ("%s:%s: Close for %p uid: %s finished.",
339 SRCNAME, __func__, mail, uid.c_str ());
340 mail->releaseCurrentItem();
343 else if (cds->dwData == EXT_API_DECRYPT)
345 log_debug ("%s:%s CopyData with external decrypt. Payload: %.*s",
346 SRCNAME, __func__, (int)cds->cbData, (char *) cds->lpData);
348 std::string uid ((char*) cds->lpData, cds->cbData);
349 auto mail = Mail::getMailForUUID (uid.c_str ());
352 log_error ("%s:%s Failed to find mail for: %s",
353 SRCNAME, __func__, uid.c_str() );
354 TRETURN DefWindowProc(hWnd, message, wParam, lParam);
357 log_debug ("%s:%s: Decrypting %p again.",
358 SRCNAME, __func__, mail);
359 mail->decryptVerify_o ();
364 return DefWindowProc(hWnd, message, wParam, lParam);
368 create_responder_window ()
371 size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1;
372 char cls_name[cls_name_len];
373 if (g_responder_window)
375 TRETURN g_responder_window;
377 /* Create Window wants a mutable string as the first parameter */
378 snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME);
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,
394 (HINSTANCE) GetModuleHandle(NULL), 0);
395 TRETURN g_responder_window;
399 send_msg_to_ui_thread (wm_ctx_t *ctx)
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);
406 HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME);
409 log_error ("%s:%s: Failed to find responder window.",
413 SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx);
418 do_in_ui_thread (gpgol_wmsg_type type, void *data)
421 wm_ctx_t ctx = {NULL, UNKNOWN, 0, 0};
422 ctx.wmsg_type = type;
425 log_debug ("%s:%s: Sending message of type %i",
426 SRCNAME, __func__, type);
428 if (send_msg_to_ui_thread (&ctx))
436 do_async (LPVOID arg)
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,
447 send_msg_to_ui_thread (ctx);
453 do_in_ui_thread_async (gpgol_wmsg_type type, void *data, int delay)
456 wm_ctx_t *ctx = (wm_ctx_t *) xcalloc (1, sizeof (wm_ctx_t));
457 ctx->wmsg_type = type;
461 CloseHandle (CreateThread (NULL, 0, do_async, (LPVOID) ctx, 0, NULL));
466 gpgol_hook(int code, WPARAM wParam, LPARAM lParam)
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;
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)
482 HWND lastChild = NULL;
483 log_debug ("%s:%s: Got WM_CLOSE",
485 if (!GpgolAddin::get_instance() || !GpgolAddin::get_instance ()->get_application())
490 LPDISPATCH explorers = get_oom_object (GpgolAddin::get_instance ()->get_application(),
495 log_error ("%s:%s: No explorers object",
499 int count = get_oom_int (explorers, "Count");
503 log_debug ("%s:%s: More then one explorer. Not shutting down.",
505 gpgol_release (explorers);
509 LPDISPATCH explorer = get_oom_object (explorers, "Item(1)");
510 gpgol_release (explorers);
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);
525 log_debug ("%s:%s: No caption.",
529 /* rctrl_renwnd32 is the window class of outlook. */
530 HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32",
534 if (hwnd == cwp->hwnd)
536 log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. "
539 GpgolAddin::get_instance ()->shutdown();
546 This comes to often and when we are closed from the icon
548 if (cwp->wParam == SC_CLOSE)
550 log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.",
552 GpgolAddin::get_instance ()->shutdown();
556 // log_debug ("WM: %x", (unsigned int) cwp->message);
559 return CallNextHookEx (NULL, code, wParam, lParam);
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. */
566 create_message_hook()
569 TRETURN SetWindowsHookEx (WH_CALLWNDPROC,
572 GetCurrentThreadId());
575 GPGRT_LOCK_DEFINE (invalidate_lock);
577 static bool invalidation_in_progress;
580 delayed_invalidate_ui (LPVOID minsleep)
583 if (invalidation_in_progress)
585 log_debug ("%s:%s: Invalidation canceled as it is in progress.",
590 invalidation_in_progress = true;
591 gpgol_lock(&invalidate_lock);
593 int sleep_ms = (intptr_t)minsleep;
596 while (invalidation_blocked)
601 log_debug ("%s:%s: Waiting for invalidation.",
606 /* Do we need an abort statement here? */
608 do_in_ui_thread (INVALIDATE_UI, nullptr);
610 invalidation_in_progress = false;
611 gpgol_unlock(&invalidate_lock);
616 close_mail (LPVOID mail)
619 do_in_ui_thread (CLOSE, mail);
627 invalidation_blocked++;
628 log_oom ("%s:%s: Invalidation block count %i",
629 SRCNAME, __func__, invalidation_blocked);
637 invalidation_blocked--;
638 log_oom ("%s:%s: Invalidation block count %i",
639 SRCNAME, __func__, invalidation_blocked);
641 if (invalidation_blocked < 0)
643 log_error ("%s:%s: Invalidation block mismatch",
645 invalidation_blocked = 0;