diff --git a/src/client.cc b/src/client.cc index 6aaa0f6..58be03b 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,530 +1,554 @@ /* 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; + const char *dir, *tmp; char *uiserver, *p; - int extra_arglen = 0; + int extra_arglen = 9; + const char * server_names[] = {"bin\\kleopatra.exe", + "kleopatra.exe", + "bin\\launch-gpa.exe", + "launch-gpa.exe", + "bin\\gpa.exe", + "gpa.exe", + NULL}; dir = gpgex_server::root_dir; if (!dir) return NULL; - uiserver = read_w32_registry_string (NULL, REGKEY, "UI Server"); + 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; - extra_arglen = 9; /* Space required for " --daemon". */ } - - name = (char*)malloc (strlen (dir) + strlen (uiserver) + extra_arglen +2); - if (!name) + 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); - return NULL; } - strcpy (stpcpy (stpcpy (name, dir), "\\"), uiserver); - for (p = name; *p; p++) - if (*p == '/') - *p = '\\'; - free (uiserver); - gpgex_server::ui_server = "Kleopatra"; - if (extra_arglen && access (name, F_OK)) + 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++) { - /* Kleopatra is not installed: Try GPA instead but if it is - also not available return the Kleopatra filename. */ - const char gpaserver[] = "launch-gpa.exe"; - char *name2; - - name2 = (char*)malloc (strlen (dir) + strlen (gpaserver) - + extra_arglen+2); - if (name2) + 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)) { - strcpy (stpcpy (stpcpy (name2, dir), "\\"), gpaserver); - for (p = name2; *p; p++) - if (*p == '/') - *p = '\\'; - if (access (name2, F_OK )) - free (name2); + /* Found a viable candidate */ + /* Set through registry and is accessible */ + if (strstr (name, "kleopatra.exe")) + { + gpgex_server::ui_server = "Kleopatra"; + strcat (name, " --daemon"); + } else { - free (name); - name = name2; gpgex_server::ui_server = "GPA"; } + return name; } } - - /* Append the --daemon arg unless the server name has been taken - from the Registry. */ - if (name && extra_arglen) - strcat (name, " --daemon"); - else - gpgex_server::ui_server = NULL; + 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 (); 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); } bool client_t::call_assuan (const char *cmd, vector &filenames) { int rc = 0; int connect_failed = 0; assuan_context_t ctx = NULL; string msg; TRACE_BEG2 (DEBUG_ASSUAN, "client_t::call_assuan", this, "%s on %u files", cmd, filenames.size ()); rc = uiserver_connect (&ctx, this->window); 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); } /* 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 (this->window, buf, "GpgEX", MB_ICONINFORMATION); } return rc ? false : true; } 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/main.cc b/src/main.cc index dcade3b..8cc0ed9 100644 --- a/src/main.cc +++ b/src/main.cc @@ -1,397 +1,405 @@ /* main.cc - DLL entry point Copyright (C) 2007, 2010, 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.1 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 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 . */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include "registry.h" #include "gpgex-class.h" #include "gpgex-factory.h" #include "main.h" /* This is the main part of the COM server component. The component is an in-process server DLL. */ /* The instance of this DLL. */ HINSTANCE gpgex_server::instance; /* The number of references to this component. */ LONG gpgex_server::refcount; /* The root of our installation. */ const char *gpgex_server::root_dir; /* The name of the UI-server or NULL if not known. */ const char *gpgex_server::ui_server; static char * get_locale_dir (void) { static wchar_t moddir[MAX_PATH+5]; char *result, *p; int nbytes; if (!GetModuleFileNameW (gpgex_server::instance, moddir, MAX_PATH)) *moddir = 0; #define SLDIR "\\share\\locale" if (*moddir) { nbytes = WideCharToMultiByte (CP_UTF8, 0, moddir, -1, NULL, 0, NULL, NULL); if (nbytes < 0) return NULL; result = (char*)malloc (nbytes + strlen (SLDIR) + 1); if (result) { nbytes = WideCharToMultiByte (CP_UTF8, 0, moddir, -1, result, nbytes, NULL, NULL); if (nbytes < 0) { free (result); result = NULL; } else { p = strrchr (result, '\\'); if (p) *p = 0; /* If we are installed below "bin" strip that part and use the top directory instead. Background: Under Windows we don't install GnuPG below bin/ but in the top directory with only share/, lib/, and etc/ below it. One of the reasons is to keep the the length of the filenames at bay so not to increase the limited length of the PATH envvar. Another and more important reason, however, is that the very first GPG versions on W32 were installed into a flat directory structure and for best compatibility with these versions we didn't changed that later. For WindowsCE we can right away install it under bin, though. The hack with detection of the bin directory part allows us to eventually migrate to such a directory layout under plain Windows without the need to change libgpg-error. */ p = strrchr (result, '\\'); if (p && !strcmp (p+1, "bin")) *p = 0; gpgex_server::root_dir = strdup (result); /* Append the static part. */ strcat (result, SLDIR); } } } else /* Use the old default value. */ { result = (char*)malloc (10 + strlen (SLDIR) + 1); if (result) { strcpy (result, "c:\\gnupg"); strcat (result, SLDIR); } } if (!gpgex_server::root_dir) gpgex_server::root_dir = "c:"; _gpgex_debug (1, "root dir is '%s'", gpgex_server::root_dir); #undef SLDIR return result; } static void drop_locale_dir (char *locale_dir) { free (locale_dir); } static void i18n_init (void) { char *locale_dir; locale_dir = get_locale_dir (); if (locale_dir) { bindtextdomain (PACKAGE_GT, locale_dir); drop_locale_dir (locale_dir); } textdomain (PACKAGE_GT); } static CRITICAL_SECTION debug_lock; /* No flags on means no debugging. */ unsigned int debug_flags = 0; /* Debug log file. */ FILE *debug_file; /* Get the filename of the debug file, if any. */ static char * get_debug_file (void) { - return read_w32_registry_string ("HKEY_LOCAL_MACHINE", REGKEY, - "GpgEX Debug File"); + char *name = read_w32_registry_string (NULL, + GPG4WIN_REGKEY_3, + "GpgEX Debug File"); + if (!name) + { + name = read_w32_registry_string (NULL, + GPG4WIN_REGKEY_2, + "GpgEX Debug File"); + } + return name; } static void debug_init (void) { char *filename; /* Sanity check. */ if (debug_file) return; InitializeCriticalSection (&debug_lock); filename = get_debug_file (); if (!filename) return; debug_file = fopen (filename, "a"); free (filename); if (!debug_file) return; /* FIXME: Make this configurable eventually. */ debug_flags = DEBUG_INIT | DEBUG_CONTEXT_MENU | DEBUG_ASSUAN; } static void debug_deinit (void) { if (debug_file) { fclose (debug_file); debug_file = NULL; } } #ifdef __cplusplus extern "C" { #if 0 } #endif #endif /* Log the formatted string FORMAT at debug level LEVEL or higher. */ extern void _gpgex_debug (unsigned int flags, const char *format, ...) { va_list arg_ptr; int saved_errno; saved_errno = errno; if (! (debug_flags & flags)) return; va_start (arg_ptr, format); EnterCriticalSection (&debug_lock); vfprintf (debug_file, format, arg_ptr); va_end (arg_ptr); if (format && *format && format[strlen (format) - 1] != '\n') putc ('\n', debug_file); LeaveCriticalSection (&debug_lock); fflush (debug_file); errno = saved_errno; } /* Return a malloced wide char string from an UTF-8 encoded input string STRING. Caller must free this value. On failure returns NULL; caller may use GetLastError to get the actual error number. The result of calling this function with STRING set to NULL is not defined. */ wchar_t * utf8_to_wchar (const char *string) { int n; wchar_t *result; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, NULL, 0); if (n < 0) return NULL; result = (wchar_t*)malloc ((n+1) * sizeof *result); if (!result) return NULL; n = MultiByteToWideChar (CP_UTF8, 0, string, -1, result, n); if (n < 0) { free (result); return NULL; } return result; } #ifdef __cplusplus #if 0 { #endif } #endif /* Entry point called by DLL loader. */ STDAPI DllMain (HINSTANCE hinst, DWORD reason, LPVOID reserved) { if (reason == DLL_PROCESS_ATTACH) { gpgex_server::instance = hinst; /* Early initializations of our subsystems. */ gpg_err_init (); debug_init (); i18n_init (); if (debug_flags & DEBUG_ASSUAN) { assuan_set_assuan_log_stream (debug_file); assuan_set_assuan_log_prefix ("gpgex:assuan"); } assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); (void) TRACE0 (DEBUG_INIT, "DllMain", hinst, "reason=DLL_PROCESS_ATTACH"); { WSADATA wsadat; WSAStartup (0x202, &wsadat); } } else if (reason == DLL_PROCESS_DETACH) { WSACleanup (); (void) TRACE0 (DEBUG_INIT, "DllMain", hinst, "reason=DLL_PROCESS_DETACH"); debug_deinit (); /* We are linking statically to libgpg-error which means there is no DllMain in libgpg-error. Thus we call the deinit function to cleanly deinitialize libgpg-error. */ gpg_err_deinit (0); } return TRUE; } /* Check if the server component, the DLL, can be unloaded. This is called by the client of this in-process server (for example through CoFreeUnusedLibrary) whenever it is considered to unload this server component. */ STDAPI DllCanUnloadNow (void) { (void) TRACE (DEBUG_INIT, "DllCanUnloadNow", gpgex_server::refcount); return (gpgex_server::refcount == 0 ? S_OK : S_FALSE); } /* Registry handling. The DLL is registered with regsvr32.exe. */ /* Register the DLL. */ STDAPI DllRegisterServer (void) { gpgex_class::init (); /* Notify the shell about the change. */ SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); return S_OK; } /* Unregister the DLL. */ STDAPI DllUnregisterServer (void) { gpgex_class::deinit (); /* Notify the shell about the change. */ SHChangeNotify (SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); return S_OK; } /* Acquire a reference for the class factory of object class RCLSID with the interface RIID (typically IID_IClassFactory) and return it in PPVOUT. Typically called by CoGetClassObject for in-process server components. */ STDAPI DllGetClassObject (REFCLSID rclsid, REFIID riid, LPVOID *ppv) { /* We have to evaluate the arguments first. */ #define _TRACE_BEG22(a,b,c,d,e,f) TRACE_BEG22(a,b,c,d,e,f) _TRACE_BEG22 (DEBUG_INIT, "DllGetClassObject", ppv, "rclsid=" GUID_FMT ", riid=" GUID_FMT, GUID_ARG (rclsid), GUID_ARG (riid)); if (rclsid == CLSID_gpgex) { HRESULT err = gpgex_factory.QueryInterface (riid, ppv); return TRACE_RES (err); } /* Be nice to broken software. */ *ppv = NULL; return TRACE_RES (CLASS_E_CLASSNOTAVAILABLE); } diff --git a/src/registry.h b/src/registry.h index 51113ab..d0e38ef 100644 --- a/src/registry.h +++ b/src/registry.h @@ -1,56 +1,65 @@ /* registry.h - registry prototypes Copyright (C) 2006, 2007 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. */ #ifndef REGISTRY_H #define REGISTRY_H #include #ifdef __cplusplus extern "C" { #if 0 } #endif #endif /* This is a helper function to load a Windows function from either of one DLLs. */ HRESULT w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e); /* Return a string from the Win32 Registry or NULL in case of error. Caller must release the return value. A NULL for root is an alias for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */ char *read_w32_registry_string (const char *root, const char *dir, const char *name); /* Retrieve the default home directory. */ const char *default_homedir (void); -/* Registry key for this software. */ -#define REGKEY "Software\\GNU\\GnuPG" +/* The Registry key used by Gpg4win. */ +#ifdef WIN64 +# define GPG4WIN_REGKEY_2 "Software\\Wow6432Node\\GNU\\GnuPG" +#else +# define GPG4WIN_REGKEY_2 "Software\\GNU\\GnuPG" +#endif +#ifdef WIN64 +# define GPG4WIN_REGKEY_3 "Software\\Wow6432Node\\Gpg4win" +#else +# define GPG4WIN_REGKEY_3 "Software\\Gpg4win" +#endif #ifdef __cplusplus #if 0 { #endif } #endif #endif /* ! REGISTRY_H */