diff --git a/src/windowmessages.cpp b/src/windowmessages.cpp index bfed366..9bb0f5c 100644 --- a/src/windowmessages.cpp +++ b/src/windowmessages.cpp @@ -1,304 +1,314 @@ /* @file windowmessages.h * @brief Helper class to work with the windowmessage handler thread. * * Copyright (C) 2015, 2016 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "windowmessages.h" #include "common.h" #include "oomhelp.h" #include "mail.h" #include "gpgoladdin.h" +#include "wks-helper.h" #include #define RESPONDER_CLASS_NAME "GpgOLResponder" /* Singleton window */ static HWND g_responder_window = NULL; LONG_PTR WINAPI gpgol_window_proc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { if (message == WM_USER + 42) { wm_ctx_t *ctx = (wm_ctx_t *) lParam; log_debug ("%s:%s: Recieved user msg: %i", SRCNAME, __func__, ctx->wmsg_type); switch (ctx->wmsg_type) { case (PARSING_DONE): { auto mail = (Mail*) ctx->data; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: Parsing done for mail which is gone.", SRCNAME, __func__); break; } mail->parsing_done(); break; } case (RECIPIENT_ADDED): { auto mail = (Mail*) ctx->data; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: Recipient add for mail which is gone.", SRCNAME, __func__); break; } mail->locate_keys(); break; } case (INVALIDATE_UI): { log_debug ("%s:%s: Invalidating UI", SRCNAME, __func__); gpgoladdin_invalidate_ui(); log_debug ("%s:%s: Invalidation done", SRCNAME, __func__); break; } case (CLOSE): { auto mail = (Mail*) ctx->data; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: Close for mail which is gone.", SRCNAME, __func__); break; } Mail::close (mail); break; } case (CRYPTO_DONE): { auto mail = (Mail*) ctx->data; if (!Mail::is_valid_ptr (mail)) { log_debug ("%s:%s: Crypto done for mail which is gone.", SRCNAME, __func__); break; } // modify the mail. if (mail->crypt_state () == Mail::NeedsUpdateInOOM) { // Save the Mail log_debug ("%s:%s: Crypto done for %p updating oom.", SRCNAME, __func__, mail); mail->update_crypt_oom(); } if (mail->crypt_state () == Mail::NeedsSecondAfterWrite) { invoke_oom_method (mail->item (), "Save", NULL); log_debug ("%s:%s: Second save done for %p Invoking second send.", SRCNAME, __func__, mail); } // Finaly this should pass. invoke_oom_method (mail->item (), "Send", NULL); + // Allow the WKS helper to queue a notification. + WKSHelper::instance()->allow_notify (); + break; + } + case (WKS_NOTIFY): + { + WKSHelper::instance ()->notify ((const char *) ctx->data); + xfree (ctx->data); + break; } default: log_debug ("%s:%s: Unknown msg %x", SRCNAME, __func__, ctx->wmsg_type); } return DefWindowProc(hWnd, message, wParam, lParam); } return DefWindowProc(hWnd, message, wParam, lParam); } HWND create_responder_window () { size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1; char cls_name[cls_name_len]; if (g_responder_window) { return g_responder_window; } /* Create Window wants a mutable string as the first parameter */ snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME); WNDCLASS windowClass; windowClass.style = CS_GLOBALCLASS | CS_DBLCLKS; windowClass.lpfnWndProc = gpgol_window_proc; windowClass.cbClsExtra = 0; windowClass.cbWndExtra = 0; windowClass.hInstance = (HINSTANCE) GetModuleHandle(NULL); windowClass.hIcon = 0; windowClass.hCursor = 0; windowClass.hbrBackground = 0; windowClass.lpszMenuName = 0; windowClass.lpszClassName = cls_name; RegisterClass(&windowClass); g_responder_window = CreateWindow (cls_name, RESPONDER_CLASS_NAME, 0, 0, 0, 0, 0, 0, (HMENU) 0, (HINSTANCE) GetModuleHandle(NULL), 0); return g_responder_window; } int send_msg_to_ui_thread (wm_ctx_t *ctx) { size_t cls_name_len = strlen(RESPONDER_CLASS_NAME) + 1; char cls_name[cls_name_len]; snprintf (cls_name, cls_name_len, "%s", RESPONDER_CLASS_NAME); HWND responder = FindWindow (cls_name, RESPONDER_CLASS_NAME); if (!responder) { log_error ("%s:%s: Failed to find responder window.", SRCNAME, __func__); return -1; } SendMessage (responder, WM_USER + 42, 0, (LPARAM) ctx); return 0; } int do_in_ui_thread (gpgol_wmsg_type type, void *data) { wm_ctx_t ctx = {NULL, UNKNOWN, 0}; ctx.wmsg_type = type; ctx.data = data; if (send_msg_to_ui_thread (&ctx)) { return -1; } return ctx.err; } static std::vector explorers; void add_explorer (LPDISPATCH explorer) { explorers.push_back (explorer); } void remove_explorer (LPDISPATCH explorer) { explorers.erase(std::remove(explorers.begin(), explorers.end(), explorer), explorers.end()); } LRESULT CALLBACK gpgol_hook(int code, WPARAM wParam, LPARAM lParam) { /* Once we are in the close events we don't have enough control to revert all our changes so we have to do it with this nice little hack by catching the WM_CLOSE message before it reaches outlook. */ LPCWPSTRUCT cwp = (LPCWPSTRUCT) lParam; switch (cwp->message) { case WM_CLOSE: { HWND lastChild = NULL; for (const auto explorer: explorers) { /* Casting to LPOLEWINDOW and calling GetWindow succeeded in Outlook 2016 but always returned the number 1. So we need this hack. */ char *caption = get_oom_string (explorer, "Caption"); if (!caption) { log_debug ("%s:%s: No caption.", SRCNAME, __func__); continue; } /* rctrl_renwnd32 is the window class of outlook. */ HWND hwnd = FindWindowExA(NULL, lastChild, "rctrl_renwnd32", caption); xfree (caption); lastChild = hwnd; if (hwnd == cwp->hwnd) { log_debug ("%s:%s: WM_CLOSE windowmessage for explorer. " "Closing all mails.", SRCNAME, __func__); Mail::close_all_mails(); break; } } break; } case WM_SYSCOMMAND: /* This comes to often and when we are closed from the icon we also get WM_CLOSE if (cwp->wParam == SC_CLOSE) { log_debug ("%s:%s: SC_CLOSE syscommand. Closing all mails.", SRCNAME, __func__); Mail::close_all_mails(); } */ break; default: break; } return CallNextHookEx (NULL, code, wParam, lParam); } /* Create the message hook for outlook's windowmessages we are especially interested in WM_QUIT to do cleanups and prevent the "Item has changed" question. */ HHOOK create_message_hook() { return SetWindowsHookEx (WH_CALLWNDPROC, gpgol_hook, NULL, GetCurrentThreadId()); } gpgrt_lock_t invalidate_lock = GPGRT_LOCK_INITIALIZER; static bool invalidation_in_progress; DWORD WINAPI delayed_invalidate_ui (LPVOID) { if (invalidation_in_progress) { log_debug ("%s:%s: Invalidation canceled as it is in progress.", SRCNAME, __func__); return 0; } gpgrt_lock_lock(&invalidate_lock); invalidation_in_progress = true; /* We sleep here a bit to prevent invalidation immediately after the selection change before we have started processing the mail. */ Sleep (250); do_in_ui_thread (INVALIDATE_UI, nullptr); invalidation_in_progress = false; gpgrt_lock_unlock(&invalidate_lock); return 0; } DWORD WINAPI close_mail (LPVOID mail) { do_in_ui_thread (CLOSE, mail); return 0; } diff --git a/src/windowmessages.h b/src/windowmessages.h index fba7088..69762af 100644 --- a/src/windowmessages.h +++ b/src/windowmessages.h @@ -1,91 +1,92 @@ /* windowmessages.h - Helper functions for Window message exchange. * Copyright (C) 2015 by Bundesamt für Sicherheit in der Informationstechnik * Software engineering by Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #ifndef WINDOWMESSAGES_H #define WINDOWMESSAGES_H #include #include "config.h" #include /** Window Message handling for GpgOL. In Outlook only one thread has access to the Outlook Object model and this is the UI Thread. We can work in other threads but to do something with outlooks data we neet to be in the UI Thread. So we create a hidden Window in this thread and use the fact that SendMessage handles Window messages in the thread where the Window was created. This way we can go back to interactct with the Outlook from another thread without working with COM Multithreading / Marshaling. The Responder Window should be initalized on startup. */ typedef enum _gpgol_wmsg_type { UNKNOWN = 1100, /* A large offset to avoid conflicts */ INVALIDATE_UI, /* The UI should be invalidated. */ PARSING_DONE, /* A mail was parsed. Data should be a pointer to the mail object. */ RECIPIENT_ADDED, /* A recipient was added. Data should be ptr to mail */ CLOSE, /* Close the message in the next event loop. */ CRYPTO_DONE, /* Sign / Encrypt done. */ + WKS_NOTIFY, /* Show a WKS Notification. */ } gpgol_wmsg_type; typedef struct { void *data; /* Pointer to arbitrary data depending on msg type */ gpgol_wmsg_type wmsg_type; /* Type of the msg. */ int err; /* Set to true on error */ } wm_ctx_t; /** Create and register the responder window. The responder window should be */ HWND create_responder_window (); /** Send a message to the UI thread through the responder Window. Returns 0 on success. */ int send_msg_to_ui_thread (wm_ctx_t *ctx); /** Uses send_msg_to_ui_thread to execute the request in the ui thread. Returns the result. */ int do_in_ui_thread (gpgol_wmsg_type type, void *data); /** Create our filter before outlook Window Messages. */ HHOOK create_message_hook(); DWORD WINAPI delayed_invalidate_ui (LPVOID); DWORD WINAPI close_mail (LPVOID); void add_explorer (LPDISPATCH explorer); void remove_explorer (LPDISPATCH explorer); /* The lock to invalide the ui */ extern gpgrt_lock_t invalidate_lock; #endif // WINDOWMESSAGES_H diff --git a/src/wks-helper.cpp b/src/wks-helper.cpp index efe3bce..49e6dbe 100644 --- a/src/wks-helper.cpp +++ b/src/wks-helper.cpp @@ -1,236 +1,389 @@ /* wks-helper.cpp - Web Key Services for GpgOL * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "wks-helper.h" #include "common.h" #include "cpphelp.h" +#include "oomhelp.h" +#include "windowmessages.h" +#include "overlay.h" #include #include #include #include #include #include #define CHECK_MIN_INTERVAL (60 * 60 * 24 * 7) +#undef _ +#define _(a) utf8_gettext (a) + static std::map s_states; static std::map s_last_checked; static WKSHelper* singleton = NULL; GPGRT_LOCK_DEFINE (wks_lock); WKSHelper::WKSHelper() { load (); } WKSHelper::~WKSHelper () { // Ensure that we are not destroyed while // worker is running. gpgrt_lock_lock (&wks_lock); gpgrt_lock_unlock (&wks_lock); } const WKSHelper* WKSHelper::instance () { if (!singleton) { singleton = new WKSHelper (); } return singleton; } WKSHelper::WKSState WKSHelper::get_state (const std::string &mbox) const { gpgrt_lock_lock (&wks_lock); const auto it = s_states.find(mbox); const auto dataEnd = s_states.end(); gpgrt_lock_unlock (&wks_lock); if (it == dataEnd) { return NotChecked; } return it->second; } time_t WKSHelper::get_check_time (const std::string &mbox) const { gpgrt_lock_lock (&wks_lock); const auto it = s_last_checked.find(mbox); const auto dataEnd = s_last_checked.end(); gpgrt_lock_unlock (&wks_lock); if (it == dataEnd) { return 0; } return it->second; } static std::string get_wks_client_path () { char *gpg4win_dir = get_gpg4win_dir (); if (!gpg4win_dir) { TRACEPOINT; return std::string (); } const auto ret = std::string (gpg4win_dir) + "\\..\\GnuPG\\bin\\gpg-wks-client.exe"; xfree (gpg4win_dir); if (!access (ret.c_str (), F_OK)) { return ret; } log_debug ("%s:%s: Failed to find wks-client in '%s'", SRCNAME, __func__, ret.c_str ()); return std::string (); } static DWORD WINAPI do_check (LPVOID arg) { const auto wksPath = get_wks_client_path (); if (wksPath.empty()) { return 0; } std::vector args; const auto mbox = std::string ((char *) arg); xfree (arg); args.push_back (wksPath); args.push_back (std::string ("--status-fd")); args.push_back (std::string ("1")); args.push_back (std::string ("--supported")); args.push_back (mbox); // Spawn the process auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); if (!ctx) { TRACEPOINT; return 0; } GpgME::Data mystdin, mystdout, mystderr; char **cargs = vector_to_cArray (args); GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), mystdin, mystdout, mystderr, GpgME::Context::SpawnNone); release_cArray (cargs); if (err) { log_debug ("%s:%s: WKS client spawn code: %i asString: %s", SRCNAME, __func__, err.code(), err.asString()); return 0; } auto data = mystdout.toString (); rtrim (data); bool success = data == "[GNUPG:] SUCCESS"; - const auto state = success ? WKSHelper::Supported : WKSHelper::NotSupported; - - gpgrt_lock_lock (&wks_lock); - - auto it = s_states.find(mbox); - - // TODO figure out if it was published. + // TODO Figure out NeedsPublish state. + const auto state = success ? WKSHelper::NeedsPublish : WKSHelper::NotSupported; if (success) { log_debug ("%s:%s: WKS client: '%s' is supported", SRCNAME, __func__, mbox.c_str ()); } - if (it != s_states.end()) - { - it->second = state; - } - else - { - s_states.insert (std::make_pair (mbox, state)); - } + WKSHelper::instance()->update_state (mbox, state); + + gpgrt_lock_lock (&wks_lock); auto tit = s_last_checked.find(mbox); auto now = time (0); if (tit != s_last_checked.end()) { tit->second = now; } else { s_last_checked.insert (std::make_pair (mbox, now)); } - gpgrt_lock_unlock (&wks_lock); + + WKSHelper::instance()->save (); return 0; } + void WKSHelper::start_check (const std::string &mbox, bool forced) const { auto lastTime = get_check_time (mbox); auto now = time (0); if (!forced && lastTime && difftime (lastTime, now) < CHECK_MIN_INTERVAL) { /* Data is new enough */ return; } if (mbox.empty()) { log_debug ("%s:%s: start check called without mbox", SRCNAME, __func__); } log_debug ("%s:%s: WKSHelper starting check", SRCNAME, __func__); /* Start the actual work that can be done in a background thread. */ CloseHandle (CreateThread (NULL, 0, do_check, strdup (mbox.c_str ()), 0, NULL)); return; } void WKSHelper::load () const { // TODO } void WKSHelper::save () const { // TODO } + +static DWORD WINAPI +do_notify (LPVOID arg) +{ + /** Wait till a message was sent */ + //Sleep (5000); + do_in_ui_thread (WKS_NOTIFY, arg); + + return 0; +} + +void +WKSHelper::allow_notify () const +{ + gpgrt_lock_lock (&wks_lock); + for (auto &pair: s_states) + { + if (pair.second == NeedsPublish) + { + CloseHandle (CreateThread (NULL, 0, do_notify, + strdup (pair.first.c_str ()), 0, + NULL)); + break; + } + } + gpgrt_lock_unlock (&wks_lock); +} + +void +WKSHelper::notify (const char *cBox) const +{ + std::string mbox = cBox; + + const auto state = get_state (mbox); + + if (state == NeedsPublish) + { + wchar_t * w_title = utf8_to_wchar (_("GpgOL: Key directory available!")); + wchar_t * w_desc = utf8_to_wchar (_("Your mail provider supports a key directory.\n\n" + "Register your key in that directory to make\n" + "it easier for others to send you encrypted mail.\n\n\n" + "Register Key?")); + if (MessageBoxW (get_active_hwnd (), + w_desc, w_title, MB_ICONINFORMATION | MB_YESNO) == IDYES) + { + start_publish (mbox); + } + else + { + update_state (mbox, PublishDenied); + } + + xfree (w_desc); + xfree (w_title); + return; + } + else + { + log_debug ("%s:%s: Unhandled notify state: %i for '%s'", + SRCNAME, __func__, state, cBox); + return; + } +} + +void +WKSHelper::start_publish (const std::string &mbox) const +{ + Overlay (get_active_hwnd (), + std::string (_("Creating registration request..."))); + + log_debug ("%s:%s: Start publish for '%s'", + SRCNAME, __func__, mbox.c_str ()); + + const auto key = GpgME::Key::locate (mbox.c_str ()); + + if (key.isNull ()) + { + MessageBox (get_active_hwnd (), + "WKS publish failed to find key for mail address.", + _("GpgOL"), + MB_ICONINFORMATION|MB_OK); + return; + } + + const auto wksPath = get_wks_client_path (); + + if (wksPath.empty()) + { + TRACEPOINT; + return; + } + + std::vector args; + + args.push_back (wksPath); + args.push_back (std::string ("--create")); + args.push_back (std::string (key.primaryFingerprint ())); + args.push_back (mbox); + + // Spawn the process + auto ctx = GpgME::Context::createForEngine (GpgME::SpawnEngine); + if (!ctx) + { + TRACEPOINT; + return; + } + + GpgME::Data mystdin, mystdout, mystderr; + + char **cargs = vector_to_cArray (args); + + GpgME::Error err = ctx->spawn (cargs[0], const_cast (cargs), + mystdin, mystdout, mystderr, + GpgME::Context::SpawnNone); + release_cArray (cargs); + + if (err) + { + log_debug ("%s:%s: WKS client spawn code: %i asString: %s", + SRCNAME, __func__, err.code(), err.asString()); + return; + } + auto data = mystdout.toString (); + + if (data.empty ()) + { + MessageBox (get_active_hwnd (), + "WKS client failed to create publishing request.", + _("GpgOL"), + MB_ICONINFORMATION|MB_OK); + return; + } + + log_debug ("%s:%s: WKS client: returned '%s'", + SRCNAME, __func__, data.c_str ()); + return; +} + + +void +WKSHelper::update_state (const std::string &mbox, WKSState state) const +{ + gpgrt_lock_lock (&wks_lock); + auto it = s_states.find(mbox); + + if (it != s_states.end()) + { + it->second = state; + } + else + { + s_states.insert (std::make_pair (mbox, state)); + } + gpgrt_lock_unlock (&wks_lock); +} diff --git a/src/wks-helper.h b/src/wks-helper.h index 29af516..2f55a3f 100644 --- a/src/wks-helper.h +++ b/src/wks-helper.h @@ -1,80 +1,95 @@ /* @file wks-helper.cpp * @brief Helper to work with a web-key-service * * Copyright (C) 2018 Intevation GmbH * * This file is part of GpgOL. * * GpgOL is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * GpgOL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . */ #include "config.h" #include /** @brief Helper for web key services. * * Everything is public to make it easy to access data * members from another windows thread. Don't mess with them. */ class WKSHelper { protected: /** Loads the list of checked keys */ explicit WKSHelper (); public: enum WKSState { NotChecked, /*<-- Supported state was not checked */ NotSupported, /* <-- WKS is not supported for this address */ Supported, /* <-- WKS is supported for this address */ NeedsPublish, /* <-- There was no key published for this address */ NeedsUpdate, /* <-- Not yet implemeted. */ RequestSent, /* <-- A publishing request has been sent. */ + PublishDenied, /* <-- A user denied publishing. */ }; ~WKSHelper (); /** Get the WKSHelper On the initial request: Ensure that the OOM is available. Will load all account addresses from OOM and then return. Starts a background thread to load info from a file and run checks if necessary. When the thread is finished initialized will be true. */ static const WKSHelper* instance (); /** If the key for the address @address should be published */ WKSState get_state (const std::string &mbox) const; /** Start a supported check for a given mbox. If force is true the check will be run. Otherwise the state will only be updated if the last check was more then 7 days ago. Returns immediately as the check is run in a background thread. */ void start_check (const std::string &mbox, bool force = false) const; + /** Starts gpg-wks-client --create */ + void start_publish (const std::string &mbox) const; + + /** Allow queueing a notification. */ + void allow_notify () const; + + /** Send a notification and start publishing accordingly */ + void notify (const char *mbox) const; + + /** Store the current static maps. */ + void save () const; + + /** Update or insert a state in the static maps. */ + void update_state (const std::string &mbox, WKSState state) const; + private: time_t get_check_time (const std::string &mbox) const; - void save() const; - void load() const;; + void load() const; };