diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/Makefile.am | 8 | ||||
-rw-r--r-- | common/asshelp.c | 43 | ||||
-rw-r--r-- | common/asshelp.h | 11 | ||||
-rw-r--r-- | common/audit.c | 4 | ||||
-rw-r--r-- | common/b64dec.c | 299 | ||||
-rw-r--r-- | common/b64enc.c | 423 | ||||
-rw-r--r-- | common/dynload.h | 11 | ||||
-rw-r--r-- | common/exechelp-posix.c | 993 | ||||
-rw-r--r-- | common/exechelp-w32.c | 1209 | ||||
-rw-r--r-- | common/exechelp.h | 231 | ||||
-rw-r--r-- | common/exectool.c | 40 | ||||
-rw-r--r-- | common/get-passphrase.c | 3 | ||||
-rw-r--r-- | common/init.c | 1 | ||||
-rw-r--r-- | common/iobuf.c | 72 | ||||
-rw-r--r-- | common/iobuf.h | 6 | ||||
-rw-r--r-- | common/miscellaneous.c | 50 | ||||
-rw-r--r-- | common/openpgpdefs.h | 9 | ||||
-rw-r--r-- | common/ssh-utils.c | 24 | ||||
-rw-r--r-- | common/sysutils.c | 182 | ||||
-rw-r--r-- | common/sysutils.h | 13 | ||||
-rw-r--r-- | common/t-b64.c | 283 | ||||
-rw-r--r-- | common/t-exechelp.c | 152 | ||||
-rw-r--r-- | common/t-iobuf.c | 56 | ||||
-rw-r--r-- | common/util.h | 34 |
24 files changed, 1906 insertions, 2251 deletions
diff --git a/common/Makefile.am b/common/Makefile.am index 91718f99e..d27603f96 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -65,7 +65,7 @@ common_sources = \ homedir.c \ gettime.c gettime.h \ yesno.c \ - b64enc.c b64dec.c zb32.c zb32.h \ + zb32.c zb32.h \ convert.c \ percent.c \ mbox-util.c mbox-util.h \ @@ -161,11 +161,12 @@ module_tests = t-stringhelp t-timestuff \ t-convert t-percent t-gettime t-sysutils t-sexputil \ t-session-env t-openpgp-oid t-ssh-utils \ t-mapstrings t-zb32 t-mbox-util t-iobuf t-strlist \ - t-name-value t-ccparray t-recsel t-w32-cmdline t-b64 + t-name-value t-ccparray t-recsel t-w32-cmdline t-exechelp + if HAVE_W32_SYSTEM module_tests += t-w32-reg else -module_tests += t-exechelp t-exectool +module_tests += t-exectool endif if MAINTAINER_MODE @@ -196,7 +197,6 @@ t_gettime_LDADD = $(t_common_ldadd) t_sysutils_LDADD = $(t_common_ldadd) t_helpfile_LDADD = $(t_common_ldadd) t_sexputil_LDADD = $(t_common_ldadd) -t_b64_LDADD = $(t_common_ldadd) t_exechelp_LDADD = $(t_common_ldadd) t_exectool_LDADD = $(t_common_ldadd) t_session_env_LDADD = $(t_common_ldadd) diff --git a/common/asshelp.c b/common/asshelp.c index eb3e41bf5..5a40e0380 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -386,7 +386,8 @@ start_new_service (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { @@ -445,7 +446,7 @@ start_new_service (assuan_context_t *r_ctx, } err = assuan_socket_connect (ctx, sockname, 0, connect_flags); - if (err && autostart) + if (err && (flags & ASSHELP_FLAG_AUTOSTART)) { char *abs_homedir; lock_spawn_t lock; @@ -523,16 +524,12 @@ start_new_service (assuan_context_t *r_ctx, && assuan_socket_connect (ctx, sockname, 0, connect_flags)) { #ifdef HAVE_W32_SYSTEM - err = gnupg_spawn_process_detached (program? program : program_name, - argv, NULL); + err = gnupg_process_spawn (program? program : program_name, argv, + GNUPG_PROCESS_DETACHED, + NULL, NULL, NULL); #else /*!W32*/ - pid_t pid; - - err = gnupg_spawn_process_fd (program? program : program_name, - argv, -1, -1, -1, &pid); - if (!err) - err = gnupg_wait_process (program? program : program_name, - pid, 1, NULL); + err = gnupg_process_spawn (program? program : program_name, argv, + 0, NULL, NULL, NULL); #endif /*!W32*/ if (err) log_error ("failed to start %s '%s': %s\n", @@ -551,7 +548,8 @@ start_new_service (assuan_context_t *r_ctx, xfree (sockname); if (err) { - if (autostart || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED) + if ((flags & ASSHELP_FLAG_AUTOSTART) + || gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED) log_error ("can't connect to the %s: %s\n", printed_name, gpg_strerror (err)); assuan_release (ctx); @@ -603,55 +601,58 @@ start_new_gpg_agent (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { return start_new_service (r_ctx, GNUPG_MODULE_NAME_AGENT, errsource, agent_program, opt_lc_ctype, opt_lc_messages, session_env, - autostart, verbose, debug, + flags, verbose, debug, status_cb, status_cb_arg); } /* Try to connect to the dirmngr via a socket. On platforms - supporting it, start it up if needed and if AUTOSTART is true. + supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_keyboxd (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *keyboxd_program, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { return start_new_service (r_ctx, GNUPG_MODULE_NAME_KEYBOXD, errsource, keyboxd_program, NULL, NULL, NULL, - autostart, verbose, debug, + flags, verbose, debug, status_cb, status_cb_arg); } /* Try to connect to the dirmngr via a socket. On platforms - supporting it, start it up if needed and if AUTOSTART is true. + supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_dirmngr (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *dirmngr_program, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { #ifndef USE_DIRMNGR_AUTO_START - autostart = 0; + flags &= ~ASSHELP_FLAG_AUTOSTART; /* Clear flag. */ #endif return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR, errsource, dirmngr_program, NULL, NULL, NULL, - autostart, verbose, debug, + flags, verbose, debug, status_cb, status_cb_arg); } diff --git a/common/asshelp.h b/common/asshelp.h index e7e43bd1b..bca50759d 100644 --- a/common/asshelp.h +++ b/common/asshelp.h @@ -37,6 +37,8 @@ #include "util.h" /*-- asshelp.c --*/ +#define ASSHELP_FLAG_AUTOSTART 1 /* Autostart the new service. */ + void setup_libassuan_logging (unsigned int *debug_var_address, int (*log_monitor)(assuan_context_t ctx, @@ -61,7 +63,8 @@ start_new_gpg_agent (assuan_context_t *r_ctx, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); @@ -71,7 +74,8 @@ gpg_error_t start_new_keyboxd (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *keyboxd_program, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); @@ -81,7 +85,8 @@ gpg_error_t start_new_dirmngr (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *dirmngr_program, - int autostart, int verbose, int debug, + unsigned int flags, + int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg); diff --git a/common/audit.c b/common/audit.c index ae0d45216..42a2cf6d6 100644 --- a/common/audit.c +++ b/common/audit.c @@ -45,8 +45,8 @@ struct log_item_s int intvalue; /* A logged integer value. */ char *string; /* A malloced string or NULL. */ ksba_cert_t cert; /* A certifciate or NULL. */ - int have_err:1; - int have_intvalue:1; + unsigned int have_err:1; + unsigned int have_intvalue:1; }; typedef struct log_item_s *log_item_t; diff --git a/common/b64dec.c b/common/b64dec.c deleted file mode 100644 index 2904b0471..000000000 --- a/common/b64dec.c +++ /dev/null @@ -1,299 +0,0 @@ -/* b64dec.c - Simple Base64 decoder. - * Copyright (C) 2008, 2011 Free Software Foundation, Inc. - * Copyright (C) 2008, 2011, 2016 g10 Code GmbH - * - * This file is part of GnuPG. - * - * This file 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. - * - * This file 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 <https://www.gnu.org/licenses/>. - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include <config.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <assert.h> - -#include "i18n.h" -#include "util.h" - - -/* The reverse base-64 list used for base-64 decoding. */ -static unsigned char const asctobin[128] = - { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, - 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, - 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, - 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, - 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff - }; - -enum decoder_states - { - s_init, s_idle, s_lfseen, s_beginseen, s_waitheader, s_waitblank, s_begin, - s_b64_0, s_b64_1, s_b64_2, s_b64_3, - s_waitendtitle, s_waitend - }; - - - -/* Initialize the context for the base64 decoder. If TITLE is NULL a - plain base64 decoding is done. If it is the empty string the - decoder will skip everything until a "-----BEGIN " line has been - seen, decoding ends at a "----END " line. */ -gpg_error_t -b64dec_start (struct b64state *state, const char *title) -{ - memset (state, 0, sizeof *state); - if (title) - { - state->title = xtrystrdup (title); - if (!state->title) - state->lasterr = gpg_error_from_syserror (); - else - state->idx = s_init; - } - else - state->idx = s_b64_0; - return state->lasterr; -} - - -/* Do in-place decoding of base-64 data of LENGTH in BUFFER. Stores the - new length of the buffer at R_NBYTES. */ -gpg_error_t -b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes) -{ - enum decoder_states ds = state->idx; - unsigned char val = state->radbuf[0]; - int pos = state->quad_count; - char *d, *s; - - if (state->lasterr) - return state->lasterr; - - if (state->stop_seen) - { - *r_nbytes = 0; - state->lasterr = gpg_error (GPG_ERR_EOF); - xfree (state->title); - state->title = NULL; - return state->lasterr; - } - - for (s=d=buffer; length && !state->stop_seen; length--, s++) - { - again: - switch (ds) - { - case s_idle: - if (*s == '\n') - { - ds = s_lfseen; - pos = 0; - } - break; - case s_init: - ds = s_lfseen; - /* fall through */ - case s_lfseen: - if (*s != "-----BEGIN "[pos]) - { - ds = s_idle; - goto again; - } - else if (pos == 10) - { - pos = 0; - ds = s_beginseen; - } - else - pos++; - break; - case s_beginseen: - if (*s != "PGP "[pos]) - ds = s_begin; /* Not a PGP armor. */ - else if (pos == 3) - ds = s_waitheader; - else - pos++; - break; - case s_waitheader: - if (*s == '\n') - ds = s_waitblank; - break; - case s_waitblank: - if (*s == '\n') - ds = s_b64_0; /* blank line found. */ - else if (*s == ' ' || *s == '\r' || *s == '\t') - ; /* Ignore spaces. */ - else - { - /* Armor header line. Note that we don't care that our - * FSM accepts a header prefixed with spaces. */ - ds = s_waitheader; /* Wait for next header. */ - } - break; - case s_begin: - if (*s == '\n') - ds = s_b64_0; - break; - case s_b64_0: - case s_b64_1: - case s_b64_2: - case s_b64_3: - { - int c; - - if (*s == '-' && state->title) - { - /* Not a valid Base64 character: assume end - header. */ - ds = s_waitend; - } - else if (*s == '=') - { - /* Pad character: stop */ - if (ds == s_b64_1) - *d++ = val; - ds = state->title? s_waitendtitle : s_waitend; - } - else if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t') - ; /* Skip white spaces. */ - else if ( (*s & 0x80) - || (c = asctobin[*(unsigned char *)s]) == 255) - { - /* Skip invalid encodings. */ - state->invalid_encoding = 1; - } - else if (ds == s_b64_0) - { - val = c << 2; - ds = s_b64_1; - } - else if (ds == s_b64_1) - { - val |= (c>>4)&3; - *d++ = val; - val = (c<<4)&0xf0; - ds = s_b64_2; - } - else if (ds == s_b64_2) - { - val |= (c>>2)&15; - *d++ = val; - val = (c<<6)&0xc0; - ds = s_b64_3; - } - else - { - val |= c&0x3f; - *d++ = val; - ds = s_b64_0; - } - } - break; - case s_waitendtitle: - if (*s == '-') - ds = s_waitend; - break; - case s_waitend: - if ( *s == '\n') - state->stop_seen = 1; - break; - default: - BUG(); - } - } - - - state->idx = ds; - state->radbuf[0] = val; - state->quad_count = pos; - *r_nbytes = (d -(char*) buffer); - return 0; -} - - -/* This function needs to be called before releasing the decoder - state. It may return an error code in case an encoding error has - been found during decoding. */ -gpg_error_t -b64dec_finish (struct b64state *state) -{ - xfree (state->title); - state->title = NULL; - - if (state->lasterr) - return state->lasterr; - - return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0; -} - - -/* Convert STRING consisting of base64 characters into its binary - * representation and store the result in a newly allocated buffer at - * R_BUFFER with its length at R_BUFLEN. If TITLE is NULL a plain - * base64 decoding is done. If it is the empty string the decoder - * will skip everything until a "-----BEGIN " line has been seen, - * decoding then ends at a "----END " line. On failure the function - * returns an error code and sets R_BUFFER to NULL. If the decoded - * data has a length of 0 a dummy buffer will still be allocated and - * the length is set to 0. */ -gpg_error_t -b64decode (const char *string, const char *title, - void **r_buffer, size_t *r_buflen) -{ - gpg_error_t err; - struct b64state state; - size_t nbytes; - char *buffer; - - *r_buffer = NULL; - *r_buflen = 0; - - buffer = xtrystrdup (string); - if (!buffer) - return gpg_error_from_syserror(); - - err = b64dec_start (&state, title); - if (err) - { - xfree (buffer); - return err; - } - b64dec_proc (&state, buffer, strlen (buffer), &nbytes); - err = b64dec_finish (&state); - if (err) - xfree (buffer); - else - { - *r_buffer = buffer; - *r_buflen = nbytes; - } - return err; -} diff --git a/common/b64enc.c b/common/b64enc.c deleted file mode 100644 index 7846dcb3e..000000000 --- a/common/b64enc.c +++ /dev/null @@ -1,423 +0,0 @@ -/* b64enc.c - Simple Base64 encoder. - * Copyright (C) 2001, 2003, 2004, 2008, 2010, - * 2011 Free Software Foundation, Inc. - * Copyright (C) 2001, 2003, 2004, 2008, 2010, - * 2011 g10 Code GmbH - * - * This file is part of GnuPG. - * - * This file 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. - * - * This file 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 <https://www.gnu.org/licenses/>. - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include <config.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <assert.h> - -#include "i18n.h" -#include "util.h" - -#define B64ENC_DID_HEADER 1 -#define B64ENC_DID_TRAILER 2 -#define B64ENC_NO_LINEFEEDS 16 -#define B64ENC_USE_PGPCRC 32 - -/* The base-64 character list */ -static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - -/* Stuff required to create the OpenPGP CRC. This crc_table has been - created using this code: - - #include <stdio.h> - #include <stdint.h> - - #define CRCPOLY 0x864CFB - - int - main (void) - { - int i, j; - uint32_t t; - uint32_t crc_table[256]; - - crc_table[0] = 0; - for (i=j=0; j < 128; j++ ) - { - t = crc_table[j]; - if ( (t & 0x00800000) ) - { - t <<= 1; - crc_table[i++] = t ^ CRCPOLY; - crc_table[i++] = t; - } - else - { - t <<= 1; - crc_table[i++] = t; - crc_table[i++] = t ^ CRCPOLY; - } - } - - puts ("static const u32 crc_table[256] = {"); - for (i=j=0; i < 256; i++) - { - printf ("%s 0x%08lx", j? "":" ", (unsigned long)crc_table[i]); - if (i != 255) - { - putchar (','); - if ( ++j > 5) - { - j = 0; - putchar ('\n'); - } - } - } - puts ("\n};"); - return 0; - } -*/ -#define CRCINIT 0xB704CE -static const u32 crc_table[256] = { - 0x00000000, 0x00864cfb, 0x018ad50d, 0x010c99f6, 0x0393e6e1, 0x0315aa1a, - 0x021933ec, 0x029f7f17, 0x07a18139, 0x0727cdc2, 0x062b5434, 0x06ad18cf, - 0x043267d8, 0x04b42b23, 0x05b8b2d5, 0x053efe2e, 0x0fc54e89, 0x0f430272, - 0x0e4f9b84, 0x0ec9d77f, 0x0c56a868, 0x0cd0e493, 0x0ddc7d65, 0x0d5a319e, - 0x0864cfb0, 0x08e2834b, 0x09ee1abd, 0x09685646, 0x0bf72951, 0x0b7165aa, - 0x0a7dfc5c, 0x0afbb0a7, 0x1f0cd1e9, 0x1f8a9d12, 0x1e8604e4, 0x1e00481f, - 0x1c9f3708, 0x1c197bf3, 0x1d15e205, 0x1d93aefe, 0x18ad50d0, 0x182b1c2b, - 0x192785dd, 0x19a1c926, 0x1b3eb631, 0x1bb8faca, 0x1ab4633c, 0x1a322fc7, - 0x10c99f60, 0x104fd39b, 0x11434a6d, 0x11c50696, 0x135a7981, 0x13dc357a, - 0x12d0ac8c, 0x1256e077, 0x17681e59, 0x17ee52a2, 0x16e2cb54, 0x166487af, - 0x14fbf8b8, 0x147db443, 0x15712db5, 0x15f7614e, 0x3e19a3d2, 0x3e9fef29, - 0x3f9376df, 0x3f153a24, 0x3d8a4533, 0x3d0c09c8, 0x3c00903e, 0x3c86dcc5, - 0x39b822eb, 0x393e6e10, 0x3832f7e6, 0x38b4bb1d, 0x3a2bc40a, 0x3aad88f1, - 0x3ba11107, 0x3b275dfc, 0x31dced5b, 0x315aa1a0, 0x30563856, 0x30d074ad, - 0x324f0bba, 0x32c94741, 0x33c5deb7, 0x3343924c, 0x367d6c62, 0x36fb2099, - 0x37f7b96f, 0x3771f594, 0x35ee8a83, 0x3568c678, 0x34645f8e, 0x34e21375, - 0x2115723b, 0x21933ec0, 0x209fa736, 0x2019ebcd, 0x228694da, 0x2200d821, - 0x230c41d7, 0x238a0d2c, 0x26b4f302, 0x2632bff9, 0x273e260f, 0x27b86af4, - 0x252715e3, 0x25a15918, 0x24adc0ee, 0x242b8c15, 0x2ed03cb2, 0x2e567049, - 0x2f5ae9bf, 0x2fdca544, 0x2d43da53, 0x2dc596a8, 0x2cc90f5e, 0x2c4f43a5, - 0x2971bd8b, 0x29f7f170, 0x28fb6886, 0x287d247d, 0x2ae25b6a, 0x2a641791, - 0x2b688e67, 0x2beec29c, 0x7c3347a4, 0x7cb50b5f, 0x7db992a9, 0x7d3fde52, - 0x7fa0a145, 0x7f26edbe, 0x7e2a7448, 0x7eac38b3, 0x7b92c69d, 0x7b148a66, - 0x7a181390, 0x7a9e5f6b, 0x7801207c, 0x78876c87, 0x798bf571, 0x790db98a, - 0x73f6092d, 0x737045d6, 0x727cdc20, 0x72fa90db, 0x7065efcc, 0x70e3a337, - 0x71ef3ac1, 0x7169763a, 0x74578814, 0x74d1c4ef, 0x75dd5d19, 0x755b11e2, - 0x77c46ef5, 0x7742220e, 0x764ebbf8, 0x76c8f703, 0x633f964d, 0x63b9dab6, - 0x62b54340, 0x62330fbb, 0x60ac70ac, 0x602a3c57, 0x6126a5a1, 0x61a0e95a, - 0x649e1774, 0x64185b8f, 0x6514c279, 0x65928e82, 0x670df195, 0x678bbd6e, - 0x66872498, 0x66016863, 0x6cfad8c4, 0x6c7c943f, 0x6d700dc9, 0x6df64132, - 0x6f693e25, 0x6fef72de, 0x6ee3eb28, 0x6e65a7d3, 0x6b5b59fd, 0x6bdd1506, - 0x6ad18cf0, 0x6a57c00b, 0x68c8bf1c, 0x684ef3e7, 0x69426a11, 0x69c426ea, - 0x422ae476, 0x42aca88d, 0x43a0317b, 0x43267d80, 0x41b90297, 0x413f4e6c, - 0x4033d79a, 0x40b59b61, 0x458b654f, 0x450d29b4, 0x4401b042, 0x4487fcb9, - 0x461883ae, 0x469ecf55, 0x479256a3, 0x47141a58, 0x4defaaff, 0x4d69e604, - 0x4c657ff2, 0x4ce33309, 0x4e7c4c1e, 0x4efa00e5, 0x4ff69913, 0x4f70d5e8, - 0x4a4e2bc6, 0x4ac8673d, 0x4bc4fecb, 0x4b42b230, 0x49ddcd27, 0x495b81dc, - 0x4857182a, 0x48d154d1, 0x5d26359f, 0x5da07964, 0x5cace092, 0x5c2aac69, - 0x5eb5d37e, 0x5e339f85, 0x5f3f0673, 0x5fb94a88, 0x5a87b4a6, 0x5a01f85d, - 0x5b0d61ab, 0x5b8b2d50, 0x59145247, 0x59921ebc, 0x589e874a, 0x5818cbb1, - 0x52e37b16, 0x526537ed, 0x5369ae1b, 0x53efe2e0, 0x51709df7, 0x51f6d10c, - 0x50fa48fa, 0x507c0401, 0x5542fa2f, 0x55c4b6d4, 0x54c82f22, 0x544e63d9, - 0x56d11cce, 0x56575035, 0x575bc9c3, 0x57dd8538 -}; - - -static gpg_error_t -enc_start (struct b64state *state, FILE *fp, estream_t stream, - const char *title) -{ - memset (state, 0, sizeof *state); - state->fp = fp; - state->stream = stream; - state->lasterr = 0; - if (title && !*title) - state->flags |= B64ENC_NO_LINEFEEDS; - else if (title) - { - if (!strncmp (title, "PGP ", 4)) - { - state->flags |= B64ENC_USE_PGPCRC; - state->crc = CRCINIT; - } - state->title = xtrystrdup (title); - if (!state->title) - state->lasterr = gpg_error_from_syserror (); - } - return state->lasterr; -} - - -/* Prepare for base-64 writing to the stream FP. If TITLE is not NULL - and not an empty string, this string will be used as the title for - the armor lines, with TITLE being an empty string, we don't write - the header lines and furthermore even don't write any linefeeds. - If TITLE starts with "PGP " the OpenPGP CRC checksum will be - written as well. With TITLE being NULL, we merely don't write - header but make sure that lines are not too long. Note, that we - don't write any output unless at least one byte get written using - b64enc_write. */ -gpg_error_t -b64enc_start (struct b64state *state, FILE *fp, const char *title) -{ - return enc_start (state, fp, NULL, title); -} - -/* Same as b64enc_start but takes an estream. */ -gpg_error_t -b64enc_start_es (struct b64state *state, estream_t fp, const char *title) -{ - return enc_start (state, NULL, fp, title); -} - - -static int -my_fputs (const char *string, struct b64state *state) -{ - if (state->stream) - return es_fputs (string, state->stream); - else - return fputs (string, state->fp); -} - - -/* Write NBYTES from BUFFER to the Base 64 stream identified by - STATE. With BUFFER and NBYTES being 0, merely do a fflush on the - stream. */ -gpg_error_t -b64enc_write (struct b64state *state, const void *buffer, size_t nbytes) -{ - unsigned char radbuf[4]; - int idx, quad_count; - const unsigned char *p; - - if (state->lasterr) - return state->lasterr; - - if (!nbytes) - { - if (buffer) - if (state->stream? es_fflush (state->stream) : fflush (state->fp)) - goto write_error; - return 0; - } - - if (!(state->flags & B64ENC_DID_HEADER)) - { - if (state->title) - { - if ( my_fputs ("-----BEGIN ", state) == EOF - || my_fputs (state->title, state) == EOF - || my_fputs ("-----\n", state) == EOF) - goto write_error; - if ( (state->flags & B64ENC_USE_PGPCRC) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - - state->flags |= B64ENC_DID_HEADER; - } - - idx = state->idx; - quad_count = state->quad_count; - assert (idx < 4); - memcpy (radbuf, state->radbuf, idx); - - if ( (state->flags & B64ENC_USE_PGPCRC) ) - { - size_t n; - u32 crc = state->crc; - - for (p=buffer, n=nbytes; n; p++, n-- ) - crc = ((u32)crc << 8) ^ crc_table[((crc >> 16)&0xff) ^ *p]; - state->crc = (crc & 0x00ffffff); - } - - for (p=buffer; nbytes; p++, nbytes--) - { - radbuf[idx++] = *p; - if (idx > 2) - { - char tmp[4]; - - tmp[0] = bintoasc[(*radbuf >> 2) & 077]; - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077]; - tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - tmp[3] = bintoasc[radbuf[2]&077]; - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - idx = 0; - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - idx = 0; - if (ferror (state->fp)) - goto write_error; - } - if (++quad_count >= (64/4)) - { - quad_count = 0; - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - } - } - memcpy (state->radbuf, radbuf, idx); - state->idx = idx; - state->quad_count = quad_count; - return 0; - - write_error: - state->lasterr = gpg_error_from_syserror (); - if (state->title) - { - xfree (state->title); - state->title = NULL; - } - return state->lasterr; -} - - -gpg_error_t -b64enc_finish (struct b64state *state) -{ - gpg_error_t err = 0; - unsigned char radbuf[4]; - int idx, quad_count; - char tmp[4]; - - if (state->lasterr) - return state->lasterr; - - if (!(state->flags & B64ENC_DID_HEADER)) - goto cleanup; - - /* Flush the base64 encoding */ - idx = state->idx; - quad_count = state->quad_count; - assert (idx < 4); - memcpy (radbuf, state->radbuf, idx); - - if (idx) - { - tmp[0] = bintoasc[(*radbuf>>2)&077]; - if (idx == 1) - { - tmp[1] = bintoasc[((*radbuf << 4) & 060) & 077]; - tmp[2] = '='; - tmp[3] = '='; - } - else - { - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; - tmp[2] = bintoasc[((radbuf[1] << 2) & 074) & 077]; - tmp[3] = '='; - } - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - if (ferror (state->fp)) - goto write_error; - } - - if (++quad_count >= (64/4)) - { - quad_count = 0; - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - } - - /* Finish the last line and write the trailer. */ - if (quad_count - && !(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - - if ( (state->flags & B64ENC_USE_PGPCRC) ) - { - /* Write the CRC. */ - my_fputs ("=", state); - radbuf[0] = state->crc >>16; - radbuf[1] = state->crc >> 8; - radbuf[2] = state->crc; - tmp[0] = bintoasc[(*radbuf>>2)&077]; - tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077]; - tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077]; - tmp[3] = bintoasc[radbuf[2]&077]; - if (state->stream) - { - for (idx=0; idx < 4; idx++) - es_putc (tmp[idx], state->stream); - if (es_ferror (state->stream)) - goto write_error; - } - else - { - for (idx=0; idx < 4; idx++) - putc (tmp[idx], state->fp); - if (ferror (state->fp)) - goto write_error; - } - if (!(state->flags & B64ENC_NO_LINEFEEDS) - && my_fputs ("\n", state) == EOF) - goto write_error; - } - - if (state->title) - { - if ( my_fputs ("-----END ", state) == EOF - || my_fputs (state->title, state) == EOF - || my_fputs ("-----\n", state) == EOF) - goto write_error; - } - - goto cleanup; - - write_error: - err = gpg_error_from_syserror (); - - cleanup: - if (state->title) - { - xfree (state->title); - state->title = NULL; - } - state->fp = NULL; - state->stream = NULL; - state->lasterr = err; - return err; -} diff --git a/common/dynload.h b/common/dynload.h index 6ac7b4e17..af3906c81 100644 --- a/common/dynload.h +++ b/common/dynload.h @@ -34,12 +34,15 @@ #ifndef __MINGW32__ # include <dlfcn.h> #else -# include <errhandlingapi.h> -# include <handleapi.h> -# include <libloaderapi.h> +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> # include "utf8conv.h" # include "mischelp.h" -# define RTLD_LAZY 0 +# ifndef RTLD_LAZY +# define RTLD_LAZY 0 +# endif static inline void * dlopen (const char *name, int flag) diff --git a/common/exechelp-posix.c b/common/exechelp-posix.c index fa613449d..7b20a3796 100644 --- a/common/exechelp-posix.c +++ b/common/exechelp-posix.c @@ -273,73 +273,6 @@ get_all_open_fds (void) } -/* The exec core used right after the fork. This will never return. */ -static void -do_exec (const char *pgmname, const char *argv[], - int fd_in, int fd_out, int fd_err, - int *except, unsigned int flags) -{ - char **arg_list; - int i, j; - int fds[3]; - int nodevnull[3]; - - fds[0] = fd_in; - fds[1] = fd_out; - fds[2] = fd_err; - - nodevnull[0] = !!(flags & GNUPG_SPAWN_KEEP_STDIN); - nodevnull[1] = !!(flags & GNUPG_SPAWN_KEEP_STDOUT); - nodevnull[2] = !!(flags & GNUPG_SPAWN_KEEP_STDERR); - - /* Create the command line argument array. */ - i = 0; - if (argv) - while (argv[i]) - i++; - arg_list = xcalloc (i+2, sizeof *arg_list); - arg_list[0] = strrchr (pgmname, '/'); - if (arg_list[0]) - arg_list[0]++; - else - arg_list[0] = xstrdup (pgmname); - if (argv) - for (i=0,j=1; argv[i]; i++, j++) - arg_list[j] = (char*)argv[i]; - - /* Assign /dev/null to unused FDs. */ - for (i=0; i <= 2; i++) - { - if (nodevnull[i]) - continue; - if (fds[i] == -1) - { - fds[i] = open ("/dev/null", i? O_WRONLY : O_RDONLY); - if (fds[i] == -1) - log_fatal ("failed to open '%s': %s\n", - "/dev/null", strerror (errno)); - } - } - - /* Connect the standard files. */ - for (i=0; i <= 2; i++) - { - if (nodevnull[i]) - continue; - if (fds[i] != i && dup2 (fds[i], i) == -1) - log_fatal ("dup2 std%s failed: %s\n", - i==0?"in":i==1?"out":"err", strerror (errno)); - } - - /* Close all other files. */ - close_all_fds (3, except); - - execv (pgmname, arg_list); - /* No way to print anything, as we have closed all streams. */ - _exit (127); -} - - static gpg_error_t do_create_pipe (int filedes[2]) { @@ -431,487 +364,669 @@ gnupg_close_pipe (int fd) close (fd); } +#include <sys/socket.h> -/* Fork and exec the PGMNAME, see exechelp.h for details. */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *except, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid) -{ - gpg_error_t err; - int inpipe[2] = {-1, -1}; - int outpipe[2] = {-1, -1}; - int errpipe[2] = {-1, -1}; - estream_t infp = NULL; - estream_t outfp = NULL; - estream_t errfp = NULL; - int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK); +struct gnupg_process { + const char *pgmname; + unsigned int terminated :1; /* or detached */ + unsigned int flags; + pid_t pid; + int fd_in; + int fd_out; + int fd_err; + int wstatus; +}; - if (r_infp) - *r_infp = NULL; - if (r_outfp) - *r_outfp = NULL; - if (r_errfp) - *r_errfp = NULL; - *pid = (pid_t)(-1); /* Always required. */ +static int gnupg_process_syscall_func_initialized; - if (r_infp) - { - err = create_pipe_and_estream (inpipe, &infp, 1, nonblock); - if (err) - return err; - } +/* Functions called before and after blocking syscalls. */ +static void (*pre_syscall_func) (void); +static void (*post_syscall_func) (void); - if (r_outfp) +static void +check_syscall_func (void) +{ + if (!gnupg_process_syscall_func_initialized) { - err = create_pipe_and_estream (outpipe, &outfp, 0, nonblock); - if (err) - { - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - return err; - } + gpgrt_get_syscall_clamp (&pre_syscall_func, &post_syscall_func); + gnupg_process_syscall_func_initialized = 1; } +} - if (r_errfp) - { - err = create_pipe_and_estream (errpipe, &errfp, 0, nonblock); - if (err) - { - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != -1) - close (outpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - - return err; - } - } +static void +pre_syscall (void) +{ + if (pre_syscall_func) + pre_syscall_func (); +} + +static void +post_syscall (void) +{ + if (post_syscall_func) + post_syscall_func (); +} - *pid = fork (); - if (*pid == (pid_t)(-1)) - { - err = my_error_from_syserror (); - log_error (_("error forking process: %s\n"), gpg_strerror (err)); - - if (infp) - es_fclose (infp); - else if (inpipe[1] != -1) - close (inpipe[1]); - if (inpipe[0] != -1) - close (inpipe[0]); - - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != -1) - close (outpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - - if (errfp) - es_fclose (errfp); - else if (errpipe[0] != -1) - close (errpipe[0]); - if (errpipe[1] != -1) - close (errpipe[1]); - return err; - } +static gpg_err_code_t +do_create_socketpair (int filedes[2]) +{ + gpg_error_t err = 0; - if (!*pid) + pre_syscall (); + if (socketpair (AF_LOCAL, SOCK_STREAM, 0, filedes) == -1) { - /* This is the child. */ - gcry_control (GCRYCTL_TERM_SECMEM); - es_fclose (infp); - es_fclose (outfp); - es_fclose (errfp); - do_exec (pgmname, argv, inpipe[0], outpipe[1], errpipe[1], - except, flags); - /*NOTREACHED*/ + err = gpg_err_code_from_syserror (); + filedes[0] = filedes[1] = -1; } + post_syscall (); - /* This is the parent. */ - if (inpipe[0] != -1) - close (inpipe[0]); - if (outpipe[1] != -1) - close (outpipe[1]); - if (errpipe[1] != -1) - close (errpipe[1]); + return err; +} - if (r_infp) - *r_infp = infp; - if (r_outfp) - *r_outfp = outfp; - if (r_errfp) - *r_errfp = errfp; +static int +posix_open_null (int for_write) +{ + int fd; - return 0; + fd = open ("/dev/null", for_write? O_WRONLY : O_RDONLY); + if (fd == -1) + log_fatal ("failed to open '/dev/null': %s\n", strerror (errno)); + return fd; } +static void +call_spawn_cb (struct spawn_cb_arg *sca, + int fd_in, int fd_out, int fd_err, + void (*spawn_cb) (struct spawn_cb_arg *), void *spawn_cb_arg) +{ + sca->fds[0] = fd_in; + sca->fds[1] = fd_out; + sca->fds[2] = fd_err; + sca->except_fds = NULL; + sca->arg = spawn_cb_arg; + if (spawn_cb) + (*spawn_cb) (sca); +} +static void +my_exec (const char *pgmname, const char *argv[], struct spawn_cb_arg *sca) +{ + int i; -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process is required. + /* Assign /dev/null to unused FDs. */ + for (i = 0; i <= 2; i++) + if (sca->fds[i] == -1) + sca->fds[i] = posix_open_null (i); - Returns 0 on success or an error code. */ -gpg_error_t -gnupg_spawn_process_fd (const char *pgmname, const char *argv[], - int infd, int outfd, int errfd, pid_t *pid) + /* Connect the standard files. */ + for (i = 0; i <= 2; i++) + if (sca->fds[i] != i) + { + if (dup2 (sca->fds[i], i) == -1) + log_fatal ("dup2 std%s failed: %s\n", + i==0?"in":i==1?"out":"err", strerror (errno)); + /* + * We don't close sca.fds[i] here, but close them by + * close_all_fds. Note that there may be same one in three of + * sca->fds[i]. + */ + } + + /* Close all other files. */ + close_all_fds (3, sca->except_fds); + + execv (pgmname, (char *const *)argv); + /* No way to print anything, as we have may have closed all streams. */ + _exit (127); +} + +static gpg_err_code_t +spawn_detached (const char *pgmname, const char *argv[], + void (*spawn_cb) (struct spawn_cb_arg *), void *spawn_cb_arg) { - gpg_error_t err; + gpg_err_code_t ec; + pid_t pid; - *pid = fork (); - if (*pid == (pid_t)(-1)) + /* FIXME: Is this GnuPG specific or should we keep it. */ + if (getuid() != geteuid()) { - err = my_error_from_syserror (); - log_error (_("error forking process: %s\n"), strerror (errno)); - return err; + xfree (argv); + return GPG_ERR_BUG; } - if (!*pid) + if (access (pgmname, X_OK)) { - gcry_control (GCRYCTL_TERM_SECMEM); - /* Run child. */ - do_exec (pgmname, argv, infd, outfd, errfd, NULL, 0); - /*NOTREACHED*/ + ec = gpg_err_code_from_syserror (); + xfree (argv); + return ec; } - return 0; -} - - - + pre_syscall (); + pid = fork (); + post_syscall (); + if (pid == (pid_t)(-1)) + { + ec = gpg_err_code_from_syserror (); + log_error (_("error forking process: %s\n"), gpg_strerror (ec)); + xfree (argv); + return ec; + } -/* Waiting for child processes. + if (!pid) + { + pid_t pid2; + struct spawn_cb_arg sca; - waitpid(2) may return information about terminated children that we - did not yet request, and there is no portable way to wait for a - specific set of children. + if (setsid() == -1 || chdir ("/")) + _exit (1); - As a workaround, we store the results of children for later use. + pid2 = fork (); /* Double fork to let init take over the new child. */ + if (pid2 == (pid_t)(-1)) + _exit (1); + if (pid2) + _exit (0); /* Let the parent exit immediately. */ - XXX: This assumes that PIDs are not reused too quickly. */ + call_spawn_cb (&sca, -1, -1, -1, spawn_cb, spawn_cb_arg); -struct terminated_child -{ - pid_t pid; - int exitcode; - struct terminated_child *next; -}; + my_exec (pgmname, argv, &sca); + /*NOTREACHED*/ + } -struct terminated_child *terminated_children; + pre_syscall (); + if (waitpid (pid, NULL, 0) == -1) + { + post_syscall (); + ec = gpg_err_code_from_syserror (); + log_error ("waitpid failed in gpgrt_spawn_process_detached: %s", + gpg_strerror (ec)); + return ec; + } + else + post_syscall (); + return 0; +} -static gpg_error_t -store_result (pid_t pid, int exitcode) +void +gnupg_spawn_helper (struct spawn_cb_arg *sca) { - struct terminated_child *c; + int *user_except = sca->arg; + sca->except_fds = user_except; +} - c = xtrymalloc (sizeof *c); - if (c == NULL) - return gpg_err_code_from_syserror (); +gpg_err_code_t +gnupg_process_spawn (const char *pgmname, const char *argv1[], + unsigned int flags, + void (*spawn_cb) (struct spawn_cb_arg *), + void *spawn_cb_arg, + gnupg_process_t *r_process) +{ + gpg_err_code_t ec; + gnupg_process_t process; + int fd_in[2]; + int fd_out[2]; + int fd_err[2]; + pid_t pid; + const char **argv; + int i, j; - c->pid = pid; - c->exitcode = exitcode; - c->next = terminated_children; - terminated_children = c; + check_syscall_func (); - return 0; -} + if (r_process) + *r_process = NULL; + /* Create the command line argument array. */ + i = 0; + if (argv1) + while (argv1[i]) + i++; + argv = xtrycalloc (i+2, sizeof *argv); + if (!argv) + return gpg_err_code_from_syserror (); + argv[0] = strrchr (pgmname, '/'); + if (argv[0]) + argv[0]++; + else + argv[0] = pgmname; -static int -get_result (pid_t pid, int *r_exitcode) -{ - struct terminated_child *c, **prevp; + if (argv1) + for (i=0, j=1; argv1[i]; i++, j++) + argv[j] = argv1[i]; - for (prevp = &terminated_children, c = terminated_children; - c; - prevp = &c->next, c = c->next) - if (c->pid == pid) - { - *prevp = c->next; - *r_exitcode = c->exitcode; - xfree (c); - return 1; - } + if ((flags & GNUPG_PROCESS_DETACHED)) + { + if ((flags & GNUPG_PROCESS_STDFDS_SETTING)) + { + xfree (argv); + return GPG_ERR_INV_FLAG; + } - return 0; -} + /* In detached case, it must be no R_PROCESS. */ + if (r_process) + { + xfree (argv); + return GPG_ERR_INV_ARG; + } + return spawn_detached (pgmname, argv, spawn_cb, spawn_cb_arg); + } -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) -{ - gpg_err_code_t ec; - int i, status; + process = xtrycalloc (1, sizeof (struct gnupg_process)); + if (process == NULL) + { + xfree (argv); + return gpg_err_code_from_syserror (); + } - if (r_exitcode) - *r_exitcode = -1; + process->pgmname = pgmname; + process->flags = flags; - if (pid == (pid_t)(-1)) - return gpg_error (GPG_ERR_INV_VALUE); + if ((flags & GNUPG_PROCESS_STDINOUT_SOCKETPAIR)) + { + ec = do_create_socketpair (fd_in); + if (ec) + { + xfree (process); + xfree (argv); + return ec; + } + fd_out[0] = dup (fd_in[0]); + fd_out[1] = dup (fd_in[1]); + } + else + { + if ((flags & GNUPG_PROCESS_STDIN_PIPE)) + { + ec = do_create_pipe (fd_in); + if (ec) + { + xfree (process); + xfree (argv); + return ec; + } + } + else if ((flags & GNUPG_PROCESS_STDIN_KEEP)) + { + fd_in[0] = 0; + fd_in[1] = -1; + } + else + { + fd_in[0] = -1; + fd_in[1] = -1; + } -#ifdef USE_NPTH - i = npth_waitpid (pid, &status, hang? 0:WNOHANG); -#else - while ((i=waitpid (pid, &status, hang? 0:WNOHANG)) == (pid_t)(-1) - && errno == EINTR); -#endif + if ((flags & GNUPG_PROCESS_STDOUT_PIPE)) + { + ec = do_create_pipe (fd_out); + if (ec) + { + if (fd_in[0] >= 0 && fd_in[0] != 0) + close (fd_in[0]); + if (fd_in[1] >= 0) + close (fd_in[1]); + xfree (process); + xfree (argv); + return ec; + } + } + else if ((flags & GNUPG_PROCESS_STDOUT_KEEP)) + { + fd_out[0] = -1; + fd_out[1] = 1; + } + else + { + fd_out[0] = -1; + fd_out[1] = -1; + } + } - if (i == (pid_t)(-1)) + if ((flags & GNUPG_PROCESS_STDERR_PIPE)) { - ec = gpg_err_code_from_errno (errno); - log_error (_("waiting for process %d to terminate failed: %s\n"), - (int)pid, strerror (errno)); + ec = do_create_pipe (fd_err); + if (ec) + { + if (fd_in[0] >= 0 && fd_in[0] != 0) + close (fd_in[0]); + if (fd_in[1] >= 0) + close (fd_in[1]); + if (fd_out[0] >= 0) + close (fd_out[0]); + if (fd_out[1] >= 0 && fd_out[1] != 1) + close (fd_out[1]); + xfree (process); + xfree (argv); + return ec; + } } - else if (!i) + else if ((flags & GNUPG_PROCESS_STDERR_KEEP)) { - ec = GPG_ERR_TIMEOUT; /* Still running. */ + fd_err[0] = -1; + fd_err[1] = 2; } - else if (WIFEXITED (status) && WEXITSTATUS (status) == 127) + else { - log_error (_("error running '%s': probably not installed\n"), pgmname); - ec = GPG_ERR_CONFIGURATION; + fd_err[0] = -1; + fd_err[1] = -1; } - else if (WIFEXITED (status) && WEXITSTATUS (status)) + + pre_syscall (); + pid = fork (); + post_syscall (); + if (pid == (pid_t)(-1)) { - if (!r_exitcode) - log_error (_("error running '%s': exit status %d\n"), pgmname, - WEXITSTATUS (status)); - else - *r_exitcode = WEXITSTATUS (status); - ec = GPG_ERR_GENERAL; + ec = gpg_err_code_from_syserror (); + log_error (_("error forking process: %s\n"), gpg_strerror (ec)); + if (fd_in[0] >= 0 && fd_in[0] != 0) + close (fd_in[0]); + if (fd_in[1] >= 0) + close (fd_in[1]); + if (fd_out[0] >= 0) + close (fd_out[0]); + if (fd_out[1] >= 0 && fd_out[1] != 1) + close (fd_out[1]); + if (fd_err[0] >= 0) + close (fd_err[0]); + if (fd_err[1] >= 0 && fd_err[1] != 2) + close (fd_err[1]); + xfree (process); + xfree (argv); + return ec; } - else if (!WIFEXITED (status)) + + if (!pid) { - log_error (_("error running '%s': terminated\n"), pgmname); - ec = GPG_ERR_GENERAL; + struct spawn_cb_arg sca; + + if (fd_in[1] >= 0) + close (fd_in[1]); + if (fd_out[0] >= 0) + close (fd_out[0]); + if (fd_err[0] >= 0) + close (fd_err[0]); + + call_spawn_cb (&sca, fd_in[0], fd_out[1], fd_err[1], + spawn_cb, spawn_cb_arg); + + /* Run child. */ + my_exec (pgmname, argv, &sca); + /*NOTREACHED*/ } - else + + xfree (argv); + process->pid = pid; + + if (fd_in[0] >= 0 && fd_in[0] != 0) + close (fd_in[0]); + if (fd_out[1] >= 0 && fd_out[1] != 1) + close (fd_out[1]); + if (fd_err[1] >= 0 && fd_err[1] != 2) + close (fd_err[1]); + process->fd_in = fd_in[1]; + process->fd_out = fd_out[0]; + process->fd_err = fd_err[0]; + process->wstatus = -1; + process->terminated = 0; + + if (r_process == NULL) { - if (r_exitcode) - *r_exitcode = 0; - ec = 0; + ec = gnupg_process_wait (process, 1); + gnupg_process_release (process); + return ec; } - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); + *r_process = process; + return 0; } -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, - int hang, int *r_exitcodes) +static gpg_err_code_t +process_kill (gnupg_process_t process, int sig) { gpg_err_code_t ec = 0; - size_t i, left; - int *dummy = NULL; + pid_t pid = process->pid; - if (r_exitcodes == NULL) + pre_syscall (); + if (kill (pid, sig) < 0) + ec = gpg_err_code_from_syserror (); + post_syscall (); + return ec; +} + +gpg_err_code_t +gnupg_process_terminate (gnupg_process_t process) +{ + return process_kill (process, SIGTERM); +} + +gpg_err_code_t +gnupg_process_get_fds (gnupg_process_t process, unsigned int flags, + int *r_fd_in, int *r_fd_out, int *r_fd_err) +{ + (void)flags; + if (r_fd_in) { - dummy = r_exitcodes = xtrymalloc (sizeof *r_exitcodes * count); - if (dummy == NULL) - return gpg_err_code_from_syserror (); + *r_fd_in = process->fd_in; + process->fd_in = -1; } - - for (i = 0, left = count; i < count; i++) + if (r_fd_out) { - int status = -1; + *r_fd_out = process->fd_out; + process->fd_out = -1; + } + if (r_fd_err) + { + *r_fd_err = process->fd_err; + process->fd_err = -1; + } - /* Skip invalid PID. */ - if (pids[i] == (pid_t)(-1)) - { - r_exitcodes[i] = -1; - left -= 1; - continue; - } + return 0; +} - /* See if there was a previously stored result for this pid. */ - if (get_result (pids[i], &status)) - left -= 1; +gpg_err_code_t +gnupg_process_get_streams (gnupg_process_t process, unsigned int flags, + gpgrt_stream_t *r_fp_in, gpgrt_stream_t *r_fp_out, + gpgrt_stream_t *r_fp_err) +{ + int nonblock = (flags & GNUPG_PROCESS_STREAM_NONBLOCK)? 1: 0; - r_exitcodes[i] = status; + if (r_fp_in) + { + *r_fp_in = es_fdopen (process->fd_in, nonblock? "w,nonblock" : "w"); + process->fd_in = -1; + } + if (r_fp_out) + { + *r_fp_out = es_fdopen (process->fd_out, nonblock? "r,nonblock" : "r"); + process->fd_out = -1; + } + if (r_fp_err) + { + *r_fp_err = es_fdopen (process->fd_err, nonblock? "r,nonblock" : "r"); + process->fd_err = -1; } + return 0; +} - while (left > 0) +static gpg_err_code_t +process_vctl (gnupg_process_t process, unsigned int request, va_list arg_ptr) +{ + switch (request) { - pid_t pid; - int status; + case GNUPG_PROCESS_NOP: + return 0; -#ifdef USE_NPTH - pid = npth_waitpid (-1, &status, hang ? 0 : WNOHANG); -#else - while ((pid = waitpid (-1, &status, hang ? 0 : WNOHANG)) == (pid_t)(-1) - && errno == EINTR); -#endif + case GNUPG_PROCESS_GET_PROC_ID: + { + int *r_id = va_arg (arg_ptr, int *); - if (pid == (pid_t)(-1)) - { - ec = gpg_err_code_from_errno (errno); - log_error (_("waiting for processes to terminate failed: %s\n"), - strerror (errno)); - break; - } - else if (!pid) - { - ec = GPG_ERR_TIMEOUT; /* Still running. */ - break; - } - else - { - for (i = 0; i < count; i++) - if (pid == pids[i]) - break; + if (r_id == NULL) + return GPG_ERR_INV_VALUE; - if (i == count) - { - /* No match, store this result. */ - ec = store_result (pid, status); - if (ec) - break; - continue; - } + *r_id = (int)process->pid; + return 0; + } - /* Process PIDS[i] died. */ - if (r_exitcodes[i] != (pid_t) -1) - { - log_error ("PID %d was reused", pid); - ec = GPG_ERR_GENERAL; - break; - } + case GNUPG_PROCESS_GET_EXIT_ID: + { + int status = process->wstatus; + int *r_exit_status = va_arg (arg_ptr, int *); - left -= 1; - r_exitcodes[i] = status; - } - } + if (!process->terminated) + return GPG_ERR_UNFINISHED; - for (i = 0; i < count; i++) - { - if (r_exitcodes[i] == -1) - continue; + if (WIFEXITED (status)) + { + if (r_exit_status) + *r_exit_status = WEXITSTATUS (status); + } + else + *r_exit_status = -1; - if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i]) == 127) - { - log_error (_("error running '%s': probably not installed\n"), - pgmnames[i]); - ec = GPG_ERR_CONFIGURATION; - } - else if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i])) - { - if (dummy) - log_error (_("error running '%s': exit status %d\n"), - pgmnames[i], WEXITSTATUS (r_exitcodes[i])); - else - r_exitcodes[i] = WEXITSTATUS (r_exitcodes[i]); - ec = GPG_ERR_GENERAL; - } - else if (!WIFEXITED (r_exitcodes[i])) - { - log_error (_("error running '%s': terminated\n"), pgmnames[i]); - ec = GPG_ERR_GENERAL; - } + return 0; + } + + case GNUPG_PROCESS_GET_PID: + { + pid_t *r_pid = va_arg (arg_ptr, pid_t *); + + if (r_pid == NULL) + return GPG_ERR_INV_VALUE; + + *r_pid = process->pid; + return 0; + } + + case GNUPG_PROCESS_GET_WSTATUS: + { + int status = process->wstatus; + int *r_if_exited = va_arg (arg_ptr, int *); + int *r_if_signaled = va_arg (arg_ptr, int *); + int *r_exit_status = va_arg (arg_ptr, int *); + int *r_termsig = va_arg (arg_ptr, int *); + + if (!process->terminated) + return GPG_ERR_UNFINISHED; + + if (WIFEXITED (status)) + { + if (r_if_exited) + *r_if_exited = 1; + if (r_if_signaled) + *r_if_signaled = 0; + if (r_exit_status) + *r_exit_status = WEXITSTATUS (status); + if (r_termsig) + *r_termsig = 0; + } + else if (WIFSIGNALED (status)) + { + if (r_if_exited) + *r_if_exited = 0; + if (r_if_signaled) + *r_if_signaled = 1; + if (r_exit_status) + *r_exit_status = 0; + if (r_termsig) + *r_termsig = WTERMSIG (status); + } + + return 0; + } + + case GNUPG_PROCESS_KILL: + { + int sig = va_arg (arg_ptr, int); + + return process_kill (process, sig); + } + + default: + break; } - xfree (dummy); - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); + return GPG_ERR_UNKNOWN_COMMAND; } - - -void -gnupg_release_process (pid_t pid) +gpg_err_code_t +gnupg_process_ctl (gnupg_process_t process, unsigned int request, ...) { - (void)pid; -} + va_list arg_ptr; + gpg_err_code_t ec; + va_start (arg_ptr, request); + ec = process_vctl (process, request, arg_ptr); + va_end (arg_ptr); + return ec; +} -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. All standard file descriptors are - connected to /dev/null. */ -gpg_error_t -gnupg_spawn_process_detached (const char *pgmname, const char *argv[], - const char *envp[] ) +gpg_err_code_t +gnupg_process_wait (gnupg_process_t process, int hang) { gpg_err_code_t ec; + int status; pid_t pid; - int i; - if (getuid() != geteuid()) - return my_error (GPG_ERR_BUG); + if (process->terminated) + /* Already terminated. */ + return 0; - if ((ec = gnupg_access (pgmname, X_OK))) - return gpg_err_make (default_errsource, ec); + pre_syscall (); + while ((pid = waitpid (process->pid, &status, hang? 0: WNOHANG)) + == (pid_t)(-1) && errno == EINTR); + post_syscall (); - pid = fork (); if (pid == (pid_t)(-1)) { - log_error (_("error forking process: %s\n"), strerror (errno)); - return my_error_from_syserror (); + ec = gpg_err_code_from_syserror (); + log_error (_("waiting for process %d to terminate failed: %s\n"), + (int)pid, gpg_strerror (ec)); } - if (!pid) + else if (!pid) { - pid_t pid2; - - gcry_control (GCRYCTL_TERM_SECMEM); - if (setsid() == -1 || chdir ("/")) - _exit (1); - - pid2 = fork (); /* Double fork to let init take over the new child. */ - if (pid2 == (pid_t)(-1)) - _exit (1); - if (pid2) - _exit (0); /* Let the parent exit immediately. */ + ec = GPG_ERR_TIMEOUT; /* Still running. */ + } + else + { + process->terminated = 1; + process->wstatus = status; + ec = 0; + } - if (envp) - for (i=0; envp[i]; i++) - putenv (xstrdup (envp[i])); + return ec; +} - do_exec (pgmname, argv, -1, -1, -1, NULL, 0); +void +gnupg_process_release (gnupg_process_t process) +{ + if (!process) + return; - /*NOTREACHED*/ + if (process->terminated) + { + gnupg_process_terminate (process); + gnupg_process_wait (process, 1); } - if (waitpid (pid, NULL, 0) == -1) - log_error ("waitpid failed in gnupg_spawn_process_detached: %s", - strerror (errno)); - - return 0; + xfree (process); } - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void -gnupg_kill_process (pid_t pid) +gpg_err_code_t +gnupg_process_wait_list (gnupg_process_t *process_list, int count, int hang) { - if (pid != (pid_t)(-1)) + gpg_err_code_t ec = 0; + int i; + + for (i = 0; i < count; i++) { - kill (pid, SIGTERM); + if (process_list[i]->terminated) + continue; + + ec = gnupg_process_wait (process_list[i], hang); + if (ec) + break; } + + return ec; } diff --git a/common/exechelp-w32.c b/common/exechelp-w32.c index 2093f0d8b..08290e442 100644 --- a/common/exechelp-w32.c +++ b/common/exechelp-w32.c @@ -33,6 +33,8 @@ #if !defined(HAVE_W32_SYSTEM) #error This code is only used on W32. +#else +#define _WIN32_WINNT 0x600 #endif #include <stdio.h> @@ -63,9 +65,11 @@ #include "util.h" #include "i18n.h" #include "sysutils.h" +#define NEED_STRUCT_SPAWN_CB_ARG #include "exechelp.h" #include <windows.h> +#include <processthreadsapi.h> /* Define to 1 do enable debugging. */ #define DEBUG_W32_SPAWN 0 @@ -405,143 +409,147 @@ gnupg_close_pipe (int fd) if (fd != -1) close (fd); } + +struct gnupg_process { + const char *pgmname; + unsigned int terminated :1; /* or detached */ + unsigned int flags; + HANDLE hProcess; + HANDLE hd_in; + HANDLE hd_out; + HANDLE hd_err; + int exitcode; +}; + +static int gnupg_process_syscall_func_initialized; + +/* Functions called before and after blocking syscalls. */ +static void (*pre_syscall_func) (void); +static void (*post_syscall_func) (void); + +static void +check_syscall_func (void) +{ + if (!gnupg_process_syscall_func_initialized) + { + gpgrt_get_syscall_clamp (&pre_syscall_func, &post_syscall_func); + gnupg_process_syscall_func_initialized = 1; + } +} -/* Fork and exec the PGMNAME, see exechelp.h for details. */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *except, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid) +static void +pre_syscall (void) { - gpg_error_t err; - SECURITY_ATTRIBUTES sec_attr; - PROCESS_INFORMATION pi = - { - NULL, /* Returns process handle. */ - 0, /* Returns primary thread handle. */ - 0, /* Returns pid. */ - 0 /* Returns tid. */ - }; - STARTUPINFOW si; - int cr_flags; - char *cmdline; - wchar_t *wcmdline = NULL; - wchar_t *wpgmname = NULL; - HANDLE inpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE}; - estream_t infp = NULL; - estream_t outfp = NULL; - estream_t errfp = NULL; - HANDLE nullhd[3] = {INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE, - INVALID_HANDLE_VALUE}; - int i, rc; - es_syshd_t syshd; - gpg_err_source_t errsource = default_errsource; - int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK); + if (pre_syscall_func) + pre_syscall_func (); +} + - (void)except; /* Not yet used. */ +static void +post_syscall (void) +{ + if (post_syscall_func) + post_syscall_func (); +} - if (r_infp) - *r_infp = NULL; - if (r_outfp) - *r_outfp = NULL; - if (r_errfp) - *r_errfp = NULL; - *pid = (pid_t)(-1); /* Always required. */ - if (r_infp) +/* + * Check if STARTUPINFOEXW supports PROC_THREAD_ATTRIBUTE_HANDLE_LIST. + */ +static int +check_windows_version (void) +{ + static int is_vista_or_later = -1; + + OSVERSIONINFO osvi; + + if (is_vista_or_later == -1) { - if (create_inheritable_pipe (inpipe, INHERIT_READ)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } + memset (&osvi,0,sizeof(osvi)); + osvi.dwOSVersionInfoSize = sizeof(osvi); + GetVersionEx (&osvi); - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = inpipe[1]; - infp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); - if (!infp) - { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (inpipe[0]); - CloseHandle (inpipe[1]); - inpipe[0] = inpipe[1] = INVALID_HANDLE_VALUE; - return err; - } + /* The feature is available on Vista or later. */ + is_vista_or_later = (osvi.dwMajorVersion >= 6); } - if (r_outfp) - { - if (create_inheritable_pipe (outpipe, INHERIT_WRITE)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } + return is_vista_or_later; +} - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = outpipe[0]; - outfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); - if (!outfp) - { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (outpipe[0]); - CloseHandle (outpipe[1]); - outpipe[0] = outpipe[1] = INVALID_HANDLE_VALUE; - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - return err; - } + +static gpg_err_code_t +spawn_detached (const char *pgmname, char *cmdline, + void (*spawn_cb) (struct spawn_cb_arg *), void *spawn_cb_arg) +{ + SECURITY_ATTRIBUTES sec_attr; + PROCESS_INFORMATION pi = { NULL, 0, 0, 0 }; + STARTUPINFOEXW si; + int cr_flags; + wchar_t *wcmdline = NULL; + wchar_t *wpgmname = NULL; + gpg_err_code_t ec; + int ret; + struct spawn_cb_arg sca; + BOOL ask_inherit = FALSE; + + ec = gnupg_access (pgmname, X_OK); + if (ec) + { + xfree (cmdline); + return ec; } - if (r_errfp) + memset (&si, 0, sizeof si); + + sca.allow_foreground_window = FALSE; + sca.hd[0] = INVALID_HANDLE_VALUE; + sca.hd[1] = INVALID_HANDLE_VALUE; + sca.hd[2] = INVALID_HANDLE_VALUE; + sca.inherit_hds = NULL; + sca.arg = spawn_cb_arg; + if (spawn_cb) + (*spawn_cb) (&sca); + + if (sca.inherit_hds) { - if (create_inheritable_pipe (errpipe, INHERIT_WRITE)) - { - err = gpg_err_make (errsource, GPG_ERR_GENERAL); - log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); - return err; - } + SIZE_T attr_list_size = 0; + HANDLE hd[16]; + HANDLE *hd_p = sca.inherit_hds; + int j = 0; - syshd.type = ES_SYSHD_HANDLE; - syshd.u.handle = errpipe[0]; - errfp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); - if (!errfp) + if (hd_p) { - err = gpg_err_make (errsource, gpg_err_code_from_syserror ()); - log_error (_("error creating a stream for a pipe: %s\n"), - gpg_strerror (err)); - CloseHandle (errpipe[0]); - CloseHandle (errpipe[1]); - errpipe[0] = errpipe[1] = INVALID_HANDLE_VALUE; - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - return err; + while (*hd_p != INVALID_HANDLE_VALUE) + if (j < DIM (hd)) + hd[j++] = *hd_p++; + else + { + log_error ("Too much handles\n"); + break; + } } + + if (j) + { + if (check_windows_version ()) + { + InitializeProcThreadAttributeList (NULL, 1, 0, &attr_list_size); + si.lpAttributeList = xtrymalloc (attr_list_size); + if (si.lpAttributeList == NULL) + { + xfree (cmdline); + return gpg_err_code_from_syserror (); + } + InitializeProcThreadAttributeList (si.lpAttributeList, 1, 0, + &attr_list_size); + UpdateProcThreadAttribute (si.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + hd, sizeof (HANDLE) * j, NULL, NULL); + } + + ask_inherit = TRUE; + } } /* Prepare security attributes. */ @@ -549,502 +557,683 @@ gnupg_spawn_process (const char *pgmname, const char *argv[], sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; - - if (inpipe[0] == INVALID_HANDLE_VALUE) - nullhd[0] = ((flags & GNUPG_SPAWN_KEEP_STDIN)? - GetStdHandle (STD_INPUT_HANDLE) : w32_open_null (0)); - if (outpipe[1] == INVALID_HANDLE_VALUE) - nullhd[1] = ((flags & GNUPG_SPAWN_KEEP_STDOUT)? - GetStdHandle (STD_OUTPUT_HANDLE) : w32_open_null (1)); - if (errpipe[1] == INVALID_HANDLE_VALUE) - nullhd[2] = ((flags & GNUPG_SPAWN_KEEP_STDERR)? - GetStdHandle (STD_ERROR_HANDLE) : w32_open_null (1)); - - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_HIDE; - si.hStdInput = inpipe[0] == INVALID_HANDLE_VALUE? nullhd[0] : inpipe[0]; - si.hStdOutput = outpipe[1] == INVALID_HANDLE_VALUE? nullhd[1] : outpipe[1]; - si.hStdError = errpipe[1] == INVALID_HANDLE_VALUE? nullhd[2] : errpipe[1]; + /* Start the process. */ + si.StartupInfo.cb = sizeof (si); + si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; + si.StartupInfo.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; cr_flags = (CREATE_DEFAULT_ERROR_MODE - | ((flags & GNUPG_SPAWN_DETACHED)? DETACHED_PROCESS : 0) | GetPriorityClass (GetCurrentProcess ()) - | CREATE_SUSPENDED); - /* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", */ - /* pgmname, cmdline); */ + | CREATE_NEW_PROCESS_GROUP + | DETACHED_PROCESS); + /* Take care: CreateProcessW may modify wpgmname */ if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; + ret = 0; else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; + ret = 0; else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* 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. */ - ); - if (!rc) + ret = CreateProcessW (wpgmname, /* Program to start. */ + wcmdline, /* Command line arguments. */ + &sec_attr, /* Process security attributes. */ + &sec_attr, /* Thread security attributes. */ + ask_inherit, /* Inherit handles. */ + cr_flags, /* Creation flags. */ + NULL, /* Environment. */ + NULL, /* Use current drive/directory. */ + (STARTUPINFOW *)&si, /* Startup information. */ + &pi /* Returns process information. */ + ); + if (!ret) { if (!wpgmname || !wcmdline) log_error ("CreateProcess failed (utf8_to_wchar): %s\n", strerror (errno)); else - log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); + log_error ("CreateProcess(detached) failed: %d\n", + (int)GetLastError ()); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); - if (infp) - es_fclose (infp); - else if (inpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - if (outfp) - es_fclose (outfp); - else if (outpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (errfp) - es_fclose (errfp); - else if (errpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[0]); - if (errpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[1]); - return gpg_err_make (errsource, GPG_ERR_GENERAL); + return GPG_ERR_GENERAL; } + if (si.lpAttributeList) + DeleteProcThreadAttributeList (si.lpAttributeList); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); - cmdline = NULL; - /* Close the inherited handles to /dev/null. */ - for (i=0; i < DIM (nullhd); i++) - if (nullhd[i] != INVALID_HANDLE_VALUE) - CloseHandle (nullhd[i]); + /* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */ + /* " dwProcessID=%d dwThreadId=%d\n", */ + /* pi.hProcess, pi.hThread, */ + /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - /* Close the inherited ends of the pipes. */ - if (inpipe[0] != INVALID_HANDLE_VALUE) - CloseHandle (inpipe[0]); - if (outpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (outpipe[1]); - if (errpipe[1] != INVALID_HANDLE_VALUE) - CloseHandle (errpipe[1]); - - /* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ - /* " dwProcessID=%d dwThreadId=%d\n", */ - /* pi.hProcess, pi.hThread, */ - /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - /* log_debug (" outfp=%p errfp=%p\n", outfp, errfp); */ + /* Note: AllowSetForegroundWindow doesn't make sense for background + process. */ - /* Fixme: For unknown reasons AllowSetForegroundWindow returns an - invalid argument error if we pass it the correct processID. As a - workaround we use -1 (ASFW_ANY). */ - if ((flags & GNUPG_SPAWN_RUN_ASFW)) - gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/); - - /* Process has been created suspended; resume it now. */ - ResumeThread (pi.hThread); CloseHandle (pi.hThread); - - if (r_infp) - *r_infp = infp; - if (r_outfp) - *r_outfp = outfp; - if (r_errfp) - *r_errfp = errfp; - - *pid = handle_to_pid (pi.hProcess); + CloseHandle (pi.hProcess); return 0; - } +void +gnupg_spawn_helper (struct spawn_cb_arg *sca) +{ + HANDLE *user_except = sca->arg; + sca->inherit_hds = user_except; +} -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process is required. - - Returns 0 on success or an error code. */ -gpg_error_t -gnupg_spawn_process_fd (const char *pgmname, const char *argv[], - int infd, int outfd, int errfd, pid_t *pid) +gpg_err_code_t +gnupg_process_spawn (const char *pgmname, const char *argv[], + unsigned int flags, + void (*spawn_cb) (struct spawn_cb_arg *), + void *spawn_cb_arg, + gnupg_process_t *r_process) { - gpg_error_t err; + gpg_err_code_t ec; + gnupg_process_t process; SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, 0, 0, 0 }; - STARTUPINFOW si; + STARTUPINFOEXW si; + int cr_flags; char *cmdline; wchar_t *wcmdline = NULL; wchar_t *wpgmname = NULL; - int i, rc; - HANDLE stdhd[3]; + int ret; + HANDLE hd_in[2]; + HANDLE hd_out[2]; + HANDLE hd_err[2]; + struct spawn_cb_arg sca; + int i; + BOOL ask_inherit = FALSE; + + check_syscall_func (); + + /* Build the command line. */ + ec = build_w32_commandline (pgmname, argv, &cmdline); + if (ec) + return ec; + + if ((flags & GNUPG_PROCESS_DETACHED)) + { + if ((flags & GNUPG_PROCESS_STDFDS_SETTING)) + { + xfree (cmdline); + return GPG_ERR_INV_FLAG; + } - /* Setup return values. */ - *pid = (pid_t)(-1); + /* In detached case, it must be no R_PROCESS. */ + if (r_process) + { + xfree (cmdline); + return GPG_ERR_INV_ARG; + } + + return spawn_detached (pgmname, cmdline, spawn_cb, spawn_cb_arg); + } + + if (r_process) + *r_process = NULL; + + process = xtrymalloc (sizeof (struct gnupg_process)); + if (process == NULL) + { + xfree (cmdline); + return gpg_err_code_from_syserror (); + } + + process->pgmname = pgmname; + process->flags = flags; + + if ((flags & GNUPG_PROCESS_STDINOUT_SOCKETPAIR)) + { + xfree (process); + xfree (cmdline); + return GPG_ERR_NOT_SUPPORTED; + } + + if ((flags & GNUPG_PROCESS_STDIN_PIPE)) + { + ec = create_inheritable_pipe (hd_in, INHERIT_READ); + if (ec) + { + xfree (process); + xfree (cmdline); + return ec; + } + } + else if ((flags & GNUPG_PROCESS_STDIN_KEEP)) + { + hd_in[0] = GetStdHandle (STD_INPUT_HANDLE); + hd_in[1] = INVALID_HANDLE_VALUE; + } + else + { + hd_in[0] = w32_open_null (0); + hd_in[1] = INVALID_HANDLE_VALUE; + } + + if ((flags & GNUPG_PROCESS_STDOUT_PIPE)) + { + ec = create_inheritable_pipe (hd_out, INHERIT_WRITE); + if (ec) + { + if (hd_in[0] != INVALID_HANDLE_VALUE) + CloseHandle (hd_in[0]); + if (hd_in[1] != INVALID_HANDLE_VALUE) + CloseHandle (hd_in[1]); + xfree (process); + xfree (cmdline); + return ec; + } + } + else if ((flags & GNUPG_PROCESS_STDOUT_KEEP)) + { + hd_out[0] = INVALID_HANDLE_VALUE; + hd_out[1] = GetStdHandle (STD_OUTPUT_HANDLE); + } + else + { + hd_out[0] = INVALID_HANDLE_VALUE; + hd_out[1] = w32_open_null (1); + } + + if ((flags & GNUPG_PROCESS_STDERR_PIPE)) + { + ec = create_inheritable_pipe (hd_err, INHERIT_WRITE); + if (ec) + { + if (hd_in[0] != INVALID_HANDLE_VALUE) + CloseHandle (hd_in[0]); + if (hd_in[1] != INVALID_HANDLE_VALUE) + CloseHandle (hd_in[1]); + if (hd_out[0] != INVALID_HANDLE_VALUE) + CloseHandle (hd_out[0]); + if (hd_out[1] != INVALID_HANDLE_VALUE) + CloseHandle (hd_out[1]); + xfree (process); + xfree (cmdline); + return ec; + } + } + else if ((flags & GNUPG_PROCESS_STDERR_KEEP)) + { + hd_err[0] = INVALID_HANDLE_VALUE; + hd_err[1] = GetStdHandle (STD_ERROR_HANDLE); + } + else + { + hd_err[0] = INVALID_HANDLE_VALUE; + hd_err[1] = w32_open_null (1); + } + + memset (&si, 0, sizeof si); + + sca.allow_foreground_window = FALSE; + sca.hd[0] = hd_in[0]; + sca.hd[1] = hd_out[1]; + sca.hd[2] = hd_err[1]; + sca.inherit_hds = NULL; + sca.arg = spawn_cb_arg; + if (spawn_cb) + (*spawn_cb) (&sca); + + i = 0; + if (sca.hd[0] != INVALID_HANDLE_VALUE) + i++; + if (sca.hd[1] != INVALID_HANDLE_VALUE) + i++; + if (sca.hd[2] != INVALID_HANDLE_VALUE) + i++; + + if (i != 0 || sca.inherit_hds) + { + SIZE_T attr_list_size = 0; + HANDLE hd[16]; + HANDLE *hd_p = sca.inherit_hds; + int j = 0; + + if (sca.hd[0] != INVALID_HANDLE_VALUE) + hd[j++] = sca.hd[0]; + if (sca.hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = sca.hd[1]; + if (sca.hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = sca.hd[2]; + if (hd_p) + { + while (*hd_p != INVALID_HANDLE_VALUE) + if (j < DIM (hd)) + hd[j++] = *hd_p++; + else + { + log_error ("Too much handles\n"); + break; + } + } + + if (j) + { + if (check_windows_version ()) + { + InitializeProcThreadAttributeList (NULL, 1, 0, &attr_list_size); + si.lpAttributeList = xtrymalloc (attr_list_size); + if (si.lpAttributeList == NULL) + { + if ((flags & GNUPG_PROCESS_STDIN_PIPE) + || !(flags & GNUPG_PROCESS_STDIN_KEEP)) + CloseHandle (hd_in[0]); + if ((flags & GNUPG_PROCESS_STDIN_PIPE)) + CloseHandle (hd_in[1]); + if ((flags & GNUPG_PROCESS_STDOUT_PIPE)) + CloseHandle (hd_out[0]); + if ((flags & GNUPG_PROCESS_STDOUT_PIPE) + || !(flags & GNUPG_PROCESS_STDOUT_KEEP)) + CloseHandle (hd_out[1]); + if ((flags & GNUPG_PROCESS_STDERR_PIPE)) + CloseHandle (hd_err[0]); + if ((flags & GNUPG_PROCESS_STDERR_PIPE) + || !(flags & GNUPG_PROCESS_STDERR_KEEP)) + CloseHandle (hd_err[1]); + xfree (process); + xfree (cmdline); + return gpg_err_code_from_syserror (); + } + InitializeProcThreadAttributeList (si.lpAttributeList, 1, 0, + &attr_list_size); + UpdateProcThreadAttribute (si.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + hd, sizeof (HANDLE) * j, NULL, NULL); + } + ask_inherit = TRUE; + } + } /* Prepare security attributes. */ memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; - - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; - stdhd[0] = infd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE; - stdhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; - stdhd[2] = errfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE; - si.hStdInput = infd == -1? stdhd[0] : (void*)_get_osfhandle (infd); - si.hStdOutput = outfd == -1? stdhd[1] : (void*)_get_osfhandle (outfd); - si.hStdError = errfd == -1? stdhd[2] : (void*)_get_osfhandle (errfd); - -/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */ - /* Take care: CreateProcessW may modify wpgmname */ + /* Start the process. */ + si.StartupInfo.cb = sizeof (si); + si.StartupInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.StartupInfo.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_HIDE; + si.StartupInfo.hStdInput = sca.hd[0]; + si.StartupInfo.hStdOutput = sca.hd[1]; + si.StartupInfo.hStdError = sca.hd[2]; + + /* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */ + cr_flags = (CREATE_DEFAULT_ERROR_MODE + | GetPriorityClass (GetCurrentProcess ()) + | CREATE_SUSPENDED); if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; + ret = 0; else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; + ret = 0; else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - TRUE, /* Inherit handles. */ - (CREATE_DEFAULT_ERROR_MODE - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_SUSPENDED | DETACHED_PROCESS), - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - ); - if (!rc) + ret = CreateProcessW (wpgmname, /* Program to start. */ + wcmdline, /* Command line arguments. */ + &sec_attr, /* Process security attributes. */ + &sec_attr, /* Thread security attributes. */ + ask_inherit, /* Inherit handles. */ + cr_flags, /* Creation flags. */ + NULL, /* Environment. */ + NULL, /* Use current drive/directory. */ + (STARTUPINFOW *)&si, /* Startup information. */ + &pi /* Returns process information. */ + ); + if (!ret) { if (!wpgmname || !wcmdline) log_error ("CreateProcess failed (utf8_to_wchar): %s\n", - strerror (errno)); + strerror (errno)); else - log_error ("CreateProcess failed: %s\n", w32_strerror (-1)); - err = my_error (GPG_ERR_GENERAL); + log_error ("CreateProcess failed: ec=%d\n", + (int)GetLastError ()); + if ((flags & GNUPG_PROCESS_STDIN_PIPE) + || !(flags & GNUPG_PROCESS_STDIN_KEEP)) + CloseHandle (hd_in[0]); + if ((flags & GNUPG_PROCESS_STDIN_PIPE)) + CloseHandle (hd_in[1]); + if ((flags & GNUPG_PROCESS_STDOUT_PIPE)) + CloseHandle (hd_out[0]); + if ((flags & GNUPG_PROCESS_STDOUT_PIPE) + || !(flags & GNUPG_PROCESS_STDOUT_KEEP)) + CloseHandle (hd_out[1]); + if ((flags & GNUPG_PROCESS_STDERR_PIPE)) + CloseHandle (hd_err[0]); + if ((flags & GNUPG_PROCESS_STDERR_PIPE) + || !(flags & GNUPG_PROCESS_STDERR_KEEP)) + CloseHandle (hd_err[1]); + xfree (wpgmname); + xfree (wcmdline); + xfree (process); + xfree (cmdline); + return GPG_ERR_GENERAL; } - else - err = 0; + + if (si.lpAttributeList) + DeleteProcThreadAttributeList (si.lpAttributeList); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); - for (i=0; i < 3; i++) - if (stdhd[i] != INVALID_HANDLE_VALUE) - CloseHandle (stdhd[i]); - if (err) - return err; -/* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ -/* " dwProcessID=%d dwThreadId=%d\n", */ -/* pi.hProcess, pi.hThread, */ -/* (int) pi.dwProcessId, (int) pi.dwThreadId); */ + if ((flags & GNUPG_PROCESS_STDIN_PIPE) + || !(flags & GNUPG_PROCESS_STDIN_KEEP)) + CloseHandle (hd_in[0]); + if ((flags & GNUPG_PROCESS_STDOUT_PIPE) + || !(flags & GNUPG_PROCESS_STDOUT_KEEP)) + CloseHandle (hd_out[1]); + if ((flags & GNUPG_PROCESS_STDERR_PIPE) + || !(flags & GNUPG_PROCESS_STDERR_KEEP)) + CloseHandle (hd_err[1]); + + /* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */ + /* " dwProcessID=%d dwThreadId=%d\n", */ + /* pi.hProcess, pi.hThread, */ + /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ + + if (sca.allow_foreground_window) + { + /* Fixme: For unknown reasons AllowSetForegroundWindow returns + * an invalid argument error if we pass it the correct + * processID. As a workaround we use -1 (ASFW_ANY). */ + if (!AllowSetForegroundWindow (ASFW_ANY /*pi.dwProcessId*/)) + log_info ("AllowSetForegroundWindow() failed: ec=%d\n", + (int)GetLastError ()); + } /* Process has been created suspended; resume it now. */ + pre_syscall (); ResumeThread (pi.hThread); CloseHandle (pi.hThread); + post_syscall (); - *pid = handle_to_pid (pi.hProcess); - return 0; + process->hProcess = pi.hProcess; + process->hd_in = hd_in[1]; + process->hd_out = hd_out[0]; + process->hd_err = hd_err[0]; + process->exitcode = -1; + process->terminated = 0; + if (r_process == NULL) + { + ec = gnupg_process_wait (process, 1); + gnupg_process_release (process); + return ec; + } + + *r_process = process; + return 0; } +gpg_err_code_t +gnupg_process_get_fds (gnupg_process_t process, unsigned int flags, + int *r_fd_in, int *r_fd_out, int *r_fd_err) +{ + (void)flags; + if (r_fd_in) + { + *r_fd_in = _open_osfhandle ((intptr_t)process->hd_in, O_APPEND); + process->hd_in = INVALID_HANDLE_VALUE; + } + if (r_fd_out) + { + *r_fd_out = _open_osfhandle ((intptr_t)process->hd_out, O_RDONLY); + process->hd_out = INVALID_HANDLE_VALUE; + } + if (r_fd_err) + { + *r_fd_err = _open_osfhandle ((intptr_t)process->hd_err, O_RDONLY); + process->hd_err = INVALID_HANDLE_VALUE; + } + + return 0; +} -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode) +gpg_err_code_t +gnupg_process_get_streams (gnupg_process_t process, unsigned int flags, + estream_t *r_fp_in, estream_t *r_fp_out, + estream_t *r_fp_err) { - return gnupg_wait_processes (&pgmname, &pid, 1, hang, r_exitcode); + int nonblock = (flags & GNUPG_PROCESS_STREAM_NONBLOCK)? 1: 0; + es_syshd_t syshd; + + syshd.type = ES_SYSHD_HANDLE; + if (r_fp_in) + { + syshd.u.handle = process->hd_in; + *r_fp_in = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); + process->hd_in = INVALID_HANDLE_VALUE; + } + if (r_fp_out) + { + syshd.u.handle = process->hd_out; + *r_fp_out = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); + process->hd_out = INVALID_HANDLE_VALUE; + } + if (r_fp_err) + { + syshd.u.handle = process->hd_err; + *r_fp_err = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); + process->hd_err = INVALID_HANDLE_VALUE; + } + return 0; } -/* See exechelp.h for a description. */ -gpg_error_t -gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count, - int hang, int *r_exitcodes) +static gpg_err_code_t +process_kill (gnupg_process_t process, unsigned int exitcode) { gpg_err_code_t ec = 0; - size_t i; - HANDLE *procs; - int code; - procs = xtrycalloc (count, sizeof *procs); - if (procs == NULL) - return my_error_from_syserror (); + pre_syscall (); + if (TerminateProcess (process->hProcess, exitcode)) + ec = gpg_err_code_from_syserror (); + post_syscall (); + return ec; +} - for (i = 0; i < count; i++) +static gpg_err_code_t +process_vctl (gnupg_process_t process, unsigned int request, va_list arg_ptr) +{ + switch (request) { - if (r_exitcodes) - r_exitcodes[i] = -1; + case GNUPG_PROCESS_NOP: + return 0; - if (pids[i] == (pid_t)(-1)) - return my_error (GPG_ERR_INV_VALUE); + case GNUPG_PROCESS_GET_PROC_ID: + { + int *r_id = va_arg (arg_ptr, int *); - procs[i] = pid_to_handle (pids[i]); - } + if (r_id == NULL) + return GPG_ERR_INV_VALUE; - /* FIXME: We should do a pth_waitpid here. However this has not yet - been implemented. A special W32 pth system call would even be - better. */ - code = WaitForMultipleObjects (count, procs, TRUE, hang? INFINITE : 0); - switch (code) - { - case WAIT_TIMEOUT: - ec = GPG_ERR_TIMEOUT; - goto leave; + *r_id = (int)GetProcessId (process->hProcess); + return 0; + } - case WAIT_FAILED: - log_error (_("waiting for processes to terminate failed: %s\n"), - w32_strerror (-1)); - ec = GPG_ERR_GENERAL; - goto leave; + case GNUPG_PROCESS_GET_EXIT_ID: + { + int *r_exit_status = va_arg (arg_ptr, int *); + unsigned long exit_code; - case WAIT_OBJECT_0: - for (i = 0; i < count; i++) - { - DWORD exc; + *r_exit_status = -1; - if (! GetExitCodeProcess (procs[i], &exc)) - { - log_error (_("error getting exit code of process %d: %s\n"), - (int) pids[i], w32_strerror (-1) ); - ec = GPG_ERR_GENERAL; - } - else if (exc) - { - if (!r_exitcodes) - log_error (_("error running '%s': exit status %d\n"), - pgmnames[i], (int)exc); - else - r_exitcodes[i] = (int)exc; - ec = GPG_ERR_GENERAL; - } - else - { - if (r_exitcodes) - r_exitcodes[i] = 0; - } - } - break; + if (!process->terminated) + return GPG_ERR_UNFINISHED; + + if (process->hProcess == INVALID_HANDLE_VALUE) + return 0; + + if (GetExitCodeProcess (process->hProcess, &exit_code) == 0) + return gpg_err_code_from_syserror (); + + *r_exit_status = (int)exit_code; + return 0; + } + + case GNUPG_PROCESS_GET_P_HANDLE: + { + HANDLE *r_hProcess = va_arg (arg_ptr, HANDLE *); + + if (r_hProcess == NULL) + return GPG_ERR_INV_VALUE; + + *r_hProcess = process->hProcess; + process->hProcess = INVALID_HANDLE_VALUE; + return 0; + } + + case GNUPG_PROCESS_GET_HANDLES: + { + HANDLE *r_hd_in = va_arg (arg_ptr, HANDLE *); + HANDLE *r_hd_out = va_arg (arg_ptr, HANDLE *); + HANDLE *r_hd_err = va_arg (arg_ptr, HANDLE *); + + if (r_hd_in) + { + *r_hd_in = process->hd_in; + process->hd_in = INVALID_HANDLE_VALUE; + } + if (r_hd_out) + { + *r_hd_out = process->hd_out; + process->hd_out = INVALID_HANDLE_VALUE; + } + if (r_hd_err) + { + *r_hd_err = process->hd_err; + process->hd_err = INVALID_HANDLE_VALUE; + } + return 0; + } + + case GNUPG_PROCESS_GET_EXIT_CODE: + { + unsigned long *r_exitcode = va_arg (arg_ptr, unsigned long *); + + if (!process->terminated) + return GPG_ERR_UNFINISHED; + + if (process->hProcess == INVALID_HANDLE_VALUE) + { + *r_exitcode = (unsigned long)-1; + return 0; + } + + if (GetExitCodeProcess (process->hProcess, r_exitcode) == 0) + return gpg_err_code_from_syserror (); + return 0; + } + + case GNUPG_PROCESS_KILL_WITH_EC: + { + unsigned int exitcode = va_arg (arg_ptr, unsigned int); + + if (process->terminated) + return 0; + + if (process->hProcess == INVALID_HANDLE_VALUE) + return 0; + + return process_kill (process, exitcode); + } default: - log_error ("WaitForMultipleObjects returned unexpected " - "code %d\n", code); - ec = GPG_ERR_GENERAL; break; } - leave: - return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); + return GPG_ERR_UNKNOWN_COMMAND; } - - -void -gnupg_release_process (pid_t pid) +gpg_err_code_t +gnupg_process_ctl (gnupg_process_t process, unsigned int request, ...) { - if (pid != (pid_t)INVALID_HANDLE_VALUE) - { - HANDLE process = (HANDLE)pid; + va_list arg_ptr; + gpg_err_code_t ec; - CloseHandle (process); - } + va_start (arg_ptr, request); + ec = process_vctl (process, request, arg_ptr); + va_end (arg_ptr); + return ec; } - -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. All standard file descriptors are - connected to /dev/null. */ -gpg_error_t -gnupg_spawn_process_detached (const char *pgmname, const char *argv[], - const char *envp[] ) +gpg_err_code_t +gnupg_process_wait (gnupg_process_t process, int hang) { - gpg_error_t err; - SECURITY_ATTRIBUTES sec_attr; - PROCESS_INFORMATION pi = - { - NULL, /* Returns process handle. */ - 0, /* Returns primary thread handle. */ - 0, /* Returns pid. */ - 0 /* Returns tid. */ - }; - STARTUPINFOW si; - int cr_flags; - char *cmdline; - wchar_t *wcmdline = NULL; - wchar_t *wpgmname = NULL; - BOOL in_job = FALSE; gpg_err_code_t ec; - int rc; - int jobdebug; + int code; - /* We don't use ENVP. */ - (void)envp; + if (process->hProcess == INVALID_HANDLE_VALUE) + return 0; - cmdline = getenv ("GNUPG_EXEC_DEBUG_FLAGS"); - jobdebug = (cmdline && (atoi (cmdline) & 1)); + pre_syscall (); + code = WaitForSingleObject (process->hProcess, hang? INFINITE : 0); + post_syscall (); - if ((ec = gnupg_access (pgmname, X_OK))) - return gpg_err_make (default_errsource, ec); + switch (code) + { + case WAIT_TIMEOUT: + ec = GPG_ERR_TIMEOUT; /* Still running. */ + break; - /* Prepare security attributes. */ - memset (&sec_attr, 0, sizeof sec_attr ); - sec_attr.nLength = sizeof sec_attr; - sec_attr.bInheritHandle = FALSE; + case WAIT_FAILED: + log_error (_("waiting for process to terminate failed: ec=%d\n"), + (int)GetLastError ()); + ec = GPG_ERR_GENERAL; + break; - /* Build the command line. */ - err = build_w32_commandline (pgmname, argv, &cmdline); - if (err) - return err; + case WAIT_OBJECT_0: + process->terminated = 1; + ec = 0; + break; - /* Start the process. */ - memset (&si, 0, sizeof si); - si.cb = sizeof (si); - si.dwFlags = STARTF_USESHOWWINDOW; - si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; + default: + log_debug ("WaitForSingleObject returned unexpected code %d\n", code); + ec = GPG_ERR_GENERAL; + break; + } - cr_flags = (CREATE_DEFAULT_ERROR_MODE - | GetPriorityClass (GetCurrentProcess ()) - | CREATE_NEW_PROCESS_GROUP - | DETACHED_PROCESS); + return ec; +} - /* Check if we were spawned as part of a Job. - * In a job we need to add CREATE_BREAKAWAY_FROM_JOB - * to the cr_flags, otherwise our child processes - * are killed when we terminate. */ - if (!IsProcessInJob (GetCurrentProcess(), NULL, &in_job)) - { - log_error ("IsProcessInJob() failed: %s\n", w32_strerror (-1)); - in_job = FALSE; - } +gpg_err_code_t +gnupg_process_terminate (gnupg_process_t process) +{ + return process_kill (process, 1); +} - if (in_job) - { - /* Only try to break away from job if it is allowed, otherwise - * CreateProcess() would fail with an "Access is denied" error. */ - JOBOBJECT_EXTENDED_LIMIT_INFORMATION info; - if (!QueryInformationJobObject (NULL, JobObjectExtendedLimitInformation, - &info, sizeof info, NULL)) - { - log_error ("QueryInformationJobObject() failed: %s\n", - w32_strerror (-1)); - } - else if ((info.BasicLimitInformation.LimitFlags & - JOB_OBJECT_LIMIT_BREAKAWAY_OK)) - { - if (jobdebug) - log_debug ("Using CREATE_BREAKAWAY_FROM_JOB flag\n"); - cr_flags |= CREATE_BREAKAWAY_FROM_JOB; - } - else if ((info.BasicLimitInformation.LimitFlags & - JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)) - { - /* The child process should automatically detach from the job. */ - if (jobdebug) - log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag; " - "JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK is set\n"); - } - else - { - /* It seems that the child process must remain in the job. - * This is not necessarily an error, although it can cause premature - * termination of the child process when the job is closed. */ - if (jobdebug) - log_debug ("Not using CREATE_BREAKAWAY_FROM_JOB flag\n"); - } - } - else - { - if (jobdebug) - log_debug ("Process is not in a Job\n"); - } +void +gnupg_process_release (gnupg_process_t process) +{ + if (!process) + return; - /* log_debug ("CreateProcess(detached), path='%s' cmdline='%s'\n", */ - /* pgmname, cmdline); */ - /* Take care: CreateProcessW may modify wpgmname */ - if (!(wpgmname = utf8_to_wchar (pgmname))) - rc = 0; - else if (!(wcmdline = utf8_to_wchar (cmdline))) - rc = 0; - else - rc = CreateProcessW (wpgmname, /* Program to start. */ - wcmdline, /* Command line arguments. */ - &sec_attr, /* Process security attributes. */ - &sec_attr, /* Thread security attributes. */ - FALSE, /* Inherit handles. */ - cr_flags, /* Creation flags. */ - NULL, /* Environment. */ - NULL, /* Use current drive/directory. */ - &si, /* Startup information. */ - &pi /* Returns process information. */ - ); - if (!rc) + if (process->terminated) { - if (!wpgmname || !wcmdline) - log_error ("CreateProcess failed (utf8_to_wchar): %s\n", - strerror (errno)); - else - log_error ("CreateProcess(detached) failed: %s\n", w32_strerror (-1)); - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - return my_error (GPG_ERR_GENERAL); + gnupg_process_terminate (process); + gnupg_process_wait (process, 1); } - xfree (wpgmname); - xfree (wcmdline); - xfree (cmdline); - cmdline = NULL; - -/* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */ -/* " dwProcessID=%d dwThreadId=%d\n", */ -/* pi.hProcess, pi.hThread, */ -/* (int) pi.dwProcessId, (int) pi.dwThreadId); */ - CloseHandle (pi.hThread); - CloseHandle (pi.hProcess); - - return 0; + CloseHandle (process->hProcess); + xfree (process); } - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void -gnupg_kill_process (pid_t pid) +gpg_err_code_t +gnupg_process_wait_list (gnupg_process_t *process_list, int count, int hang) { - if (pid != (pid_t) INVALID_HANDLE_VALUE) + gpg_err_code_t ec = 0; + int i; + + for (i = 0; i < count; i++) { - HANDLE process = (HANDLE) pid; + if (process_list[i]->terminated) + continue; - /* Arbitrary error code. */ - TerminateProcess (process, 1); + ec = gnupg_process_wait (process_list[i], hang); + if (ec) + break; } + + return ec; } diff --git a/common/exechelp.h b/common/exechelp.h index 3343fe598..0370b23a4 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -73,140 +73,103 @@ gpg_error_t gnupg_create_pipe (int filedes[2]); void gnupg_close_pipe (int fd); -#define GNUPG_SPAWN_NONBLOCK 16 -#define GNUPG_SPAWN_RUN_ASFW 64 -#define GNUPG_SPAWN_DETACHED 128 -#define GNUPG_SPAWN_KEEP_STDIN 256 -#define GNUPG_SPAWN_KEEP_STDOUT 512 -#define GNUPG_SPAWN_KEEP_STDERR 1024 - -/* Fork and exec the program PGMNAME. - - If R_INFP is NULL connect stdin of the new process to /dev/null; if - it is not NULL store the address of a pointer to a new estream - there. If R_OUTFP is NULL connect stdout of the new process to - /dev/null; if it is not NULL store the address of a pointer to a - new estream there. If R_ERRFP is NULL connect stderr of the new - process to /dev/null; if it is not NULL store the address of a - pointer to a new estream there. On success the pid of the new - process is stored at PID. On error -1 is stored at PID and if - R_OUTFP or R_ERRFP are not NULL, NULL is stored there. - - The arguments for the process are expected in the NULL terminated - array ARGV. The program name itself should not be included there. - If PREEXEC is not NULL, the given function will be called right - before the exec. - - IF EXCEPT is not NULL, it is expected to be an ordered list of file - descriptors, terminated by an entry with the value (-1). These - file descriptors won't be closed before spawning a new program. - - Returns 0 on success or an error code. Calling gnupg_wait_process - and gnupg_release_process is required if the function succeeded. - - FLAGS is a bit vector: - - GNUPG_SPAWN_NONBLOCK - If set the two output streams are created in non-blocking - mode and the input stream is switched to non-blocking mode. - This is merely a convenience feature because the caller - could do the same with gpgrt_set_nonblock. Does not yet - work for Windows. - - GNUPG_SPAWN_DETACHED - If set the process will be started as a background process. - This flag is only useful under W32 (but not W32CE) systems, - so that no new console is created and pops up a console - window when starting the server. Does not work on W32CE. - - GNUPG_SPAWN_RUN_ASFW - On W32 (but not on W32CE) run AllowSetForegroundWindow for - the child. Note that due to unknown problems this actually - allows SetForegroundWindow for all children of this process. - - GNUPG_SPAWN_KEEP_STDIN - GNUPG_SPAWN_KEEP_STDOUT - GNUPG_SPAWN_KEEP_STDERR - Do not assign /dev/null to a non-required standard file - descriptor. - - */ -gpg_error_t -gnupg_spawn_process (const char *pgmname, const char *argv[], - int *execpt, unsigned int flags, - estream_t *r_infp, - estream_t *r_outfp, - estream_t *r_errfp, - pid_t *pid); - - -/* Simplified version of gnupg_spawn_process. This function forks and - then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout - and ERRFD to stderr (any of them may be -1 to connect them to - /dev/null). The arguments for the process are expected in the NULL - terminated array ARGV. The program name itself should not be - included there. Calling gnupg_wait_process and - gnupg_release_process is required. Returns 0 on success or an - error code. */ -gpg_error_t gnupg_spawn_process_fd (const char *pgmname, - const char *argv[], - int infd, int outfd, int errfd, - pid_t *pid); - - -/* If HANG is true, waits for the process identified by PID to exit; - if HANG is false, checks whether the process has terminated. - PGMNAME should be the same as supplied to the spawn function and is - only used for diagnostics. Return values: - - 0 - The process exited successful. 0 is stored at R_EXITCODE. - - GPG_ERR_GENERAL - The process exited without success. The exit code of process - is then stored at R_EXITCODE. An exit code of -1 indicates - that the process terminated abnormally (e.g. due to a signal). - - GPG_ERR_TIMEOUT - The process is still running (returned only if HANG is false). - - GPG_ERR_INV_VALUE - An invalid PID has been specified. - - Other error codes may be returned as well. Unless otherwise noted, - -1 will be stored at R_EXITCODE. R_EXITCODE may be passed as NULL - if the exit code is not required (in that case an error message will - be printed). Note that under Windows PID is not the process id but - the handle of the process. */ -gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang, - int *r_exitcode); - -/* Like gnupg_wait_process, but for COUNT processes. */ -gpg_error_t gnupg_wait_processes (const char **pgmnames, pid_t *pids, - size_t count, int hang, int *r_exitcodes); - - -/* Kill a process; that is send an appropriate signal to the process. - gnupg_wait_process must be called to actually remove the process - from the system. An invalid PID is ignored. */ -void gnupg_kill_process (pid_t pid); - -/* Release the process identified by PID. This function is actually - only required for Windows but it does not harm to always call it. - It is a nop if PID is invalid. */ -void gnupg_release_process (pid_t pid); - - -/* Spawn a new process and immediately detach from it. The name of - the program to exec is PGMNAME and its arguments are in ARGV (the - programname is automatically passed as first argument). - Environment strings in ENVP are set. An error is returned if - pgmname is not executable; to make this work it is necessary to - provide an absolute file name. */ -gpg_error_t gnupg_spawn_process_detached (const char *pgmname, - const char *argv[], - const char *envp[] ); - +/* The opaque type for a subprocess. */ +typedef struct gnupg_process *gnupg_process_t; +#ifdef HAVE_W32_SYSTEM +struct spawn_cb_arg; +#ifdef NEED_STRUCT_SPAWN_CB_ARG +struct spawn_cb_arg { + HANDLE hd[3]; + HANDLE *inherit_hds; + BOOL allow_foreground_window; + void *arg; +}; +#endif +#else +struct spawn_cb_arg { + int fds[3]; + int *except_fds; + void *arg; +}; +#endif + +#define GNUPG_PROCESS_DETACHED (1 << 1) + +/* Specify how to keep/connect standard fds. */ +#define GNUPG_PROCESS_STDIN_PIPE (1 << 8) +#define GNUPG_PROCESS_STDOUT_PIPE (1 << 9) +#define GNUPG_PROCESS_STDERR_PIPE (1 << 10) +#define GNUPG_PROCESS_STDINOUT_SOCKETPAIR (1 << 11) +#define GNUPG_PROCESS_STDIN_KEEP (1 << 12) +#define GNUPG_PROCESS_STDOUT_KEEP (1 << 13) +#define GNUPG_PROCESS_STDERR_KEEP (1 << 14) +#define GNUPG_PROCESS_STDFDS_SETTING ( GNUPG_PROCESS_STDIN_PIPE \ + | GNUPG_PROCESS_STDOUT_PIPE | GNUPG_PROCESS_STDERR_PIPE \ + | GNUPG_PROCESS_STDINOUT_SOCKETPAIR | GNUPG_PROCESS_STDIN_KEEP \ + | GNUPG_PROCESS_STDOUT_KEEP | GNUPG_PROCESS_STDERR_KEEP) + +#define GNUPG_PROCESS_STREAM_NONBLOCK (1 << 16) + +/* Spawn helper. */ +void gnupg_spawn_helper (struct spawn_cb_arg *sca); + +/* Spawn PGMNAME. */ +gpg_err_code_t gnupg_process_spawn (const char *pgmname, const char *argv[], + unsigned int flags, + void (*spawn_cb) (struct spawn_cb_arg *), + void *spawn_cb_arg, + gnupg_process_t *r_process); + +/* Get FDs for subprocess I/O. It is the caller which should care + FDs (closing FDs). */ +gpg_err_code_t gnupg_process_get_fds (gnupg_process_t process, + unsigned int flags, + int *r_fd_in, int *r_fd_out, + int *r_fd_err); + +/* Get STREAMs for subprocess I/O. It is the caller which should care + STREAMs (closing STREAMs). */ +gpg_err_code_t gnupg_process_get_streams (gnupg_process_t process, + unsigned int flags, + gpgrt_stream_t *r_fp_in, + gpgrt_stream_t *r_fp_out, + gpgrt_stream_t *r_fp_err); + +enum gnupg_process_requests + { + /* Portable requests */ + GNUPG_PROCESS_NOP = 0, + GNUPG_PROCESS_GET_PROC_ID = 1, + GNUPG_PROCESS_GET_EXIT_ID = 2, + + /* POSIX only */ + GNUPG_PROCESS_GET_PID = 16, + GNUPG_PROCESS_GET_WSTATUS = 17, + GNUPG_PROCESS_KILL = 18, + + /* Windows only */ + GNUPG_PROCESS_GET_P_HANDLE = 32, + GNUPG_PROCESS_GET_HANDLES = 33, + GNUPG_PROCESS_GET_EXIT_CODE = 34, + GNUPG_PROCESS_KILL_WITH_EC = 35 + }; + +/* Control of a process. */ +gpg_err_code_t gnupg_process_ctl (gnupg_process_t process, + unsigned int request, ...); + +/* Wait for a single PROCESS. */ +gpg_err_code_t gnupg_process_wait (gnupg_process_t process, int hang); + +/* Terminate a PROCESS. */ +gpg_err_code_t gnupg_process_terminate (gnupg_process_t process); + +/* Release PROCESS resources. */ +void gnupg_process_release (gnupg_process_t process); + +/* Wait for a multiple processes. */ +gpg_err_code_t gnupg_process_wait_list (gnupg_process_t *process_list, + int count, int hang); #endif /*GNUPG_COMMON_EXECHELP_H*/ diff --git a/common/exectool.c b/common/exectool.c index aaf5898d0..3505c25f1 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -38,10 +38,14 @@ #include <gpg-error.h> #include <assuan.h> + #include "i18n.h" #include "logging.h" #include "membuf.h" #include "mischelp.h" +#ifdef HAVE_W32_SYSTEM +#define NEED_STRUCT_SPAWN_CB_ARG 1 +#endif #include "exechelp.h" #include "sysutils.h" #include "util.h" @@ -301,7 +305,6 @@ copy_buffer_flush (struct copy_buffer *c, estream_t sink) } - /* Run the program PGMNAME with the command line arguments given in * the NULL terminates array ARGV. If INPUT is not NULL it will be * fed to stdin of the process. stderr is logged using log_info and @@ -321,7 +324,7 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], void *status_cb_value) { gpg_error_t err; - pid_t pid = (pid_t) -1; + gnupg_process_t proc = NULL; estream_t infp = NULL; estream_t extrafp = NULL; estream_t outfp = NULL, errfp = NULL; @@ -335,7 +338,6 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; int quiet = 0; - int dummy_exitcode; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); @@ -411,10 +413,15 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], else exceptclose[0] = -1; - err = gnupg_spawn_process (pgmname, argv, - exceptclose, GNUPG_SPAWN_NONBLOCK, - input? &infp : NULL, - &outfp, &errfp, &pid); + err = gnupg_process_spawn (pgmname, argv, + ((input + ? GNUPG_PROCESS_STDIN_PIPE + : 0) + | GNUPG_PROCESS_STDOUT_PIPE + | GNUPG_PROCESS_STDERR_PIPE), + gnupg_spawn_helper, exceptclose, &proc); + gnupg_process_get_streams (proc, GNUPG_PROCESS_STREAM_NONBLOCK, + input? &infp : NULL, &outfp, &errfp); if (extrapipe[0] != -1) close (extrapipe[0]); if (argsave) @@ -546,20 +553,25 @@ gnupg_exec_tool_stream (const char *pgmname, const char *argv[], es_fclose (outfp); outfp = NULL; es_fclose (errfp); errfp = NULL; - err = gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); - pid = (pid_t)(-1); + err = gnupg_process_wait (proc, 1); + if (!err) + { /* To be compatible to old wait_process. */ + int status; + + gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &status); + if (status) + err = gpg_error (GPG_ERR_GENERAL); + } leave: - if (err && pid != (pid_t) -1) - gnupg_kill_process (pid); + if (err && proc) + gnupg_process_terminate (proc); es_fclose (infp); es_fclose (extrafp); es_fclose (outfp); es_fclose (errfp); - if (pid != (pid_t)(-1)) - gnupg_wait_process (pgmname, pid, 1, quiet? &dummy_exitcode : NULL); - gnupg_release_process (pid); + gnupg_process_release (proc); copy_buffer_shred (cpbuf_in); xfree (cpbuf_in); diff --git a/common/get-passphrase.c b/common/get-passphrase.c index c24b40e88..8ea822710 100644 --- a/common/get-passphrase.c +++ b/common/get-passphrase.c @@ -94,7 +94,8 @@ start_agent (void) agentargs.lc_ctype, agentargs.lc_messages, agentargs.session_env, - 1, agentargs.verbosity, 0, NULL, NULL); + ASSHELP_FLAG_AUTOSTART, + agentargs.verbosity, 0, NULL, NULL); if (!err) { /* Tell the agent that we support Pinentry notifications. No diff --git a/common/init.c b/common/init.c index 62a48f8c7..8ea51c8b0 100644 --- a/common/init.c +++ b/common/init.c @@ -37,6 +37,7 @@ # include <winsock2.h> # endif # include <windows.h> +# include <wctype.h> #endif #include <gcrypt.h> diff --git a/common/iobuf.c b/common/iobuf.c index 748e6935d..e46eeac95 100644 --- a/common/iobuf.c +++ b/common/iobuf.c @@ -166,7 +166,8 @@ block_filter_ctx_t; /* Local prototypes. */ static int underflow (iobuf_t a, int clear_pending_eof); static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target); -static int translate_file_handle (int fd, int for_write); +static iobuf_t do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open); + /* Sends any pending data to the filter's FILTER function. Note: this works on the filter and not on the whole pipeline. That is, @@ -389,7 +390,7 @@ fd_cache_close (const char *fname, gnupg_fd_t fp) close (fp); #endif if (DBG_IOBUF) - log_debug ("fd_cache_close (%d) real\n", (int)fp); + log_debug ("fd_cache_close (%d) real\n", FD_DBG (fp)); return; } /* try to reuse a slot */ @@ -696,7 +697,7 @@ file_filter (void *opaque, int control, iobuf_t chain, byte * buf, if (f != FD_FOR_STDIN && f != FD_FOR_STDOUT) { if (DBG_IOBUF) - log_debug ("%s: close fd/handle %d\n", a->fname, FD2INT (f)); + log_debug ("%s: close fd/handle %d\n", a->fname, FD_DBG (f)); if (!a->keep_open) fd_cache_close (a->no_cache ? NULL : a->fname, f); } @@ -1410,7 +1411,7 @@ iobuf_is_pipe_filename (const char *fname) { if (!fname || (*fname=='-' && !fname[1]) ) return 1; - return check_special_filename (fname, 0, 1) != -1; + return gnupg_check_special_filename (fname) != GNUPG_INVALID_FD; } @@ -1423,7 +1424,7 @@ do_open (const char *fname, int special_filenames, file_filter_ctx_t *fcx; size_t len = 0; int print_only = 0; - int fd; + gnupg_fd_t fd; byte desc[MAX_IOBUF_DESC]; log_assert (use == IOBUF_INPUT || use == IOBUF_OUTPUT); @@ -1447,9 +1448,8 @@ do_open (const char *fname, int special_filenames, else if (!fname) return NULL; else if (special_filenames - && (fd = check_special_filename (fname, 0, 1)) != -1) - return iobuf_fdopen (translate_file_handle (fd, use == IOBUF_INPUT ? 0 : 1), - opentype); + && (fd = gnupg_check_special_filename (fname)) != GNUPG_INVALID_FD) + return do_iobuf_fdopen (fd, opentype, 0); else { if (use == IOBUF_INPUT) @@ -1472,7 +1472,8 @@ do_open (const char *fname, int special_filenames, file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); if (DBG_IOBUF) log_debug ("iobuf-%d.%d: open '%s' desc=%s fd=%d\n", - a->no, a->subno, fname, iobuf_desc (a, desc), FD2INT (fcx->fp)); + a->no, a->subno, fname, iobuf_desc (a, desc), + FD_DBG (fcx->fp)); return a; } @@ -1497,22 +1498,19 @@ iobuf_openrw (const char *fname) static iobuf_t -do_iobuf_fdopen (int fd, const char *mode, int keep_open) +do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open) { iobuf_t a; - gnupg_fd_t fp; file_filter_ctx_t *fcx; size_t len = 0; - fp = INT2FD (fd); - a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT, iobuf_buffer_size); fcx = xmalloc (sizeof *fcx + 20); fcx->fp = fp; fcx->print_only_name = 1; fcx->keep_open = keep_open; - sprintf (fcx->fname, "[fd %d]", fd); + sprintf (fcx->fname, "[fd %d]", FD_DBG (fp)); a->filter = file_filter; a->filter_ov = fcx; file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len); @@ -1525,15 +1523,15 @@ do_iobuf_fdopen (int fd, const char *mode, int keep_open) iobuf_t -iobuf_fdopen (int fd, const char *mode) +iobuf_fdopen (gnupg_fd_t fp, const char *mode) { - return do_iobuf_fdopen (fd, mode, 0); + return do_iobuf_fdopen (fp, mode, 0); } iobuf_t -iobuf_fdopen_nc (int fd, const char *mode) +iobuf_fdopen_nc (gnupg_fd_t fp, const char *mode) { - return do_iobuf_fdopen (fd, mode, 1); + return do_iobuf_fdopen (fp, mode, 1); } @@ -1585,7 +1583,7 @@ iobuf_sockopen (int fd, const char *mode) log_debug ("iobuf-%d.%d: sockopen '%s'\n", a->no, a->subno, scx->fname); iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL); #else - a = iobuf_fdopen (fd, mode); + a = do_iobuf_fdopen (fd, mode, 0); #endif return a; } @@ -2644,20 +2642,20 @@ iobuf_get_filelength (iobuf_t a) } -int +gnupg_fd_t iobuf_get_fd (iobuf_t a) { for (; a->chain; a = a->chain) ; if (a->filter != file_filter) - return -1; + return GNUPG_INVALID_FD; { file_filter_ctx_t *b = a->filter_ov; gnupg_fd_t fp = b->fp; - return FD2INT (fp); + return fp; } } @@ -2948,36 +2946,6 @@ iobuf_read_line (iobuf_t a, byte ** addr_of_buffer, return nbytes; } -static int -translate_file_handle (int fd, int for_write) -{ -#if defined(HAVE_W32_SYSTEM) - { - int x; - - (void)for_write; - - if (fd == 0) - x = (int) GetStdHandle (STD_INPUT_HANDLE); - else if (fd == 1) - x = (int) GetStdHandle (STD_OUTPUT_HANDLE); - else if (fd == 2) - x = (int) GetStdHandle (STD_ERROR_HANDLE); - else - x = fd; - - if (x == -1) - log_debug ("GetStdHandle(%d) failed: ec=%d\n", - fd, (int) GetLastError ()); - - fd = x; - } -#else - (void)for_write; -#endif - return fd; -} - void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial) diff --git a/common/iobuf.h b/common/iobuf.h index 04e6b4421..4354c718d 100644 --- a/common/iobuf.h +++ b/common/iobuf.h @@ -333,11 +333,11 @@ iobuf_t iobuf_openrw (const char *fname); creates an input filter. Note: MODE must reflect the file descriptors actual mode! When the filter is destroyed, the file descriptor is closed. */ -iobuf_t iobuf_fdopen (int fd, const char *mode); +iobuf_t iobuf_fdopen (gnupg_fd_t fd, const char *mode); /* Like iobuf_fdopen, but doesn't close the file descriptor when the filter is destroyed. */ -iobuf_t iobuf_fdopen_nc (int fd, const char *mode); +iobuf_t iobuf_fdopen_nc (gnupg_fd_t fd, const char *mode); /* Create a filter using an existing estream. If MODE contains the letter 'w', creates an output filter. Otherwise, creates an input @@ -590,7 +590,7 @@ uint64_t iobuf_get_filelength (iobuf_t a); /* Return the file descriptor designating the underlying file. This only works with file_filter based pipelines. */ -int iobuf_get_fd (iobuf_t a); +gnupg_fd_t iobuf_get_fd (iobuf_t a); /* Return the real filename, if available. This only supports pipelines that end in file filters. Returns NULL if not diff --git a/common/miscellaneous.c b/common/miscellaneous.c index 1a090b1f5..28d3e7a2e 100644 --- a/common/miscellaneous.c +++ b/common/miscellaneous.c @@ -687,3 +687,53 @@ parse_compatibility_flags (const char *string, unsigned int *flagvar, *flagvar |= result; return 0; } + + +/* Convert STRING consisting of base64 characters into its binary + * representation and store the result in a newly allocated buffer at + * R_BUFFER with its length at R_BUFLEN. If TITLE is NULL a plain + * base64 decoding is done. If it is the empty string the decoder + * will skip everything until a "-----BEGIN " line has been seen, + * decoding then ends at a "----END " line. On failure the function + * returns an error code and sets R_BUFFER to NULL. If the decoded + * data has a length of 0 a dummy buffer will still be allocated and + * the length is set to 0. */ +gpg_error_t +b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + gpgrt_b64state_t state; + size_t nbytes; + char *buffer; + + *r_buffer = NULL; + *r_buflen = 0; + + buffer = xtrystrdup (string); + if (!buffer) + return gpg_error_from_syserror(); + + state = gpgrt_b64dec_start (title); + if (!state) + { + err = gpg_error_from_syserror (); + xfree (buffer); + return err; + } + err = gpgrt_b64dec_proc (state, buffer, strlen (buffer), &nbytes); + if (!err) + { + err = gpgrt_b64dec_finish (state); + state = NULL; + } + if (err) + xfree (buffer); + else + { + *r_buffer = buffer; + *r_buflen = nbytes; + } + gpgrt_b64dec_finish (state); /* Make sure it is released. */ + return err; +} diff --git a/common/openpgpdefs.h b/common/openpgpdefs.h index 180680bcc..d8672ac47 100644 --- a/common/openpgpdefs.h +++ b/common/openpgpdefs.h @@ -171,7 +171,12 @@ typedef enum PUBKEY_ALGO_ECDSA = 19, /* RFC-6637 */ PUBKEY_ALGO_ELGAMAL = 20, /* Elgamal encrypt+sign (legacy). */ /* 21 reserved by OpenPGP. */ - PUBKEY_ALGO_EDDSA = 22, /* EdDSA (not yet assigned). */ + PUBKEY_ALGO_EDDSA = 22, /* EdDSA. */ + PUBKEY_ALGO_KY768_25519 = 29, /* Kyber768 + X25519 (aka ML-KEM-768) */ + PUBKEY_ALGO_KY1024_448 = 30, /* Kyber1024 + X448 (aka ML-KEM-1024) */ + PUBKEY_ALGO_DIL3_25519 = 35, /* Dilithium3 + Ed25519 (aka ML-DSA-65) */ + PUBKEY_ALGO_DIL5_448 = 36, /* Dilithium5 + Ed448 (aka ML-DSA-87) */ + PUBKEY_ALGO_SPHINX_SHA2 = 41, /* SPHINX+-simple-SHA2 (aka SLH-DSA-SHA2) */ PUBKEY_ALGO_PRIVATE10 = 110 } pubkey_algo_t; @@ -206,7 +211,7 @@ compress_algo_t; #define OPENPGP_MAX_NPKEY 5 /* Maximum number of public key parameters. */ #define OPENPGP_MAX_NSKEY 7 /* Maximum number of secret key parameters. */ #define OPENPGP_MAX_NSIG 2 /* Maximum number of signature parameters. */ -#define OPENPGP_MAX_NENC 2 /* Maximum number of encryption parameters. */ +#define OPENPGP_MAX_NENC 4 /* Maximum number of encryption parameters. */ /* Decode an rfc4880 encoded S2K count. */ diff --git a/common/ssh-utils.c b/common/ssh-utils.c index ab29558cf..d27e2e200 100644 --- a/common/ssh-utils.c +++ b/common/ssh-utils.c @@ -259,7 +259,7 @@ get_fingerprint (gcry_sexp_t key, int algo, } else { - struct b64state b64s; + gpgrt_b64state_t b64s; estream_t stream; char *p; long int len; @@ -273,15 +273,15 @@ get_fingerprint (gcry_sexp_t key, int algo, goto leave; } - err = b64enc_start_es (&b64s, stream, ""); - if (err) + b64s = gpgrt_b64enc_start (stream, ""); + if (!b64s) { es_fclose (stream); goto leave; } - err = b64enc_write (&b64s, - gcry_md_read (md, algo), gcry_md_get_algo_dlen (algo)); + err = gpgrt_b64enc_write (b64s, gcry_md_read (md, algo), + gcry_md_get_algo_dlen (algo)); if (err) { es_fclose (stream); @@ -289,7 +289,7 @@ get_fingerprint (gcry_sexp_t key, int algo, } /* Finish, get the length, and close the stream. */ - err = b64enc_finish (&b64s); + err = gpgrt_b64enc_finish (b64s); len = es_ftell (stream); es_fclose (stream); if (err) @@ -566,7 +566,7 @@ ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, const char *identifier = NULL; void *blob = NULL; size_t bloblen; - struct b64state b64_state; + gpgrt_b64state_t b64_state; algo = get_pk_algo_from_key (key); if (algo == 0) @@ -624,15 +624,15 @@ ssh_public_key_in_base64 (gcry_sexp_t key, estream_t stream, es_fprintf (stream, "%s ", identifier); - err = b64enc_start_es (&b64_state, stream, ""); - if (err) + b64_state = gpgrt_b64enc_start (stream, ""); + if (!b64_state) { es_free (blob); - return err; + return gpg_error_from_syserror (); } - err = b64enc_write (&b64_state, blob, bloblen); - b64enc_finish (&b64_state); + err = gpgrt_b64enc_write (b64_state, blob, bloblen); + gpgrt_b64enc_finish (b64_state); es_free (blob); if (err) return err; diff --git a/common/sysutils.c b/common/sysutils.c index 90627b7c8..6c7d616b9 100644 --- a/common/sysutils.c +++ b/common/sysutils.c @@ -559,17 +559,15 @@ translate_sys2libc_fd (gnupg_fd_t fd, int for_write) } /* This is the same as translate_sys2libc_fd but takes an integer - which is assumed to be such an system handle. On WindowsCE the - passed FD is a rendezvous ID and the function finishes the pipe - creation. */ + which is assumed to be such an system handle. */ int translate_sys2libc_fd_int (int fd, int for_write) { #ifdef HAVE_W32_SYSTEM if (fd <= 2) - return fd; /* Do not do this for error, stdin, stdout, stderr. */ + return fd; /* Do not do this for stdin, stdout, and stderr. */ - return translate_sys2libc_fd ((void*)fd, for_write); + return translate_sys2libc_fd ((void*)(intptr_t)fd, for_write); #else (void)for_write; return fd; @@ -577,6 +575,70 @@ translate_sys2libc_fd_int (int fd, int for_write) } +/* + * Parse the string representation of a file reference (file handle on + * Windows or file descriptor on POSIX) in FDSTR. The string + * representation may be either of folllowing: + + * (1) 0, 1, or 2 which means stdin, stdout, and stderr, respectively. + * (2) Integer representation (by %d of printf). + * (3) Hex representation which starts as "0x". + * + * Then, fill R_SYSHD, according to the value of a file reference. + * + */ +gpg_error_t +gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd) +{ + int fd = -1; +#ifdef HAVE_W32_SYSTEM + gnupg_fd_t hd; + char *endptr; + int base; + + if (!strcmp (fdstr, "0")) + fd = 0; + else if (!strcmp (fdstr, "1")) + fd = 1; + else if (!strcmp (fdstr, "2")) + fd = 2; + + if (fd >= 0) + { + r_syshd->type = ES_SYSHD_FD; + r_syshd->u.fd = fd; + return 0; + } + + if (!strncmp (fdstr, "0x", 2)) + { + base = 16; + fdstr += 2; + } + else + base = 10; + + gpg_err_set_errno (0); +#ifdef _WIN64 + hd = (gnupg_fd_t)strtoll (fdstr, &endptr, base); +#else + hd = (gnupg_fd_t)strtol (fdstr, &endptr, base); +#endif + if (errno != 0 || endptr == fdstr || *endptr != '\0') + return gpg_error (GPG_ERR_INV_ARG); + + r_syshd->type = ES_SYSHD_HANDLE; + r_syshd->u.handle = hd; + return 0; +#else + fd = atoi (fdstr); + r_syshd->type = ES_SYSHD_FD; + r_syshd->u.fd = fd; + return 0; +#endif +} + + /* Check whether FNAME has the form "-&nnnn", where N is a non-zero * number. Returns this number or -1 if it is not the case. If the * caller wants to use the file descriptor for writing FOR_WRITE shall @@ -594,13 +656,74 @@ check_special_filename (const char *fname, int for_write, int notranslate) for (i=0; digitp (fname+i); i++ ) ; if (!fname[i]) - return notranslate? atoi (fname) - /**/ : translate_sys2libc_fd_int (atoi (fname), for_write); + { + if (notranslate) + return atoi (fname); + else + { + es_syshd_t syshd; + + if (gnupg_parse_fdstr (fname, &syshd)) + return -1; + +#ifdef HAVE_W32_SYSTEM + if (syshd.type == ES_SYSHD_FD) + return syshd.u.fd; + else + return translate_sys2libc_fd ((gnupg_fd_t)syshd.u.handle, for_write); +#else + (void)for_write; + return syshd.u.fd; +#endif + } + } } return -1; } +/* Check whether FNAME has the form "-&nnnn", where N is a number + * representing a file. Returns GNUPG_INVALID_FD if it is not the + * case. Returns a file descriptor on POSIX, a system handle on + * Windows. */ +gnupg_fd_t +gnupg_check_special_filename (const char *fname) +{ + if (allow_special_filenames + && fname && *fname == '-' && fname[1] == '&') + { + int i; + + fname += 2; + for (i=0; digitp (fname+i); i++ ) + ; + if (!fname[i]) + { + es_syshd_t syshd; + + if (gnupg_parse_fdstr (fname, &syshd)) + return GNUPG_INVALID_FD; + +#ifdef HAVE_W32_SYSTEM + if (syshd.type == ES_SYSHD_FD) + { + if (syshd.u.fd == 0) + return GetStdHandle (STD_INPUT_HANDLE); + else if (syshd.u.fd == 1) + return GetStdHandle (STD_OUTPUT_HANDLE); + else if (syshd.u.fd == 2) + return GetStdHandle (STD_ERROR_HANDLE); + } + else + return syshd.u.handle; +#else + return syshd.u.fd; +#endif + } + } + return GNUPG_INVALID_FD; +} + /* Replacement for tmpfile(). This is required because the tmpfile function of Windows' runtime library is broken, insecure, ignores TMPDIR and so on. In addition we create a file with an inheritable @@ -1158,6 +1281,19 @@ gnupg_setenv (const char *name, const char *value, int overwrite) return setenv (name, value, overwrite); #else /*!HAVE_SETENV*/ if (! getenv (name) || overwrite) +#if defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) + { + int e = _putenv_s (name, value); + + if (e) + { + gpg_err_set_errno (e); + return -1; + } + else + return 0; + } +#else { char *buf; @@ -1175,6 +1311,7 @@ gnupg_setenv (const char *name, const char *value, int overwrite) # endif return putenv (buf); } +#endif /*!HAVE_W32_SYSTEM*/ return 0; #endif /*!HAVE_SETENV*/ } @@ -1199,6 +1336,18 @@ gnupg_unsetenv (const char *name) #ifdef HAVE_UNSETENV return unsetenv (name); +#elif defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES) + { + int e = _putenv_s (name, ""); + + if (e) + { + gpg_err_set_errno (e); + return -1; + } + else + return 0; + } #else /*!HAVE_UNSETENV*/ { char *buf; @@ -1829,3 +1978,22 @@ gnupg_fd_valid (int fd) close (d); return 1; } + + +/* Open a stream from FD (a file descriptor on POSIX, a system + handle on Windows), non-closed. */ +estream_t +open_stream_nc (gnupg_fd_t fd, const char *mode) +{ + es_syshd_t syshd; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = fd; +#else + syshd.type = ES_SYSHD_FD; + syshd.u.fd = fd; +#endif + + return es_sysopen_nc (&syshd, mode); +} diff --git a/common/sysutils.h b/common/sysutils.h index a78a81c64..dac2d9244 100644 --- a/common/sysutils.h +++ b/common/sysutils.h @@ -38,12 +38,20 @@ typedef void *gnupg_fd_t; #define GNUPG_INVALID_FD ((void*)(-1)) #define INT2FD(s) ((void *)(s)) -#define FD2INT(h) ((unsigned int)(h)) +# ifdef _WIN64 +# define FD2INT(h) ((intptr_t)(h)) +# else +# define FD2INT(h) ((unsigned int)(h)) +# endif +#define FD_DBG(h) ((int)(intptr_t)(h)) +#define FD2NUM(h) ((int)(intptr_t)(h)) #else typedef int gnupg_fd_t; #define GNUPG_INVALID_FD (-1) #define INT2FD(s) (s) #define FD2INT(h) (h) +#define FD_DBG(h) (h) +#define FD2NUM(h) (h) #endif #ifdef HAVE_STAT @@ -74,7 +82,9 @@ void gnupg_sleep (unsigned int seconds); void gnupg_usleep (unsigned int usecs); int translate_sys2libc_fd (gnupg_fd_t fd, int for_write); int translate_sys2libc_fd_int (int fd, int for_write); +gpg_error_t gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd); int check_special_filename (const char *fname, int for_write, int notranslate); +gnupg_fd_t gnupg_check_special_filename (const char *fname); FILE *gnupg_tmpfile (void); void gnupg_reopen_std (const char *pgmname); void gnupg_inhibit_set_foregound_window (int yes); @@ -108,6 +118,7 @@ gpg_error_t gnupg_inotify_watch_delete_self (int *r_fd, const char *fname); gpg_error_t gnupg_inotify_watch_socket (int *r_fd, const char *socket_name); int gnupg_inotify_has_name (int fd, const char *name); +estream_t open_stream_nc (gnupg_fd_t fd, const char *mode); #ifdef HAVE_W32_SYSTEM int gnupg_w32_set_errno (int ec); diff --git a/common/t-b64.c b/common/t-b64.c deleted file mode 100644 index 16c079d1d..000000000 --- a/common/t-b64.c +++ /dev/null @@ -1,283 +0,0 @@ -/* t-b64.c - Module tests for b64enc.c and b64dec.c - * Copyright (C) 2008 Free Software Foundation, Inc. - * Copyright (C) 2008, 2023 g10 Code GmbH - * - * This file is part of GnuPG. - * - * This file 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. - * - * This file 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 <https://www.gnu.org/licenses/>. - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include <config.h> -#include <stdio.h> -#include <stdlib.h> - -#include "util.h" - -#define pass() do { ; } while(0) -#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ - __FILE__,__LINE__, (a)); \ - errcount++; \ - } while(0) -#define oops() do { fprintf (stderr, "%s:%d: ooops\n", \ - __FILE__,__LINE__); \ - exit (2); \ - } while(0) - -static int verbose; -static int errcount; - - -/* Convert STRING consisting of hex characters into its binary - * representation and return it as an allocated buffer. The valid - * length of the buffer is returned at R_LENGTH. The string is - * delimited by end of string. The function returns NULL on - * error. */ -static void * -hex2buffer (const char *string, size_t *r_length) -{ - const char *s; - unsigned char *buffer; - size_t length; - - buffer = xmalloc (strlen(string)/2+1); - length = 0; - for (s=string; *s; s +=2 ) - { - if (!hexdigitp (s) || !hexdigitp (s+1)) - return NULL; /* Invalid hex digits. */ - ((unsigned char*)buffer)[length++] = xtoi_2 (s); - } - *r_length = length; - return buffer; -} - - -static void -test_b64decode (void) -{ - static struct { - const char *string; /* String to test. */ - const char *title; /* title parameter. */ - gpg_error_t err; /* expected error. */ - const char *datastr; /* Expected data (hex encoded) */ - } tests[] = { - { "YQ==", NULL, 0, - "61" }, - { "YWE==", NULL, 0, - "6161" }, - { "YWFh", NULL, 0, - "616161" }, - { "YWFhYQ==", NULL, 0, - "61616161" }, - { "YWJjZA==", NULL, 0, - "61626364" }, - { "AA=", NULL, 0, - "00" }, - { "AAEA=", NULL, 0, - "000100" }, - { "/w==", NULL, 0, - "ff" }, - { "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==", NULL, 0, - "a1143012a0030a0103a10b06092a864882f712010202" }, - { "oRQwEqADCgEDoQsGCSqGSIL3EgECA-==", NULL, GPG_ERR_BAD_DATA, - "a1143012a0030a0103a10b06092a864882f712010202" }, - { "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==", "", 0, - "" }, - { "-----BEGIN PGP\n\n" - "oRQwEqADCgEDoQsGCSqGSIL3EgECAg==\n" - "-----END PGP\n", "", 0, - "a1143012a0030a0103a10b06092a864882f712010202" }, - - { "", NULL, 0, - "" } - }; - int tidx; - gpg_error_t err; - void *data = NULL; - size_t datalen; - char *wantdata = NULL; - size_t wantdatalen; - - for (tidx = 0; tidx < DIM(tests); tidx++) - { - xfree (wantdata); - if (!(wantdata = hex2buffer (tests[tidx].datastr, &wantdatalen))) - oops (); - xfree (data); - err = b64decode (tests[tidx].string, tests[tidx].title, &data, &datalen); - if (verbose) - fprintf (stderr, "%s:%d: test %d, err=%d, datalen=%zu\n", - __FILE__, __LINE__, tidx, err, datalen); - if (gpg_err_code (err) != tests[tidx].err) - fail (tidx); - else if (err) - pass (); - else if (wantdatalen != datalen) - fail (tidx); - else if (memcmp (wantdata, data, datalen)) - fail (tidx); - else - pass (); - } - xfree (wantdata); - xfree (data); -} - - -static void -test_b64enc_pgp (const char *string) -{ - gpg_error_t err; - struct b64state state; - - if (!string) - string = "a"; - - err = b64enc_start (&state, stdout, "PGP MESSAGE"); - if (err) - fail (1); - - err = b64enc_write (&state, string, strlen (string)); - if (err) - fail (2); - - err = b64enc_finish (&state); - if (err) - fail (3); - - pass (); -} - - -static void -test_b64enc_file (const char *fname) -{ - gpg_error_t err; - struct b64state state; - FILE *fp; - char buffer[50]; - size_t nread; - - fp = fname ? fopen (fname, "r") : stdin; - if (!fp) - { - fprintf (stderr, "%s:%d: can't open '%s': %s\n", - __FILE__, __LINE__, fname? fname:"[stdin]", strerror (errno)); - fail (0); - } - - err = b64enc_start (&state, stdout, "DATA"); - if (err) - fail (1); - - while ( (nread = fread (buffer, 1, sizeof buffer, fp)) ) - { - err = b64enc_write (&state, buffer, nread); - if (err) - fail (2); - } - - err = b64enc_finish (&state); - if (err) - fail (3); - - fclose (fp); - pass (); -} - - -static void -test_b64dec_file (const char *fname) -{ - gpg_error_t err; - struct b64state state; - FILE *fp; - char buffer[50]; - size_t nread, nbytes; - - fp = fname ? fopen (fname, "r") : stdin; - if (!fp) - { - fprintf (stderr, "%s:%d: can't open '%s': %s\n", - __FILE__, __LINE__, fname? fname:"[stdin]", strerror (errno)); - fail (0); - } - - err = b64dec_start (&state, ""); - if (err) - fail (1); - - while ( (nread = fread (buffer, 1, sizeof buffer, fp)) ) - { - err = b64dec_proc (&state, buffer, nread, &nbytes); - if (err) - { - if (gpg_err_code (err) == GPG_ERR_EOF) - break; - fail (2); - } - else if (nbytes) - fwrite (buffer, 1, nbytes, stdout); - } - - err = b64dec_finish (&state); - if (err) - fail (3); - - fclose (fp); - pass (); -} - - - -int -main (int argc, char **argv) -{ - int do_encode = 0; - int do_decode = 0; - int do_pgpdecode = 0; - - if (argc) - { argc--; argv++; } - if (argc && !strcmp (argv[0], "--verbose")) - { - verbose = 1; - argc--; argv++; - } - - if (argc && !strcmp (argv[0], "--encode")) - { - do_encode = 1; - argc--; argv++; - } - else if (argc && !strcmp (argv[0], "--decode")) - { - do_decode = 1; - argc--; argv++; - } - else if (argc) - do_pgpdecode = 1; - - if (do_encode) - test_b64enc_file (argc? *argv: NULL); - else if (do_decode) - test_b64dec_file (argc? *argv: NULL); - else if (do_pgpdecode) - test_b64enc_pgp (argc? *argv: NULL); - else - test_b64decode (); - - return !!errcount; -} diff --git a/common/t-exechelp.c b/common/t-exechelp.c index 3bf082bbb..2179ef2a0 100644 --- a/common/t-exechelp.c +++ b/common/t-exechelp.c @@ -29,7 +29,7 @@ static int verbose; - +#ifndef HAVE_W32_SYSTEM static void print_open_fds (int *array) { @@ -169,20 +169,168 @@ test_close_all_fds (void) } } +#endif + +static char buff12k[1024*12]; +static char buff4k[1024*4]; + +static void +run_server (void) +{ + estream_t fp; + int i; + char *p; + unsigned int len; + int ret; + es_syshd_t syshd; + size_t n; + off_t o; + +#ifdef HAVE_W32_SYSTEM + syshd.type = ES_SYSHD_HANDLE; + syshd.u.handle = (HANDLE)_get_osfhandle (1); +#else + syshd.type = ES_SYSHD_FD; + syshd.u.fd = 1; +#endif + + fp = es_sysopen_nc (&syshd, "w"); + if (fp == NULL) + { + fprintf (stderr, "es_fdopen failed\n"); + exit (1); + } + + /* Fill the buffer by ASCII chars. */ + p = buff12k; + for (i = 0; i < sizeof (buff12k); i++) + if ((i % 64) == 63) + *p++ = '\n'; + else + *p++ = (i % 64) + '@'; + + len = sizeof (buff12k); + + ret = es_write (fp, (void *)&len, sizeof (len), NULL); + if (ret) + { + fprintf (stderr, "es_write (1) failed\n"); + exit (1); + } + + es_fflush (fp); + + o = 0; + n = len; + + while (1) + { + size_t n0, n1; + + n0 = n > 4096 ? 4096 : n; + memcpy (buff4k, buff12k + o, n0); + + ret = es_write (fp, buff4k, n0, &n1); + if (ret || n0 != n1) + { + fprintf (stderr, "es_write (2) failed\n"); + exit (1); + } + + o += n0; + n -= n0; + if (n == 0) + break; + } + + es_fclose (fp); + exit (0); +} + + +static void +test_pipe_stream (const char *pgmname) +{ + gpg_error_t err; + gnupg_process_t proc; + estream_t outfp; + const char *argv[2]; + unsigned int len; + size_t n; + off_t o; + int ret; + + argv[0] = "--server"; + argv[1] = NULL; + + err = gnupg_process_spawn (pgmname, argv, + (GNUPG_PROCESS_STDOUT_PIPE + |GNUPG_PROCESS_STDERR_KEEP), + NULL, NULL, &proc); + if (err) + { + fprintf (stderr, "gnupg_process_spawn failed\n"); + exit (1); + } + + gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL); + + ret = es_read (outfp, (void *)&len, sizeof (len), NULL); + if (ret) + { + fprintf (stderr, "es_read (1) failed\n"); + exit (1); + } + + o = 0; + while (1) + { + if (es_feof (outfp)) + break; + + ret = es_read (outfp, buff4k, sizeof (buff4k), &n); + if (ret) + { + fprintf (stderr, "es_read (2) failed\n"); + exit (1); + } + + memcpy (buff12k + o, buff4k, n); + o += n; + } + + if (o != sizeof (buff12k)) + { + fprintf (stderr, "received data with wrong length %d\n", (int)o); + exit (1); + } + es_fclose (outfp); + gnupg_process_release (proc); +} int main (int argc, char **argv) { + const char *myname = "no-pgm"; + if (argc) - { argc--; argv++; } + { + myname = argv[0]; + argc--; argv++; + } if (argc && !strcmp (argv[0], "--verbose")) { verbose = 1; argc--; argv++; } + if (argc && !strcmp (argv[0], "--server")) + run_server (); +#ifndef HAVE_W32_SYSTEM test_close_all_fds (); +#endif + test_pipe_stream (myname); return 0; } diff --git a/common/t-iobuf.c b/common/t-iobuf.c index bdeab99a4..aacf27a8b 100644 --- a/common/t-iobuf.c +++ b/common/t-iobuf.c @@ -1,3 +1,36 @@ +/* t-iobuf.c - Simple module test for iobuf.c + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later) + */ + +/* The whole code here does not very fill into our general test frame + * work patter. But let's keep it as it is. */ + #include <config.h> #include <stdio.h> #include <string.h> @@ -7,6 +40,20 @@ #include "iobuf.h" #include "stringhelp.h" + +static void * +xmalloc (size_t n) +{ + void *p = malloc (n); + if (!p) + { + fprintf (stderr, "t-iobuf: out of core\n"); + abort (); + } + return p; +} + + /* Return every other byte. In particular, reads two bytes, returns the second one. */ static int @@ -86,7 +133,7 @@ static struct content_filter_state * content_filter_new (const char *buffer) { struct content_filter_state *state - = malloc (sizeof (struct content_filter_state)); + = xmalloc (sizeof (struct content_filter_state)); state->pos = 0; state->len = strlen (buffer); @@ -215,8 +262,7 @@ main (int argc, char *argv[]) allocate a buffer that is 5 bytes long, then no reallocation should be required. */ size = 5; - buffer = malloc (size); - assert (buffer); + buffer = xmalloc (size); max_len = 100; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 4); @@ -229,7 +275,7 @@ main (int argc, char *argv[]) requires 6 bytes of storage. We pass a buffer that is 5 bytes large and we allow the buffer to be grown. */ size = 5; - buffer = malloc (size); + buffer = xmalloc (size); max_len = 100; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 5); @@ -243,7 +289,7 @@ main (int argc, char *argv[]) requires 7 bytes of storage. We pass a buffer that is 5 bytes large and we don't allow the buffer to be grown. */ size = 5; - buffer = malloc (size); + buffer = xmalloc (size); max_len = 5; n = iobuf_read_line (iobuf, &buffer, &size, &max_len); assert (n == 4); diff --git a/common/util.h b/common/util.h index 803ab3d5c..e2d95b1af 100644 --- a/common/util.h +++ b/common/util.h @@ -149,36 +149,6 @@ ssize_t read_line (FILE *fp, size_t *max_length); -/*-- b64enc.c and b64dec.c --*/ -struct b64state -{ - unsigned int flags; - int idx; - int quad_count; - FILE *fp; - estream_t stream; - char *title; - unsigned char radbuf[4]; - u32 crc; - int stop_seen:1; - int invalid_encoding:1; - gpg_error_t lasterr; -}; - -gpg_error_t b64enc_start (struct b64state *state, FILE *fp, const char *title); -gpg_error_t b64enc_start_es (struct b64state *state, estream_t fp, - const char *title); -gpg_error_t b64enc_write (struct b64state *state, - const void *buffer, size_t nbytes); -gpg_error_t b64enc_finish (struct b64state *state); - -gpg_error_t b64dec_start (struct b64state *state, const char *title); -gpg_error_t b64dec_proc (struct b64state *state, void *buffer, size_t length, - size_t *r_nbytes); -gpg_error_t b64dec_finish (struct b64state *state); -gpg_error_t b64decode (const char *string, const char *title, - void **r_buffer, size_t *r_buflen); - /*-- sexputil.c */ char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen); void log_printcanon (const char *text, @@ -388,6 +358,10 @@ struct compatibility_flags_s int parse_compatibility_flags (const char *string, unsigned int *flagvar, const struct compatibility_flags_s *flags); +gpg_error_t b64decode (const char *string, const char *title, + void **r_buffer, size_t *r_buflen); + + /*-- Simple replacement functions. */ |