diff options
-rw-r--r-- | src/gpg-error.def.in | 1 | ||||
-rw-r--r-- | src/gpg-error.h.in | 2 | ||||
-rw-r--r-- | src/gpg-error.vers | 1 | ||||
-rw-r--r-- | src/gpgrt-int.h | 2 | ||||
-rw-r--r-- | src/spawn-posix.c | 47 | ||||
-rw-r--r-- | src/spawn-w32.c | 210 | ||||
-rw-r--r-- | src/visibility.c | 7 | ||||
-rw-r--r-- | src/visibility.h | 2 | ||||
-rw-r--r-- | tests/Makefile.am | 2 | ||||
-rw-r--r-- | tests/t-spawn.c | 220 |
10 files changed, 485 insertions, 9 deletions
diff --git a/src/gpg-error.def.in b/src/gpg-error.def.in index 31a28ef..3b9f462 100644 --- a/src/gpg-error.def.in +++ b/src/gpg-error.def.in @@ -254,5 +254,6 @@ EXPORTS gpgrt_spawn_actions_set_envvars @194 gpgrt_spawn_actions_set_redirect @195 gpgrt_spawn_actions_set_inherit_handles @196 + gpgrt_spawn_actions_set_envchange @197 ;; end of file with public symbols for Windows. diff --git a/src/gpg-error.h.in b/src/gpg-error.h.in index 68a9347..44566ae 100644 --- a/src/gpg-error.h.in +++ b/src/gpg-error.h.in @@ -1120,6 +1120,8 @@ typedef struct gpgrt_process *gpgrt_process_t; typedef struct gpgrt_spawn_actions *gpgrt_spawn_actions_t; gpg_err_code_t gpgrt_spawn_actions_new (gpgrt_spawn_actions_t *r_act); void gpgrt_spawn_actions_release (gpgrt_spawn_actions_t act); +void gpgrt_spawn_actions_set_envchange (gpgrt_spawn_actions_t, + const char *const*); @define:spawn_actions_functions@ enum gpgrt_process_requests diff --git a/src/gpg-error.vers b/src/gpg-error.vers index c4704df..2868f3c 100644 --- a/src/gpg-error.vers +++ b/src/gpg-error.vers @@ -221,6 +221,7 @@ GPG_ERROR_1.0 { gpgrt_spawn_actions_set_redirect; gpgrt_spawn_actions_set_inherit_fds; gpgrt_spawn_actions_set_atfork; + gpgrt_spawn_actions_set_envchange; local: *; diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h index 4a37686..55933a1 100644 --- a/src/gpgrt-int.h +++ b/src/gpgrt-int.h @@ -637,6 +637,8 @@ gpg_err_code_t _gpgrt_make_pipe (int filedes[2], estream_t *r_fp, /* Actions (at spawning a child process), which is OS-specific. */ gpg_err_code_t _gpgrt_spawn_actions_new (gpgrt_spawn_actions_t *r_act); void _gpgrt_spawn_actions_release (gpgrt_spawn_actions_t act); +void _gpgrt_spawn_actions_set_envchange (gpgrt_spawn_actions_t, + const char *const*); #ifdef HAVE_W32_SYSTEM void _gpgrt_spawn_actions_set_envvars (gpgrt_spawn_actions_t, char *); void _gpgrt_spawn_actions_set_redirect (gpgrt_spawn_actions_t, diff --git a/src/spawn-posix.c b/src/spawn-posix.c index 9787ec1..4bb90ca 100644 --- a/src/spawn-posix.c +++ b/src/spawn-posix.c @@ -57,9 +57,6 @@ #include "gpgrt-int.h" -/* (Only glibc's unistd.h declares this iff _GNU_SOURCE is used.) */ -extern char **environ; - /* Definition for the gpgrt_spawn_actions_t. Note that there is a * different one for Windows. */ @@ -67,6 +64,7 @@ struct gpgrt_spawn_actions { int fd[3]; const int *except_fds; char **environ; + const char *const *envchange; void (*atfork) (void *); void *atfork_arg; }; @@ -318,6 +316,31 @@ posix_open_null (int for_write) } +static gpg_err_code_t +prepare_environ (const char *const *envchange) +{ + const char *const *envp; + const char *e; + + for (envp = envchange; (e = *envp); envp++) + { + char *name = xtrystrdup (e); + char *p; + + if (!name) + return _gpg_err_code_from_syserror (); + + p = strchr (name, '='); + if (p) + *p++ = 0; + + _gpgrt_setenv (name, p, 1); + xfree (name); + } + + return 0; +} + static int my_exec (const char *pgmname, const char *argv[], gpgrt_spawn_actions_t act) { @@ -345,8 +368,8 @@ my_exec (const char *pgmname, const char *argv[], gpgrt_spawn_actions_t act) /* Close all other files. */ _gpgrt_close_all_fds (3, act->except_fds); - if (act->environ) - environ = act->environ; + if (act->envchange && prepare_environ (act->envchange)) + goto leave; if (act->atfork) act->atfork (act->atfork_arg); @@ -355,8 +378,13 @@ my_exec (const char *pgmname, const char *argv[], gpgrt_spawn_actions_t act) if (pgmname == NULL) return 0; - execv (pgmname, (char *const *)argv); + if (act->environ) + execve (pgmname, (char *const *)argv, act->environ); + else + execv (pgmname, (char *const *)argv); + /* No way to print anything, as we have may have closed all streams. */ + leave: _exit (127); return -1; } @@ -456,6 +484,13 @@ _gpgrt_spawn_actions_set_environ (gpgrt_spawn_actions_t act, } void +_gpgrt_spawn_actions_set_envchange (gpgrt_spawn_actions_t act, + const char *const *envchange) +{ + act->envchange = envchange; +} + +void _gpgrt_spawn_actions_set_atfork (gpgrt_spawn_actions_t act, void (*atfork)(void *), void *arg) { diff --git a/src/spawn-w32.c b/src/spawn-w32.c index 909929c..c21ac4b 100644 --- a/src/spawn-w32.c +++ b/src/spawn-w32.c @@ -73,6 +73,7 @@ struct gpgrt_spawn_actions { void *hd[3]; void **inherit_hds; char *env; + const char *const *envchange; }; @@ -328,6 +329,147 @@ check_windows_version (void) return is_vista_or_later; } +static gpg_err_code_t +prepare_env_block (char **r_env, const char *const *envchange) +{ + gpg_err_code_t ec; + wchar_t *orig_env_block; + wchar_t *wp; + int i; + size_t envlen[256]; + wchar_t *env_block; + size_t env_block_len; + + const char *const *envp; + const char *e; + int env_num; + + wp = orig_env_block = GetEnvironmentStringsW (); + for (i = 0; *wp != L'\0'; i++) + if (i >= DIM (envlen)) + { + FreeEnvironmentStringsW (orig_env_block); + return GPG_ERR_TOO_LARGE; + } + else + wp += ((envlen[i] = wcslen (wp)) + 1); + wp++; + env_num = i; + + env_block_len = (char *)wp - (char *)orig_env_block; + env_block = xtrymalloc (env_block_len); + if (!env_block) + { + FreeEnvironmentStringsW (orig_env_block); + return _gpg_err_code_from_syserror (); + } + memcpy (env_block, orig_env_block, env_block_len); + FreeEnvironmentStringsW (orig_env_block); + + for (envp = envchange; (e = *envp); envp++) + { + wchar_t *we; + int off = 0; + + we = _gpgrt_utf8_to_wchar (e); + if (!we) + { + ec = _gpg_err_code_from_syserror (); + goto leave; + } + + wp = wcschr (we, L'='); + if (!wp) + { + /* Remove WE entry in the environment block. */ + for (i = 0; i < env_num; i++) + if (!wcsncmp (&env_block[off], we, wcslen (we)) + && env_block[off+wcslen (we)] == L'=') + break; + else + off += envlen[i] + 1; + + if (i == env_num) + /* not found */; + else + { + env_block_len -= (envlen[i] + 1) * sizeof (wchar_t); + env_num--; + for (; i < env_num; i++) + { + int off0 = off; + + off += envlen[i] + 1; + memmove (&env_block[off0], &env_block[off], + ((envlen[i] = envlen[i+1]) + 1) * sizeof (wchar_t)); + } + env_block[(env_block_len / sizeof (wchar_t)) - 1] = L'\0'; + } + } + else + { + size_t old_env_block_len; + + for (i = 0; i < env_num; i++) + if (!wcsncmp (&env_block[off], we, wp - we + 1)) + break; + else + off += envlen[i] + 1; + + if (i < env_num) + { + int off0 = off; + + off += envlen[i] + 1; + /* If an existing entry, remove it. */ + env_block_len -= (envlen[i] + 1) * sizeof (wchar_t); + env_num--; + for (; i < env_num; i++) + { + size_t len = (envlen[i] = envlen[i+1]) + 1; + + memmove (&env_block[off0], &env_block[off], + len * sizeof (wchar_t)); + off0 += len; + off += len; + } + env_block[(env_block_len / sizeof (wchar_t)) - 1] = L'\0'; + } + + if (i >= DIM (envlen) - 1) + { + ec = GPG_ERR_TOO_LARGE; + _gpgrt_free_wchar (we); + goto leave; + } + + old_env_block_len = env_block_len; + env_block_len += ((envlen[i++] = wcslen (we)) + 1) * sizeof (wchar_t); + env_num = i; + env_block = xtryrealloc (env_block, env_block_len); + if (!env_block) + { + ec = _gpg_err_code_from_syserror (); + _gpgrt_free_wchar (we); + goto leave; + } + memmove ((char *)env_block + old_env_block_len - sizeof (wchar_t), + we, (envlen[env_num - 1] + 1) * sizeof (wchar_t)); + env_block[(env_block_len / sizeof (wchar_t)) - 1] = L'\0'; + } + + _gpgrt_free_wchar (we); + } + ec = 0; + + leave: + if (ec) + xfree (env_block); + else + *r_env = (char *)env_block; + + return ec; +} static gpg_err_code_t spawn_detached (const char *pgmname, char *cmdline, gpgrt_spawn_actions_t act) @@ -342,6 +484,7 @@ spawn_detached (const char *pgmname, char *cmdline, gpgrt_spawn_actions_t act) int ret; BOOL ask_inherit = FALSE; int i; + char *env = NULL; ec = _gpgrt_access (pgmname, X_OK); if (ec) @@ -425,6 +568,29 @@ spawn_detached (const char *pgmname, char *cmdline, gpgrt_spawn_actions_t act) | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS); + if (act->env) + { + /* Either ENV or ENVCHANGE can be specified, not both. */ + if (act->envchange) + { + xfree (cmdline); + return GPG_ERR_INV_ARG; + } + + env = act->env; + } + else if (act->envchange) + { + ec = prepare_env_block (&env, act->envchange); + if (ec) + { + xfree (cmdline); + return ec; + } + + cr_flags |= CREATE_UNICODE_ENVIRONMENT; + } + /* Take care: CreateProcessW may modify wpgmname */ if (!(wpgmname = _gpgrt_utf8_to_wchar (pgmname))) ret = 0; @@ -437,11 +603,14 @@ spawn_detached (const char *pgmname, char *cmdline, gpgrt_spawn_actions_t act) &sec_attr, /* Thread security attributes. */ ask_inherit, /* Inherit handles. */ cr_flags, /* Creation flags. */ - act->env, /* Environment. */ + env, /* Environment. */ NULL, /* Use current drive/directory. */ (STARTUPINFOW *)&si, /* Startup information. */ &pi /* Returns process information. */ ); + if (act->envchange) + xfree (env); + env = NULL; if (!ret) { if (!wpgmname || !wcmdline) @@ -505,6 +674,13 @@ _gpgrt_spawn_actions_release (gpgrt_spawn_actions_t act) xfree (act); } +void +_gpgrt_spawn_actions_set_envchange (gpgrt_spawn_actions_t act, + const char *const *envchange) +{ + act->envchange = envchange; +} + /* Set the environment block for child process. * ENV is an ASCII encoded string, terminated by two zero bytes. */ @@ -552,6 +728,7 @@ _gpgrt_process_spawn (const char *pgmname, const char *argv[], int i; BOOL ask_inherit = FALSE; struct gpgrt_spawn_actions act_default; + char *env = NULL; if (!act) { @@ -783,6 +960,32 @@ _gpgrt_process_spawn (const char *pgmname, const char *argv[], | ((flags & GPGRT_PROCESS_NO_CONSOLE) ? DETACHED_PROCESS : 0) | GetPriorityClass (GetCurrentProcess ()) | CREATE_SUSPENDED); + + if (act->env) + { + /* Either ENV or ENVCHANGE can be specified, not both. */ + if (act->envchange) + { + xfree (process); + xfree (cmdline); + return GPG_ERR_INV_ARG; + } + + env = act->env; + } + else if (act->envchange) + { + ec = prepare_env_block (&env, act->envchange); + if (ec) + { + xfree (process); + xfree (cmdline); + return ec; + } + + cr_flags |= CREATE_UNICODE_ENVIRONMENT; + } + if (!(wpgmname = _gpgrt_utf8_to_wchar (pgmname))) ret = 0; else if (!(wcmdline = _gpgrt_utf8_to_wchar (cmdline))) @@ -794,11 +997,14 @@ _gpgrt_process_spawn (const char *pgmname, const char *argv[], &sec_attr, /* Thread security attributes. */ ask_inherit, /* Inherit handles. */ cr_flags, /* Creation flags. */ - act->env, /* Environment. */ + env, /* Environment. */ NULL, /* Use current drive/directory. */ (STARTUPINFOW *)&si, /* Startup information. */ &pi /* Returns process information. */ ); + if (act->envchange) + xfree (env); + env = NULL; if (!ret) { if (!wpgmname || !wcmdline) diff --git a/src/visibility.c b/src/visibility.c index 0e8121e..15b38b4 100644 --- a/src/visibility.c +++ b/src/visibility.c @@ -1145,6 +1145,13 @@ gpgrt_spawn_actions_release (gpgrt_spawn_actions_t act) _gpgrt_spawn_actions_release (act); } +void +gpgrt_spawn_actions_set_envchange (gpgrt_spawn_actions_t act, + const char *const *env) +{ + _gpgrt_spawn_actions_set_envchange (act, env); +} + #ifdef HAVE_W32_SYSTEM void gpgrt_spawn_actions_set_envvars (gpgrt_spawn_actions_t act, diff --git a/src/visibility.h b/src/visibility.h index 29ebaee..748a42a 100644 --- a/src/visibility.h +++ b/src/visibility.h @@ -231,6 +231,7 @@ MARK_VISIBLE (gpgrt_absfnameconcat) MARK_VISIBLE (gpgrt_spawn_actions_new) MARK_VISIBLE (gpgrt_spawn_actions_release) +MARK_VISIBLE (gpgrt_spawn_actions_set_envchange) #ifdef HAVE_W32_SYSTEM MARK_VISIBLE (gpgrt_spawn_actions_set_envvars) MARK_VISIBLE (gpgrt_spawn_actions_set_redirect) @@ -423,6 +424,7 @@ MARK_VISIBLE (gpgrt_spawn_actions_set_atfork) #define gpgrt_spawn_actions_new _gpgrt_USE_UNDERSCORED_FUNCTION #define gpgrt_spawn_actions_release _gpgrt_USE_UNDERSCORED_FUNCTION +#define gpgrt_spawn_actions_set_envchange _gpgrt_USE_UNDERSCORED_FUNCTION #ifdef HAVE_W32_SYSTEM #define gpgrt_spawn_actions_set_envvars _gpgrt_USE_UNDERSCORED_FUNCTION #define gpgrt_spawn_actions_set_redirect _gpgrt_USE_UNDERSCORED_FUNCTION diff --git a/tests/Makefile.am b/tests/Makefile.am index d421b3b..3e3f60c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -24,7 +24,7 @@ EXTRA_DIST = t-argparse.conf etc/t-argparse.conf gpg_error_lib = ../src/libgpg-error.la TESTS = t-version t-strerror t-syserror t-lock t-printf t-poll t-b64 \ - t-argparse t-logging t-stringutils t-malloc + t-argparse t-logging t-stringutils t-malloc t-spawn if HAVE_LOCK_OPTIMIZATION TESTS += t-lock-single-posix diff --git a/tests/t-spawn.c b/tests/t-spawn.c new file mode 100644 index 0000000..96c890a --- /dev/null +++ b/tests/t-spawn.c @@ -0,0 +1,220 @@ +/* t-spawn.c - Check the spawn functions. + * Copyright (C) 2024 g10 Code GmbH + * + * This file is part of libgpg-error. + * + * libgpg-error 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. + * + * libgpg-error 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> +#include <unistd.h> + +#define PGM "t-spawn" + +#include "t-common.h" + +#define MAXLINE 1024 + +static void +run_test (const char *progname) +{ + gpg_err_code_t rc; + int r; + gpgrt_spawn_actions_t act; + gpgrt_process_t process; + gpgrt_stream_t fp_out; + const char *const envchange[] = { + "ADD=0", /* Add */ + "REPLACE=1", /* Replace */ + "GNUPGHOME", /* Remove */ + NULL, + }; + const char *argv1[] = { + "--child", + NULL, + }; + char line[MAXLINE]; + int ok_add, ok_replace, ok_remove; + + /* Make sure ADD is not defined in the parent process. */ + rc = gpgrt_setenv ("ADD", NULL, 1); + if (rc) + fail ("gpgrt_setenv failed at %d: %s", __LINE__, gpg_strerror (rc)); + + /* Set REPLACE and GNUPGHOME in the parent process. */ + rc = gpgrt_setenv ("REPLACE", "0", 1); + if (rc) + fail ("gpgrt_setenv failed at %d: %s", __LINE__, gpg_strerror (rc)); + rc = gpgrt_setenv ("GNUPGHOME", "/tmp/test", 1); + if (rc) + fail ("gpgrt_setenv failed at %d: %s", __LINE__, gpg_strerror (rc)); + + rc = gpgrt_spawn_actions_new (&act); + if (rc) + fail ("gpgrt_spawn_actions_new failed at %d: %s", + __LINE__, gpg_strerror (rc)); + + gpgrt_spawn_actions_set_envchange (act, envchange); + + rc = gpgrt_process_spawn (progname, argv1, + (GPGRT_PROCESS_STDIN_KEEP + | GPGRT_PROCESS_STDOUT_PIPE + | GPGRT_PROCESS_STDERR_KEEP), + act, &process); + if (rc) + fail ("gpgrt_process_spawn failed at %d: %s", __LINE__, gpg_strerror (rc)); + + gpgrt_spawn_actions_release (act); + + rc = gpgrt_process_get_streams (process, 0, NULL, &fp_out, NULL); + if (rc) + fail ("gpgrt_process_get_streams failed at %d: %s", + __LINE__, gpg_strerror (rc)); + + ok_add = ok_replace = 0; + ok_remove = 1; + + while (gpgrt_fgets (line, DIM (line), fp_out)) + { +#ifdef HAVE_W32_SYSTEM + { /* Take care of CRLF. */ + char *p = strchr (line, '\r'); + if (p) + *p = '\n'; + } +#endif + if (!strncmp (line, "ADD=", 3)) + { + if (!strncmp (line+4, "0\n", 2)) + ok_add = 1; + } + if (!strncmp (line, "REPLACE=", 8)) + { + if (!strncmp (line+8, "1\n", 2)) + ok_replace = 1; + } + if (!strncmp (line, "GNUPGHOME=", 10)) + ok_remove = 0; + } + + r = gpgrt_fclose (fp_out); + if (r) + fail ("gpgrt_fclose failed at %d: %d", __LINE__, r); + + rc = gpgrt_process_wait (process, 1); + if (rc) + fail ("gpgrt_process_wait failed at %d: %s", __LINE__, gpg_strerror (rc)); + + gpgrt_process_release (process); + + if (verbose) + show ("Result: add=%s, replace=%s, remove=%s\n", + ok_add? "OK" : "FAIL", ok_replace? "OK" : "FAIL", + ok_remove? "OK" : "FAIL"); + + if (!ok_add) + fail ("ADD failed"); + if (!ok_replace) + fail ("REPLACE failed"); + if (!ok_remove) + fail ("GNUPGHOME failed"); +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + int child = 0; + const char *progname = argv[0]; + + if (argc) + { + argc--; argv++; + } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--help")) + { + puts ( +"usage: ./t-spawn [options]\n" +"\n" +"Options:\n" +" --verbose Show what is going on\n" +" --debug Flyswatter\n" +" --child Internal use for the child process\n" +); + exit (0); + } + if (!strcmp (*argv, "--verbose")) + { + verbose = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose = debug = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--child")) + { + child = 1; + argc--; argv++; + } + } + + if (!gpg_error_check_version (GPG_ERROR_VERSION)) + { + die ("gpg_error_check_version returned an error"); + errorcount++; + } + + if (child) + { + char *e; + + e = gpgrt_getenv ("ADD"); + if (e) + { + printf ("ADD=%s\n", e); + gpgrt_free (e); + } + e = gpgrt_getenv ("REPLACE"); + if (e) + { + printf ("REPLACE=%s\n", e); + gpgrt_free (e); + } + e = gpgrt_getenv ("GNUPGHOME"); + if (e) + { + printf ("GNUPGHOME=%s\n", e); + gpgrt_free (e); + } + return 0; + } + + run_test (progname); + return errorcount ? 1 : 0; +} |