diff --git a/src/client.cc b/src/client.cc index b31320b..93e732b 100644 --- a/src/client.cc +++ b/src/client.cc @@ -1,579 +1,655 @@ /* 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)) +/* Find the gpgconf binary which is used to return installation + * properties of the GnuPG system. We can't simply snatch some code + * from GnuPG because that would also require that we link to + * Libgcrypt or implement our own SHA-1 function to build the + * directory name for a non-default GNUPGHOME. It is just better to + * that info from gpgconf and try hard to find the correct + * gpgconf. */ +static const char * +get_gpgconf_name (void) +{ + static int tried; + static char *name; + + if (!tried) + { + const char *dir, **tmp; + char *instdir, *p; + const char *possible_names[] = + { + "gpgconf.exe", + "..\\bin\\gpgconf.exe", + "..\\GnuPG\\bin\\gpgconf.exe", + "..\\..\\GnuPG\\bin\\gpgconf.exe", + NULL + }; + + tried = 1; + + instdir = read_w32_registry_string (NULL, GPG4WIN_REGKEY_1, + "Install Directory"); + if (instdir) + { + name = (char*)malloc (strlen (instdir) + 16 + 1); + if (!name) + return NULL; + strcpy (stpcpy (name, instdir), "/bin/gpgconf.exe"); + for (p = name; *p; p++) + if (*p == '/') + *p = '\\'; + free (instdir); + } + if (name && !gpgrt_access (name, F_OK)) + return name; /* Yeah, Installed. */ + + dir = gpgex_server::root_dir; + if (!dir) + return NULL; /* No way. */ + + /* Try fallbacks */ + for (tmp = possible_names; *tmp; tmp++) + { + if (name) + free (name); + name = (char*)malloc (strlen (dir) + 1 + strlen (*tmp) + 1); + if (!name) + return NULL; /* Ooops. */ + + strcpy (stpcpy (stpcpy (name, dir), "\\"), *tmp); + for (p = name; *p; p++) + if (*p == '/') + *p = '\\'; + if (!gpgrt_access (name, F_OK)) + return name; /* Found. */ + } + } + + return name; +} + + static const char * default_socket_name (void) { + static int tried; static char *name; - if (!name) + if (!tried) { - const char *dir; + const char *gpgconf; + 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); - } - } + tried = 1; + gpgconf = get_gpgconf_name (); + if (!gpgconf) + return NULL; + + if (gpgex_spawn_get_string (gpgconf, "gpgconf -0 --list-dirs socketdir", + &dir)) + return NULL; + + name = (char *)malloc (strlen (dir) + strlen (sockname) + 1); + if (name) + strcpy (stpcpy (name, dir), sockname); + free (dir); } 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_name (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)) + if (name && !gpgrt_access (name, F_OK)) { /* Set through registry or default kleo */ if (strstr (name, "kleopatra.exe")) { gpgex_server::ui_server = "Kleopatra"; } 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)) + if (!gpgrt_access (name, F_OK)) { /* Found a viable candidate */ /* Set through registry and is accessible */ if (strstr (name, "kleopatra.exe")) { gpgex_server::ui_server = "Kleopatra"; } 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) 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"); const char *cmdline = NULL; const char *program = default_uiserver_name (); if (gpgex_server::ui_server && !strcmp (gpgex_server::ui_server, "Kleopatra")) { cmdline = "--daemon"; } /* 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 (program, 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; } /* 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/exechelp.c b/src/exechelp.c index b636794..6050a58 100644 --- a/src/exechelp.c +++ b/src/exechelp.c @@ -1,153 +1,361 @@ /* exechelp.c - fork and exec helpers * Copyright (C) 2004, 2007, 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #if HAVE_CONFIG_H #include #endif #include #include #include #include +#include #include #include #include "debug.h" #include "exechelp.h" /* Define to 1 do enable debugging. */ #define DEBUG_W32_SPAWN 0 +struct private_membuf_s +{ + size_t len; + size_t size; + char *buf; + int out_of_core; +}; + +typedef struct private_membuf_s membuf_t; + + + +/* A simple implementation of a dynamic buffer. Use init_membuf() to + create a buffer, put_membuf to append bytes and get_membuf to + release and return the buffer. Allocation errors are detected but + only returned at the final get_membuf(), this helps not to clutter + the code with out of core checks. */ +void +init_membuf (membuf_t *mb, int initiallen) +{ + mb->len = 0; + mb->size = initiallen; + mb->out_of_core = 0; + mb->buf = malloc (initiallen); + if (!mb->buf) + mb->out_of_core = errno; +} + + +/* Shift the content of the membuf MB by AMOUNT bytes. The next + operation will then behave as if AMOUNT bytes had not been put into + the buffer. If AMOUNT is greater than the actual accumulated + bytes, the membuf is basically reset to its initial state. */ +void +clear_membuf (membuf_t *mb, size_t amount) +{ + /* No need to clear if we are already out of core. */ + if (mb->out_of_core) + return; + if (amount >= mb->len) + mb->len = 0; + else + { + mb->len -= amount; + memmove (mb->buf, mb->buf+amount, mb->len); + } +} + + +void +put_membuf (membuf_t *mb, const void *buf, size_t len) +{ + if (mb->out_of_core || !len) + return; + + if (mb->len + len >= mb->size) + { + char *p; + + mb->size += len + 1024; + p = realloc (mb->buf, mb->size); + if (!p) + { + mb->out_of_core = errno ? errno : ENOMEM; + /* /\* Wipe out what we already accumulated. This is required */ + /* in case we are storing sensitive data here. The membuf */ + /* API does not provide another way to cleanup after an */ + /* error. *\/ */ + /* wipememory (mb->buf, mb->len); */ + return; + } + mb->buf = p; + } + if (buf) + memcpy (mb->buf + mb->len, buf, len); + else + memset (mb->buf + mb->len, 0, len); + mb->len += len; +} + + +void * +get_membuf (membuf_t *mb, size_t *len) +{ + char *p; + + if (mb->out_of_core) + { + if (mb->buf) + { + /* wipememory (mb->buf, mb->len); */ + free (mb->buf); + mb->buf = NULL; + } + gpg_err_set_errno (mb->out_of_core); + return NULL; + } + + p = mb->buf; + if (len) + *len = mb->len; + mb->buf = NULL; + mb->out_of_core = ENOMEM; /* hack to make sure it won't get reused. */ + return p; +} + + + /* Lock a spawning process. The caller needs to provide the address of a variable to store the lock information and the name or the process. */ gpg_error_t gpgex_lock_spawning (lock_spawn_t *lock) { int waitrc; int timeout = 5; _TRACE (DEBUG_ASSUAN, "gpgex_lock_spawning", lock); *lock = CreateMutexW (NULL, FALSE, L"spawn_gnupg_uiserver_sentinel"); if (!*lock) { TRACE_LOG1 ("failed to create the spawn mutex: rc=%d", GetLastError ()); return gpg_error (GPG_ERR_GENERAL); } retry: waitrc = WaitForSingleObject (*lock, 1000); if (waitrc == WAIT_OBJECT_0) return 0; if (waitrc == WAIT_TIMEOUT && timeout) { timeout--; goto retry; } if (waitrc == WAIT_TIMEOUT) TRACE_LOG ("error waiting for the spawn mutex: timeout"); else TRACE_LOG2 ("error waiting for the spawn mutex: (code=%d) rc=%d", waitrc, GetLastError ()); return gpg_error (GPG_ERR_GENERAL); } /* Unlock the spawning process. */ void gpgex_unlock_spawning (lock_spawn_t *lock) { if (*lock) { _TRACE (DEBUG_ASSUAN, "gpgex_unlock_spawning", lock); if (!ReleaseMutex (*lock)) TRACE_LOG1 ("failed to release the spawn mutex: rc=%d", GetLastError()); CloseHandle (*lock); *lock = NULL; } } /* Fork and exec the program with /dev/null as stdin, stdout and stderr. Returns 0 on success or an error code. */ gpg_error_t gpgex_spawn_detached (const char *pgmname, const char *cmdline) { SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; STARTUPINFO si; int cr_flags; TRACE_BEG1 (DEBUG_ASSUAN, "gpgex_spawn_detached", cmdline, "cmdline=%s", cmdline); /* Prepare security attributes. */ memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; /* Start the process. Note that we can't run the PREEXEC function because this would change our own environment. */ memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = DEBUG_W32_SPAWN ? SW_SHOW : SW_MINIMIZE; cr_flags = (CREATE_DEFAULT_ERROR_MODE | GetPriorityClass (GetCurrentProcess ()) | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS); if (!CreateProcess (pgmname, /* pgmname; Program to start. */ (char *) cmdline, /* Command line arguments. */ &sec_attr, /* Process security attributes. */ &sec_attr, /* Thread security attributes. */ TRUE, /* Inherit handles. */ cr_flags, /* Creation flags. */ NULL, /* Environment. */ NULL, /* Use current drive/directory. */ &si, /* Startup information. */ &pi /* Returns process information. */ )) { (void) TRACE_LOG1 ("CreateProcess failed: %i\n", GetLastError ()); return gpg_error (GPG_ERR_GENERAL); } /* Process has been created suspended; resume it now. */ CloseHandle (pi.hThread); CloseHandle (pi.hProcess); return 0; } + + +/* Fork and exec PGMNAME with args in CMDLINE and /dev/null connected + * to stdin and stderr. Read from stdout and return the result as a + * malloced string at R_STRING. Returns 0 on success or an error code. */ +gpg_error_t +gpgex_spawn_get_string (const char *pgmname, const char *cmdline, + char **r_string) +{ + SECURITY_ATTRIBUTES sec_attr; + HANDLE rh, wh; + PROCESS_INFORMATION pi = + { + NULL, /* Returns process handle. */ + 0, /* Returns primary thread handle. */ + 0, /* Returns pid. */ + 0 /* Returns tid. */ + }; + STARTUPINFO si; + membuf_t mb; + + *r_string = NULL; + + TRACE_BEG1 (DEBUG_ASSUAN, "gpgex_spawn_get_string", cmdline, + "cmdline=%s", cmdline); + + /* Set the inherit flag for the pipe into the securit attributes. */ + memset (&sec_attr, 0, sizeof sec_attr); + sec_attr.nLength = sizeof sec_attr; + sec_attr.bInheritHandle = TRUE; + + /* Create a pipe to read form stdout. */ + if (!CreatePipe (&rh, &wh, &sec_attr, 0)) + { + TRACE_LOG1 ("CreatePipe failed: ec=%d", (int) GetLastError ()); + return gpg_error (GPG_ERR_GENERAL); + } + + /* Set the read end to non-inheritable. */ + if (!SetHandleInformation (rh, HANDLE_FLAG_INHERIT, 0)) + { + TRACE_LOG1 ("SHI failed: ec=%d", (int) GetLastError ()); + CloseHandle (rh); + CloseHandle (wh); + return gpg_error (GPG_ERR_GENERAL); + } + + /* Start the process. Note that we can't run the PREEXEC function + because this would change our own environment. */ + memset (&si, 0, sizeof si); + si.cb = sizeof (si); + si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; + si.wShowWindow = SW_HIDE; + si.hStdInput = INVALID_HANDLE_VALUE; + si.hStdOutput = wh; + si.hStdError = INVALID_HANDLE_VALUE; + + if (!CreateProcess (pgmname, /* pgmname; Program to start. */ + (char *)cmdline, /* Command line arguments. */ + NULL, /* Process security attributes. */ + NULL, /* Thread security attributes. */ + TRUE, /* Inherit handles. */ + 0, /* Creation flags. */ + NULL, /* Use current environment. */ + NULL, /* Use current drive/directory. */ + &si, /* Startup information. */ + &pi /* Returns process information. */ + )) + { + (void) TRACE_LOG1 ("CreateProcess failed: %i\n", GetLastError ()); + CloseHandle (rh); + CloseHandle (wh); + return gpg_error (GPG_ERR_GENERAL); + } + + CloseHandle (pi.hThread); + CloseHandle (pi.hProcess); + + CloseHandle (wh); /* This end is used by the child. */ + + init_membuf (&mb, 1024); + for (;;) + { + char readbuf[1024]; + DWORD nread; + + if (!ReadFile (rh, readbuf, sizeof readbuf, &nread, NULL) || !nread) + break; + put_membuf (&mb, readbuf, nread); + } + CloseHandle (rh); /* Ready with reading. */ + + put_membuf (&mb, "", 1); /* Terminate string. */ + *r_string = get_membuf (&mb, NULL); + if (!*r_string) + return gpg_error (GPG_ERR_EIO); + + return 0; +} diff --git a/src/exechelp.h b/src/exechelp.h index 288ee70..0d53508 100644 --- a/src/exechelp.h +++ b/src/exechelp.h @@ -1,49 +1,56 @@ /* exechelp.h - fork and exec helpers * Copyright (C) 2004, 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #ifndef GPGEX_EXECHELP_H #define GPGEX_EXECHELP_H #include #ifdef __cplusplus extern "C" { #if 0 } #endif #endif #define lock_spawn_t HANDLE gpg_error_t gpgex_lock_spawning (lock_spawn_t *lock); void gpgex_unlock_spawning (lock_spawn_t *lock); /* Fork and exec CMDLINE with /dev/null as stdin, stdout and stderr. Returns 0 on success or an error code. */ gpg_error_t gpgex_spawn_detached (const char *pgmname, const char *cmdline); +/* Fork and exec PGMNAME with args in CMDLINE and /dev/null connected + * to stdin and stderr. Read from stdout and return the result as a + * malloced string at R_STRING. Returns 0 on success or an error code. */ +gpg_error_t gpgex_spawn_get_string (const char *pgmname, const char *cmdline, + char **r_string); + + #ifdef __cplusplus #if 0 { #endif } #endif #endif /* GPGEX_EXECHELP_H */ diff --git a/src/registry.h b/src/registry.h index d0e38ef..3ed8489 100644 --- a/src/registry.h +++ b/src/registry.h @@ -1,65 +1,64 @@ /* 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); -/* The Registry key used by Gpg4win. */ +/* The Registry keys used by Gpg4win and GnUPG. */ #ifdef WIN64 +# define GPG4WIN_REGKEY_1 "Software\\Wow6432Node\\GnuPG" # 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_1 "Software\\GnuPG" +# define GPG4WIN_REGKEY_2 "Software\\GNU\\GnuPG" # define GPG4WIN_REGKEY_3 "Software\\Gpg4win" #endif #ifdef __cplusplus #if 0 { #endif } #endif #endif /* ! REGISTRY_H */