diff --git a/src/client.cc b/src/client.cc index ccfa0ac..a9ce5f0 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,584 +1,573 @@ /* client.cc - gpgex assuan client implementation Copyright (C) 2007, 2008, 2013, 2014 g10 Code GmbH This file is part of GpgEX. GpgEX 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 of the License, or (at your option) any later version. GpgEX 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include using std::vector; using std::string; #include #include #include #include "main.h" #include "registry.h" #include "exechelp.h" #include "client.h" static inline char * _gpgex_stpcpy (char *a, const char *b) { while (*b) *a++ = *b++; *a = 0; return a; } #define stpcpy(a,b) _gpgex_stpcpy ((a), (b)) static const char * default_socket_name (void) { static char *name; if (!name) { const char *dir; const char sockname[] = "\\S.uiserver"; dir = default_homedir (); if (dir) { name = (char *)malloc (strlen (dir) + strlen (sockname) + 1); if (name) { strcpy (name, dir); strcat (name, sockname); } } } return name; } /* Return the name of the default UI server. This name is used to auto start an UI server if an initial connect failed. */ static const char * default_uiserver_cmdline (void) { static char *name; if (!name) #if ENABLE_GPA_ONLY { const char gpaserver[] = "bin\\launch-gpa.exe"; const char *dir; char *p; dir = gpgex_server::root_dir; if (!dir) return NULL; name = (char*)malloc (strlen (dir) + strlen (gpaserver) + 9 + 2); if (!name) return NULL; strcpy (stpcpy (stpcpy (name, dir), "\\"), gpaserver); for (p = name; *p; p++) if (*p == '/') *p = '\\'; strcat (name, " --daemon"); gpgex_server::ui_server = "GPA"; } #else /*!ENABLE_GPA_ONLY*/ { const char *dir, **tmp; char *uiserver, *p; int extra_arglen = 9; const char * server_names[] = {"kleopatra.exe", "bin\\kleopatra.exe", "launch-gpa.exe", "bin\\launch-gpa.exe", "gpa.exe", "bin\\gpa.exe", NULL}; dir = gpgex_server::root_dir; if (!dir) return NULL; uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_2, "UI Server"); if (!uiserver) { uiserver = read_w32_registry_string (NULL, GPG4WIN_REGKEY_3, "UI Server"); } if (!uiserver) { uiserver = strdup ("kleopatra.exe"); if (!uiserver) return NULL; } if (uiserver) { name = (char*) malloc (strlen (dir) + strlen (uiserver) + extra_arglen + 2); if (!name) return NULL; strcpy (stpcpy (stpcpy (name, dir), "\\"), uiserver); for (p = name; *p; p++) if (*p == '/') *p = '\\'; free (uiserver); } if (name && !access (name, F_OK)) { /* Set through registry or default kleo */ if (strstr (name, "kleopatra.exe")) { gpgex_server::ui_server = "Kleopatra"; strcat (name, " --daemon"); } else { gpgex_server::ui_server = "GPA"; } return name; } /* Fallbacks */ for (tmp = server_names; *tmp; tmp++) { if (name) { free (name); } name = (char*) malloc (strlen (dir) + strlen (*tmp) + extra_arglen + 2); if (!name) return NULL; strcpy (stpcpy (stpcpy (name, dir), "\\"), *tmp); for (p = name; *p; p++) if (*p == '/') *p = '\\'; if (!access (name, F_OK)) { /* Found a viable candidate */ /* Set through registry and is accessible */ if (strstr (name, "kleopatra.exe")) { gpgex_server::ui_server = "Kleopatra"; strcat (name, " --daemon"); } else { gpgex_server::ui_server = "GPA"; } return name; } } gpgex_server::ui_server = NULL; } #endif /*!ENABLE_GPA_ONLY*/ return name; } #define tohex_lower(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'a')) /* Percent-escape the string STR by replacing colons with '%3a'. If EXTRA is not NULL all characters in it are also escaped. */ static char * percent_escape (const char *str, const char *extra) { int i, j; char *ptr; if (!str) return NULL; for (i=j=0; str[i]; i++) if (str[i] == ':' || str[i] == '%' || (extra && strchr (extra, str[i]))) j++; ptr = (char *) malloc (i + 2 * j + 1); i = 0; while (*str) { /* FIXME: Work around a bug in Kleo. */ if (*str == ':') { ptr[i++] = '%'; ptr[i++] = '3'; ptr[i++] = 'a'; } else if (*str == '%') { ptr[i++] = '%'; ptr[i++] = '2'; ptr[i++] = '5'; } else if (extra && strchr (extra, *str)) { ptr[i++] = '%'; ptr[i++] = tohex_lower ((*str >> 4) & 15); ptr[i++] = tohex_lower (*str & 15); } else ptr[i++] = *str; str++; } ptr[i] = '\0'; return ptr; } static string escape (string str) { char *arg_esc = percent_escape (str.c_str (), "+= "); if (arg_esc == NULL) - throw std::bad_alloc (); + return std::string(); string res = arg_esc; free (arg_esc); return res; } /* Send options to the UI server and return the server's PID. */ static gpg_error_t send_one_option (assuan_context_t ctx, const char *name, const char *value) { gpg_error_t err; char buffer[1024]; if (! value || ! *value) err = 0; /* Avoid sending empty strings. */ else { snprintf (buffer, sizeof (buffer), "OPTION %s=%s", name, value); err = assuan_transact (ctx, buffer, NULL, NULL, NULL, NULL, NULL, NULL); } return err; } static gpg_error_t getinfo_pid_cb (void *opaque, const void *buffer, size_t length) { pid_t *pid = (pid_t *) opaque; *pid = (pid_t) strtoul ((char *) buffer, NULL, 10); return 0; } static gpg_error_t send_options (assuan_context_t ctx, HWND hwnd, pid_t *r_pid) { gpg_error_t rc = 0; char numbuf[50]; TRACE_BEG (DEBUG_ASSUAN, "client_t::send_options", ctx); *r_pid = (pid_t) (-1); rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, r_pid, NULL, NULL, NULL, NULL); if (! rc && *r_pid == (pid_t) (-1)) { (void) TRACE_LOG ("server did not return a PID"); rc = gpg_error (GPG_ERR_ASSUAN_SERVER_FAULT); } if (! rc && *r_pid != (pid_t) (-1) && ! AllowSetForegroundWindow (*r_pid)) { (void) TRACE_LOG ("AllowSetForegroundWindow (%u) failed"); TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); /* Ignore the error, though. */ } if (! rc && hwnd) { /* We hope that HWND is limited to 32 bit. If not a 32 bit UI-server would not be able to do anything with this window-id. */ uintptr_t tmp = (uintptr_t)hwnd; if (!(tmp & ~0xffffffff)) { /* HWND fits into 32 bit - send it. */ snprintf (numbuf, sizeof (numbuf), "%lx", (unsigned long)tmp); rc = send_one_option (ctx, "window-id", numbuf); } } return TRACE_GPGERR (rc); } static gpg_error_t uiserver_connect (assuan_context_t *ctx, HWND hwnd) { gpg_error_t rc; const char *socket_name = NULL; pid_t pid; lock_spawn_t lock; TRACE_BEG (DEBUG_ASSUAN, "client_t::uiserver_connect", ctx); socket_name = default_socket_name (); if (! socket_name || ! *socket_name) { (void) TRACE_LOG ("invalid socket name"); return TRACE_GPGERR (gpg_error (GPG_ERR_INV_ARG)); } (void) TRACE_LOG1 ("socket name: %s", socket_name); rc = assuan_new (ctx); if (rc) { (void) TRACE_LOG ("could not allocate context"); return TRACE_GPGERR (rc); } rc = assuan_socket_connect (*ctx, socket_name, -1, 0); if (rc) { int count; (void) TRACE_LOG ("UI server not running, starting it"); /* Now try to connect again with the spawn lock taken. */ if (!(rc = gpgex_lock_spawning (&lock)) && assuan_socket_connect (*ctx, socket_name, -1, 0)) { rc = gpgex_spawn_detached (default_uiserver_cmdline ()); if (!rc) { /* Give it a bit of time to start up and try a couple of times. */ for (count = 0; count < 10; count++) { Sleep (1000); rc = assuan_socket_connect (*ctx, socket_name, -1, 0); if (!rc) break; } } } gpgex_unlock_spawning (&lock); } if (! rc) { if (debug_flags & DEBUG_ASSUAN) assuan_set_log_stream (*ctx, debug_file); rc = send_options (*ctx, hwnd, &pid); if (rc) { assuan_release (*ctx); *ctx = NULL; } } return TRACE_GPGERR (rc); } typedef struct async_arg { const char *cmd; vector filenames; HWND wid; } async_arg_t; static DWORD WINAPI call_assuan_async (LPVOID arg) { async_arg_t *async_args = (async_arg_t *)arg; int rc = 0; int connect_failed = 0; const char *cmd = async_args->cmd; const vector filenames = async_args->filenames; assuan_context_t ctx = NULL; string msg; TRACE_BEG2 (DEBUG_ASSUAN, "client_t::call_assuan_async", 0, "%s on %u files", cmd, filenames.size ()); rc = uiserver_connect (&ctx, async_args->wid); if (rc) { connect_failed = 1; goto leave; } - try - { - /* Set the input files. We don't specify the output files. */ - for (unsigned int i = 0; i < filenames.size (); i++) - { - msg = "FILE " + escape (filenames[i]); - - (void) TRACE_LOG1 ("sending cmd: %s", msg.c_str ()); - - rc = assuan_transact (ctx, msg.c_str (), - NULL, NULL, NULL, NULL, NULL, NULL); - if (rc) - goto leave; - } - - /* Set the --nohup option, so that the operation continues and - completes in the background. */ - msg = ((string) cmd) + " --nohup"; - (void) TRACE_LOG1 ("sending cmd: %s", msg.c_str ()); - rc = assuan_transact (ctx, msg.c_str (), - NULL, NULL, NULL, NULL, NULL, NULL); - } - catch (std::bad_alloc) - { - rc = gpg_error (GPG_ERR_ENOMEM); - } - catch (...) - { - rc = gpg_error (GPG_ERR_GENERAL); - } + /* Set the input files. We don't specify the output files. */ + for (unsigned int i = 0; i < filenames.size (); i++) + { + msg = "FILE " + escape (filenames[i]); + + (void) TRACE_LOG1 ("sending cmd: %s", msg.c_str ()); + + rc = assuan_transact (ctx, msg.c_str (), + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + goto leave; + } + + /* Set the --nohup option, so that the operation continues and + completes in the background. */ + msg = ((string) cmd) + " --nohup"; + (void) TRACE_LOG1 ("sending cmd: %s", msg.c_str ()); + rc = assuan_transact (ctx, msg.c_str (), + NULL, NULL, NULL, NULL, NULL, NULL); /* Fall-through. */ leave: TRACE_GPGERR (rc); if (ctx) assuan_release (ctx); if (rc) { char buf[256]; if (connect_failed) snprintf (buf, sizeof (buf), _("Can not connect to the GnuPG user interface%s%s%s:\r\n%s"), gpgex_server::ui_server? " (":"", gpgex_server::ui_server? gpgex_server::ui_server:"", gpgex_server::ui_server? ")":"", gpg_strerror (rc)); else snprintf (buf, sizeof (buf), _("Error returned by the GnuPG user interface%s%s%s:\r\n%s"), gpgex_server::ui_server? " (":"", gpgex_server::ui_server? gpgex_server::ui_server:"", gpgex_server::ui_server? ")":"", gpg_strerror (rc)); MessageBox (async_args->wid, buf, "GpgEX", MB_ICONINFORMATION); } delete async_args; return 0; } void client_t::call_assuan (const char *cmd, vector &filenames) { TRACE_BEG (DEBUG_ASSUAN, "client_t::call_assuan", cmd); async_arg_t * args = new async_arg_t; args->cmd = cmd; args->filenames = filenames; args->wid = this->window; /* We move the call in a different thread as the Windows explorer is blocked until our call finishes. We don't want that. Additionally Kleopatra / Qt5 SendsMessages to the parent window provided in wid. Qt does this with blocking calls so Kleopatra blocks until the explorer processes more Window Messages and we block the explorer. This is a deadlock. */ CreateThread (NULL, 0, call_assuan_async, (LPVOID) args, 0, NULL); return; } void client_t::decrypt_verify (vector &filenames) { this->call_assuan ("DECRYPT_VERIFY_FILES", filenames); } void client_t::verify (vector &filenames) { this->call_assuan ("VERIFY_FILES", filenames); } void client_t::decrypt (vector &filenames) { this->call_assuan ("DECRYPT_FILES", filenames); } void client_t::sign_encrypt (vector &filenames) { this->call_assuan ("ENCRYPT_SIGN_FILES", filenames); } void client_t::encrypt (vector &filenames) { this->call_assuan ("ENCRYPT_FILES", filenames); } void client_t::sign (vector &filenames) { this->call_assuan ("SIGN_FILES", filenames); } void client_t::import (vector &filenames) { this->call_assuan ("IMPORT_FILES", filenames); } void client_t::create_checksums (vector &filenames) { this->call_assuan ("CHECKSUM_CREATE_FILES", filenames); } void client_t::verify_checksums (vector &filenames) { this->call_assuan ("CHECKSUM_VERIFY_FILES", filenames); } diff --git a/src/gpgex.cc b/src/gpgex.cc index 8c973d8..84e9901 100644 --- a/src/gpgex.cc +++ b/src/gpgex.cc @@ -1,749 +1,731 @@ /* gpgex.cc - gpgex implementation Copyright (C) 2007, 2013 g10 Code GmbH This file is part of GpgEX. GpgEX 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 of the License, or (at your option) any later version. GpgEX 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #if HAVE_CONFIG_H #include #endif #include #include #include using std::vector; using std::string; #include /* For the start_help() function. */ #include #include "main.h" #include "client.h" #include "registry.h" #include "gpgex.h" /* For context menus. */ #define ID_CMD_HELP 0 #define ID_CMD_DECRYPT_VERIFY 1 #define ID_CMD_DECRYPT 2 #define ID_CMD_VERIFY 3 #define ID_CMD_SIGN_ENCRYPT 4 #define ID_CMD_ENCRYPT 5 #define ID_CMD_SIGN 6 #define ID_CMD_IMPORT 7 #define ID_CMD_CREATE_CHECKSUMS 8 #define ID_CMD_VERIFY_CHECKSUMS 9 #define ID_CMD_POPUP 10 #define ID_CMD_ABOUT 11 #define ID_CMD_MAX 11 #define ID_CMD_STR_HELP _("Help on GpgEX") #define ID_CMD_STR_ABOUT _("About GpgEX") #define ID_CMD_STR_DECRYPT_VERIFY _("Decrypt and verify") #define ID_CMD_STR_DECRYPT _("Decrypt") #define ID_CMD_STR_VERIFY _("Verify") #define ID_CMD_STR_SIGN_ENCRYPT _("Sign and encrypt") #define ID_CMD_STR_ENCRYPT _("Encrypt") #define ID_CMD_STR_SIGN _("Sign") #define ID_CMD_STR_IMPORT _("Import keys") #define ID_CMD_STR_CREATE_CHECKSUMS _("Create checksums") #define ID_CMD_STR_VERIFY_CHECKSUMS _("Verify checksums") /* Reset the instance between operations. */ void gpgex_t::reset (void) { this->filenames.clear (); this->all_files_gpg = TRUE; } STDMETHODIMP gpgex_t::QueryInterface (REFIID riid, void **ppv) { #define _TRACE_BEG12(a,b,c,d,e,f) TRACE_BEG12(a,b,c,d,e,f) _TRACE_BEG12 (DEBUG_INIT, "gpgex_t::QueryInterface", this, "riid=" GUID_FMT ", ppv=%p", GUID_ARG (riid), ppv); if (ppv == NULL) return TRACE_RES (E_INVALIDARG); /* Be nice to broken software. */ *ppv = NULL; /* The static casts ensure that the virtual function table layout of the returned object is correct. We can not cast to IUnknown because that base class is ambiguous (because it is not virtual), so we pick one of the derived classes instead. */ if (riid == IID_IUnknown) *ppv = static_cast (this); else if (riid == IID_IShellExtInit) *ppv = static_cast (this); else if (riid == IID_IContextMenu) *ppv = static_cast (this); #if 0 /* FIXME: Enable this when the functions are actually implemented. */ else if (riid == IID_IContextMenu2) *ppv = static_cast (this); else if (riid == IID_IContextMenu3) *ppv = static_cast (this); #endif else return TRACE_RES (E_NOINTERFACE); /* We have to acquire a reference to the returned object. We lost the type information, but we know that all object classes inherit from IUnknown, which is good enough. */ reinterpret_cast(*ppv)->AddRef (); return TRACE_RES (S_OK); } STDMETHODIMP_(ULONG) gpgex_t::AddRef (void) { (void) TRACE1 (DEBUG_INIT, "gpgex_t::AddRef", this, "new_refcount=%i", this->refcount + 1); return InterlockedIncrement (&this->refcount); } STDMETHODIMP_(ULONG) gpgex_t::Release (void) { LONG count; (void) TRACE1 (DEBUG_INIT, "gpgex_t::Release", this, "new_refcount=%i", this->refcount - 1); count = InterlockedDecrement (&this->refcount); if (count == 0) delete this; return count; } /* IShellExtInit methods. */ STDMETHODIMP gpgex_t::Initialize (LPCITEMIDLIST pIDFolder, IDataObject *pDataObj, HKEY hRegKey) { HRESULT err = S_OK; TRACE_BEG3 (DEBUG_INIT, "gpgex_t::Initialize", this, "pIDFolder=%p, pDataObj=%p, hRegKey=%p", pIDFolder, pDataObj, hRegKey); /* This function is called for the Shortcut (context menu), Drag-and-Drop, and Property Sheet extensions. */ this->reset (); - try + if (pDataObj) { - if (pDataObj) - { - /* The data object contains a drop item which we extract. */ - FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; - STGMEDIUM medium; - UINT count; - - if (SUCCEEDED (pDataObj->GetData (&fe, &medium))) - { - HDROP drop = (HDROP) GlobalLock (medium.hGlobal); - unsigned int i; - - /* Now that we have the drop item, we can extract the - file names. */ - count = DragQueryFile (drop, (UINT) -1, NULL, 0); - if (count == 0) - throw std::invalid_argument ("no filenames"); - - try - { - for (i = 0; i < count; i++) - { - char filename[MAX_PATH]; - UINT len; - len = DragQueryFile (drop, i, - filename, sizeof (filename) - 1); - if (len == 0) - throw std::invalid_argument ("zero-length filename"); - - /* Take a look at the ending. */ - char *ending = strrchr (filename, '.'); - if (ending) - { - BOOL gpg = false; - - ending++; - if (! strcasecmp (ending, "gpg") - || ! strcasecmp (ending, "pgp") - || ! strcasecmp (ending, "asc") - || ! strcasecmp (ending, "sig") - || ! strcasecmp (ending, "pem") - || ! strcasecmp (ending, "p7m") - || ! strcasecmp (ending, "p7s") - ) - gpg = true; - - if (gpg == false) - this->all_files_gpg = FALSE; - } - else - this->all_files_gpg = FALSE; - - this->filenames.push_back (filename); - } - } - catch (...) - { - GlobalUnlock (medium.hGlobal); - ReleaseStgMedium (&medium); - throw; - } - - GlobalUnlock (medium.hGlobal); - ReleaseStgMedium (&medium); - } - } - } - catch (std::bad_alloc) - { - err = E_OUTOFMEMORY; - } - catch (std::invalid_argument &e) - { - (VOID) TRACE_LOG1 ("exception: E_INVALIDARG: %s", e.what ()); - err = E_INVALIDARG; - } - catch (...) - { - err = E_UNEXPECTED; + /* The data object contains a drop item which we extract. */ + FORMATETC fe = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + STGMEDIUM medium; + UINT count; + + if (SUCCEEDED (pDataObj->GetData (&fe, &medium))) + { + HDROP drop = (HDROP) GlobalLock (medium.hGlobal); + unsigned int i; + + /* Now that we have the drop item, we can extract the + file names. */ + count = DragQueryFile (drop, (UINT) -1, NULL, 0); + if (count == 0) + { + err = E_INVALIDARG; + } + + if (!err) + { + + for (i = 0; i < count; i++) + { + char filename[MAX_PATH]; + UINT len; + len = DragQueryFile (drop, i, + filename, sizeof (filename) - 1); + if (len == 0) + { + err = E_INVALIDARG; + break; + } + /* Take a look at the ending. */ + char *ending = strrchr (filename, '.'); + if (ending) + { + BOOL gpg = false; + + ending++; + if (! strcasecmp (ending, "gpg") + || ! strcasecmp (ending, "pgp") + || ! strcasecmp (ending, "asc") + || ! strcasecmp (ending, "sig") + || ! strcasecmp (ending, "pem") + || ! strcasecmp (ending, "p7m") + || ! strcasecmp (ending, "p7s") + ) + gpg = true; + + if (gpg == false) + this->all_files_gpg = FALSE; + } + else + this->all_files_gpg = FALSE; + + this->filenames.push_back (filename); + } + GlobalUnlock (medium.hGlobal); + ReleaseStgMedium (&medium); + } + } } if (err != S_OK) this->reset (); return TRACE_RES (err); } /* IContextMenu methods. */ /* The argument HMENU contains the context menu, and INDEXMENU points to the first index where we can add items. IDCMDFIRST and IDCMDLAST is the range of command ID values which we can use. */ STDMETHODIMP gpgex_t::QueryContextMenu (HMENU hMenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) { BOOL res; TRACE_BEG5 (DEBUG_CONTEXT_MENU, "gpgex_t::QueryContextMenu", this, "hMenu=%p, indexMenu=%u, idCmdFirst=%u, idCmdLast=%u, uFlags=%x", hMenu, indexMenu, idCmdFirst, idCmdLast, uFlags); /* FIXME: Do something if idCmdLast - idCmdFirst + 1 is not big enough. */ /* If the flags include CMF_DEFAULTONLY then nothing should be done. */ if (uFlags & CMF_DEFAULTONLY) return TRACE_RES (MAKE_HRESULT (SEVERITY_SUCCESS, FACILITY_NULL, 0)); res = InsertMenu (hMenu, indexMenu++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if (! res) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); /* First we add the file-specific menus. */ if (this->all_files_gpg) { res = InsertMenu (hMenu, indexMenu++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_DECRYPT_VERIFY, ID_CMD_STR_DECRYPT_VERIFY); if (! res) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); } else { /* FIXME: Check error. */ res = InsertMenu (hMenu, indexMenu++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_SIGN_ENCRYPT, ID_CMD_STR_SIGN_ENCRYPT); if (! res) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); } /* Now generate and add the generic command popup menu. */ HMENU popup; UINT idx = 0; /* FIXME: Check error. */ popup = CreatePopupMenu (); if (popup == NULL) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); MENUITEMINFO mii = { sizeof (MENUITEMINFO) }; mii.fMask = MIIM_SUBMENU | MIIM_STRING | MIIM_ID; mii.wID = idCmdFirst + ID_CMD_POPUP; mii.hSubMenu = popup; mii.dwTypeData = (CHAR *) _("More GpgEX options"); res = InsertMenuItem (hMenu, indexMenu++, TRUE, &mii); if (!res) { DWORD last_error = GetLastError (); DestroyMenu (popup); return TRACE_RES (HRESULT_FROM_WIN32 (last_error)); } if (this->key_bitmap) { // indexMenu - 1!!! res = SetMenuItemBitmaps (hMenu, indexMenu - 1, MF_BYPOSITION, this->key_bitmap, this->key_bitmap); } if (res) res = InsertMenu (hMenu, indexMenu++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if (! res) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_DECRYPT, ID_CMD_STR_DECRYPT); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_VERIFY, ID_CMD_STR_VERIFY); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_DECRYPT_VERIFY, ID_CMD_STR_DECRYPT_VERIFY); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_ENCRYPT, ID_CMD_STR_ENCRYPT); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_SIGN, ID_CMD_STR_SIGN); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_SIGN_ENCRYPT, ID_CMD_STR_SIGN_ENCRYPT); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_IMPORT, ID_CMD_STR_IMPORT); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_CREATE_CHECKSUMS, ID_CMD_STR_CREATE_CHECKSUMS); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_VERIFY_CHECKSUMS, ID_CMD_STR_VERIFY_CHECKSUMS); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_HELP, ID_CMD_STR_HELP); if (res) res = InsertMenu (popup, idx++, MF_BYPOSITION | MF_STRING, idCmdFirst + ID_CMD_ABOUT, ID_CMD_STR_ABOUT); if (! res) return TRACE_RES (HRESULT_FROM_WIN32 (GetLastError ())); /* We should return a HRESULT that indicates success and the offset to the next free command ID after the last one we used, relative to idCmdFirst. In other words: max_used - idCmdFirst + 1. */ return TRACE_RES (MAKE_HRESULT (SEVERITY_SUCCESS, FACILITY_NULL, ID_CMD_MAX + 1)); } /* Get a verb or help text for the command IDCOMMAND (which is the offset to IDCMDFIRST of QueryContextMenu, ie zero based). UFLAGS has GCS_HELPTEXT set if the help-text is requested (otherwise a verb is requested). If UFLAGS has the GCS_UNICODE bit set, we need to return a wide character string. */ STDMETHODIMP gpgex_t::GetCommandString (UINT_PTR idCommand, UINT uFlags, LPUINT lpReserved, LPSTR pszName, UINT uMaxNameLen) { const char *txt; TRACE_BEG5 (DEBUG_CONTEXT_MENU, "gpgex_t::GetCommandString", this, "idCommand=%u, uFlags=%x, lpReserved=%lu, pszName=%p, " "uMaxNameLen=%u", (unsigned int)(idCommand & 0xffffffff), uFlags, lpReserved, pszName, uMaxNameLen); if (! (uFlags & GCS_HELPTEXT)) return TRACE_RES (E_INVALIDARG); if (idCommand > ID_CMD_MAX) return TRACE_RES (E_INVALIDARG); switch (idCommand) { case ID_CMD_HELP: txt = _("Invoke the GpgEX documentation."); break; case ID_CMD_ABOUT: txt = _("Show the version of GpgEX."); break; case ID_CMD_DECRYPT_VERIFY: txt = _("Decrypt and verify the marked files."); break; case ID_CMD_DECRYPT: txt = _("Decrypt the marked files."); break; case ID_CMD_VERIFY: txt = _("Verify the marked files."); break; case ID_CMD_SIGN_ENCRYPT: txt = _("Sign and encrypt the marked files."); break; case ID_CMD_ENCRYPT: txt = _("Encrypt the marked files."); break; case ID_CMD_SIGN: txt = _("Sign the marked files."); break; case ID_CMD_IMPORT: txt = _("Import the marked files."); break; case ID_CMD_CREATE_CHECKSUMS: txt = _("Create checksums."); break; case ID_CMD_VERIFY_CHECKSUMS: txt = _("Verify checksums."); break; case ID_CMD_POPUP: txt = _("Show more GpgEX options."); break; default: return TRACE_RES (E_INVALIDARG); } if (uFlags & GCS_UNICODE) { /* FIXME: Convert to unicode. */ lstrcpynW ((LPWSTR) pszName, L"(Unicode help not available yet)", uMaxNameLen); } else lstrcpynA (pszName, txt, uMaxNameLen); return TRACE_RES (S_OK); } /* Return the lang name. This is either "xx" or "xx_YY". On error "en" is returned. */ static const char * get_lang_name (void) { static char *name; const char *s; char *p; int count = 0; if (!name) { s = gettext_localename (); if (!s) s = "en"; else if (!strcmp (s, "C") || !strcmp (s, "POSIX")) s = "en"; name = strdup (s); if (!name) return "en"; for (p = name; *p; p++) { if (*p == '.' || *p == '@' || *p == '/' /*(safeguard)*/) *p = 0; else if (*p == '_') { if (count++) *p = 0; /* Also cut at a underscore in the territory. */ } } } return name; } /* According to MSDN using ShellExecute may be problematic when using within the Shell. Thus we call Internet explorer directly. It is anyway only used for local files. */ static void start_help (HWND hwnd) { HRESULT res; CLSID clsid; LPUNKNOWN browser = NULL; IWebBrowser2 *web = NULL; CLSIDFromProgID (OLESTR ("InternetExplorer.Application"), &clsid); res = CoCreateInstance (clsid, NULL, CLSCTX_SERVER, IID_IUnknown, (void **) &browser); if (! SUCCEEDED (res)) { MessageBox (hwnd, "Can not open browser", "GpgEX", MB_ICONINFORMATION); return; } browser->QueryInterface (IID_IWebBrowser2, (void **) &web); browser->Release (); /* FIXME: Pick a good configuration. */ // Only for IE7? // web->put_Resizable (VARIANT_TRUE); // web->put_ToolBar (FALSE); // web->put_AddressBar (VARIANT_FALSE); // web->put_MenuBar (VARIANT_FALSE); // web->put_StatusBar (VARIANT_FALSE); // width, height web->put_Visible (VARIANT_TRUE); wchar_t *wurl; { #define URLSIZE 512 char url[URLSIZE]; const char *lang = get_lang_name (); snprintf (url, URLSIZE, "file:///%s\\share\\doc\\gpgex\\gpgex-%s.html", gpgex_server::root_dir, lang); url[URLSIZE - 1] = '\0'; wurl = utf8_to_wchar (url); /* We need to test whether we need to fall back to the generic lang id. */ if (wurl && strchr (lang, '_') && _waccess (wurl+8, 0)) { snprintf (url, URLSIZE, "file:///%s\\share\\doc\\gpgex\\gpgex-%.2s.html", gpgex_server::root_dir, lang); url[URLSIZE - 1] = '\0'; free (wurl); wurl = utf8_to_wchar (url); } /* If the help file does not exists fall back to the english version. */ if (wurl && _waccess (wurl+8, 0)) { snprintf (url, URLSIZE, "file:///%s\\share\\doc\\gpgex\\gpgex-en.html", gpgex_server::root_dir); url[URLSIZE - 1] = '\0'; free (wurl); wurl = utf8_to_wchar (url); } } if (wurl) { BSTR burl = SysAllocString ((const OLECHAR *)wurl); VARIANT vars[4]; memset (vars, 0, sizeof (vars)); res = web->Navigate (burl, vars, vars + 1, vars + 2, vars + 3); SysFreeString (burl); free (wurl); if (!SUCCEEDED (res)) { web->Release (); return; } } /* Do more stuff. */ web->Release (); } /* Show the version informatione etc. */ static void show_about (HWND hwnd) { const char cpynotice[] = "Copyright (C) 2013 g10 Code GmbH"; const char en_notice[] = "GpgEX is an Explorer plugin for data encryption and signing\n" "It uses the GnuPG software (http://www.gnupg.org).\n" "\n" "GpgEX is free software; you can redistribute it and/or\n" "modify it under the terms of the GNU Lesser General Public\n" "License as published by the Free Software Foundation; either\n" "version 2.1 of the License, or (at your option) any later version.\n" "\n" "GpgEX is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU Lesser General Public License for more details.\n" "\n" "You should have received a copy of the GNU Lesser General Public " "License\n" "along with this program; if not, see ."; /* TRANSLATORS: See the source for the full english text. */ const char notice_key[] = N_("-#GpgEXFullHelpText#-"); const char *notice; char header[300]; char *buffer; size_t nbuffer; snprintf (header, sizeof header, _("This is GpgEX version %s (%s)"), PACKAGE_VERSION, #ifdef HAVE_W64_SYSTEM "64 bit" #else "32 bit" #endif ); notice = _(notice_key); if (!strcmp (notice, notice_key)) notice = en_notice; nbuffer = strlen (header) + strlen (cpynotice) + strlen (notice) + 20; buffer = (char*)malloc (nbuffer); if (buffer) { snprintf (buffer, nbuffer, "%s\n%s\n\n%s\n", header, cpynotice, notice); MessageBox (hwnd, buffer, "GpgEx", MB_OK); free (buffer); } else MessageBox (hwnd, header, "GpgEx", MB_OK); } STDMETHODIMP gpgex_t::InvokeCommand (LPCMINVOKECOMMANDINFO lpcmi) { TRACE_BEG1 (DEBUG_CONTEXT_MENU, "gpgex_t::InvokeCommand", this, "lpcmi=%p", lpcmi); /* If lpVerb really points to a string, ignore this function call and bail out. */ if (HIWORD (lpcmi->lpVerb) != 0) return TRACE_RES (E_INVALIDARG); client_t client (lpcmi->hwnd); /* Get the command index, which is the offset to IDCMDFIRST of QueryContextMenu, ie zero based). */ switch (LOWORD (lpcmi->lpVerb)) { case ID_CMD_HELP: start_help (lpcmi->hwnd); break; case ID_CMD_ABOUT: show_about (lpcmi->hwnd); break; case ID_CMD_DECRYPT_VERIFY: client.decrypt_verify (this->filenames); break; case ID_CMD_DECRYPT: client.decrypt (this->filenames); break; case ID_CMD_VERIFY: client.verify (this->filenames); break; case ID_CMD_SIGN_ENCRYPT: client.sign_encrypt (this->filenames); break; case ID_CMD_ENCRYPT: client.encrypt (this->filenames); break; case ID_CMD_SIGN: client.sign (this->filenames); break; case ID_CMD_IMPORT: client.import (this->filenames); break; case ID_CMD_CREATE_CHECKSUMS: client.create_checksums (this->filenames); break; case ID_CMD_VERIFY_CHECKSUMS: client.verify_checksums (this->filenames); break; default: return TRACE_RES (E_INVALIDARG); break; } return TRACE_RES (S_OK); } /* IContextMenu2 methods. */ STDMETHODIMP gpgex_t::HandleMenuMsg (UINT uMsg, WPARAM wParam, LPARAM lParam) { /* FIXME */ return S_OK; } /* IContextMenu3 methods. */ STDMETHODIMP gpgex_t::HandleMenuMsg2 (UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult) { /* FIXME */ return S_OK; }