diff --git a/agent/genkey.c b/agent/genkey.c index bb7e74e9b..9a8b3c2aa 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -1,713 +1,713 @@ /* genkey.c - Generate a keypair * Copyright (C) 2002, 2003, 2004, 2007, 2010 Free Software Foundation, Inc. * Copyright (C) 2015 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include "agent.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" void clear_ephemeral_keys (ctrl_t ctrl) { while (ctrl->ephemeral_keys) { ephemeral_private_key_t next = ctrl->ephemeral_keys->next; if (ctrl->ephemeral_keys->keybuf) { wipememory (ctrl->ephemeral_keys->keybuf, ctrl->ephemeral_keys->keybuflen); xfree (ctrl->ephemeral_keys->keybuf); } xfree (ctrl->ephemeral_keys); ctrl->ephemeral_keys = next; } } /* Store the key either to a file, or in ctrl->ephemeral_mode in the * session data. */ static gpg_error_t store_key (ctrl_t ctrl, gcry_sexp_t private, const char *passphrase, int force, unsigned long s2k_count, time_t timestamp) { gpg_error_t err; unsigned char *buf; size_t len; unsigned char grip[KEYGRIP_LEN]; if ( !gcry_pk_get_keygrip (private, grip) ) { log_error ("can't calculate keygrip\n"); return gpg_error (GPG_ERR_GENERAL); } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (len); buf = gcry_malloc_secure (len); if (!buf) { err = gpg_error_from_syserror (); goto leave; } len = gcry_sexp_sprint (private, GCRYSEXP_FMT_CANON, buf, len); log_assert (len); if (passphrase) { unsigned char *p; err = agent_protect (buf, passphrase, &p, &len, s2k_count); if (err) goto leave; xfree (buf); buf = p; } if (ctrl->ephemeral_mode) { ephemeral_private_key_t ek; for (ek = ctrl->ephemeral_keys; ek; ek = ek->next) if (!memcmp (ek->grip, grip, KEYGRIP_LEN)) break; if (!ek) { ek = xtrycalloc (1, sizeof *ek); if (!ek) { err = gpg_error_from_syserror (); goto leave; } memcpy (ek->grip, grip, KEYGRIP_LEN); ek->next = ctrl->ephemeral_keys; ctrl->ephemeral_keys = ek; } if (ek->keybuf) { wipememory (ek->keybuf, ek->keybuflen); xfree (ek->keybuf); } ek->keybuf = buf; buf = NULL; ek->keybuflen = len; err = 0; } else err = agent_write_private_key (ctrl, grip, buf, len, force, NULL, NULL, NULL, timestamp); if (!err) { char hexgrip[2*KEYGRIP_LEN+1]; bin2hex (grip, KEYGRIP_LEN, hexgrip); agent_write_status (ctrl, "KEYGRIP", hexgrip, NULL); } leave: xfree (buf); return err; } /* Count the number of non-alpha characters in S. Control characters and non-ascii characters are not considered. */ static size_t nonalpha_count (const char *s) { size_t n; for (n=0; *s; s++) if (isascii (*s) && ( isdigit (*s) || ispunct (*s) )) n++; return n; } /* Check PW against a list of pattern. Return 0 if PW does not match these pattern. If CHECK_CONSTRAINTS_NEW_SYMKEY is set in flags and --check-sym-passphrase-pattern has been configured, use the pattern file from that option. */ static int do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) { gpg_error_t err = 0; const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); estream_t stream_to_check_pattern = NULL; const char *argv[10]; gnupg_process_t proc; int result, i; const char *pattern; char *patternfname; (void)ctrl; pattern = opt.check_passphrase_pattern; if ((flags & CHECK_CONSTRAINTS_NEW_SYMKEY) && opt.check_sym_passphrase_pattern) pattern = opt.check_sym_passphrase_pattern; if (!pattern) return 1; /* Oops - Assume password should not be used */ if (strchr (pattern, '/') || strchr (pattern, '\\') || (*pattern == '~' && pattern[1] == '/')) patternfname = make_absfilename_try (pattern, NULL); else patternfname = make_filename_try (gnupg_sysconfdir (), pattern, NULL); if (!patternfname) { log_error ("error making filename from '%s': %s\n", pattern, gpg_strerror (gpg_error_from_syserror ())); return 1; /* Do not pass the check. */ } /* Make debugging a broken config easier by printing a useful error * message. */ if (gnupg_access (patternfname, F_OK)) { log_error ("error accessing '%s': %s\n", patternfname, gpg_strerror (gpg_error_from_syserror ())); xfree (patternfname); return 1; /* Do not pass the check. */ } i = 0; argv[i++] = "--null"; argv[i++] = "--", argv[i++] = patternfname, argv[i] = NULL; log_assert (i < sizeof argv); if (gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDIN_PIPE, - NULL, NULL, &proc)) + NULL, &proc)) result = 1; /* Execute error - assume password should no be used. */ else { int status; gnupg_process_get_streams (proc, 0, &stream_to_check_pattern, NULL, NULL); es_set_binary (stream_to_check_pattern); if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing to pipe: %s\n"), gpg_strerror (err)); result = 1; /* Error - assume password should not be used. */ } else es_fflush (stream_to_check_pattern); es_fclose (stream_to_check_pattern); gnupg_process_wait (proc, 1); gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &status); if (status) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ gnupg_process_release (proc); } xfree (patternfname); return result; } static int take_this_one_anyway (ctrl_t ctrl, const char *desc, const char *anyway_btn) { return agent_get_confirmation (ctrl, desc, anyway_btn, L_("Enter new passphrase"), 0); } /* Check whether the passphrase PW is suitable. Returns 0 if the * passphrase is suitable and true if it is not and the user should be * asked to provide a different one. If FAILED_CONSTRAINT is set, a * message describing the problem is returned at FAILED_CONSTRAINT. * The FLAGS are: * CHECK_CONSTRAINTS_NOT_EMPTY * Do not allow an empty passphrase * CHECK_CONSTRAINTS_NEW_SYMKEY * Hint that the passphrase is used for a new symmetric key. */ int check_passphrase_constraints (ctrl_t ctrl, const char *pw, unsigned int flags, char **failed_constraint) { gpg_error_t err = 0; unsigned int minlen = opt.min_passphrase_len; unsigned int minnonalpha = opt.min_passphrase_nonalpha; char *msg1 = NULL; char *msg2 = NULL; char *msg3 = NULL; int no_empty = !!(flags & CHECK_CONSTRAINTS_NOT_EMPTY); if (ctrl && ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) return 0; if (!pw) pw = ""; /* The first check is to warn about an empty passphrase. */ if (!*pw) { const char *desc = (opt.enforce_passphrase_constraints || no_empty? L_("You have not entered a passphrase!%0A" "An empty passphrase is not allowed.") : L_("You have not entered a passphrase - " "this is in general a bad idea!%0A" "Please confirm that you do not want to " "have any protection on your key.")); err = 1; if (failed_constraint) { if (opt.enforce_passphrase_constraints || no_empty) *failed_constraint = xstrdup (desc); else err = take_this_one_anyway (ctrl, desc, L_("Yes, protection is not needed")); } goto leave; } /* Now check the constraints and collect the error messages unless in silent mode which returns immediately. */ if (utf8_charcount (pw, -1) < minlen ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg1 = xtryasprintf ( ngettext ("A passphrase should be at least %u character long.", "A passphrase should be at least %u characters long.", minlen), minlen ); if (!msg1) { err = gpg_error_from_syserror (); goto leave; } } if (nonalpha_count (pw) < minnonalpha ) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg2 = xtryasprintf ( ngettext ("A passphrase should contain at least %u digit or%%0A" "special character.", "A passphrase should contain at least %u digits or%%0A" "special characters.", minnonalpha), minnonalpha ); if (!msg2) { err = gpg_error_from_syserror (); goto leave; } } /* If configured check the passphrase against a list of known words and pattern. The actual test is done by an external program. The warning message is generic to give the user no hint on how to circumvent this list. */ if (*pw && (opt.check_passphrase_pattern || opt.check_sym_passphrase_pattern) && do_check_passphrase_pattern (ctrl, pw, flags)) { if (!failed_constraint) { err = gpg_error (GPG_ERR_INV_PASSPHRASE); goto leave; } msg3 = xtryasprintf (L_("A passphrase may not be a known term or match%%0A" "certain pattern.")); if (!msg3) { err = gpg_error_from_syserror (); goto leave; } } if (failed_constraint && (msg1 || msg2 || msg3)) { char *msg; size_t n; msg = strconcat (L_("Warning: You have entered an insecure passphrase."), "%0A%0A", msg1? msg1 : "", msg1? "%0A" : "", msg2? msg2 : "", msg2? "%0A" : "", msg3? msg3 : "", msg3? "%0A" : "", NULL); if (!msg) { err = gpg_error_from_syserror (); goto leave; } /* Strip a trailing "%0A". */ n = strlen (msg); if (n > 3 && !strcmp (msg + n - 3, "%0A")) msg[n-3] = 0; err = 1; if (opt.enforce_passphrase_constraints) *failed_constraint = msg; else { err = take_this_one_anyway (ctrl, msg, L_("Take this one anyway")); xfree (msg); } } leave: xfree (msg1); xfree (msg2); xfree (msg3); return err; } /* Callback function to compare the first entered PIN with the one currently being entered. */ static gpg_error_t reenter_compare_cb (struct pin_entry_info_s *pi) { const char *pin1 = pi->check_cb_arg; if (!strcmp (pin1, pi->pin)) return 0; /* okay */ return gpg_error (GPG_ERR_BAD_PASSPHRASE); } /* Ask the user for a new passphrase using PROMPT. On success the function returns 0 and store the passphrase at R_PASSPHRASE; if the user opted not to use a passphrase NULL will be stored there. The user needs to free the returned string. In case of an error and error code is returned and NULL stored at R_PASSPHRASE. */ gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase) { gpg_error_t err; const char *text1 = prompt; const char *text2 = L_("Please re-enter this passphrase"); char *initial_errtext = NULL; struct pin_entry_info_s *pi, *pi2; *r_passphrase = NULL; if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) { size_t size; unsigned char *buffer; err = pinentry_loopback (ctrl, "NEW_PASSPHRASE", &buffer, &size, MAX_PASSPHRASE_LEN); if (!err) { if (size) { buffer[size] = 0; *r_passphrase = buffer; } else *r_passphrase = NULL; } return err; } pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); if (!pi) return gpg_error_from_syserror (); pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); if (!pi2) { err = gpg_error_from_syserror (); xfree (pi); return err; } pi->max_length = MAX_PASSPHRASE_LEN + 1; pi->max_tries = 3; pi->with_qualitybar = 0; pi->with_repeat = 1; pi2->max_length = MAX_PASSPHRASE_LEN + 1; pi2->max_tries = 3; pi2->check_cb = reenter_compare_cb; pi2->check_cb_arg = pi->pin; next_try: err = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0); xfree (initial_errtext); initial_errtext = NULL; if (!err) { if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0; goto next_try; } /* Unless the passphrase is empty or the pinentry told us that it already did the repetition check, ask to confirm it. */ if (*pi->pin && !pi->repeat_okay) { err = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0); if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE) { /* The re-entered one did not match and the user did not hit cancel. */ initial_errtext = xtrystrdup (L_("does not match - try again")); if (initial_errtext) goto next_try; err = gpg_error_from_syserror (); } } } if (!err && *pi->pin) { /* User wants a passphrase. */ *r_passphrase = xtrystrdup (pi->pin); if (!*r_passphrase) err = gpg_error_from_syserror (); } xfree (initial_errtext); xfree (pi2); xfree (pi); return err; } /* Generate a new keypair according to the parameters given in * KEYPARAM. If CACHE_NONCE is given first try to lookup a passphrase * using the cache nonce. If NO_PROTECTION is true the key will not * be protected by a passphrase. If OVERRIDE_PASSPHRASE is true that * passphrase will be used for the new key. If TIMESTAMP is not zero * it will be recorded as creation date of the key (unless extended * format is disabled). In ctrl_ephemeral_mode the key is stored in * the session data and an identifier is returned using a status * line. */ int agent_genkey (ctrl_t ctrl, unsigned int flags, const char *cache_nonce, time_t timestamp, const char *keyparam, size_t keyparamlen, const char *override_passphrase, membuf_t *outbuf) { gcry_sexp_t s_keyparam, s_key, s_private, s_public; char *passphrase_buffer = NULL; const char *passphrase; int rc; size_t len; char *buf; rc = gcry_sexp_sscan (&s_keyparam, NULL, keyparam, keyparamlen); if (rc) { log_error ("failed to convert keyparam: %s\n", gpg_strerror (rc)); return gpg_error (GPG_ERR_INV_DATA); } /* Get the passphrase now, cause key generation may take a while. */ if (override_passphrase) passphrase = override_passphrase; else if ((flags & GENKEY_FLAG_NO_PROTECTION) || !cache_nonce) passphrase = NULL; else { passphrase_buffer = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE); passphrase = passphrase_buffer; } if (passphrase || (flags & GENKEY_FLAG_NO_PROTECTION)) ; /* No need to ask for a passphrase. */ else { rc = agent_ask_new_passphrase (ctrl, L_("Please enter the passphrase to%0A" "protect your new key"), &passphrase_buffer); if (rc) { gcry_sexp_release (s_keyparam); return rc; } passphrase = passphrase_buffer; } rc = gcry_pk_genkey (&s_key, s_keyparam ); gcry_sexp_release (s_keyparam); if (rc) { log_error ("key generation failed: %s\n", gpg_strerror (rc)); xfree (passphrase_buffer); return rc; } /* break out the parts */ s_private = gcry_sexp_find_token (s_key, "private-key", 0); if (!s_private) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } s_public = gcry_sexp_find_token (s_key, "public-key", 0); if (!s_public) { log_error ("key generation failed: invalid return value\n"); gcry_sexp_release (s_private); gcry_sexp_release (s_key); xfree (passphrase_buffer); return gpg_error (GPG_ERR_INV_DATA); } gcry_sexp_release (s_key); s_key = NULL; /* store the secret key */ if (opt.verbose) log_info ("storing %sprivate key\n", ctrl->ephemeral_mode?"ephemeral ":""); rc = store_key (ctrl, s_private, passphrase, 0, ctrl->s2k_count, timestamp); if (!rc && !ctrl->ephemeral_mode) { /* FIXME: or does it make sense to also cache passphrases in * ephemeral mode using a dedicated cache? */ if (!cache_nonce) { char tmpbuf[12]; gcry_create_nonce (tmpbuf, 12); cache_nonce = bin2hex (tmpbuf, 12, NULL); } if (cache_nonce && !(flags & GENKEY_FLAG_NO_PROTECTION) && !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE, passphrase, ctrl->cache_ttl_opt_preset)) agent_write_status (ctrl, "CACHE_NONCE", cache_nonce, NULL); if ((flags & GENKEY_FLAG_PRESET) && !(flags & GENKEY_FLAG_NO_PROTECTION)) { unsigned char grip[20]; char hexgrip[40+1]; if (gcry_pk_get_keygrip (s_private, grip)) { bin2hex(grip, 20, hexgrip); rc = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, passphrase, ctrl->cache_ttl_opt_preset); } } } xfree (passphrase_buffer); passphrase_buffer = NULL; passphrase = NULL; gcry_sexp_release (s_private); if (rc) { gcry_sexp_release (s_public); return rc; } /* return the public key */ if (DBG_CRYPTO) log_debug ("returning public key\n"); len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, NULL, 0); log_assert (len); buf = xtrymalloc (len); if (!buf) { gpg_error_t tmperr = out_of_core (); gcry_sexp_release (s_private); gcry_sexp_release (s_public); return tmperr; } len = gcry_sexp_sprint (s_public, GCRYSEXP_FMT_CANON, buf, len); log_assert (len); put_membuf (outbuf, buf, len); gcry_sexp_release (s_public); xfree (buf); return 0; } /* Apply a new passphrase to the key S_SKEY and store it. If PASSPHRASE_ADDR and *PASSPHRASE_ADDR are not NULL, use that passphrase. If PASSPHRASE_ADDR is not NULL store a newly entered passphrase at that address. */ gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey, char **passphrase_addr) { gpg_error_t err; if (passphrase_addr && *passphrase_addr) { /* Take an empty string as request not to protect the key. */ err = store_key (ctrl, s_skey, **passphrase_addr? *passphrase_addr:NULL, 1, ctrl->s2k_count, 0); } else { char *pass = NULL; if (passphrase_addr) { xfree (*passphrase_addr); *passphrase_addr = NULL; } err = agent_ask_new_passphrase (ctrl, L_("Please enter the new passphrase"), &pass); if (!err) err = store_key (ctrl, s_skey, pass, 1, ctrl->s2k_count, 0); if (!err && passphrase_addr) *passphrase_addr = pass; else xfree (pass); } return err; } diff --git a/common/asshelp.c b/common/asshelp.c index 5a40e0380..957ca994d 100644 --- a/common/asshelp.c +++ b/common/asshelp.c @@ -1,748 +1,747 @@ /* asshelp.c - Helper functions for Assuan * Copyright (C) 2002, 2004, 2007, 2009, 2010 Free Software Foundation, Inc. * * 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/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include "i18n.h" #include "util.h" #include "exechelp.h" #include "sysutils.h" #include "status.h" #include "membuf.h" #include "asshelp.h" /* The type we use for lock_agent_spawning. */ #ifdef HAVE_W32_SYSTEM # define lock_spawn_t HANDLE #else # define lock_spawn_t dotlock_t #endif /* The time we wait until the agent or the dirmngr are ready for operation after we started them before giving up. */ #define SECS_TO_WAIT_FOR_AGENT 5 #define SECS_TO_WAIT_FOR_KEYBOXD 5 #define SECS_TO_WAIT_FOR_DIRMNGR 5 /* A bitfield that specifies the assuan categories to log. This is identical to the default log handler of libassuan. We need to do it ourselves because we use a custom log handler and want to use the same assuan variables to select the categories to log. */ static int log_cats; #define TEST_LOG_CAT(x) (!! (log_cats & (1 << (x - 1)))) /* The assuan log monitor used to temporary inhibit log messages from * assuan. */ static int (*my_log_monitor) (assuan_context_t ctx, unsigned int cat, const char *msg); static int my_libassuan_log_handler (assuan_context_t ctx, void *hook, unsigned int cat, const char *msg) { unsigned int dbgval; if (! TEST_LOG_CAT (cat)) return 0; dbgval = hook? *(unsigned int*)hook : 0; if (!(dbgval & 1024)) return 0; /* Assuan debugging is not enabled. */ if (ctx && my_log_monitor && !my_log_monitor (ctx, cat, msg)) return 0; /* Temporary disabled. */ if (msg) log_string (GPGRT_LOGLVL_DEBUG, msg); return 1; } /* Setup libassuan to use our own logging functions. Should be used early at startup. */ void setup_libassuan_logging (unsigned int *debug_var_address, int (*log_monitor)(assuan_context_t ctx, unsigned int cat, const char *msg)) { char *flagstr; flagstr = getenv ("ASSUAN_DEBUG"); if (flagstr) log_cats = atoi (flagstr); else /* Default to log the control channel. */ log_cats = (1 << (ASSUAN_LOG_CONTROL - 1)); my_log_monitor = log_monitor; assuan_set_log_cb (my_libassuan_log_handler, debug_var_address); } /* Change the Libassuan log categories to those given by NEWCATS. NEWCATS is 0 the default category of ASSUAN_LOG_CONTROL is selected. Note, that setup_libassuan_logging overrides the values given here. */ void set_libassuan_log_cats (unsigned int newcats) { if (newcats) log_cats = newcats; else /* Default to log the control channel. */ log_cats = (1 << (ASSUAN_LOG_CONTROL - 1)); } static gpg_error_t send_one_option (assuan_context_t ctx, gpg_err_source_t errsource, const char *name, const char *value, int use_putenv) { gpg_error_t err; char *optstr; (void)errsource; if (!value || !*value) err = 0; /* Avoid sending empty strings. */ else if (asprintf (&optstr, "OPTION %s%s=%s", use_putenv? "putenv=":"", name, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } return err; } /* Send the assuan commands pertaining to the pinentry environment. The OPT_* arguments are optional and may be used to override the defaults taken from the current locale. */ gpg_error_t send_pinentry_environment (assuan_context_t ctx, gpg_err_source_t errsource, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env) { gpg_error_t err = 0; #if defined(HAVE_SETLOCALE) char *old_lc = NULL; #endif char *dft_lc = NULL; const char *dft_ttyname; int iterator; const char *name, *assname, *value; int is_default; iterator = 0; while ((name = session_env_list_stdenvnames (&iterator, &assname))) { value = session_env_getenv_or_default (session_env, name, NULL); if (!value) continue; if (assname) err = send_one_option (ctx, errsource, assname, value, 0); else { err = send_one_option (ctx, errsource, name, value, 1); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* Server too old; can't pass the new envvars. */ } if (err) return err; } dft_ttyname = session_env_getenv_or_default (session_env, "GPG_TTY", &is_default); if (dft_ttyname && !is_default) dft_ttyname = NULL; /* We need the default value. */ /* Send the value for LC_CTYPE. */ #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) old_lc = setlocale (LC_CTYPE, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_CTYPE, ""); #endif if (opt_lc_ctype || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-ctype", opt_lc_ctype ? opt_lc_ctype : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_CTYPE) if (old_lc) { setlocale (LC_CTYPE, old_lc); xfree (old_lc); } #endif if (err) return err; /* Send the value for LC_MESSAGES. */ #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) old_lc = setlocale (LC_MESSAGES, NULL); if (old_lc) { old_lc = xtrystrdup (old_lc); if (!old_lc) return gpg_error_from_syserror (); } dft_lc = setlocale (LC_MESSAGES, ""); #endif if (opt_lc_messages || (dft_ttyname && dft_lc)) { err = send_one_option (ctx, errsource, "lc-messages", opt_lc_messages ? opt_lc_messages : dft_lc, 0); } #if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES) if (old_lc) { setlocale (LC_MESSAGES, old_lc); xfree (old_lc); } #endif if (err) return err; return 0; } /* Lock a spawning process. The caller needs to provide the address of a variable to store the lock information and the name or the process. */ static gpg_error_t lock_spawning (lock_spawn_t *lock, const char *homedir, const char *name, int verbose) { char *fname; (void)verbose; *lock = NULL; fname = make_absfilename_try (homedir, !strcmp (name, "agent")? "gnupg_spawn_agent_sentinel": !strcmp (name, "dirmngr")? "gnupg_spawn_dirmngr_sentinel": !strcmp (name, "keyboxd")? "gnupg_spawn_keyboxd_sentinel": /* */ "gnupg_spawn_unknown_sentinel", NULL); if (!fname) return gpg_error_from_syserror (); *lock = dotlock_create (fname, 0); xfree (fname); if (!*lock) return gpg_error_from_syserror (); /* FIXME: We should use a timeout of 5000 here - however make_dotlock does not yet support values other than -1 and 0. */ if (dotlock_take (*lock, -1)) return gpg_error_from_syserror (); return 0; } /* Unlock the spawning process. */ static void unlock_spawning (lock_spawn_t *lock, const char *name) { if (*lock) { (void)name; dotlock_destroy (*lock); *lock = NULL; } } /* Helper to start a service. SECS gives the number of seconds to * wait. SOCKNAME is the name of the socket to connect. VERBOSE is * the usual verbose flag. CTX is the assuan context. CONNECT_FLAGS * are the assuan connect flags. DID_SUCCESS_MSG will be set to 1 if * a success messages has been printed. */ static gpg_error_t wait_for_sock (int secs, int module_name_id, const char *sockname, unsigned int connect_flags, int verbose, assuan_context_t ctx, int *did_success_msg) { gpg_error_t err = 0; int target_us = secs * 1000000; int elapsed_us = 0; /* * 977us * 1024 = just a little more than 1s. * so we will double this timeout 10 times in the first * second, and then switch over to 1s checkins. */ int next_sleep_us = 977; int lastalert = secs+1; int secsleft; while (elapsed_us < target_us) { if (verbose) { secsleft = (target_us - elapsed_us + 999999)/1000000; /* log_clock ("left=%d last=%d targ=%d elap=%d next=%d\n", */ /* secsleft, lastalert, target_us, elapsed_us, */ /* next_sleep_us); */ if (secsleft < lastalert) { log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR? _("waiting for the dirmngr to come up ... (%ds)\n"): module_name_id == GNUPG_MODULE_NAME_KEYBOXD? _("waiting for the keyboxd to come up ... (%ds)\n"): _("waiting for the agent to come up ... (%ds)\n"), secsleft); lastalert = secsleft; } } gnupg_usleep (next_sleep_us); elapsed_us += next_sleep_us; err = assuan_socket_connect (ctx, sockname, 0, connect_flags); if (!err) { if (verbose) { log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR? _("connection to the dirmngr established\n"): module_name_id == GNUPG_MODULE_NAME_KEYBOXD? _("connection to the keyboxd established\n"): _("connection to the agent established\n")); *did_success_msg = 1; } break; } next_sleep_us *= 2; if (next_sleep_us > 1000000) next_sleep_us = 1000000; } return err; } /* Try to connect to a new service via socket or start it if it is not * running and AUTOSTART is set. Handle the server's initial * greeting. Returns a new assuan context at R_CTX or an error code. * MODULE_NAME_ID is one of: * GNUPG_MODULE_NAME_AGENT * GNUPG_MODULE_NAME_DIRMNGR */ static gpg_error_t start_new_service (assuan_context_t *r_ctx, int module_name_id, gpg_err_source_t errsource, const char *program_name, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, unsigned int flags, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { gpg_error_t err; assuan_context_t ctx; int did_success_msg = 0; char *sockname; const char *printed_name; const char *lock_name; const char *status_start_line; int no_service_err; int seconds_to_wait; unsigned int connect_flags = 0; const char *argv[6]; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } switch (module_name_id) { case GNUPG_MODULE_NAME_AGENT: sockname = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); lock_name = "agent"; printed_name = "gpg-agent"; status_start_line = "starting_agent ? 0 0"; no_service_err = GPG_ERR_NO_AGENT; seconds_to_wait = SECS_TO_WAIT_FOR_AGENT; break; case GNUPG_MODULE_NAME_DIRMNGR: sockname = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL); lock_name = "dirmngr"; printed_name = "dirmngr"; status_start_line = "starting_dirmngr ? 0 0"; no_service_err = GPG_ERR_NO_DIRMNGR; seconds_to_wait = SECS_TO_WAIT_FOR_DIRMNGR; break; case GNUPG_MODULE_NAME_KEYBOXD: sockname = make_filename (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); lock_name = "keyboxd"; printed_name = "keyboxd"; status_start_line = "starting_keyboxd ? 0 0"; no_service_err = GPG_ERR_NO_KEYBOXD; seconds_to_wait = SECS_TO_WAIT_FOR_KEYBOXD; connect_flags |= ASSUAN_SOCKET_CONNECT_FDPASSING; break; default: err = gpg_error (GPG_ERR_INV_ARG); assuan_release (ctx); return err; } err = assuan_socket_connect (ctx, sockname, 0, connect_flags); if (err && (flags & ASSHELP_FLAG_AUTOSTART)) { char *abs_homedir; lock_spawn_t lock; char *program = NULL; const char *program_arg = NULL; char *p; const char *s; int i; /* With no success start a new server. */ if (!program_name || !*program_name) program_name = gnupg_module_name (module_name_id); else if ((s=strchr (program_name, '|')) && s[1] == '-' && s[2]=='-') { /* Hack to insert an additional option on the command line. */ program = xtrystrdup (program_name); if (!program) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); xfree (sockname); assuan_release (ctx); return tmperr; } p = strchr (program, '|'); *p++ = 0; program_arg = p; } if (verbose) log_info (_("no running %s - starting '%s'\n"), printed_name, program_name); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, status_start_line, NULL); /* We better pass an absolute home directory to the service just * in case the service does not convert the passed name to an * absolute one (which it should do). */ abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); if (!abs_homedir) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error building filename: %s\n", gpg_strerror (tmperr)); xfree (sockname); assuan_release (ctx); xfree (program); return tmperr; } if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); xfree (sockname); assuan_release (ctx); xfree (abs_homedir); xfree (program); return tmperr; } i = 0; argv[i++] = "--homedir"; argv[i++] = abs_homedir; if (module_name_id == GNUPG_MODULE_NAME_AGENT) argv[i++] = "--use-standard-socket"; if (program_arg) argv[i++] = program_arg; argv[i++] = "--daemon"; argv[i++] = NULL; if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose)) && assuan_socket_connect (ctx, sockname, 0, connect_flags)) { #ifdef HAVE_W32_SYSTEM err = gnupg_process_spawn (program? program : program_name, argv, - GNUPG_PROCESS_DETACHED, - NULL, NULL, NULL); + GNUPG_PROCESS_DETACHED, NULL, NULL); #else /*!W32*/ err = gnupg_process_spawn (program? program : program_name, argv, - 0, NULL, NULL, NULL); + 0, NULL, NULL); #endif /*!W32*/ if (err) log_error ("failed to start %s '%s': %s\n", printed_name, program? program : program_name, gpg_strerror (err)); else err = wait_for_sock (seconds_to_wait, module_name_id, sockname, connect_flags, verbose, ctx, &did_success_msg); } unlock_spawning (&lock, lock_name); xfree (abs_homedir); xfree (program); } xfree (sockname); if (err) { 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); return gpg_err_make (errsource, no_service_err); } if (debug && !did_success_msg) log_debug ("connection to the %s established\n", printed_name); if (module_name_id == GNUPG_MODULE_NAME_AGENT) err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (!err && module_name_id == GNUPG_MODULE_NAME_AGENT) { err = send_pinentry_environment (ctx, errsource, opt_lc_ctype, opt_lc_messages, session_env); if (gpg_err_code (err) == GPG_ERR_FORBIDDEN && gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT) { /* Check whether the agent is in restricted mode. */ if (!assuan_transact (ctx, "GETINFO restricted", NULL, NULL, NULL, NULL, NULL, NULL)) { if (verbose) log_info (_("connection to the agent is in restricted mode\n")); err = 0; } } } if (err) { assuan_release (ctx); return err; } *r_ctx = ctx; return 0; } /* Try to connect to the agent or start a new one. */ gpg_error_t start_new_gpg_agent (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *agent_program, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, 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, 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 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, 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, 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 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, 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 flags &= ~ASSHELP_FLAG_AUTOSTART; /* Clear flag. */ #endif return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR, errsource, dirmngr_program, NULL, NULL, NULL, flags, verbose, debug, status_cb, status_cb_arg); } /* Return the version of a server using "GETINFO version". On success 0 is returned and R_VERSION receives a malloced string with the version which must be freed by the caller. On error NULL is stored at R_VERSION and an error code returned. Mode is in general 0 but certain values may be used to modify the used version command: MODE == 0 = Use "GETINFO version" MODE == 2 - Use "SCD GETINFO version" */ gpg_error_t get_assuan_server_version (assuan_context_t ctx, int mode, char **r_version) { gpg_error_t err; membuf_t data; init_membuf (&data, 64); err = assuan_transact (ctx, mode == 2? "SCD GETINFO version" /**/ : "GETINFO version", put_membuf_cb, &data, NULL, NULL, NULL, NULL); if (err) { xfree (get_membuf (&data, NULL)); *r_version = NULL; } else { put_membuf (&data, "", 1); *r_version = get_membuf (&data, NULL); if (!*r_version) err = gpg_error_from_syserror (); } return err; } /* Print a warning if the server's version number is less than our * version number. Returns an error code on a connection problem. * CTX is the Assuan context, SERVERNAME is the name of teh server, * STATUS_FUNC and STATUS_FUNC_DATA is a callback to emit status * messages. If PRINT_HINTS is set additional hints are printed. For * MODE see get_assuan_server_version. */ gpg_error_t warn_server_version_mismatch (assuan_context_t ctx, const char *servername, int mode, gpg_error_t (*status_func)(ctrl_t ctrl, int status_no, ...), void *status_func_ctrl, int print_hints) { gpg_error_t err; char *serverversion; const char *myversion = gpgrt_strusage (13); err = get_assuan_server_version (ctx, mode, &serverversion); if (err) log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR, _("error getting version from '%s': %s\n"), servername, gpg_strerror (err)); else if (compare_version_strings (serverversion, myversion) < 0) { char *warn; warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"), servername, serverversion, myversion); if (!warn) err = gpg_error_from_syserror (); else { log_info (_("WARNING: %s\n"), warn); if (print_hints) { log_info (_("Note: Outdated servers may lack important" " security fixes.\n")); log_info (_("Note: Use the command \"%s\" to restart them.\n"), "gpgconf --kill all"); } if (status_func) status_func (status_func_ctrl, STATUS_WARNING, "server_version_mismatch 0", warn, NULL); xfree (warn); } } xfree (serverversion); return err; } diff --git a/common/exechelp-posix.c b/common/exechelp-posix.c index 3f124ab80..97d8fa4ad 100644 --- a/common/exechelp-posix.c +++ b/common/exechelp-posix.c @@ -1,1026 +1,1084 @@ /* exechelp.c - Fork and exec helpers for POSIX * Copyright (C) 2004, 2007-2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2004, 2006-2012, 2014-2017 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 GPL-2.0+) */ #include <config.h> #if defined(HAVE_W32_SYSTEM) #error This code is only used on POSIX #endif #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <assert.h> #ifdef HAVE_SIGNAL_H # include <signal.h> #endif #include <unistd.h> #include <fcntl.h> #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ #undef HAVE_NPTH #undef USE_NPTH #endif #ifdef HAVE_NPTH #include <npth.h> #endif #include <sys/wait.h> #ifdef HAVE_GETRLIMIT #include <sys/time.h> #include <sys/resource.h> #endif /*HAVE_GETRLIMIT*/ #ifdef HAVE_STAT # include <sys/stat.h> #endif #if __linux__ # include <sys/types.h> # include <dirent.h> #endif /*__linux__ */ #include "util.h" #include "i18n.h" #include "sysutils.h" #include "exechelp.h" /* Helper */ static inline gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } /* Return the maximum number of currently allowed open file descriptors. Only useful on POSIX systems but returns a value on other systems too. */ int get_max_fds (void) { int max_fds = -1; #ifdef HAVE_GETRLIMIT struct rlimit rl; /* Under Linux we can figure out the highest used file descriptor by * reading /proc/PID/fd. This is in the common cases much fast than * for example doing 4096 close calls where almost all of them will * fail. On a system with a limit of 4096 files and only 8 files * open with the highest number being 10, we speedup close_all_fds * from 125ms to 0.4ms including readdir. * * Another option would be to close the file descriptors as returned * from reading that directory - however then we need to snapshot * that list before starting to close them. */ #ifdef __linux__ { DIR *dir = NULL; struct dirent *dir_entry; const char *s; int x; dir = opendir ("/proc/self/fd"); if (dir) { while ((dir_entry = readdir (dir))) { s = dir_entry->d_name; if ( *s < '0' || *s > '9') continue; x = atoi (s); if (x > max_fds) max_fds = x; } closedir (dir); } if (max_fds != -1) return max_fds + 1; } #endif /* __linux__ */ # ifdef RLIMIT_NOFILE if (!getrlimit (RLIMIT_NOFILE, &rl)) max_fds = rl.rlim_max; # endif # ifdef RLIMIT_OFILE if (max_fds == -1 && !getrlimit (RLIMIT_OFILE, &rl)) max_fds = rl.rlim_max; # endif #endif /*HAVE_GETRLIMIT*/ #ifdef _SC_OPEN_MAX if (max_fds == -1) { long int scres = sysconf (_SC_OPEN_MAX); if (scres >= 0) max_fds = scres; } #endif #ifdef _POSIX_OPEN_MAX if (max_fds == -1) max_fds = _POSIX_OPEN_MAX; #endif #ifdef OPEN_MAX if (max_fds == -1) max_fds = OPEN_MAX; #endif if (max_fds == -1) max_fds = 256; /* Arbitrary limit. */ /* AIX returns INT32_MAX instead of a proper value. We assume that this is always an error and use an arbitrary limit. */ #ifdef INT32_MAX if (max_fds == INT32_MAX) max_fds = 256; #endif return max_fds; } /* Close all file descriptors starting with descriptor FIRST. If EXCEPT is not NULL, it is expected to be a list of file descriptors which shall not be closed. This list shall be sorted in ascending order with the end marked by -1. */ void -close_all_fds (int first, int *except) +close_all_fds (int first, const int *except) { int max_fd = get_max_fds (); int fd, i, except_start; if (except) { except_start = 0; for (fd=first; fd < max_fd; fd++) { for (i=except_start; except[i] != -1; i++) { if (except[i] == fd) { /* If we found the descriptor in the exception list we can start the next compare run at the next index because the exception list is ordered. */ except_start = i + 1; break; } } if (except[i] == -1) close (fd); } } else { for (fd=first; fd < max_fd; fd++) close (fd); } gpg_err_set_errno (0); } /* Returns an array with all currently open file descriptors. The end of the array is marked by -1. The caller needs to release this array using the *standard free* and not with xfree. This allow the use of this function right at startup even before libgcrypt has been initialized. Returns NULL on error and sets ERRNO accordingly. */ int * get_all_open_fds (void) { int *array; size_t narray; int fd, max_fd, idx; #ifndef HAVE_STAT array = calloc (1, sizeof *array); if (array) array[0] = -1; #else /*HAVE_STAT*/ struct stat statbuf; max_fd = get_max_fds (); narray = 32; /* If you change this change also t-exechelp.c. */ array = calloc (narray, sizeof *array); if (!array) return NULL; /* Note: The list we return is ordered. */ for (idx=0, fd=0; fd < max_fd; fd++) if (!(fstat (fd, &statbuf) == -1 && errno == EBADF)) { if (idx+1 >= narray) { int *tmp; narray += (narray < 256)? 32:256; tmp = realloc (array, narray * sizeof *array); if (!tmp) { free (array); return NULL; } array = tmp; } array[idx++] = fd; } array[idx] = -1; #endif /*HAVE_STAT*/ return array; } static gpg_error_t do_create_pipe (int filedes[2]) { gpg_error_t err = 0; if (pipe (filedes) == -1) { err = my_error_from_syserror (); filedes[0] = filedes[1] = -1; } return err; } static gpg_error_t create_pipe_and_estream (int filedes[2], estream_t *r_fp, int outbound, int nonblock) { gpg_error_t err; if (pipe (filedes) == -1) { err = my_error_from_syserror (); log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); filedes[0] = filedes[1] = -1; *r_fp = NULL; return err; } if (!outbound) *r_fp = es_fdopen (filedes[0], nonblock? "r,nonblock" : "r"); else *r_fp = es_fdopen (filedes[1], nonblock? "w,nonblock" : "w"); if (!*r_fp) { err = my_error_from_syserror (); log_error (_("error creating a stream for a pipe: %s\n"), gpg_strerror (err)); close (filedes[0]); close (filedes[1]); filedes[0] = filedes[1] = -1; return err; } return 0; } /* Portable function to create a pipe. Under Windows the write end is inheritable. If R_FP is not NULL, an estream is created for the read end and stored at R_FP. */ gpg_error_t gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) { if (r_fp) return create_pipe_and_estream (filedes, r_fp, 0, nonblock); else return do_create_pipe (filedes); } /* Portable function to create a pipe. Under Windows the read end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) { if (r_fp) return create_pipe_and_estream (filedes, r_fp, 1, nonblock); else return do_create_pipe (filedes); } /* Portable function to create a pipe. Under Windows both ends are inheritable. */ gpg_error_t gnupg_create_pipe (int filedes[2]) { return do_create_pipe (filedes); } /* Close the end of a pipe. */ void gnupg_close_pipe (int fd) { if (fd != -1) close (fd); } #include <sys/socket.h> 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; }; 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; } } static void pre_syscall (void) { if (pre_syscall_func) pre_syscall_func (); } static void post_syscall (void) { if (post_syscall_func) post_syscall_func (); } static gpg_err_code_t do_create_socketpair (int filedes[2]) { gpg_error_t err = 0; pre_syscall (); if (socketpair (AF_LOCAL, SOCK_STREAM, 0, filedes) == -1) { err = gpg_err_code_from_syserror (); filedes[0] = filedes[1] = -1; } post_syscall (); return err; } static int posix_open_null (int for_write) { int fd; 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) +struct gnupg_spawn_actions { + int fd[3]; + const int *except_fds; + char **environ; + void (*atfork) (void *); + void *atfork_arg; +}; + +gpg_err_code_t +gnupg_spawn_actions_new (gnupg_spawn_actions_t *r_act) { - 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); + gnupg_spawn_actions_t act; + int i; + + *r_act = NULL; + + act = xtrycalloc (1, sizeof (struct gnupg_spawn_actions)); + if (act == NULL) + return gpg_err_code_from_syserror (); + + for (i = 0; i <= 2; i++) + act->fd[i] = -1; + + *r_act = act; + return 0; +} + +void +gnupg_spawn_actions_release (gnupg_spawn_actions_t act) +{ + if (!act) + return; + + xfree (act); +} + +void +gnupg_spawn_actions_set_environ (gnupg_spawn_actions_t act, + char **environ_for_child) +{ + act->environ = environ_for_child; +} + +void +gnupg_spawn_actions_set_atfork (gnupg_spawn_actions_t act, + void (*atfork)(void *), void *arg) +{ + act->atfork = atfork; + act->atfork_arg = arg; +} + +void +gnupg_spawn_actions_set_redirect (gnupg_spawn_actions_t act, + int in, int out, int err) +{ + act->fd[0] = in; + act->fd[1] = out; + act->fd[2] = err; +} + +void +gnupg_spawn_actions_set_inherit_fds (gnupg_spawn_actions_t act, + const int *fds) +{ + act->except_fds = fds; } static void -my_exec (const char *pgmname, const char *argv[], struct spawn_cb_arg *sca) +my_exec (const char *pgmname, const char *argv[], gnupg_spawn_actions_t act) { int i; /* Assign /dev/null to unused FDs. */ for (i = 0; i <= 2; i++) - if (sca->fds[i] == -1) - sca->fds[i] = posix_open_null (i); + if (act->fd[i] == -1) + act->fd[i] = posix_open_null (i); /* Connect the standard files. */ for (i = 0; i <= 2; i++) - if (sca->fds[i] != i) + if (act->fd[i] != i) { - if (dup2 (sca->fds[i], i) == -1) + if (dup2 (act->fd[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 + * We don't close act->fd[i] here, but close them by * close_all_fds. Note that there may be same one in three of - * sca->fds[i]. + * act->fd[i]. */ } /* Close all other files. */ - close_all_fds (3, sca->except_fds); + close_all_fds (3, act->except_fds); + + if (act->environ) + environ = act->environ; + + if (act->atfork) + act->atfork (act->atfork_arg); 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) + gnupg_spawn_actions_t act) { gpg_err_code_t ec; pid_t pid; /* FIXME: Is this GnuPG specific or should we keep it. */ if (getuid() != geteuid()) { xfree (argv); return GPG_ERR_BUG; } if (access (pgmname, X_OK)) { ec = gpg_err_code_from_syserror (); xfree (argv); return ec; } 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; } if (!pid) { pid_t pid2; - struct spawn_cb_arg sca; 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. */ - call_spawn_cb (&sca, -1, -1, -1, spawn_cb, spawn_cb_arg); - - my_exec (pgmname, argv, &sca); + my_exec (pgmname, argv, act); /*NOTREACHED*/ } 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", + log_error ("waitpid failed in spawn_detached: %s", gpg_strerror (ec)); return ec; } else post_syscall (); return 0; } -void -gnupg_spawn_helper (struct spawn_cb_arg *sca) -{ - int *user_except = sca->arg; - sca->except_fds = user_except; -} - 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, + unsigned int flags, gnupg_spawn_actions_t act, 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; + struct gnupg_spawn_actions act_default; + + if (!act) + { + memset (&act_default, 0, sizeof (act_default)); + for (i = 0; i <= 2; i++) + act_default.fd[i] = -1; + act = &act_default; + } check_syscall_func (); 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; if (argv1) for (i=0, j=1; argv1[i]; i++, j++) argv[j] = argv1[i]; if ((flags & GNUPG_PROCESS_DETACHED)) { if ((flags & GNUPG_PROCESS_STDFDS_SETTING)) { xfree (argv); return GPG_ERR_INV_FLAG; } /* 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); + return spawn_detached (pgmname, argv, act); } process = xtrycalloc (1, sizeof (struct gnupg_process)); if (process == NULL) { xfree (argv); return gpg_err_code_from_syserror (); } process->pgmname = pgmname; process->flags = flags; 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; } 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 ((flags & GNUPG_PROCESS_STDERR_PIPE)) { 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 ((flags & GNUPG_PROCESS_STDERR_KEEP)) { fd_err[0] = -1; fd_err[1] = 2; } else { fd_err[0] = -1; fd_err[1] = -1; } 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)); 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; } if (!pid) { - 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); + if (act->fd[0] < 0) + act->fd[0] = fd_in[0]; + if (act->fd[1] < 0) + act->fd[1] = fd_out[1]; + if (act->fd[2] < 0) + act->fd[2] = fd_err[1]; /* Run child. */ - my_exec (pgmname, argv, &sca); + my_exec (pgmname, argv, act); /*NOTREACHED*/ } 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) { ec = gnupg_process_wait (process, 1); gnupg_process_release (process); return ec; } *r_process = process; return 0; } static gpg_err_code_t process_kill (gnupg_process_t process, int sig) { gpg_err_code_t ec = 0; pid_t pid = process->pid; 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) { *r_fd_in = process->fd_in; process->fd_in = -1; } if (r_fd_out) { *r_fd_out = process->fd_out; process->fd_out = -1; } if (r_fd_err) { *r_fd_err = process->fd_err; process->fd_err = -1; } return 0; } 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; 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; } static gpg_err_code_t process_vctl (gnupg_process_t process, unsigned int request, va_list arg_ptr) { switch (request) { case GNUPG_PROCESS_NOP: return 0; case GNUPG_PROCESS_GET_PROC_ID: { int *r_id = va_arg (arg_ptr, int *); if (r_id == NULL) return GPG_ERR_INV_VALUE; *r_id = (int)process->pid; return 0; } case GNUPG_PROCESS_GET_EXIT_ID: { int status = process->wstatus; int *r_exit_status = va_arg (arg_ptr, int *); if (!process->terminated) return GPG_ERR_UNFINISHED; if (WIFEXITED (status)) { if (r_exit_status) *r_exit_status = WEXITSTATUS (status); } else *r_exit_status = -1; 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; } return GPG_ERR_UNKNOWN_COMMAND; } gpg_err_code_t gnupg_process_ctl (gnupg_process_t process, unsigned int request, ...) { 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; } gpg_err_code_t gnupg_process_wait (gnupg_process_t process, int hang) { gpg_err_code_t ec; int status; pid_t pid; if (process->terminated) /* Already terminated. */ return 0; pre_syscall (); while ((pid = waitpid (process->pid, &status, hang? 0: WNOHANG)) == (pid_t)(-1) && errno == EINTR); post_syscall (); if (pid == (pid_t)(-1)) { ec = gpg_err_code_from_syserror (); log_error (_("waiting for process %d to terminate failed: %s\n"), (int)pid, gpg_strerror (ec)); } else if (!pid) { ec = GPG_ERR_TIMEOUT; /* Still running. */ } else { process->terminated = 1; process->wstatus = status; ec = 0; } return ec; } void gnupg_process_release (gnupg_process_t process) { if (!process) return; if (!process->terminated) { gnupg_process_terminate (process); gnupg_process_wait (process, 1); } xfree (process); } gpg_err_code_t gnupg_process_wait_list (gnupg_process_t *process_list, int count, int hang) { gpg_err_code_t ec = 0; int i; for (i = 0; i < count; i++) { 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 d1764d1f6..46fb9ae92 100644 --- a/common/exechelp-w32.c +++ b/common/exechelp-w32.c @@ -1,1239 +1,1298 @@ /* exechelp-w32.c - Fork and exec helpers for W32. * Copyright (C) 2004, 2007-2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2004, 2006-2012, 2014-2017 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 GPL-2.0+) */ #include <config.h> #if !defined(HAVE_W32_SYSTEM) #error This code is only used on W32. #else #define _WIN32_WINNT 0x600 #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <assert.h> #ifdef HAVE_SIGNAL_H # include <signal.h> #endif #include <unistd.h> #include <fcntl.h> #ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ #undef HAVE_NPTH #undef USE_NPTH #endif #ifdef HAVE_NPTH #include <npth.h> #endif #ifdef HAVE_STAT # include <sys/stat.h> #endif #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 /* It seems Vista doesn't grok X_OK and so fails access() tests. Previous versions interpreted X_OK as F_OK anyway, so we'll just use F_OK directly. */ #undef X_OK #define X_OK F_OK /* We assume that a HANDLE can be represented by an intptr_t which should be true for all systems (HANDLE is defined as void *). Further we assume that -1 denotes an invalid handle. */ # define fd_to_handle(a) ((HANDLE)(a)) # define handle_to_fd(a) ((intptr_t)(a)) # define pid_to_handle(a) ((HANDLE)(a)) # define handle_to_pid(a) ((int)(a)) /* Helper */ static inline gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static inline gpg_error_t my_error (int errcode) { return gpg_err_make (default_errsource, errcode); } /* Return the maximum number of currently allowed open file descriptors. Only useful on POSIX systems but returns a value on other systems too. */ int get_max_fds (void) { int max_fds = -1; #ifdef OPEN_MAX if (max_fds == -1) max_fds = OPEN_MAX; #endif if (max_fds == -1) max_fds = 256; /* Arbitrary limit. */ return max_fds; } /* Under Windows this is a dummy function. */ void -close_all_fds (int first, int *except) +close_all_fds (int first, const int *except) { (void)first; (void)except; } /* Returns an array with all currently open file descriptors. The end * of the array is marked by -1. The caller needs to release this * array using the *standard free* and not with xfree. This allow the * use of this function right at startup even before libgcrypt has * been initialized. Returns NULL on error and sets ERRNO * accordingly. Note that fstat prints a warning to DebugView for all * invalid fds which is a bit annoying. We actually do not need this * function in real code (close_all_fds is a dummy anyway) but we keep * it for use by t-exechelp.c. */ int * get_all_open_fds (void) { int *array; size_t narray; int fd, max_fd, idx; #ifndef HAVE_STAT array = calloc (1, sizeof *array); if (array) array[0] = -1; #else /*HAVE_STAT*/ struct stat statbuf; max_fd = get_max_fds (); narray = 32; /* If you change this change also t-exechelp.c. */ array = calloc (narray, sizeof *array); if (!array) return NULL; /* Note: The list we return is ordered. */ for (idx=0, fd=0; fd < max_fd; fd++) if (!(fstat (fd, &statbuf) == -1 && errno == EBADF)) { if (idx+1 >= narray) { int *tmp; narray += (narray < 256)? 32:256; tmp = realloc (array, narray * sizeof *array); if (!tmp) { free (array); return NULL; } array = tmp; } array[idx++] = fd; } array[idx] = -1; #endif /*HAVE_STAT*/ return array; } /* Helper function to build_w32_commandline. */ static char * build_w32_commandline_copy (char *buffer, const char *string) { char *p = buffer; const char *s; if (!*string) /* Empty string. */ p = stpcpy (p, "\"\""); else if (strpbrk (string, " \t\n\v\f\"")) { /* Need to do some kind of quoting. */ p = stpcpy (p, "\""); for (s=string; *s; s++) { *p++ = *s; if (*s == '\"') *p++ = *s; } *p++ = '\"'; *p = 0; } else p = stpcpy (p, string); return p; } /* Build a command line for use with W32's CreateProcess. On success CMDLINE gets the address of a newly allocated string. */ static gpg_error_t build_w32_commandline (const char *pgmname, const char * const *argv, char **cmdline) { int i, n; const char *s; char *buf, *p; *cmdline = NULL; n = 0; s = pgmname; n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ for (; *s; s++) if (*s == '\"') n++; /* Need to double inner quotes. */ for (i=0; (s=argv[i]); i++) { n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */ for (; *s; s++) if (*s == '\"') n++; /* Need to double inner quotes. */ } n++; buf = p = xtrymalloc (n); if (!buf) return my_error_from_syserror (); p = build_w32_commandline_copy (p, pgmname); for (i=0; argv[i]; i++) { *p++ = ' '; p = build_w32_commandline_copy (p, argv[i]); } *cmdline= buf; return 0; } #define INHERIT_READ 1 #define INHERIT_WRITE 2 #define INHERIT_BOTH (INHERIT_READ|INHERIT_WRITE) /* Create pipe. FLAGS indicates which ends are inheritable. */ static int create_inheritable_pipe (HANDLE filedes[2], int flags) { HANDLE r, w; SECURITY_ATTRIBUTES sec_attr; memset (&sec_attr, 0, sizeof sec_attr ); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = TRUE; if (!CreatePipe (&r, &w, &sec_attr, 0)) return -1; if ((flags & INHERIT_READ) == 0) if (! SetHandleInformation (r, HANDLE_FLAG_INHERIT, 0)) goto fail; if ((flags & INHERIT_WRITE) == 0) if (! SetHandleInformation (w, HANDLE_FLAG_INHERIT, 0)) goto fail; filedes[0] = r; filedes[1] = w; return 0; fail: log_error ("SetHandleInformation failed: %s\n", w32_strerror (-1)); CloseHandle (r); CloseHandle (w); return -1; } static HANDLE w32_open_null (int for_write) { HANDLE hfile; hfile = CreateFileW (L"nul", for_write? GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hfile == INVALID_HANDLE_VALUE) log_debug ("can't open 'nul': %s\n", w32_strerror (-1)); return hfile; } static gpg_error_t create_pipe_and_estream (int filedes[2], int flags, estream_t *r_fp, int outbound, int nonblock) { gpg_error_t err = 0; HANDLE fds[2]; es_syshd_t syshd; filedes[0] = filedes[1] = -1; err = my_error (GPG_ERR_GENERAL); if (!create_inheritable_pipe (fds, flags)) { filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), O_RDONLY); if (filedes[0] == -1) { log_error ("failed to translate osfhandle %p\n", fds[0]); CloseHandle (fds[1]); } else { filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), O_APPEND); if (filedes[1] == -1) { log_error ("failed to translate osfhandle %p\n", fds[1]); close (filedes[0]); filedes[0] = -1; CloseHandle (fds[1]); } else err = 0; } } if (! err && r_fp) { syshd.type = ES_SYSHD_HANDLE; if (!outbound) { syshd.u.handle = fds[0]; *r_fp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r"); } else { syshd.u.handle = fds[1]; *r_fp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w"); } if (!*r_fp) { err = my_error_from_syserror (); log_error (_("error creating a stream for a pipe: %s\n"), gpg_strerror (err)); close (filedes[0]); close (filedes[1]); filedes[0] = filedes[1] = -1; return err; } } return err; } /* Portable function to create a pipe. Under Windows the write end is inheritable. If R_FP is not NULL, an estream is created for the read end and stored at R_FP. */ gpg_error_t gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) { return create_pipe_and_estream (filedes, INHERIT_WRITE, r_fp, 0, nonblock); } /* Portable function to create a pipe. Under Windows the read end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock) { return create_pipe_and_estream (filedes, INHERIT_READ, r_fp, 1, nonblock); } /* Portable function to create a pipe. Under Windows both ends are inheritable. */ gpg_error_t gnupg_create_pipe (int filedes[2]) { return create_pipe_and_estream (filedes, INHERIT_BOTH, NULL, 0, 0); } /* Close the end of a pipe. */ void gnupg_close_pipe (int fd) { if (fd != -1) close (fd); } +struct gnupg_spawn_actions { + void *hd[3]; + void **inherit_hds; + char *env; +}; + 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; } } static void pre_syscall (void) { if (pre_syscall_func) pre_syscall_func (); } static void post_syscall (void) { if (post_syscall_func) post_syscall_func (); } /* * 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) { memset (&osvi,0,sizeof(osvi)); osvi.dwOSVersionInfoSize = sizeof(osvi); GetVersionEx (&osvi); /* The feature is available on Vista or later. */ is_vista_or_later = (osvi.dwMajorVersion >= 6); } return is_vista_or_later; } static gpg_err_code_t -spawn_detached (const char *pgmname, char *cmdline, - void (*spawn_cb) (struct spawn_cb_arg *), void *spawn_cb_arg) +spawn_detached (const char *pgmname, char *cmdline, gnupg_spawn_actions_t act) { 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; + int i; ec = gnupg_access (pgmname, X_OK); if (ec) { xfree (cmdline); return ec; } 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); + i = 0; + if (act->hd[0] != INVALID_HANDLE_VALUE) + i++; + if (act->hd[1] != INVALID_HANDLE_VALUE) + i++; + if (act->hd[2] != INVALID_HANDLE_VALUE) + i++; - if (sca.inherit_hds) + if (i != 0 || act->inherit_hds) { SIZE_T attr_list_size = 0; HANDLE hd[16]; - HANDLE *hd_p = sca.inherit_hds; + HANDLE *hd_p = act->inherit_hds; int j = 0; + if (act->hd[0] != INVALID_HANDLE_VALUE) + hd[j++] = act->hd[0]; + if (act->hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = act->hd[1]; + if (act->hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = act->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) { 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; /* Start the process. */ si.StartupInfo.cb = sizeof (si); - si.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; + si.StartupInfo.dwFlags = ((i > 0 ? STARTF_USESTDHANDLES : 0) + | STARTF_USESHOWWINDOW); si.StartupInfo.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE; cr_flags = (CREATE_DEFAULT_ERROR_MODE | GetPriorityClass (GetCurrentProcess ()) | CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS); /* Take care: CreateProcessW may modify wpgmname */ if (!(wpgmname = utf8_to_wchar (pgmname))) ret = 0; else if (!(wcmdline = utf8_to_wchar (cmdline))) ret = 0; else 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. */ + act->env, /* 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(detached) failed: %d\n", (int)GetLastError ()); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); return GPG_ERR_GENERAL; } if (si.lpAttributeList) DeleteProcThreadAttributeList (si.lpAttributeList); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); /* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */ /* " dwProcessID=%d dwThreadId=%d\n", */ /* pi.hProcess, pi.hThread, */ /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ /* Note: AllowSetForegroundWindow doesn't make sense for background process. */ CloseHandle (pi.hThread); CloseHandle (pi.hProcess); return 0; } +gpg_err_code_t +gnupg_spawn_actions_new (gnupg_spawn_actions_t *r_act) +{ + gnupg_spawn_actions_t act; + int i; + + *r_act = NULL; + + act = xtrycalloc (1, sizeof (struct gnupg_spawn_actions)); + if (act == NULL) + return gpg_err_code_from_syserror (); + + for (i = 0; i <= 2; i++) + act->hd[i] = INVALID_HANDLE_VALUE; + + *r_act = act; + return 0; +} + +void +gnupg_spawn_actions_release (gnupg_spawn_actions_t act) +{ + if (!act) + return; + + xfree (act); +} + +void +gnupg_spawn_actions_set_envvars (gnupg_spawn_actions_t act, char *env) +{ + act->env = env; +} + void -gnupg_spawn_helper (struct spawn_cb_arg *sca) +gnupg_spawn_actions_set_redirect (gnupg_spawn_actions_t act, + void *in, void *out, void *err) { - HANDLE *user_except = sca->arg; - sca->inherit_hds = user_except; + act->hd[0] = in; + act->hd[1] = out; + act->hd[2] = err; } +void +gnupg_spawn_actions_set_inherit_handles (gnupg_spawn_actions_t act, + void **handles) +{ + act->inherit_hds = handles; +} + + 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, + unsigned int flags, gnupg_spawn_actions_t act, gnupg_process_t *r_process) { gpg_err_code_t ec; gnupg_process_t process; SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, 0, 0, 0 }; STARTUPINFOEXW si; int cr_flags; char *cmdline; wchar_t *wcmdline = NULL; wchar_t *wpgmname = NULL; 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; + BOOL allow_foreground_window = FALSE; + struct gnupg_spawn_actions act_default; + + if (!act) + { + memset (&act_default, 0, sizeof (act_default)); + for (i = 0; i <= 2; i++) + act_default.hd[i] = INVALID_HANDLE_VALUE; + act = &act_default; + } 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; } /* 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); + return spawn_detached (pgmname, cmdline, act); } 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); + if (act->hd[0] == INVALID_HANDLE_VALUE) + act->hd[0] = hd_in[0]; + if (act->hd[1] == INVALID_HANDLE_VALUE) + act->hd[1] = hd_out[1]; + if (act->hd[2] == INVALID_HANDLE_VALUE) + act->hd[2] = hd_err[1]; i = 0; - if (sca.hd[0] != INVALID_HANDLE_VALUE) + if (act->hd[0] != INVALID_HANDLE_VALUE) i++; - if (sca.hd[1] != INVALID_HANDLE_VALUE) + if (act->hd[1] != INVALID_HANDLE_VALUE) i++; - if (sca.hd[2] != INVALID_HANDLE_VALUE) + if (act->hd[2] != INVALID_HANDLE_VALUE) i++; - if (i != 0 || sca.inherit_hds) + if (i != 0 || act->inherit_hds) { SIZE_T attr_list_size = 0; HANDLE hd[16]; - HANDLE *hd_p = sca.inherit_hds; + HANDLE *hd_p = act->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 (act->hd[0] != INVALID_HANDLE_VALUE) + hd[j++] = act->hd[0]; + if (act->hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = act->hd[1]; + if (act->hd[1] != INVALID_HANDLE_VALUE) + hd[j++] = act->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; /* 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]; + si.StartupInfo.hStdInput = act->hd[0]; + si.StartupInfo.hStdOutput = act->hd[1]; + si.StartupInfo.hStdError = act->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))) ret = 0; else if (!(wcmdline = utf8_to_wchar (cmdline))) ret = 0; else 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. */ + act->env, /* Environment. */ NULL, /* Use current drive/directory. */ - (STARTUPINFOW *)&si, /* Startup information. */ + (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: 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; } if (si.lpAttributeList) DeleteProcThreadAttributeList (si.lpAttributeList); xfree (wpgmname); xfree (wcmdline); xfree (cmdline); 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) + if (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 (); 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; } 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) { 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; } static gpg_err_code_t process_kill (gnupg_process_t process, unsigned int exitcode) { gpg_err_code_t ec = 0; pre_syscall (); if (TerminateProcess (process->hProcess, exitcode)) ec = gpg_err_code_from_syserror (); post_syscall (); return ec; } static gpg_err_code_t process_vctl (gnupg_process_t process, unsigned int request, va_list arg_ptr) { switch (request) { case GNUPG_PROCESS_NOP: return 0; case GNUPG_PROCESS_GET_PROC_ID: { int *r_id = va_arg (arg_ptr, int *); if (r_id == NULL) return GPG_ERR_INV_VALUE; *r_id = (int)GetProcessId (process->hProcess); return 0; } case GNUPG_PROCESS_GET_EXIT_ID: { int *r_exit_status = va_arg (arg_ptr, int *); unsigned long exit_code; *r_exit_status = -1; 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: break; } return GPG_ERR_UNKNOWN_COMMAND; } gpg_err_code_t gnupg_process_ctl (gnupg_process_t process, unsigned int request, ...) { 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; } gpg_err_code_t gnupg_process_wait (gnupg_process_t process, int hang) { gpg_err_code_t ec; int code; if (process->hProcess == INVALID_HANDLE_VALUE) return 0; pre_syscall (); code = WaitForSingleObject (process->hProcess, hang? INFINITE : 0); post_syscall (); switch (code) { case WAIT_TIMEOUT: ec = GPG_ERR_TIMEOUT; /* Still running. */ break; case WAIT_FAILED: log_error (_("waiting for process to terminate failed: ec=%d\n"), (int)GetLastError ()); ec = GPG_ERR_GENERAL; break; case WAIT_OBJECT_0: process->terminated = 1; ec = 0; break; default: log_debug ("WaitForSingleObject returned unexpected code %d\n", code); ec = GPG_ERR_GENERAL; break; } return ec; } gpg_err_code_t gnupg_process_terminate (gnupg_process_t process) { return process_kill (process, 1); } void gnupg_process_release (gnupg_process_t process) { if (!process) return; if (!process->terminated) { gnupg_process_terminate (process); gnupg_process_wait (process, 1); } CloseHandle (process->hProcess); xfree (process); } gpg_err_code_t gnupg_process_wait_list (gnupg_process_t *process_list, int count, int hang) { gpg_err_code_t ec = 0; int i; for (i = 0; i < count; i++) { if (process_list[i]->terminated) continue; ec = gnupg_process_wait (process_list[i], hang); if (ec) break; } return ec; } diff --git a/common/exechelp.h b/common/exechelp.h index 0370b23a4..93ba12eeb 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -1,175 +1,170 @@ /* exechelp.h - Definitions for the fork and exec helpers * Copyright (C) 2004, 2009, 2010 Free Software Foundation, Inc. * Copyright (C) 2004, 2006-2012, 2014-2017 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 GPL-2.0+) */ #ifndef GNUPG_COMMON_EXECHELP_H #define GNUPG_COMMON_EXECHELP_H /* Return the maximum number of currently allowed file descriptors. Only useful on POSIX systems. */ int get_max_fds (void); /* Close all file descriptors starting with descriptor FIRST. If EXCEPT is not NULL, it is expected to be a list of file descriptors which are not to close. This list shall be sorted in ascending order with its end marked by -1. */ -void close_all_fds (int first, int *except); +void close_all_fds (int first, const int *except); /* Returns an array with all currently open file descriptors. The end of the array is marked by -1. The caller needs to release this array using the *standard free* and not with xfree. This allow the use of this function right at startup even before libgcrypt has been initialized. Returns NULL on error and sets ERRNO accordingly. */ int *get_all_open_fds (void); /* Portable function to create a pipe. Under Windows the write end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock); /* Portable function to create a pipe. Under Windows the read end is inheritable. If R_FP is not NULL, an estream is created for the write end and stored at R_FP. */ gpg_error_t gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock); /* Portable function to create a pipe. Under Windows both ends are inheritable. */ gpg_error_t gnupg_create_pipe (int filedes[2]); /* Close the end of a pipe. */ void gnupg_close_pipe (int fd); /* The opaque type for a subprocess. */ typedef struct gnupg_process *gnupg_process_t; +typedef struct gnupg_spawn_actions *gnupg_spawn_actions_t; +gpg_err_code_t gnupg_spawn_actions_new (gnupg_spawn_actions_t *r_act); +void gnupg_spawn_actions_release (gnupg_spawn_actions_t act); #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 +void gnupg_spawn_actions_set_envvars (gnupg_spawn_actions_t, char *); +void gnupg_spawn_actions_set_redirect (gnupg_spawn_actions_t, + void *, void *, void *); +void gnupg_spawn_actions_set_inherit_handles (gnupg_spawn_actions_t, void **); #else -struct spawn_cb_arg { - int fds[3]; - int *except_fds; - void *arg; -}; +void gnupg_spawn_actions_set_environ (gnupg_spawn_actions_t, char **); +void gnupg_spawn_actions_set_redirect (gnupg_spawn_actions_t, int, int, int); +void gnupg_spawn_actions_set_inherit_fds (gnupg_spawn_actions_t, + const int *); +void gnupg_spawn_actions_set_atfork (gnupg_spawn_actions_t, + void (*atfork)(void *), 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[], +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_spawn_actions_t act, 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 3505c25f1..05504de98 100644 --- a/common/exectool.c +++ b/common/exectool.c @@ -1,673 +1,683 @@ /* exectool.c - Utility functions to execute a helper tool * Copyright (C) 2015 Werner Koch * Copyright (C) 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 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/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h> #include <errno.h> #include <assert.h> #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" #include "exectool.h" typedef struct { const char *pgmname; exec_tool_status_cb_t status_cb; void *status_cb_value; int cont; int quiet; size_t used; size_t buffer_size; char *buffer; } read_and_log_buffer_t; static inline gpg_error_t my_error_from_syserror (void) { return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); } static void read_and_log_stderr (read_and_log_buffer_t *state, es_poll_t *fderr) { gpg_error_t err; int c; if (!fderr) { /* Flush internal buffer. */ if (state->used) { const char *pname; int len; state->buffer[state->used] = 0; state->used = 0; pname = strrchr (state->pgmname, '/'); if (pname && pname != state->pgmname && pname[1]) pname++; else pname = state->pgmname; len = strlen (pname); if (state->status_cb && !strncmp (state->buffer, "[GNUPG:] ", 9) && state->buffer[9] >= 'A' && state->buffer[9] <= 'Z') { char *rest; rest = strchr (state->buffer + 9, ' '); if (!rest) { /* Set REST to an empty string. */ rest = state->buffer + strlen (state->buffer); } else { *rest++ = 0; trim_spaces (rest); } state->status_cb (state->status_cb_value, state->buffer + 9, rest); } else if (state->quiet) ; else if (!state->cont && !strncmp (state->buffer, pname, len) && strlen (state->buffer) > strlen (pname) && state->buffer[len] == ':' ) { /* PGMNAME plus colon is identical to the start of the output: print only the output. */ log_info ("%s\n", state->buffer); } else log_info ("%s%c %s\n", pname, state->cont? '+':':', state->buffer); } state->cont = 0; return; } for (;;) { c = es_fgetc (fderr->stream); if (c == EOF) { if (es_feof (fderr->stream)) { fderr->ignore = 1; /* Not anymore needed. */ } else if (es_ferror (fderr->stream)) { err = my_error_from_syserror (); log_error ("error reading stderr of '%s': %s\n", state->pgmname, gpg_strerror (err)); fderr->ignore = 1; /* Disable. */ } break; } else if (c == '\n') { read_and_log_stderr (state, NULL); } else { if (state->used >= state->buffer_size - 1) { if (state->status_cb) { /* A status callback requires that we have a full * line. Thus we need to enlarget the buffer in * this case. */ char *newbuffer; size_t newsize = state->buffer_size + 256; newbuffer = xtrymalloc (newsize); if (!newbuffer) { log_error ("error allocating memory for status cb: %s\n", gpg_strerror (my_error_from_syserror ())); /* We better disable the status CB in this case. */ state->status_cb = NULL; read_and_log_stderr (state, NULL); state->cont = 1; } else { memcpy (newbuffer, state->buffer, state->used); xfree (state->buffer); state->buffer = newbuffer; state->buffer_size = newsize; } } else { read_and_log_stderr (state, NULL); state->cont = 1; } } state->buffer[state->used++] = c; } } } /* A buffer to copy from one stream to another. */ struct copy_buffer { char buffer[4096]; char *writep; size_t nread; }; /* Initialize a copy buffer. */ static void copy_buffer_init (struct copy_buffer *c) { c->writep = c->buffer; c->nread = 0; } /* Securely wipe a copy buffer. */ static void copy_buffer_shred (struct copy_buffer *c) { if (c == NULL) return; wipememory (c->buffer, sizeof c->buffer); c->writep = NULL; c->nread = ~0U; } /* Copy data from SOURCE to SINK using copy buffer C. */ static gpg_error_t copy_buffer_do_copy (struct copy_buffer *c, estream_t source, estream_t sink) { gpg_error_t err; size_t nwritten = 0; if (c->nread == 0) { c->writep = c->buffer; if (es_read (source, c->buffer, sizeof c->buffer, &c->nread)) { err = my_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EAGAIN) return 0; /* We will just retry next time. */ return err; } log_assert (c->nread <= sizeof c->buffer); } if (c->nread == 0) return 0; /* Done copying. */ nwritten = 0; if (sink && es_write (sink, c->writep, c->nread, &nwritten)) err = my_error_from_syserror (); else err = 0; log_assert (nwritten <= c->nread); c->writep += nwritten; c->nread -= nwritten; log_assert (c->writep - c->buffer <= sizeof c->buffer); if (err) { if (gpg_err_code (err) == GPG_ERR_EAGAIN) return 0; /* We will just retry next time. */ return err; } if (sink && es_fflush (sink) && errno != EAGAIN) err = my_error_from_syserror (); return err; } /* Flush the remaining data to SINK. */ static gpg_error_t copy_buffer_flush (struct copy_buffer *c, estream_t sink) { gpg_error_t err = 0; size_t nwritten = 0; if (es_write (sink, c->writep, c->nread, &nwritten)) err = my_error_from_syserror (); log_assert (nwritten <= c->nread); c->writep += nwritten; c->nread -= nwritten; log_assert (c->writep - c->buffer <= sizeof c->buffer); if (err) return err; if (es_fflush (sink)) err = my_error_from_syserror (); return err; } /* 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 * the process's stdout is written to OUTPUT. If OUTPUT is NULL the * output is discarded. If INEXTRA is given, an additional input * stream will be passed to the child; to tell the child about this * ARGV is scanned and the first occurrence of an argument * "-&@INEXTRA@" is replaced by the concatenation of "-&" and the * child's file descriptor of the pipe created for the INEXTRA stream. * * On error a diagnostic is printed and an error code returned. */ gpg_error_t gnupg_exec_tool_stream (const char *pgmname, const char *argv[], estream_t input, estream_t inextra, estream_t output, exec_tool_status_cb_t status_cb, void *status_cb_value) { gpg_error_t err; gnupg_process_t proc = NULL; estream_t infp = NULL; estream_t extrafp = NULL; estream_t outfp = NULL, errfp = NULL; es_poll_t fds[4]; int exceptclose[2]; int extrapipe[2] = {-1, -1}; char extrafdbuf[20]; const char *argsave = NULL; int argsaveidx; int count; read_and_log_buffer_t fderrstate; struct copy_buffer *cpbuf_in = NULL, *cpbuf_out = NULL, *cpbuf_extra = NULL; int quiet = 0; + gnupg_spawn_actions_t act = NULL; memset (fds, 0, sizeof fds); memset (&fderrstate, 0, sizeof fderrstate); /* If the first argument to the program is "--quiet" avoid all extra * diagnostics. */ quiet = (argv && argv[0] && !strcmp (argv[0], "--quiet")); cpbuf_in = xtrymalloc (sizeof *cpbuf_in); if (cpbuf_in == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_in); cpbuf_out = xtrymalloc (sizeof *cpbuf_out); if (cpbuf_out == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_out); cpbuf_extra = xtrymalloc (sizeof *cpbuf_extra); if (cpbuf_extra == NULL) { err = my_error_from_syserror (); goto leave; } copy_buffer_init (cpbuf_extra); fderrstate.pgmname = pgmname; fderrstate.quiet = quiet; fderrstate.status_cb = status_cb; fderrstate.status_cb_value = status_cb_value; fderrstate.buffer_size = 256; fderrstate.buffer = xtrymalloc (fderrstate.buffer_size); if (!fderrstate.buffer) { err = my_error_from_syserror (); goto leave; } if (inextra) { err = gnupg_create_outbound_pipe (extrapipe, &extrafp, 1); if (err) { log_error ("error creating outbound pipe for extra fp: %s\n", gpg_strerror (err)); goto leave; } exceptclose[0] = extrapipe[0]; /* Do not close in child. */ exceptclose[1] = -1; /* Now find the argument marker and replace by the pipe's fd. Yeah, that is an ugly non-thread safe hack but it safes us to create a copy of the array. */ #ifdef HAVE_W32_SYSTEM snprintf (extrafdbuf, sizeof extrafdbuf, "-&%lu", (unsigned long)_get_osfhandle (extrapipe[0])); #else snprintf (extrafdbuf, sizeof extrafdbuf, "-&%d", extrapipe[0]); #endif for (argsaveidx=0; argv[argsaveidx]; argsaveidx++) if (!strcmp (argv[argsaveidx], "-&@INEXTRA@")) { argsave = argv[argsaveidx]; argv[argsaveidx] = extrafdbuf; break; } } else exceptclose[0] = -1; + err = gnupg_spawn_actions_new (&act); + if (err) + goto leave; + +#ifdef HAVE_W32_SYSTEM + gnupg_spawn_actions_set_inherit_handles (act, exceptclose); +#else + gnupg_spawn_actions_set_inherit_fds (act, exceptclose); +#endif 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_STDERR_PIPE), act, &proc); gnupg_process_get_streams (proc, GNUPG_PROCESS_STREAM_NONBLOCK, input? &infp : NULL, &outfp, &errfp); if (extrapipe[0] != -1) close (extrapipe[0]); if (argsave) argv[argsaveidx] = argsave; if (err) { if (!quiet) log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[0].stream = infp; fds[0].want_write = 1; if (!input) fds[0].ignore = 1; fds[1].stream = outfp; fds[1].want_read = 1; fds[2].stream = errfp; fds[2].want_read = 1; fds[3].stream = extrafp; fds[3].want_write = 1; if (!inextra) fds[3].ignore = 1; /* Now read as long as we have something to poll. We continue reading even after EOF or error on stdout so that we get the other error messages or remaining output. */ while (! (fds[1].ignore && fds[2].ignore)) { count = es_poll (fds, DIM(fds), -1); if (count == -1) { err = my_error_from_syserror (); log_error ("error polling '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (!count) { log_debug ("unexpected timeout while polling '%s'\n", pgmname); break; } if (fds[0].got_write) { err = copy_buffer_do_copy (cpbuf_in, input, fds[0].stream); if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (input)) { err = copy_buffer_flush (cpbuf_in, fds[0].stream); if (gpg_err_code (err) == GPG_ERR_EAGAIN) continue; /* Retry next time. */ if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[0].ignore = 1; /* ready. */ es_fclose (infp); infp = NULL; } } if (fds[3].got_write) { log_assert (inextra); err = copy_buffer_do_copy (cpbuf_extra, inextra, fds[3].stream); if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (inextra)) { err = copy_buffer_flush (cpbuf_extra, fds[3].stream); if (gpg_err_code (err) == GPG_ERR_EAGAIN) continue; /* Retry next time. */ if (err) { log_error ("error feeding data to '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[3].ignore = 1; /* ready. */ es_fclose (extrafp); extrafp = NULL; } } if (fds[1].got_read) { err = copy_buffer_do_copy (cpbuf_out, fds[1].stream, output); if (err) { log_error ("error reading data from '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } if (es_feof (fds[1].stream)) { err = copy_buffer_flush (cpbuf_out, output); if (err) { log_error ("error reading data from '%s': %s\n", pgmname, gpg_strerror (err)); goto leave; } fds[1].ignore = 1; /* ready. */ } } if (fds[2].got_read) read_and_log_stderr (&fderrstate, fds + 2); } read_and_log_stderr (&fderrstate, NULL); /* Flush. */ es_fclose (infp); infp = NULL; es_fclose (extrafp); extrafp = NULL; es_fclose (outfp); outfp = NULL; es_fclose (errfp); errfp = NULL; 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 && proc) gnupg_process_terminate (proc); es_fclose (infp); es_fclose (extrafp); es_fclose (outfp); es_fclose (errfp); gnupg_process_release (proc); + gnupg_spawn_actions_release (act); copy_buffer_shred (cpbuf_in); xfree (cpbuf_in); copy_buffer_shred (cpbuf_out); xfree (cpbuf_out); copy_buffer_shred (cpbuf_extra); xfree (cpbuf_extra); xfree (fderrstate.buffer); return err; } /* A dummy free function to pass to 'es_mopen'. */ static void nop_free (void *ptr) { (void) ptr; } /* Run the program PGMNAME with the command line arguments given in the NULL terminates array ARGV. If INPUT_STRING is not NULL it will be fed to stdin of the process. stderr is logged using log_info and the process's stdout is returned in a newly malloced buffer RESULT with the length stored at RESULTLEN if not given as NULL. A hidden Nul is appended to the output. On error NULL is stored at RESULT, a diagnostic is printed, and an error code returned. */ gpg_error_t gnupg_exec_tool (const char *pgmname, const char *argv[], const char *input_string, char **result, size_t *resultlen) { gpg_error_t err; estream_t input = NULL; estream_t output; size_t len; size_t nread; *result = NULL; if (resultlen) *resultlen = 0; if (input_string) { len = strlen (input_string); input = es_mopen ((char *) input_string, len, len, 0 /* don't grow */, NULL, nop_free, "rb"); if (! input) return my_error_from_syserror (); } output = es_fopenmem (0, "wb"); if (! output) { err = my_error_from_syserror (); goto leave; } err = gnupg_exec_tool_stream (pgmname, argv, input, NULL, output, NULL, NULL); if (err) goto leave; len = es_ftello (output); err = es_fseek (output, 0, SEEK_SET); if (err) goto leave; *result = xtrymalloc (len + 1); if (!*result) { err = my_error_from_syserror (); goto leave; } if (len) { if (es_read (output, *result, len, &nread)) { err = my_error_from_syserror (); goto leave; } if (nread != len) log_fatal ("%s: short read from memstream\n", __func__); } (*result)[len] = 0; if (resultlen) *resultlen = len; leave: es_fclose (input); es_fclose (output); if (err) { xfree (*result); *result = NULL; } return err; } diff --git a/common/t-exechelp.c b/common/t-exechelp.c index 2179ef2a0..f25c91d3a 100644 --- a/common/t-exechelp.c +++ b/common/t-exechelp.c @@ -1,336 +1,336 @@ /* t-exechelp.c - Module test for exechelp.c * Copyright (C) 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <assert.h> #include <unistd.h> #include "util.h" #include "exechelp.h" static int verbose; #ifndef HAVE_W32_SYSTEM static void print_open_fds (int *array) { int n; if (!verbose) return; for (n=0; array[n] != -1; n++) ; printf ("open file descriptors: %d", n); putchar (' '); putchar (' '); putchar ('('); for (n=0; array[n] != -1; n++) printf ("%d%s", array[n], array[n+1] == -1?"":" "); putchar (')'); putchar ('\n'); } static int * xget_all_open_fds (void) { int *array; array = get_all_open_fds (); if (!array) { fprintf (stderr, "%s:%d: get_all_open_fds failed: %s\n", __FILE__, __LINE__, strerror (errno)); exit (1); } return array; } /* That is a very crude test. To do a proper test we would need to fork a test process and best return information by some other means than file descriptors. */ static void test_close_all_fds (void) { int max_fd = get_max_fds (); int *array; int fd; int initial_count, count, n; #if 0 char buffer[100]; snprintf (buffer, sizeof buffer, "/bin/ls -l /proc/%d/fd", (int)getpid ()); system (buffer); #endif if (verbose) printf ("max. file descriptors: %d\n", max_fd); array = xget_all_open_fds (); print_open_fds (array); for (initial_count=n=0; array[n] != -1; n++) initial_count++; free (array); /* Some dups to get more file descriptors and close one. */ dup (1); dup (1); fd = dup (1); dup (1); close (fd); array = xget_all_open_fds (); if (verbose) print_open_fds (array); for (count=n=0; array[n] != -1; n++) count++; if (count != initial_count+3) { fprintf (stderr, "%s:%d: dup or close failed\n", __FILE__, __LINE__); exit (1); } free (array); /* Close the non standard ones. */ close_all_fds (3, NULL); /* Get a list to check whether they are all closed. */ array = xget_all_open_fds (); if (verbose) print_open_fds (array); for (count=n=0; array[n] != -1; n++) count++; if (count > initial_count) { fprintf (stderr, "%s:%d: not all files were closed\n", __FILE__, __LINE__); exit (1); } initial_count = count; free (array); /* Now let's check the realloc we use. We do this and the next tests only if we are allowed to open enough descriptors. */ if (get_max_fds () > 32) { int except[] = { 20, 23, 24, -1 }; for (n=initial_count; n < 31; n++) dup (1); array = xget_all_open_fds (); if (verbose) print_open_fds (array); free (array); for (n=0; n < 5; n++) { dup (1); array = xget_all_open_fds (); if (verbose) print_open_fds (array); free (array); } /* Check whether the except list works. */ close_all_fds (3, except); array = xget_all_open_fds (); if (verbose) print_open_fds (array); for (count=n=0; array[n] != -1; n++) count++; free (array); if (count != initial_count + DIM(except)-1) { fprintf (stderr, "%s:%d: close_all_fds failed\n", __FILE__, __LINE__); exit (1); } } } #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); + 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) { 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/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c index 2ec944c72..a6d58d3b2 100644 --- a/dirmngr/ldap-wrapper.c +++ b/dirmngr/ldap-wrapper.c @@ -1,933 +1,933 @@ /* ldap-wrapper.c - LDAP access via a wrapper process * Copyright (C) 2004, 2005, 2007, 2008, 2018 g10 Code GmbH * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ /* * We can't use LDAP directly for these reasons: * * 1. The LDAP library is linked to separate crypto library like * OpenSSL and even if it is linked to the library we use in dirmngr * (ntbtls or gnutls) it is sometimes a different version of that * library with all the surprising failures you may get due to this. * * 2. It is huge library in particular if TLS comes into play. So * problems with unfreed memory might turn up and we don't want * this in a long running daemon. * * 3. There is no easy way for timeouts. In particular the timeout * value does not work for DNS lookups (well, this is usual) and it * seems not to work while loading a large attribute like a * CRL. Having a separate process allows us to either tell the * process to commit suicide or have our own housekepping function * kill it after some time. The latter also allows proper * cancellation of a query at any point of time. * * 4. Given that we are going out to the network and usually get back * a long response, the fork/exec overhead is acceptable. * * Note that under WindowsCE the number of processes is strongly * limited (32 processes including the kernel processes) and thus we * don't use the process approach but implement a different wrapper in * ldap-wrapper-ce.c. */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <npth.h> #include "dirmngr.h" #include "../common/exechelp.h" #include "misc.h" #include "ldap-wrapper.h" #ifdef HAVE_W32_SYSTEM #define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) #else #define pth_close(fd) close(fd) #endif /* In case sysconf does not return a value we need to have a limit. */ #ifdef _POSIX_OPEN_MAX #define MAX_OPEN_FDS _POSIX_OPEN_MAX #else #define MAX_OPEN_FDS 20 #endif #define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ #define TIMERTICK_INTERVAL 2 /* To keep track of the LDAP wrapper state we use this structure. */ struct wrapper_context_s { struct wrapper_context_s *next; gnupg_process_t proc;/* The wrapper process. */ int printable_pid; /* Helper to print diagnostics after the process has * been cleaned up. */ estream_t fp; /* Connected with stdout of the ldap wrapper. */ gpg_error_t fp_err; /* Set to the gpg_error of the last read error * if any. */ estream_t log_fp; /* Connected with stderr of the ldap wrapper. */ ctrl_t ctrl; /* Connection data. */ int ready; /* Internally used to mark to be removed contexts. */ ksba_reader_t reader;/* The ksba reader object or NULL. */ char *line; /* Used to print the log lines (malloced). */ size_t linesize; /* Allocated size of LINE. */ size_t linelen; /* Use size of LINE. */ time_t stamp; /* The last time we noticed ativity. */ int reaper_idx; /* Private to ldap_wrapper_thread. */ }; /* We keep a global list of spawned wrapper process. A separate * thread makes use of this list to log error messages and to watch * out for finished processes. Access to list is protected by a * mutex. The condition variable is used to wakeup the reaper * thread. */ static struct wrapper_context_s *reaper_list; static npth_mutex_t reaper_list_mutex = NPTH_MUTEX_INITIALIZER; static npth_cond_t reaper_run_cond = NPTH_COND_INITIALIZER; /* We need to know whether we are shutting down the process. */ static int shutting_down; /* Close the estream fp and set it to NULL. */ #define SAFE_CLOSE(fp) \ do { estream_t _fp = fp; es_fclose (_fp); fp = NULL; } while (0) static void lock_reaper_list (void) { if (npth_mutex_lock (&reaper_list_mutex)) log_fatal ("%s: failed to acquire mutex: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); } static void unlock_reaper_list (void) { if (npth_mutex_unlock (&reaper_list_mutex)) log_fatal ("%s: failed to release mutex: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); } /* Read a fixed amount of data from READER into BUFFER. */ static gpg_error_t read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) { gpg_error_t err; size_t nread; while (count) { err = ksba_reader_read (reader, buffer, count, &nread); if (err) return err; buffer += nread; count -= nread; } return 0; } /* Release the wrapper context and kill a running wrapper process. */ static void destroy_wrapper (struct wrapper_context_s *ctx) { if (ctx->proc) { gnupg_process_terminate (ctx->proc); gnupg_process_release (ctx->proc); } ksba_reader_release (ctx->reader); SAFE_CLOSE (ctx->fp); SAFE_CLOSE (ctx->log_fp); xfree (ctx->line); xfree (ctx); } /* Print the content of LINE to the log stream but make sure to only print complete lines. Using NULL for LINE will flush any pending output. LINE may be modified by this function. */ static void print_log_line (struct wrapper_context_s *ctx, char *line) { char *s; size_t n; if (!line) { if (ctx->line && ctx->linelen) { log_info ("%s\n", ctx->line); ctx->linelen = 0; } return; } while ((s = strchr (line, '\n'))) { *s = 0; if (ctx->line && ctx->linelen) { log_info ("%s", ctx->line); ctx->linelen = 0; log_printf ("%s\n", line); } else log_info ("%s\n", line); line = s + 1; } n = strlen (line); if (n) { if (ctx->linelen + n + 1 >= ctx->linesize) { char *tmp; size_t newsize; newsize = ctx->linesize + ((n + 255) & ~255) + 1; tmp = (ctx->line ? xtryrealloc (ctx->line, newsize) : xtrymalloc (newsize)); if (!tmp) { log_error (_("error printing log line: %s\n"), strerror (errno)); return; } ctx->line = tmp; ctx->linesize = newsize; } memcpy (ctx->line + ctx->linelen, line, n); ctx->linelen += n; ctx->line[ctx->linelen] = 0; } } /* Read data from the log stream. Returns true if the log stream * indicated EOF or error. */ static int read_log_data (struct wrapper_context_s *ctx) { int rc; size_t n; char line[256]; rc = es_read (ctx->log_fp, line, sizeof line - 1, &n); if (rc || !n) /* Error or EOF. */ { if (rc) { gpg_error_t err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EAGAIN) return 0; log_error (_("error reading log from ldap wrapper %d: %s\n"), ctx->printable_pid, gpg_strerror (err)); } print_log_line (ctx, NULL); /* Flush. */ SAFE_CLOSE (ctx->log_fp); return 1; } line[n] = 0; print_log_line (ctx, line); if (ctx->stamp != (time_t)(-1)) ctx->stamp = time (NULL); return 0; } /* This function is run by a separate thread to maintain the list of wrappers and to log error messages from these wrappers. */ void * ldap_reaper_thread (void *dummy) { gpg_error_t err; struct wrapper_context_s *ctx; struct wrapper_context_s *ctx_prev; struct timespec abstime; struct timespec curtime; struct timespec timeout; int millisecs; gpgrt_poll_t *fparray = NULL; int fparraysize = 0; int count, i; int ret; time_t exptime; (void)dummy; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; for (;;) { int any_action = 0; /* Wait until we are needed and then setup the FPARRAY. */ /* Note: There is one unlock inside the block! */ lock_reaper_list (); { while (!reaper_list && !shutting_down) { if (npth_cond_wait (&reaper_run_cond, &reaper_list_mutex)) log_error ("ldap-reaper: waiting on condition failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); } for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) if (ctx->log_fp) count++; if (count > fparraysize || !fparray) { /* Need to realloc the array. We simply discard it and * replace it by a new one. */ xfree (fparray); fparray = xtrycalloc (count? count : 1, sizeof *fparray); if (!fparray) { err = gpg_error_from_syserror (); log_error ("ldap-reaper can't allocate poll array: %s" " - waiting 1s\n", gpg_strerror (err)); /* Note: Here we unlock and continue! */ unlock_reaper_list (); gnupg_sleep (1); continue; } fparraysize = count; } for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) { if (ctx->log_fp) { log_assert (count < fparraysize); fparray[count].stream = ctx->log_fp; fparray[count].want_read = 1; fparray[count].ignore = 0; ctx->reaper_idx = count; count++; } else { ctx->reaper_idx = -1; fparray[count].ignore = 1; } } for (i=count; i < fparraysize; i++) fparray[i].ignore = 1; } unlock_reaper_list (); /* Note the one unlock inside the block. */ /* Compute the next timeout. */ npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { /* Inactivity is checked below. Nothing else to do. */ npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); millisecs = timeout.tv_sec * 1000; millisecs += timeout.tv_nsec / 1000000; if (millisecs < 0) millisecs = 1; if (DBG_EXTPROG) { log_debug ("ldap-reaper: next run (count=%d size=%d timeout=%d)\n", count, fparraysize, millisecs); for (count=0; count < fparraysize; count++) if (!fparray[count].ignore) log_debug ("ldap-reaper: fp[%d] stream=%p %s\n", count, fparray[count].stream, fparray[count].want_read? "want_read":""); } ret = es_poll (fparray, fparraysize, millisecs); if (ret < 0) { err = gpg_error_from_syserror (); log_error ("ldap-reaper failed to poll: %s" " - waiting 1s\n", gpg_strerror (err)); /* In case the reason for the error is a too large array, we * release it so that it will be allocated smaller in the * next round. */ xfree (fparray); fparray = NULL; fparraysize = 0; gnupg_sleep (1); continue; } if (DBG_EXTPROG) { for (count=0; count < fparraysize; count++) if (!fparray[count].ignore) log_debug ("ldap-reaper: fp[%d] stream=%p rc=%d %c%c%c%c%c%c%c\n", count, fparray[count].stream, ret, fparray[count].got_read? 'r':'-', fparray[count].got_write?'w':'-', fparray[count].got_oob? 'o':'-', fparray[count].got_rdhup?'H':'-', fparray[count].got_err? 'e':'-', fparray[count].got_hup? 'h':'-', fparray[count].got_nval? 'n':'-'); } /* All timestamps before exptime should be considered expired. */ exptime = time (NULL); if (exptime > INACTIVITY_TIMEOUT) exptime -= INACTIVITY_TIMEOUT; lock_reaper_list (); { for (ctx = reaper_list; ctx; ctx = ctx->next) { /* Check whether there is any logging to be done. We need * to check FPARRAYSIZE because it can be 0 in case * es_poll returned a timeout. */ if (fparraysize && ctx->log_fp && ctx->reaper_idx >= 0) { log_assert (ctx->reaper_idx < fparraysize); if (fparray[ctx->reaper_idx].got_read) { if (read_log_data (ctx)) { SAFE_CLOSE (ctx->log_fp); any_action = 1; } } } /* Check whether the process is still running. */ if (ctx->proc) { err = gnupg_process_wait (ctx->proc, 0); if (!err) { int status; gnupg_process_ctl (ctx->proc, GNUPG_PROCESS_GET_EXIT_ID, &status); if (DBG_EXTPROG) log_info (_("ldap wrapper %d ready"), ctx->printable_pid); ctx->ready = 1; gnupg_process_release (ctx->proc); ctx->proc = NULL; any_action = 1; if (status == 10) log_info (_("ldap wrapper %d ready: timeout\n"), ctx->printable_pid); else log_info (_("ldap wrapper %d ready: exitcode=%d\n"), ctx->printable_pid, status); } else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) { log_error (_("waiting for ldap wrapper %d failed: %s\n"), ctx->printable_pid, gpg_strerror (err)); any_action = 1; } } /* Check whether we should terminate the process. */ if (ctx->proc && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) { gnupg_process_terminate (ctx->proc); ctx->stamp = (time_t)(-1); log_info (_("ldap wrapper %d stalled - killing\n"), ctx->printable_pid); /* We need to close the log stream because the cleanup * loop waits for it. */ SAFE_CLOSE (ctx->log_fp); any_action = 1; } } /* If something has been printed to the log file or we got an * EOF from a wrapper, we now print the list of active * wrappers. */ if (any_action && DBG_EXTPROG) { log_debug ("ldap worker states:\n"); for (ctx = reaper_list; ctx; ctx = ctx->next) log_debug (" c=%p pid=%d rdr=%p logfp=%p" " ctrl=%p/%d la=%lu rdy=%d\n", ctx, ctx->printable_pid, ctx->reader, ctx->log_fp, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, (unsigned long)ctx->stamp, ctx->ready); } /* An extra loop to check whether ready marked wrappers may be * removed. We may only do so if the ksba reader object is * not anymore in use or we are in shutdown state. */ again: for (ctx_prev=NULL, ctx=reaper_list; ctx; ctx_prev=ctx, ctx=ctx->next) { if (ctx->ready && ((!ctx->log_fp && !ctx->reader) || shutting_down)) { if (ctx_prev) ctx_prev->next = ctx->next; else reaper_list = ctx->next; destroy_wrapper (ctx); goto again; } } } unlock_reaper_list (); } /*NOTREACHED*/ return NULL; /* Make the compiler happy. */ } /* Start the reaper thread for the ldap wrapper. */ void ldap_reaper_launch_thread (void) { static int done; npth_attr_t tattr; npth_t thread; int err; if (done) return; done = 1; #ifdef HAVE_W32_SYSTEM /* Static init does not yet work in W32 nPth. */ if (npth_cond_init (&reaper_run_cond, NULL)) log_fatal ("%s: failed to init condition variable: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); #endif npth_attr_init (&tattr); npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); if (npth_create (&thread, &tattr, ldap_reaper_thread, NULL)) { err = gpg_error_from_syserror (); log_error ("error spawning ldap reaper reaper thread: %s\n", gpg_strerror (err) ); dirmngr_exit (1); } npth_setname_np (thread, "ldap-reaper"); npth_attr_destroy (&tattr); } /* Wait until all ldap wrappers have terminated. We assume that the kill has already been sent to all of them. */ void ldap_wrapper_wait_connections (void) { lock_reaper_list (); { shutting_down = 1; if (npth_cond_signal (&reaper_run_cond)) log_error ("%s: Ooops: signaling condition failed: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); } unlock_reaper_list (); while (reaper_list) gnupg_usleep (200); } /* This function is to be used to release a context associated with the given reader object. */ void ldap_wrapper_release_context (ksba_reader_t reader) { struct wrapper_context_s *ctx; if (!reader ) return; lock_reaper_list (); { for (ctx=reaper_list; ctx; ctx=ctx->next) if (ctx->reader == reader) { if (DBG_EXTPROG) log_debug ("releasing ldap worker c=%p pid=%d rdr=%p" " ctrl=%p/%d\n", ctx, ctx->printable_pid, ctx->reader, ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); ctx->reader = NULL; SAFE_CLOSE (ctx->fp); if (ctx->ctrl) { ctx->ctrl->refcount--; ctx->ctrl = NULL; } if (ctx->fp_err) log_info ("%s: reading from ldap wrapper %d failed: %s\n", __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); break; } } unlock_reaper_list (); } /* Cleanup all resources held by the connection associated with CTRL. This is used after a cancel to kill running wrappers. */ void ldap_wrapper_connection_cleanup (ctrl_t ctrl) { struct wrapper_context_s *ctx; lock_reaper_list (); { for (ctx=reaper_list; ctx; ctx=ctx->next) if (ctx->ctrl && ctx->ctrl == ctrl) { ctx->ctrl->refcount--; ctx->ctrl = NULL; if (ctx->proc) gnupg_process_terminate (ctx->proc); if (ctx->fp_err) log_info ("%s: reading from ldap wrapper %d failed: %s\n", __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); } } unlock_reaper_list (); } /* This is the callback used by the ldap wrapper to feed the ksba * reader with the wrapper's stdout. See the description of * ksba_reader_set_cb for details. */ static int reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) { struct wrapper_context_s *ctx = cb_value; size_t nleft = count; struct timespec abstime; struct timespec curtime; struct timespec timeout; int millisecs; gpgrt_poll_t fparray[1]; int ret; gpg_error_t err; /* FIXME: We might want to add some internal buffering because the ksba code does not do any buffering for itself (because a ksba reader may be detached from another stream to read other data and then it would be cumbersome to get back already buffered stuff). */ if (!buffer && !count && !nread) return -1; /* Rewind is not supported. */ /* If we ever encountered a read error, don't continue (we don't want to possibly overwrite the last error cause). Bail out also if the file descriptor has been closed. */ if (ctx->fp_err || !ctx->fp) { *nread = 0; return -1; } memset (fparray, 0, sizeof fparray); fparray[0].stream = ctx->fp; fparray[0].want_read = 1; npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; while (nleft > 0) { npth_clock_gettime (&curtime); if (!(npth_timercmp (&curtime, &abstime, <))) { err = dirmngr_tick (ctx->ctrl); if (err) { ctx->fp_err = err; SAFE_CLOSE (ctx->fp); return -1; } npth_clock_gettime (&abstime); abstime.tv_sec += TIMERTICK_INTERVAL; } npth_timersub (&abstime, &curtime, &timeout); millisecs = timeout.tv_sec * 1000; millisecs += timeout.tv_nsec / 1000000; if (millisecs < 0) millisecs = 1; if (DBG_EXTPROG) { log_debug ("%s: fp[0] stream=%p %s\n", __func__, fparray[0].stream, fparray[0].want_read?"want_read":""); } ret = es_poll (fparray, DIM (fparray), millisecs); if (ret < 0) { ctx->fp_err = gpg_error_from_syserror (); log_error ("error polling stdout of ldap wrapper %d: %s\n", ctx->printable_pid, gpg_strerror (ctx->fp_err)); SAFE_CLOSE (ctx->fp); return -1; } if (DBG_EXTPROG) { log_debug ("%s: fp[0] stream=%p rc=%d %c%c%c%c%c%c%c\n", __func__, fparray[0].stream, ret, fparray[0].got_read? 'r':'-', fparray[0].got_write?'w':'-', fparray[0].got_oob? 'o':'-', fparray[0].got_rdhup?'H':'-', fparray[0].got_err? 'e':'-', fparray[0].got_hup? 'h':'-', fparray[0].got_nval? 'n':'-'); } if (!ret) { /* Timeout. Will be handled when calculating the next timeout. */ continue; } if (fparray[0].got_read) { size_t n; if (es_read (ctx->fp, buffer, nleft, &n)) { ctx->fp_err = gpg_error_from_syserror (); if (gpg_err_code (ctx->fp_err) == GPG_ERR_EAGAIN) ctx->fp_err = 0; else { log_error ("%s: error reading: %s (%d)\n", __func__, gpg_strerror (ctx->fp_err), ctx->fp_err); SAFE_CLOSE (ctx->fp); return -1; } } else if (!n) /* EOF */ { if (nleft == count) return -1; /* EOF. */ break; } nleft -= n; buffer += n; if (n > 0 && ctx->stamp != (time_t)(-1)) ctx->stamp = time (NULL); } } *nread = count - nleft; return 0; } /* Fork and exec the LDAP wrapper and return a new libksba reader object at READER. ARGV is a NULL terminated list of arguments for the wrapper. The function returns 0 on success or an error code. Special hack to avoid passing a password through the command line which is globally visible: If the first element of ARGV is "--pass" it will be removed and instead the environment variable DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern OSes the environment is not visible to other users. For those old systems where it can't be avoided, we don't want to go into the hassle of passing the password via stdin; it's just too complicated and an LDAP password used for public directory lookups should not be that confidential. */ gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) { gpg_error_t err; gnupg_process_t process; struct wrapper_context_s *ctx; int i; int j; const char **arg_list; const char *pgmname; estream_t outfp, errfp; /* It would be too simple to connect stderr just to our logging stream. The problem is that if we are running multi-threaded everything gets intermixed. Clearly we don't want this. So the only viable solutions are either to have another thread responsible for logging the messages or to add an option to the wrapper module to do the logging on its own. Given that we anyway need a way to reap the child process and this is best done using a general reaping thread, that thread can do the logging too. */ ldap_reaper_launch_thread (); *reader = NULL; /* Files: We need to prepare stdin and stdout. We get stderr from the function. */ if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); else pgmname = opt.ldap_wrapper_program; /* Create command line argument array. */ for (i = 0; argv[i]; i++) ; arg_list = xtrycalloc (i + 2, sizeof *arg_list); if (!arg_list) { err = gpg_error_from_syserror (); log_error (_("error allocating memory: %s\n"), strerror (errno)); return err; } for (i = j = 0; argv[i]; i++, j++) if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) { arg_list[j] = "--env-pass"; setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); i++; } else arg_list[j] = (char*) argv[i]; ctx = xtrycalloc (1, sizeof *ctx); if (!ctx) { err = gpg_error_from_syserror (); log_error (_("error allocating memory: %s\n"), strerror (errno)); xfree (arg_list); return err; } err = gnupg_process_spawn (pgmname, arg_list, (GNUPG_PROCESS_STDOUT_PIPE | GNUPG_PROCESS_STDERR_PIPE), - NULL, NULL, &process); + NULL, &process); if (err) { xfree (arg_list); xfree (ctx); log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); return err; } gnupg_process_get_streams (process, GNUPG_PROCESS_STREAM_NONBLOCK, NULL, &outfp, &errfp); gnupg_process_ctl (process, GNUPG_PROCESS_GET_PROC_ID, &ctx->printable_pid); ctx->proc = process; ctx->fp = outfp; ctx->log_fp = errfp; ctx->ctrl = ctrl; ctrl->refcount++; ctx->stamp = time (NULL); err = ksba_reader_new (reader); if (!err) err = ksba_reader_set_cb (*reader, reader_callback, ctx); if (err) { xfree (arg_list); log_error (_("error initializing reader object: %s\n"), gpg_strerror (err)); destroy_wrapper (ctx); ksba_reader_release (*reader); *reader = NULL; return err; } /* Hook the context into our list of running wrappers. */ lock_reaper_list (); { ctx->reader = *reader; ctx->next = reaper_list; reaper_list = ctx; if (npth_cond_signal (&reaper_run_cond)) log_error ("ldap-wrapper: Ooops: signaling condition failed: %s (%d)\n", gpg_strerror (gpg_error_from_syserror ()), errno); } unlock_reaper_list (); if (DBG_EXTPROG) { log_debug ("ldap wrapper %d started (%p, %s)", ctx->printable_pid, ctx->reader, pgmname); for (i=0; arg_list[i]; i++) log_printf (" [%s]", arg_list[i]); log_printf ("\n"); } xfree (arg_list); /* Need to wait for the first byte so we are able to detect an empty output and not let the consumer see an EOF without further error indications. The CRL loading logic assumes that after return from this function, a failed search (e.g. host not found ) is indicated right away. */ { unsigned char c; err = read_buffer (*reader, &c, 1); if (err) { ldap_wrapper_release_context (*reader); ksba_reader_release (*reader); *reader = NULL; if (gpg_err_code (err) == GPG_ERR_EOF) return gpg_error (GPG_ERR_NO_DATA); else return err; } ksba_reader_unread (*reader, &c, 1); } return 0; } diff --git a/g10/photoid.c b/g10/photoid.c index 8cc7e3a20..b226cbedd 100644 --- a/g10/photoid.c +++ b/g10/photoid.c @@ -1,823 +1,823 @@ /* photoid.c - photo ID handling code * Copyright (C) 2001, 2002, 2005, 2006, 2008, 2011 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <string.h> #include <unistd.h> #ifdef _WIN32 # ifdef HAVE_WINSOCK2_H # include <winsock2.h> # endif # include <windows.h> #endif #include "gpg.h" #include "../common/util.h" #include "packet.h" #include "../common/status.h" #include "keydb.h" #include "../common/i18n.h" #include "../common/iobuf.h" #include "options.h" #include "main.h" #include "photoid.h" #include "../common/ttyio.h" #include "trustdb.h" #if defined (_WIN32) /* This is a nicer system() for windows that waits for programs to return before returning control to the caller. I hate helpful computers. */ static int w32_system (const char *command) { if (!strncmp (command, "!ShellExecute ", 14)) { SHELLEXECUTEINFOW see; wchar_t *wname; int waitms; command = command + 14; while (spacep (command)) command++; waitms = atoi (command); if (waitms < 0) waitms = 0; else if (waitms > 60*1000) waitms = 60000; while (*command && !spacep (command)) command++; while (spacep (command)) command++; wname = utf8_to_wchar (command); if (!wname) return -1; memset (&see, 0, sizeof see); see.cbSize = sizeof see; see.fMask = (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE); see.lpVerb = L"open"; see.lpFile = (LPCWSTR)wname; see.nShow = SW_SHOW; if (DBG_EXTPROG) log_debug ("running ShellExecuteEx(open,'%s')\n", command); if (!ShellExecuteExW (&see)) { if (DBG_EXTPROG) log_debug ("ShellExecuteEx failed: rc=%d\n", (int)GetLastError ()); xfree (wname); return -1; } if (DBG_EXTPROG) { /* hInstApp has HINSTANCE type. The documentations says that it's not a true HINSTANCE and it can be cast only to an int. */ int hinstance = (intptr_t)see.hInstApp; log_debug ("ShellExecuteEx succeeded (hProcess=%p,hInstApp=%d)\n", see.hProcess, hinstance); } if (!see.hProcess) { gnupg_usleep (waitms*1000); if (DBG_EXTPROG) log_debug ("ShellExecuteEx ready (wait=%dms)\n", waitms); } else { WaitForSingleObject (see.hProcess, INFINITE); if (DBG_EXTPROG) log_debug ("ShellExecuteEx ready\n"); } CloseHandle (see.hProcess); xfree (wname); } else { char *string; wchar_t *wstring; PROCESS_INFORMATION pi; STARTUPINFOW si; /* We must use a copy of the command as CreateProcess modifies * this argument. */ string = xstrdup (command); wstring = utf8_to_wchar (string); xfree (string); if (!wstring) return -1; memset (&pi, 0, sizeof(pi)); memset (&si, 0, sizeof(si)); si.cb = sizeof (si); if (!CreateProcessW (NULL, wstring, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi)) { xfree (wstring); return -1; } /* Wait for the child to exit */ WaitForSingleObject (pi.hProcess, INFINITE); CloseHandle (pi.hProcess); CloseHandle (pi.hThread); xfree (wstring); } return 0; } #endif /*_W32*/ /* Generate a new photo id packet, or return NULL if canceled. FIXME: Should we add a duplicates check similar to generate_user_id? */ PKT_user_id * generate_photo_id (ctrl_t ctrl, PKT_public_key *pk,const char *photo_name) { PKT_user_id *uid; int error=1,i; uint64_t len; char *filename; byte *photo=NULL; byte header[16]; IOBUF file; header[0]=0x10; /* little side of photo header length */ header[1]=0; /* big side of photo header length */ header[2]=1; /* 1 == version of photo header */ header[3]=1; /* 1 == JPEG */ for(i=4;i<16;i++) /* The reserved bytes */ header[i]=0; #define EXTRA_UID_NAME_SPACE 71 uid=xmalloc_clear(sizeof(*uid)+71); if(photo_name && *photo_name) filename=make_filename(photo_name,(void *)NULL); else { tty_printf(_("\nPick an image to use for your photo ID." " The image must be a JPEG file.\n" "Remember that the image is stored within your public key." " If you use a\n" "very large picture, your key will become very large" " as well!\n" "Keeping the image close to 240x288 is a good size" " to use.\n")); filename=NULL; } while(photo==NULL) { if(filename==NULL) { char *tempname; tty_printf("\n"); tty_enable_completion(NULL); tempname=cpr_get("photoid.jpeg.add", _("Enter JPEG filename for photo ID: ")); tty_disable_completion(); filename=make_filename(tempname,(void *)NULL); xfree(tempname); if(strlen(filename)==0) goto scram; } file=iobuf_open(filename); if (file && is_secured_file (iobuf_get_fd (file))) { iobuf_close (file); file = NULL; gpg_err_set_errno (EPERM); } if(!file) { log_error(_("unable to open JPEG file '%s': %s\n"), filename,strerror(errno)); xfree(filename); filename=NULL; continue; } len = iobuf_get_filelength(file); if(len>6144) { /* We silently skip JPEGs larger than 1MiB because we have a * 2MiB limit on the user ID packets and we need some limit * anyway because the returned u64 is larger than the u32 or * OpenPGP. Note that the diagnostic may print a wrong * value if the value is really large; we don't fix this to * avoid a string change. */ tty_printf( _("This JPEG is really large (%d bytes) !\n"), (int)len); if(len > 1024*1024 || !cpr_get_answer_is_yes("photoid.jpeg.size", _("Are you sure you want to use it? (y/N) "))) { iobuf_close(file); xfree(filename); filename=NULL; continue; } } photo=xmalloc(len); iobuf_read(file,photo,len); iobuf_close(file); /* Is it a JPEG? */ if(photo[0]!=0xFF || photo[1]!=0xD8) { log_error(_("'%s' is not a JPEG file\n"),filename); xfree(photo); photo=NULL; xfree(filename); filename=NULL; continue; } /* Build the packet */ build_attribute_subpkt(uid,1,photo,len,header,16); parse_attribute_subpkts(uid); make_attribute_uidname(uid, EXTRA_UID_NAME_SPACE); /* Showing the photo is not safe when noninteractive since the "user" may not be able to dismiss a viewer window! */ if(opt.command_fd==-1) { show_photos (ctrl, uid->attribs, uid->numattribs, pk, uid); switch(cpr_get_answer_yes_no_quit("photoid.jpeg.okay", _("Is this photo correct (y/N/q)? "))) { case -1: goto scram; case 0: free_attributes(uid); xfree(photo); photo=NULL; xfree(filename); filename=NULL; continue; } } } error=0; uid->ref=1; scram: xfree(filename); xfree(photo); if(error) { free_attributes(uid); xfree(uid); return NULL; } return uid; } /* Returns 0 for error, 1 for valid */ int parse_image_header(const struct user_attribute *attr,byte *type,u32 *len) { u16 headerlen; if(attr->len<3) return 0; /* For historical reasons (i.e. "oops!"), the header length is little endian. */ headerlen=(attr->data[1]<<8) | attr->data[0]; if(headerlen>attr->len) return 0; if(type && attr->len>=4) { if(attr->data[2]==1) /* header version 1 */ *type=attr->data[3]; else *type=0; } *len=attr->len-headerlen; if(*len==0) return 0; return 1; } /* style==0 for extension, 1 for name, 2 for MIME type. Remember that the "name" style string could be used in a user ID name field, so make sure it is not too big (see parse-packet.c:parse_attribute). Extensions should be 3 characters long for the best cross-platform compatibility. */ const char * image_type_to_string(byte type,int style) { const char *string; switch(type) { case 1: /* jpeg */ if(style==0) string="jpg"; else if(style==1) string="jpeg"; else string="image/jpeg"; break; default: if(style==0) string="bin"; else if(style==1) string="unknown"; else string="image/x-unknown"; break; } return string; } #if !defined(FIXED_PHOTO_VIEWER) && !defined(DISABLE_PHOTO_VIEWER) static const char * get_default_photo_command(void) { #if defined(_WIN32) return "!ShellExecute 400 %i"; #elif defined(__APPLE__) /* OS X. This really needs more than just __APPLE__. */ return "open %I"; #else if (!path_access ("xloadimage", X_OK)) return "xloadimage -fork -quiet -title 'KeyID 0x%k' stdin"; else if (!path_access ("display",X_OK)) return "display -title 'KeyID 0x%k' %i"; else if (getuid () && !path_access ("xdg-open", X_OK)) { /* xdg-open spawns the actual program and exits so we need to * keep the temp file */ return "xdg-open %I"; } else return "/bin/true"; #endif } #endif #ifndef DISABLE_PHOTO_VIEWER struct spawn_info { unsigned int keep_temp_file; char *command; char *tempdir; char *tempfile; }; #ifdef NO_EXEC static void show_photo (const char *command, const char *name, const void *image, u32 len) { log_error(_("no remote program execution supported\n")); return GPG_ERR_GENERAL; } #else /* ! NO_EXEC */ #include "../common/membuf.h" #include "../common/exechelp.h" /* Makes a temp directory and filenames */ static int setup_input_file (struct spawn_info *info, const char *name) { char *tmp = opt.temp_dir; int len; #define TEMPLATE "gpg-XXXXXX" /* Initialize by the length of last part in the path + 1 */ len = strlen (DIRSEP_S) + strlen (TEMPLATE) + 1; /* Make up the temp dir and file in case we need them */ if (tmp) { len += strlen (tmp); info->tempdir = xmalloc (len); } else { #if defined (_WIN32) int ret; tmp = xmalloc (MAX_PATH+1); if (!tmp) return -1; ret = GetTempPath (MAX_PATH-len, tmp); if (ret == 0 || ret >= MAX_PATH-len) strcpy (tmp, "c:\\windows\\temp"); else { /* GetTempPath may return with \ on the end */ while (ret > 0 && tmp[ret-1] == '\\') { tmp[ret-1]='\0'; ret--; } } len += ret; info->tempdir = tmp; #else /* More unixish systems */ if (!(tmp = getenv ("TMPDIR")) && !(tmp = getenv ("TMP"))) tmp = "/tmp"; len += strlen (tmp); info->tempdir = xmalloc (len); #endif } if (info->tempdir == NULL) return -1; sprintf (info->tempdir, "%s" DIRSEP_S TEMPLATE, tmp); if (gnupg_mkdtemp (info->tempdir) == NULL) { log_error (_("can't create directory '%s': %s\n"), info->tempdir, strerror (errno)); return -1; } info->tempfile = xmalloc (strlen (info->tempdir) + strlen (DIRSEP_S) + strlen (name) + 1); if (info->tempfile == NULL) { xfree (info->tempdir); info->tempdir = NULL; return -1; } sprintf (info->tempfile, "%s" DIRSEP_S "%s", info->tempdir, name); return 0; } /* Expands %i or %I in the args to the full temp file within the temp directory. */ static int expand_args (struct spawn_info *info, const char *args_in, const char *name) { const char *ch = args_in; membuf_t command; info->keep_temp_file = 0; if (DBG_EXTPROG) log_debug ("expanding string \"%s\"\n", args_in); init_membuf (&command, 100); while (*ch != '\0') { if (*ch == '%') { const char *append = NULL; ch++; switch (*ch) { case 'I': info->keep_temp_file = 1; /* fall through */ case 'i': /* in */ if (info->tempfile == NULL) { if (setup_input_file (info, name) < 0) goto fail; } append = info->tempfile; break; case '%': append = "%"; break; } if (append) put_membuf_str (&command, append); } else put_membuf (&command, ch, 1); ch++; } put_membuf (&command, "", 1); /* Terminate string. */ info->command = get_membuf (&command, NULL); if (!info->command) return -1; if(DBG_EXTPROG) log_debug("args expanded to \"%s\", use %s, keep %u\n", info->command, info->tempfile, info->keep_temp_file); return 0; fail: xfree (get_membuf (&command, NULL)); return -1; } #ifndef EXEC_TEMPFILE_ONLY static void fill_command_argv (const char *argv[4], const char *command) { argv[0] = getenv ("SHELL"); if (argv[0] == NULL) argv[0] = "/bin/sh"; argv[1] = "-c"; argv[2] = command; argv[3] = NULL; } #endif static void run_with_pipe (struct spawn_info *info, const void *image, u32 len) { #ifdef EXEC_TEMPFILE_ONLY (void)info; (void)image; (void)len; log_error (_("this platform requires temporary files when calling" " external programs\n")); return; #else /* !EXEC_TEMPFILE_ONLY */ gpg_error_t err; const char *argv[4]; gnupg_process_t proc; fill_command_argv (argv, info->command); err = gnupg_process_spawn (argv[0], argv+1, GNUPG_PROCESS_STDIN_PIPE, - NULL, NULL, &proc); + NULL, &proc); if (err) log_error (_("unable to execute shell '%s': %s\n"), argv[0], gpg_strerror (err)); else { int fd_in; err = gnupg_process_get_fds (proc, 0, &fd_in, NULL, NULL); if (err) log_error ("unable to get pipe connection '%s': %s\n", argv[2], gpg_strerror (err)); else { write (fd_in, image, len); close (fd_in); } err = gnupg_process_wait (proc, 1); if (err) log_error (_("unnatural exit of external program\n")); gnupg_process_release (proc); } #endif /* !EXEC_TEMPFILE_ONLY */ } static int create_temp_file (struct spawn_info *info, const void *ptr, u32 len) { if (DBG_EXTPROG) log_debug ("using temp file '%s'\n", info->tempfile); /* It's not fork/exec/pipe, so create a temp file */ if ( is_secured_filename (info->tempfile) ) { log_error (_("can't create '%s': %s\n"), info->tempfile, strerror (EPERM)); gpg_err_set_errno (EPERM); return -1; } else { estream_t fp = es_fopen (info->tempfile, "wb"); if (fp) { es_fwrite (ptr, len, 1, fp); es_fclose (fp); return 0; } else { int save = errno; log_error (_("can't create '%s': %s\n"), info->tempfile, strerror(errno)); gpg_err_set_errno (save); return -1; } } } static void show_photo (const char *command, const char *name, const void *image, u32 len) { struct spawn_info *spawn; spawn = xmalloc_clear (sizeof (struct spawn_info)); if (!spawn) return; /* Expand the args */ if (expand_args (spawn, command, name) < 0) { xfree (spawn); return; } if (DBG_EXTPROG) log_debug ("running command: %s\n", spawn->command); if (spawn->tempfile == NULL) run_with_pipe (spawn, image, len); else if (create_temp_file (spawn, image, len) == 0) { #if defined (_WIN32) if (w32_system (spawn->command) < 0) log_error (_("system error while calling external program: %s\n"), strerror (errno)); #else gpg_error_t err; const char *argv[4]; fill_command_argv (argv, spawn->command); - err = gnupg_process_spawn (argv[0], argv+1, 0, NULL, NULL, NULL); + err = gnupg_process_spawn (argv[0], argv+1, 0, NULL, NULL); if (err) log_error (_("unnatural exit of external program\n")); #endif if (!spawn->keep_temp_file) { if (unlink (spawn->tempfile) < 0) log_info (_("WARNING: unable to remove tempfile (%s) '%s': %s\n"), "in", spawn->tempfile, strerror(errno)); if (rmdir (spawn->tempdir) < 0) log_info (_("WARNING: unable to remove temp directory '%s': %s\n"), spawn->tempdir, strerror(errno)); } } xfree(spawn->command); xfree(spawn->tempdir); xfree(spawn->tempfile); xfree(spawn); } #endif #endif void show_photos (ctrl_t ctrl, const struct user_attribute *attrs, int count, PKT_public_key *pk, PKT_user_id *uid) { #ifdef DISABLE_PHOTO_VIEWER (void)attrs; (void)count; (void)pk; (void)uid; #else /*!DISABLE_PHOTO_VIEWER*/ int i; struct expando_args args; u32 len; u32 kid[2]={0,0}; if (opt.exec_disable && !opt.no_perm_warn) { log_info (_("external program calls are disabled due to unsafe " "options file permissions\n")); return; } #if defined(HAVE_GETUID) && defined(HAVE_GETEUID) /* There should be no way to get to this spot while still carrying setuid privs. Just in case, bomb out if we are. */ if ( getuid () != geteuid ()) BUG (); #endif memset (&args, 0, sizeof(args)); args.pk = pk; args.validity_info = get_validity_info (ctrl, NULL, pk, uid); args.validity_string = get_validity_string (ctrl, pk, uid); namehash_from_uid (uid); args.namehash = uid->namehash; if (pk) keyid_from_pk (pk, kid); es_fflush (es_stdout); #ifdef FIXED_PHOTO_VIEWER opt.photo_viewer = FIXED_PHOTO_VIEWER; #else if (!opt.photo_viewer) opt.photo_viewer = get_default_photo_command (); #endif for (i=0; i<count; i++) if (attrs[i].type == ATTRIB_IMAGE && parse_image_header (&attrs[i], &args.imagetype, &len)) { char *command, *name; int offset = attrs[i].len-len; /* make command grow */ command = pct_expando (ctrl, opt.photo_viewer,&args); if(!command) goto fail; if (!*command) { xfree (command); goto fail; } name = xmalloc (1 + 16 + strlen(EXTSEP_S) + strlen (image_type_to_string (args.imagetype, 0))); if (!name) { xfree (command); goto fail; } /* Make the filename. Notice we are not using the image encoding type for more than cosmetics. Most external image viewers can handle a multitude of types, and even if one cannot understand a particular type, we have no way to know which. The spec permits this, by the way. -dms */ #ifdef USE_ONLY_8DOT3 sprintf (name,"%08lX" EXTSEP_S "%s", (ulong)kid[1], image_type_to_string (args.imagetype, 0)); #else sprintf (name, "%08lX%08lX" EXTSEP_S "%s", (ulong)kid[0], (ulong)kid[1], image_type_to_string (args.imagetype, 0)); #endif show_photo (command, name, &attrs[i].data[offset], len); xfree (name); xfree (command); } return; fail: log_error(_("unable to display photo ID!\n")); #endif /*!DISABLE_PHOTO_VIEWER*/ } diff --git a/scd/app.c b/scd/app.c index a9591a12c..09a148416 100644 --- a/scd/app.c +++ b/scd/app.c @@ -1,2786 +1,2791 @@ /* app.c - Application selection. * Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <npth.h> #include "scdaemon.h" #include "../common/exechelp.h" #include "iso7816.h" #include "apdu.h" #include "../common/tlv.h" #include "../common/membuf.h" /* Forward declaration of internal function. */ static gpg_error_t select_additional_application_internal (card_t card, apptype_t req_apptype); static gpg_error_t send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl); static gpg_error_t run_reselect (ctrl_t ctrl, card_t c, app_t a, app_t a_prev); /* * Multiple readers, single writer (MRSW) lock. */ struct mrsw_lock { npth_mutex_t lock; npth_cond_t cond; int num_readers_active; int num_writers_waiting; int writer_active; npth_cond_t notify_cond; }; /* MRSW lock to protect the list of cards. * * This structure is used for serializing access to the list of cards * (by CARD_TOP). While allowing multiple accesses by different * connections as "r" access (for a CARD in the list), "w" access to * update the list is only possible with a single thread. * * Each use of a CARD (in the list) does "r" access. * * For "w" access, the app_send_devinfo function may wait on any * change of the list. For other cases of "w" access are opening new * card or removal of card, updating the list of card. * * Note that for serializing access to each CARD (and its associated * applications) itself, it is done separately by another mutex with * lock_card/unlock_card. */ static struct mrsw_lock card_list_lock; /* A list of card contexts. A card is a collection of applications * (described by app_t) on the same physical token. */ static card_t card_top; /* The list of application names and their select function. If no * specific application is selected the first available application on * a card is selected. */ struct app_priority_list_s { apptype_t apptype; char const *name; gpg_error_t (*select_func)(app_t); }; static struct app_priority_list_s app_priority_list[] = {{ APPTYPE_OPENPGP , "openpgp", app_select_openpgp }, { APPTYPE_PIV , "piv", app_select_piv }, { APPTYPE_NKS , "nks", app_select_nks }, { APPTYPE_P15 , "p15", app_select_p15 }, { APPTYPE_GELDKARTE, "geldkarte", app_select_geldkarte }, { APPTYPE_DINSIG , "dinsig", app_select_dinsig }, { APPTYPE_SC_HSM , "sc-hsm", app_select_sc_hsm }, { APPTYPE_NONE , NULL, NULL } /* APPTYPE_UNDEFINED is special and not listed here. */ }; /* Map a cardtype to a string. Never returns NULL. */ const char * strcardtype (cardtype_t t) { switch (t) { case CARDTYPE_GENERIC: return "generic"; case CARDTYPE_GNUK: return "gnuk"; case CARDTYPE_YUBIKEY: return "yubikey"; case CARDTYPE_ZEITCONTROL: return "zeitcontrol"; case CARDTYPE_SCE7: return "smartcafe"; } return "?"; } /* Map an application type to a string. Never returns NULL. */ const char * strapptype (apptype_t t) { int i; for (i=0; app_priority_list[i].apptype; i++) if (app_priority_list[i].apptype == t) return app_priority_list[i].name; return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none"; } const char * xstrapptype (app_t app) { return app? strapptype (app->apptype) : "[no_app]"; } /* Return the apptype for NAME. */ static apptype_t apptype_from_name (const char *name) { int i; if (!name) return APPTYPE_NONE; for (i=0; app_priority_list[i].apptype; i++) if (!ascii_strcasecmp (app_priority_list[i].name, name)) return app_priority_list[i].apptype; if (!ascii_strcasecmp ("undefined", name)) return APPTYPE_UNDEFINED; return APPTYPE_NONE; } /* Return the apptype for KEYREF. This is the first part of the * KEYREF up to the dot. */ static apptype_t apptype_from_keyref (const char *keyref) { int i; unsigned int n; const char *s; if (!keyref) return APPTYPE_NONE; s = strchr (keyref, '.'); if (!s || s == keyref || !s[1]) return APPTYPE_NONE; /* Not a valid keyref. */ n = s - keyref; for (i=0; app_priority_list[i].apptype; i++) if (strlen (app_priority_list[i].name) == n && !ascii_strncasecmp (app_priority_list[i].name, keyref, n)) return app_priority_list[i].apptype; return APPTYPE_NONE; } /* Return true if both serilanumbers are the same. This function * takes care of some peculiarities. */ static int is_same_serialno (const unsigned char *sna, size_t snalen, const unsigned char *snb, size_t snblen) { if ((!sna && !snb) || (!snalen && !snblen)) return 1; if (!sna || !snb) return 0; /* One of them is NULL. (Both NULL tested above). */ if (snalen != snblen) return 0; /* (No special cases for this below). */ /* The special case for OpenPGP cards where we ignore the version * bytes (vvvv). Example: D276000124010304000500009D8A0000 * ^^^^^^^^^^^^vvvvmmmmssssssssrrrr */ if (snalen == 16 && !memcmp (sna, "\xD2\x76\x00\x01\x24\x01", 6)) { if (memcmp (snb, "\xD2\x76\x00\x01\x24\x01", 6)) return 0; /* No */ return !memcmp (sna + 8, snb + 8, 8); } return !memcmp (sna, snb, snalen); } /* Initialization function to change the default app_priority_list. * LIST is a list of comma or space separated strings with application * names. Unknown names will only result in warning message. * Application not mentioned in LIST are used in their original order * after the given once. */ void app_update_priority_list (const char *arg) { struct app_priority_list_s save; char **names; int i, j, idx; names = strtokenize (arg, ", "); if (!names) log_fatal ("strtokenize failed: %s\n", gpg_strerror (gpg_error_from_syserror ())); idx = 0; for (i=0; names[i]; i++) { ascii_strlwr (names[i]); for (j=0; j < i; j++) if (!strcmp (names[j], names[i])) break; if (j < i) { log_info ("warning: duplicate application '%s' in priority list\n", names[i]); continue; } for (j=idx; app_priority_list[j].name; j++) if (!strcmp (names[i], app_priority_list[j].name)) break; if (!app_priority_list[j].name) { log_info ("warning: unknown application '%s' in priority list\n", names[i]); continue; } save = app_priority_list[idx]; app_priority_list[idx] = app_priority_list[j]; app_priority_list[j] = save; idx++; } log_assert (idx < DIM (app_priority_list)); xfree (names); for (i=0; app_priority_list[i].name; i++) log_info ("app priority %d: %s\n", i, app_priority_list[i].name); } static void print_progress_line (void *opaque, const char *what, int pc, int cur, int tot) { ctrl_t ctrl = opaque; char line[100]; if (ctrl) { snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot); send_status_direct (ctrl, "PROGRESS", line); } } /* Lock the CARD. This function shall be used right before calling * any of the actual application functions to serialize access to the * reader. We do this always even if the card is not actually used. * This allows an actual connection to assume that it never shares a * card (while performing one command). Returns 0 on success; only * then the unlock_reader function must be called after returning from * the handler. Right now we assume a that a reader has just one * card; this may eventually need refinement. */ static gpg_error_t lock_card (card_t card, ctrl_t ctrl) { if (npth_mutex_lock (&card->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to acquire CARD lock for %p: %s\n", card, gpg_strerror (err)); return err; } apdu_set_progress_cb (card->slot, print_progress_line, ctrl); apdu_set_prompt_cb (card->slot, popup_prompt, ctrl); return 0; } /* Release a lock on a card. See lock_reader(). */ static void unlock_card (card_t card) { apdu_set_progress_cb (card->slot, NULL, NULL); apdu_set_prompt_cb (card->slot, NULL, NULL); if (npth_mutex_unlock (&card->lock)) { gpg_error_t err = gpg_error_from_syserror (); log_error ("failed to release CARD lock for %p: %s\n", card, gpg_strerror (err)); } } static void card_list_r_lock (void) { npth_mutex_lock (&card_list_lock.lock); while (card_list_lock.num_writers_waiting || card_list_lock.writer_active) npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock); card_list_lock.num_readers_active++; npth_mutex_unlock (&card_list_lock.lock); } static void card_list_r_unlock (void) { npth_mutex_lock (&card_list_lock.lock); if (--card_list_lock.num_readers_active == 0) npth_cond_broadcast (&card_list_lock.cond); npth_mutex_unlock (&card_list_lock.lock); } static void card_list_w_lock (void) { npth_mutex_lock (&card_list_lock.lock); card_list_lock.num_writers_waiting++; while (card_list_lock.num_readers_active || card_list_lock.writer_active) npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock); card_list_lock.num_writers_waiting--; card_list_lock.writer_active++; npth_mutex_unlock (&card_list_lock.lock); } static void card_list_w_unlock (void) { npth_mutex_lock (&card_list_lock.lock); card_list_lock.writer_active--; npth_cond_broadcast (&card_list_lock.cond); npth_mutex_unlock (&card_list_lock.lock); } static void card_list_signal (void) { npth_cond_broadcast (&card_list_lock.notify_cond); } static void card_list_wait (void) { npth_mutex_lock (&card_list_lock.lock); card_list_lock.writer_active--; npth_cond_broadcast (&card_list_lock.cond); npth_cond_wait (&card_list_lock.notify_cond, &card_list_lock.lock); card_list_lock.num_writers_waiting++; while (card_list_lock.num_readers_active || card_list_lock.writer_active) npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock); card_list_lock.num_writers_waiting--; card_list_lock.writer_active++; npth_mutex_unlock (&card_list_lock.lock); } /* This function may be called to print information pertaining to the * current state of this module to the log. */ void app_dump_state (void) { card_t c; app_t a; card_list_r_lock (); for (c = card_top; c; c = c->next) { log_info ("app_dump_state: card=%p slot=%d type=%s refcount=%u\n", c, c->slot, strcardtype (c->cardtype), c->ref_count); /* FIXME The use of log_info risks a race! */ for (a=c->app; a; a = a->next) log_info ("app_dump_state: app=%p type='%s'\n", a, strapptype (a->apptype)); } card_list_r_unlock (); } /* * Send information for all available cards. * * With KEEP_LOOPING=0, it only outputs once. * With KEEP_LOOPING<0, it keeps looping, until it detects no device. * With KEEP_LOOPING>0, it keeps looping forever. */ gpg_error_t app_send_devinfo (ctrl_t ctrl, int keep_looping) { card_t c; app_t a; int no_device; card_list_w_lock (); while (1) { no_device = (card_top == NULL); if (no_device && keep_looping < 0) break; send_status_direct (ctrl, "DEVINFO_START", ""); for (c = card_top; c; c = c->next) { char *serialno; char card_info[80]; serialno = card_get_serialno (c); snprintf (card_info, sizeof card_info, "DEVICE %s %s", strcardtype (c->cardtype), serialno); xfree (serialno); for (a = c->app; a; a = a->next) send_status_direct (ctrl, card_info, strapptype (a->apptype)); } send_status_direct (ctrl, "DEVINFO_END", ""); if (keep_looping == 0) break; card_list_wait (); } card_list_w_unlock (); return no_device ? gpg_error (GPG_ERR_NOT_FOUND): 0; } /* Check whether the application NAME is allowed. This does not mean we have support for it though. */ static int is_app_allowed (const char *name) { strlist_t l; for (l=opt.disabled_applications; l; l = l->next) if (!strcmp (l->d, name)) return 0; /* no */ return 1; /* yes */ } /* This function is mainly used by the serialno command to check for * an application conflict which may appear if the serialno command is * used to request a specific application and the connection has * already done a select_application. Return values are: * 0 - No conflict * GPG_ERR_FALSE - Another application is in use but it is possible * to switch to the requested application. * Other code - Switching is not possible. * * If SERIALNO_BIN is not NULL a conflict is only asserted if the * serialno of the card matches. */ gpg_error_t check_application_conflict (card_t card, const char *name, const unsigned char *serialno_bin, size_t serialno_bin_len) { apptype_t apptype; if (!card || !name) return 0; if (!card->app) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); /* Should not happen. */ if (serialno_bin && card->serialno) { if (!is_same_serialno (card->serialno, card->serialnolen, serialno_bin, serialno_bin_len)) return 0; /* The card does not match the requested S/N. */ } apptype = apptype_from_name (name); if (card->app->apptype == apptype) return 0; if (card->app->apptype == APPTYPE_UNDEFINED) return 0; if (card->cardtype == CARDTYPE_YUBIKEY) { if (card->app->apptype == APPTYPE_OPENPGP) { /* Current app is OpenPGP. */ if (!ascii_strcasecmp (name, "piv")) return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */ } else if (card->app->apptype == APPTYPE_PIV) { /* Current app is PIV. */ if (!ascii_strcasecmp (name, "openpgp")) return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */ } } log_info ("application '%s' in use - can't switch\n", strapptype (card->app->apptype)); return gpg_error (GPG_ERR_CONFLICT); } gpg_error_t card_reset (card_t card) { gpg_error_t err = 0; int sw; sw = apdu_reset (card->slot); if (sw) err = gpg_error (GPG_ERR_CARD_RESET); card->reset_requested = 1; scd_kick_the_loop (); gnupg_sleep (1); return err; } /* Return the card type from (ATR,ATRLEN) or CARDTYPE_GENERIC in case * of error or if the ATR was not found. If ATR is NULL, SLOT is used * to retrieve the ATR from the reader. */ static cardtype_t atr_to_cardtype (int slot, const unsigned char *atr, size_t atrlen) { #define X(a) ((unsigned char const *)(a)) static struct { size_t atrlen; unsigned char const *atr; cardtype_t type; } atrlist[] = { { 19, X("\x3b\xf9\x96\x00\x00\x80\x31\xfe" "\x45\x53\x43\x45\x37\x20\x0f\x00\x20\x46\x4e"), CARDTYPE_SCE7 }, { 0 } }; #undef X unsigned char *atrbuf = NULL; cardtype_t cardtype = 0; int i; if (atr) { atrbuf = apdu_get_atr (slot, &atrlen); if (!atrbuf) return 0; atr = atrbuf; } for (i=0; atrlist[i].atrlen; i++) if (atrlist[i].atrlen == atrlen && !memcmp (atrlist[i].atr, atr, atrlen)) { cardtype = atrlist[i].type; break; } xfree (atrbuf); return cardtype; } static gpg_error_t app_new_register (int slot, ctrl_t ctrl, const char *name, int periodical_check_needed) { gpg_error_t err = 0; card_t card = NULL; app_t app = NULL; unsigned char *result = NULL; size_t resultlen; int want_undefined; int i; /* Need to allocate a new card object */ card = xtrycalloc (1, sizeof *card); if (!card) { err = gpg_error_from_syserror (); log_info ("error allocating context: %s\n", gpg_strerror (err)); return err; } card->slot = slot; card->card_status = (unsigned int)-1; if (npth_mutex_init (&card->lock, NULL)) { err = gpg_error_from_syserror (); log_error ("error initializing mutex: %s\n", gpg_strerror (err)); xfree (card); return err; } err = lock_card (card, ctrl); if (err) { xfree (card); return err; } want_undefined = (name && !strcmp (name, "undefined")); /* Try to read the GDO file first to get a default serial number. We skip this if the undefined application has been requested. */ if (!want_undefined) { err = iso7816_select_file (slot, 0x3F00, 1); if (gpg_err_code (err) == GPG_ERR_CARD) { /* Might be SW==0x7D00. Let's test whether it is a Yubikey * by selecting its manager application and then reading the * config. */ static char const yk_aid[] = { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/ static char const otp_aid[] = { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/ unsigned char *buf; size_t buflen; const unsigned char *s0; unsigned char formfactor; size_t n; if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid, 0x0001) && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, NULL, &buf, &buflen)) { card->cardtype = CARDTYPE_YUBIKEY; if (opt.verbose) { log_info ("Yubico: config="); log_printhex (buf, buflen, ""); } /* We skip the first byte which seems to be the total * length of the config data. */ if (buflen > 1) { s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */ formfactor = (s0 && n == 1)? *s0 : 0; s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */ if (s0 && n <= 4) { card->serialno = xtrymalloc (3 + 1 + 4); if (card->serialno) { card->serialnolen = 3 + 1 + 4; card->serialno[0] = 0xff; card->serialno[1] = 0x02; card->serialno[2] = 0x0; card->serialno[3] = formfactor; memset (card->serialno + 4, 0, 4 - n); memcpy (card->serialno + 4 + 4 - n, s0, n); err = app_munge_serialno (card); } } s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */ if (s0 && n == 3) card->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]); else if (!s0) { /* No version - this is not a Yubikey 5. We now * switch to the OTP app and take the first * three bytes of the response as version * number. */ xfree (buf); buf = NULL; if (!iso7816_select_application_ext (slot, otp_aid, sizeof otp_aid, 1, &buf, &buflen) && buflen > 3) card->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]); } } xfree (buf); } else card->cardtype = atr_to_cardtype (slot, NULL, 0); } else /* Got 3F00 */ { unsigned char *atr; size_t atrlen; /* This is heuristics to identify different implementations. */ /* FIXME: The first two checks are pretty OpenPGP card specific. */ atr = apdu_get_atr (slot, &atrlen); if (atr) { if (atrlen == 21 && atr[2] == 0x11) card->cardtype = CARDTYPE_GNUK; else if (atrlen == 21 && atr[7] == 0x75) card->cardtype = CARDTYPE_ZEITCONTROL; else card->cardtype = atr_to_cardtype (slot, atr, atrlen); xfree (atr); } } if (!err && card->cardtype != CARDTYPE_YUBIKEY) err = iso7816_select_file (slot, 0x2F02, 0); if (!err && card->cardtype != CARDTYPE_YUBIKEY) err = iso7816_read_binary (slot, 0, 0, &result, &resultlen); if (!err && card->cardtype != CARDTYPE_YUBIKEY) { size_t n; const unsigned char *p; p = find_tlv_unchecked (result, resultlen, 0x5A, &n); if (p) resultlen -= (p-result); if (p && n > resultlen && n == 0x0d && resultlen+1 == n) { /* The object does not fit into the buffer. This is an invalid encoding (or the buffer is too short. However, I have some test cards with such an invalid encoding and therefore I use this ugly workaround to return something I can further experiment with. */ log_info ("enabling BMI testcard workaround\n"); n--; } if (p && n <= resultlen) { /* The GDO file is pretty short, thus we simply reuse it for storing the serial number. */ memmove (result, p, n); card->serialno = result; card->serialnolen = n; err = app_munge_serialno (card); if (err) goto leave; } else xfree (result); result = NULL; } } /* Allocate a new app object. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_syserror (); log_info ("error allocating app context: %s\n", gpg_strerror (err)); goto leave; } card->app = app; app->card = card; /* Figure out the application to use. */ if (want_undefined) { /* We switch to the "undefined" application only if explicitly requested. */ app->apptype = APPTYPE_UNDEFINED; /* Clear the error so that we don't run through the application * selection chain. */ err = 0; } else { /* For certain error codes, there is no need to try more. */ if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT || gpg_err_code (err) == GPG_ERR_ENODEV) goto leave; /* Set a default error so that we run through the application * selection chain. */ err = gpg_error (GPG_ERR_NOT_FOUND); } /* Find the first available app if NAME is NULL or the matching * NAME but only if that application is also enabled. */ for (i=0; err && app_priority_list[i].name; i++) { if (is_app_allowed (app_priority_list[i].name) && (!name || !strcmp (name, app_priority_list[i].name))) err = app_priority_list[i].select_func (app); } if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE) err = gpg_error (GPG_ERR_NOT_SUPPORTED); leave: if (err) { if (name) log_info ("can't select application '%s': %s\n", name, gpg_strerror (err)); else log_info ("no supported card application found: %s\n", gpg_strerror (err)); unlock_card (card); xfree (app); xfree (card); return err; } card->periodical_check_needed = periodical_check_needed; card->next = card_top; card_top = card; unlock_card (card); return 0; } /* If called with NAME as NULL, select the best fitting application * and return its card context; otherwise select the application with * NAME and return its card context. Returns an error code and stores * NULL at R_CARD if no application was found or no card is present. */ gpg_error_t select_application (ctrl_t ctrl, const char *name, int scan, const unsigned char *serialno_bin, size_t serialno_bin_len) { gpg_error_t err = 0; card_t card, card_prev = NULL; card_list_w_lock (); ctrl->card_ctx = NULL; if (scan || !card_top) { struct dev_list *l; int new_card = 0; /* Scan the devices to find new device(s). */ err = apdu_dev_list_start (opt.reader_port, &l); if (err) { card_list_w_unlock (); return err; } while (1) { int slot; int periodical_check_needed_this; slot = apdu_open_reader (l); if (slot < 0) break; periodical_check_needed_this = apdu_connect (slot); if (periodical_check_needed_this < 0) { /* We close a reader with no card. */ err = gpg_error (GPG_ERR_ENODEV); } else { err = app_new_register (slot, ctrl, name, periodical_check_needed_this); new_card++; } if (err) { pincache_put (ctrl, slot, NULL, NULL, NULL, 0); apdu_close_reader (slot); } } apdu_dev_list_finish (l); /* If new device(s), kick the scdaemon loop. */ if (new_card) scd_kick_the_loop (); } for (card = card_top; card; card = card->next) { lock_card (card, ctrl); if (serialno_bin == NULL) break; if (is_same_serialno (card->serialno, card->serialnolen, serialno_bin, serialno_bin_len)) break; unlock_card (card); card_prev = card; } if (card) { err = check_application_conflict (card, name, NULL, 0); if (!err) ctrl->current_apptype = card->app ? card->app->apptype : APPTYPE_NONE; else if (gpg_err_code (err) == GPG_ERR_FALSE) { apptype_t req_apptype = apptype_from_name (name); if (!req_apptype) err = gpg_error (GPG_ERR_NOT_FOUND); else { err = select_additional_application_internal (card, req_apptype); if (!err) ctrl->current_apptype = req_apptype; } } if (!err) { card->ref_count++; ctrl->card_ctx = card; if (card_prev) { card_prev->next = card->next; card->next = card_top; card_top = card; } } unlock_card (card); } else err = gpg_error (GPG_ERR_ENODEV); card_list_w_unlock (); return err; } /* Switch the current card for the session CTRL and print a SERIALNO * status line on success. (SERIALNO, SERIALNOLEN) is the binary s/n * of the card to switch to. */ gpg_error_t app_switch_current_card (ctrl_t ctrl, const unsigned char *serialno, size_t serialnolen) { gpg_error_t err; card_t card, cardtmp; card_list_r_lock (); cardtmp = ctrl->card_ctx; if (!cardtmp) { err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); goto leave; } if (serialno && serialnolen) { for (card = card_top; card; card = card->next) { if (is_same_serialno (card->serialno, card->serialnolen, serialno, serialnolen)) break; } if (!card) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } /* Note: We do not lock CARD and CARDTMP here because we only * swap the context of the current session and there is no * chance of a context switch. This also works if the card * stays the same. */ ctrl->card_ctx = card; card->ref_count++; card_unref_locked (cardtmp); } /* Print the status line. */ err = send_serialno_and_app_status (ctrl->card_ctx, 0, ctrl); leave: card_list_r_unlock (); return err; } static gpg_error_t select_additional_application_internal (card_t card, apptype_t req_apptype) { gpg_error_t err = 0; app_t app; int i; /* Check that the requested app has not yet been put onto the list. */ for (app = card->app; app; app = app->next) if (app->apptype == req_apptype) { /* We already got this one. Note that in this case we don't * make it the current one but it doesn't matter because * maybe_switch_app will do that anyway. */ err = 0; app = NULL; goto leave; } /* Allocate a new app object. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_syserror (); log_info ("error allocating app context: %s\n", gpg_strerror (err)); goto leave; } app->card = card; /* Find the app and run the select. */ for (i=0; app_priority_list[i].apptype; i++) { if (app_priority_list[i].apptype == req_apptype && is_app_allowed (app_priority_list[i].name)) { err = app_priority_list[i].select_func (app); break; } } if (!app_priority_list[i].apptype || (err && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)) err = gpg_error (GPG_ERR_NOT_SUPPORTED); if (err) goto leave; /* Add this app. We make it the current one to avoid an extra * reselect by maybe_switch_app after the select we just did. */ app->next = card->app; card->app = app; log_info ("added app '%s' to the card context and switched\n", strapptype (app->apptype)); leave: if (err) xfree (app); return err; } /* Add all possible additional applications to the card context but do * not change the current one. This currently works only for Yubikeys. */ static gpg_error_t select_all_additional_applications_internal (ctrl_t ctrl, card_t card) { gpg_error_t err = 0; apptype_t candidates[3]; int i, j; int any_new = 0; if (card->cardtype == CARDTYPE_YUBIKEY) { candidates[0] = APPTYPE_OPENPGP; candidates[1] = APPTYPE_PIV; candidates[2] = APPTYPE_NONE; } else { candidates[0] = APPTYPE_NONE; } /* Find the app and run the select. */ for (i=0; app_priority_list[i].apptype; i++) { app_t app, app_r, app_prev; for (j=0; candidates[j]; j++) if (candidates[j] == app_priority_list[i].apptype && is_app_allowed (app_priority_list[i].name)) break; if (!candidates[j]) continue; for (app = card->app; app; app = app->next) if (app->apptype == candidates[j]) break; if (app) continue; /* Already on the list of apps. */ app = xtrycalloc (1, sizeof *app); if (!app) { err = gpg_error_from_syserror (); log_info ("error allocating app context: %s\n", gpg_strerror (err)); goto leave; } app->card = card; err = app_priority_list[i].select_func (app); if (err) { log_error ("error selecting additional app '%s': %s - skipped\n", strapptype (candidates[j]), gpg_strerror (err)); err = 0; xfree (app); } else { /* Append to the list of apps. */ app_prev = card->app; for (app_r=app_prev->next; app_r; app_prev=app_r, app_r=app_r->next) ; app_prev->next = app; log_info ("added app '%s' to the card context\n", strapptype (app->apptype)); any_new = 1; } } /* If we found a new application we need to reselect the original * application so that we are in a well defined state. */ if (!err && any_new && card->app && card->app->fnc.reselect) err = run_reselect (ctrl, card, card->app, NULL); leave: return err; } /* This function needs to be called with the NAME of the new * application to be selected on CARD. On success the application is * added to the list of the card's active applications as currently * active application. On error no new application is allocated. * Selecting an already selected application has no effect. */ gpg_error_t select_additional_application (card_t card, ctrl_t ctrl, const char *name) { gpg_error_t err = 0; apptype_t req_apptype; if (!name) req_apptype = 0; else { req_apptype = apptype_from_name (name); if (!req_apptype) return gpg_error (GPG_ERR_NOT_FOUND); } if (req_apptype) { err = select_additional_application_internal (card, req_apptype); if (!err) { ctrl->current_apptype = req_apptype; if (DBG_APP) log_debug ("current_apptype is set to %s\n", name); } } else { err = select_all_additional_applications_internal (ctrl, card); } return err; } char * get_supported_applications (void) { int idx; size_t nbytes; char *buffer, *p; const char *s; for (nbytes=1, idx=0; (s=app_priority_list[idx].name); idx++) nbytes += strlen (s) + 1 + 1; buffer = xtrymalloc (nbytes); if (!buffer) return NULL; for (p=buffer, idx=0; (s=app_priority_list[idx].name); idx++) if (is_app_allowed (s)) p = stpcpy (stpcpy (p, s), ":\n"); *p = 0; return buffer; } /* Deallocate the application. */ static void deallocate_card (card_t card) { card_t c, c_prev = NULL; app_t a, anext; for (c = card_top; c; c = c->next) if (c == card) { if (c_prev == NULL) card_top = c->next; else c_prev->next = c->next; break; } else c_prev = c; if (card->ref_count) log_error ("releasing still used card context (%d)\n", card->ref_count); for (a = card->app; a; a = anext) { if (a->fnc.deinit) { a->fnc.deinit (a); a->fnc.deinit = NULL; } anext = a->next; xfree (a); } xfree (card->serialno); unlock_card (card); xfree (card); } static card_t do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str, int capability) { int locked = 0; card_t c; app_t a, a_prev; for (c = card_top; c; c = c->next) { if (lock_card (c, ctrl)) { c = NULL; goto leave_the_loop; } locked = 1; a_prev = NULL; for (a = c->app; a; a = a->next) { if (!a->fnc.with_keygrip || a->need_reset) continue; /* Note that we need to do a re-select even for the current * app because the last selected application (e.g. after * init) might be a different one and we do not run * maybe_switch_app here. Of course we we do this only iff * we have an additional app. */ if (c->app->next) { if (run_reselect (ctrl, c, a, a_prev)) continue; } a_prev = a; if (DBG_APP) log_debug ("slot %d, app %s: calling with_keygrip(%s)\n", c->slot, xstrapptype (a), action == KEYGRIP_ACTION_SEND_DATA? "send_data": action == KEYGRIP_ACTION_WRITE_STATUS? "status": action == KEYGRIP_ACTION_LOOKUP? "lookup":"?"); if (!a->fnc.with_keygrip (a, ctrl, action, keygrip_str, capability)) goto leave_the_loop; /* ACTION_LOOKUP succeeded. */ } /* Select the first app again. */ if (c->app->next) run_reselect (ctrl, c, c->app, a_prev); unlock_card (c); locked = 0; } leave_the_loop: /* Force switching of the app if the selected one is not the current * one. Changing the current apptype is sufficient to do this. */ if (c && c->app && c->app->apptype != a->apptype) ctrl->current_apptype = a->apptype; if (locked && c) { unlock_card (c); locked = 0; } return c; } /* Locking access to the card-list and CARD, returns CARD. */ card_t card_get (ctrl_t ctrl, const char *keygrip) { card_t card; card_list_r_lock (); if (keygrip) card = do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keygrip, 0); else card = ctrl->card_ctx; if (!card) { card_list_r_unlock (); return NULL; } lock_card (card, NULL); return card; } /* Release the lock of CARD and the card-list. */ void card_put (card_t card) { /* We don't deallocate CARD here. Instead, we keep it. This is useful so that a card does not get reset even if only one session is using the card - this way the PIN cache and other cached data are preserved. */ unlock_card (card); card_list_r_unlock (); } /* This is the same as card_unref but assumes that CARD is already * locked. */ void card_unref_locked (card_t card) { if (!card) return; if (!card->ref_count) log_bug ("tried to release an already released card context\n"); --card->ref_count; } /* The serial number may need some cosmetics. Do it here. This function shall only be called once after a new serial number has been put into APP->serialno. Prefixes we use: FF 00 00 = For serial numbers starting with an FF FF 01 00 = Some german p15 cards return an empty serial number so the serial number from the EF(TokenInfo) is used instead. FF 02 00 = Serial number from Yubikey config. This is normally not seen because we modify this here to an OpenPGP Card s/n. FF 7F 00 = No serialno. All other serial numbers not starting with FF are used as they are. */ gpg_error_t app_munge_serialno (card_t card) { if (card->cardtype == CARDTYPE_YUBIKEY && card->serialnolen == 3 + 1 + 4 && !memcmp (card->serialno, "\xff\x02\x00", 3)) { /* An example for a serial number is * FF020001008A77C1 * ~~~~~~--~~~~~~~~ * ! ! !--------- 4 byte s/n * ! !----------- Form factor * !----------------- Our prefix * Yubico seems to use the decimalized version of their S/N * as the OpenPGP card S/N. Thus in theory we can contruct the * number from this information so that we do not rely on having * the OpenPGP app enabled. */ unsigned long sn; sn = card->serialno[4] * 16777216; sn += card->serialno[5] * 65536; sn += card->serialno[6] * 256; sn += card->serialno[7]; if (sn <= 99999999ul) { char *buf = xtrymalloc (16); if (!buf) return gpg_error_from_syserror (); memcpy (buf, "\xD2\x76\x00\x01\x24\x01", 6); buf[6] = 0; /* Application version which we don't know */ buf[7] = 0; /* thus we use 0.0 and don't use this directly. */ buf[8] = 0; /* Manufacturer: Yubico (0x0006). */ buf[9] = 6; buf[13] = (sn % 10); sn /= 10; buf[13] |= (sn % 10) << 4; sn /= 10; buf[12] = (sn % 10); sn /= 10; buf[12] |= (sn % 10) << 4; sn /= 10; buf[11] = (sn % 10); sn /= 10; buf[11] |= (sn % 10) << 4; sn /= 10; buf[10] = (sn % 10); sn /= 10; buf[10] |= (sn % 10) << 4; sn /= 10; buf[14] = 0; /* Last two bytes are RFU. */ buf[15] = 0; xfree (card->serialno); card->serialno = buf; card->serialnolen = 16; } } else if (card->serialnolen && card->serialno[0] == 0xff) { /* The serial number starts with our special prefix. This requires that we put our default prefix "FF0000" in front. */ unsigned char *p = xtrymalloc (card->serialnolen + 3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\0", 3); memcpy (p+3, card->serialno, card->serialnolen); card->serialnolen += 3; xfree (card->serialno); card->serialno = p; } else if (!card->serialnolen) { unsigned char *p = xtrymalloc (3); if (!p) return gpg_error_from_syserror (); memcpy (p, "\xff\x7f", 3); card->serialnolen = 3; xfree (card->serialno); card->serialno = p; } return 0; } /* Retrieve the serial number of the card. The serial number is returned as a malloced string (hex encoded) in SERIAL. Caller must free SERIAL unless the function returns an error. */ char * card_get_serialno (card_t card) { char *serial; if (!card) return NULL; if (!card->serialnolen) serial = xtrystrdup ("FF7F00"); else serial = bin2hex (card->serialno, card->serialnolen, NULL); return serial; } /* Same as card_get_serialno but takes an APP object. */ char * app_get_serialno (app_t app) { if (!app || !app->card) { gpg_err_set_errno (0); return NULL; } return card_get_serialno (app->card); } /* Return an allocated string with the serial number in a format to be * show to the user. With NOFALLBACK set to true return NULL if such an * abbreviated S/N is not available, else return the full serial * number as a hex string. May return NULL on malloc problem. */ char * card_get_dispserialno (card_t card, int nofallback) { char *result, *p; unsigned long sn; if (card && card->serialno && card->serialnolen == 3+1+4 && !memcmp (card->serialno, "\xff\x02\x00", 3)) { /* This is a 4 byte S/N of a Yubikey which seems to be printed * on the token in decimal. Maybe they will print larger S/N * also in decimal but we can't be sure, thus do it only for * these 32 bit numbers. */ sn = card->serialno[4] * 16777216; sn += card->serialno[5] * 65536; sn += card->serialno[6] * 256; sn += card->serialno[7]; if ((card->cardversion >> 16) >= 5) result = xtryasprintf ("%lu %03lu %03lu", (sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul)); else result = xtryasprintf ("%lu", sn); } else if (card && card->cardtype == CARDTYPE_YUBIKEY) { /* Get back the printed Yubikey number from the OpenPGP AID * Example: D2760001240100000006120808620000 */ result = card_get_serialno (card); if (result && strlen (result) >= 28 && !strncmp (result+16, "0006", 4)) { sn = atoi_4 (result+20) * 10000; sn += atoi_4 (result+24); if ((card->cardversion >> 16) >= 5) p = xtryasprintf ("%lu %03lu %03lu", (sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul)); else p = xtryasprintf ("%lu", sn); if (p) { xfree (result); result = p; } } else if (nofallback) { xfree (result); result = NULL; } } else if (card && card->app && card->app->apptype == APPTYPE_OPENPGP) { /* Extract number from standard OpenPGP AID. */ result = card_get_serialno (card); if (result && strlen (result) > 16+12) { memcpy (result, result+16, 4); result[4] = ' '; memcpy (result+5, result+20, 8); result[13] = 0; } else if (nofallback) { xfree (result); result = NULL; } } else if (nofallback) result = NULL; /* No Abbreviated S/N. */ else result = card_get_serialno (card); return result; } /* Same as card_get_dispserialno but takes an APP object. */ char * app_get_dispserialno (app_t app, int nofallback) { if (!app || !app->card) { gpg_err_set_errno (0); return NULL; } return card_get_dispserialno (app->card, nofallback); } /* Helper to run the reselect function. */ static gpg_error_t run_reselect (ctrl_t ctrl, card_t c, app_t a, app_t a_prev) { gpg_error_t err; if (!a->fnc.reselect) { log_info ("slot %d, app %s: re-select not implemented\n", c->slot, xstrapptype (a)); return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); } /* Give the current app a chance to save some state before another * app is selected. We ignore errors here because that state saving * (e.g. putting PINs into a cache) is a convenience feature and not * required to always work. */ if (a_prev && a_prev->fnc.prep_reselect) { if (a_prev->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = a_prev->fnc.prep_reselect (a_prev, ctrl); if (err) log_error ("slot %d, app %s: preparing re-select from %s failed: %s\n", c->slot, xstrapptype (a), xstrapptype (a_prev), gpg_strerror (err)); } if (a->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = a->fnc.reselect (a, ctrl); if (err) { log_error ("slot %d, app %s: error re-selecting: %s\n", c->slot, xstrapptype (a), gpg_strerror (err)); return err; } if (DBG_APP) log_debug ("slot %d, app %s: re-selected\n", c->slot, xstrapptype (a)); return 0; } /* * Check external interference before each use of the application on * card. Returns -1 when detecting some external interference. * Returns 0 if not. * * Note: This kind of detection can't be perfect. At most, it may be * possibly useful kludge, in some limited situations. */ static int check_external_interference (app_t app, ctrl_t ctrl) { /* * Only when a user is using Yubikey with pcsc-shared configuration, * we need this detection. Otherwise, the card/token is under full * control of scdaemon, there's no problem at all. However, if the * APDU command has been used we better also check whether the AID * is still valid. */ if (app && app->card && app->card->maybe_check_aid) app->card->maybe_check_aid = 0; else if (!opt.pcsc_shared || app->card->cardtype != CARDTYPE_YUBIKEY) return 0; if (app->fnc.check_aid) { unsigned char *aid; size_t aidlen; gpg_error_t err; int slot = app_get_slot (app); err = iso7816_get_data (slot, 0, 0x004F, &aid, &aidlen); if (err) return -1; err = app->fnc.check_aid (app, ctrl, aid, aidlen); xfree (aid); if (err) return -1; } return 0; } /* Check that the card has been initialized and whether we need to * switch to another application on the same card. Switching means * that the new active app will be moved to the head of the list at * CARD->app. This function must be called with the card lock held. */ static gpg_error_t maybe_switch_app (ctrl_t ctrl, card_t card, const char *keyref) { gpg_error_t err; app_t app; app_t app_prev = NULL; apptype_t apptype; if (!card->app) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); if (card->maybe_check_aid && card->app->fnc.reselect && check_external_interference (card->app, ctrl)) { if (DBG_APP) log_debug ("slot %d, app %s: forced re-select due to direct APDU use\n", card->slot, xstrapptype (card->app)); err = card->app->fnc.reselect (card->app, ctrl); if (err) log_error ("slot %d, app %s: forced re-select failed: %s - ignored\n", card->slot, xstrapptype (card->app), gpg_strerror (err)); err = 0; } if (!ctrl->current_apptype) { /* For whatever reasons the current apptype has not been set - * fix that and use the current app. */ if (DBG_APP) log_debug ("slot %d: no current app switching to %s\n", card->slot, strapptype (card->app->apptype)); ctrl->current_apptype = card->app->apptype; return 0; } for (app = card->app; app; app = app->next) if (app->apptype == ctrl->current_apptype) break; if (!app) { /* The current app is not supported by this card. Set the first * app of the card as current. */ if (DBG_APP) log_debug ("slot %d: current app %s not available switching to %s\n", card->slot, strapptype (ctrl->current_apptype), strapptype (card->app->apptype)); ctrl->current_apptype = card->app->apptype; return 0; } if (DBG_APP) log_debug ("slot %d: have=%s want=%s keyref=%s\n", card->slot, strapptype (card->app->apptype), strapptype (ctrl->current_apptype), keyref? keyref:"[none]"); app = NULL; if (keyref) { /* Switch based on the requested KEYREF. */ apptype = apptype_from_keyref (keyref); if (apptype) { for (app = card->app; app; app_prev = app, app = app->next) if (app->apptype == apptype) break; if (!app_prev && ctrl->current_apptype == card->app->apptype) if (check_external_interference (app, ctrl) == 0) return 0; /* Already the first app - no need to switch. */ } else if (strlen (keyref) == 40) { /* This looks like a keygrip. Iterate over all apps to find * the corresponding app. */ for (app = card->app; app; app_prev = app, app = app->next) if (app->fnc.with_keygrip && !app->need_reset && !app->fnc.with_keygrip (app, ctrl, KEYGRIP_ACTION_LOOKUP, keyref, 0)) break; if (!app_prev && ctrl->current_apptype == card->app->apptype) if (check_external_interference (app, ctrl) == 0) return 0; /* Already the first app - no need to switch. */ } } if (!app) { /* Switch based on the current application of this connection or * if a keyref based switch didn't worked. */ if (ctrl->current_apptype == card->app->apptype) return 0; /* No need to switch. */ app_prev = card->app; for (app = app_prev->next; app; app_prev = app, app = app->next) if (app->apptype == ctrl->current_apptype) break; } if (!app) return gpg_error (GPG_ERR_WRONG_CARD); err = run_reselect (ctrl, card, app, app_prev); if (err) return err; /* Swap APP with the head of the app list if needed. Note that APP * is not the head of the list. */ if (app_prev) { app_prev->next = app->next; app->next = card->app; card->app = app; } if (opt.verbose) log_info ("slot %d, app %s: %s\n", card->slot, xstrapptype (app), app_prev? "switched":"re-selected"); ctrl->current_apptype = app->apptype; return 0; } /* Helper for app_write_learn_status. */ static gpg_error_t write_learn_status_core (card_t card, app_t app, ctrl_t ctrl, unsigned int flags) { gpg_error_t err; /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ if (!(flags & APP_LEARN_FLAG_KEYPAIRINFO)) { if (card && card->cardtype) send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype)); if (card && card->cardversion) send_status_printf (ctrl, "CARDVERSION", "%X", card->cardversion); if (app->apptype) send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype)); if (app->appversion) send_status_printf (ctrl, "APPVERSION", "%X", app->appversion); } if (app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else { err = app->fnc.learn_status (app, ctrl, flags); if (err && (flags & APP_LEARN_FLAG_REREAD)) app->need_reset = 1; } return err; } /* Write out the application specific status lines for the LEARN command. */ gpg_error_t app_write_learn_status (card_t card, ctrl_t ctrl, unsigned int flags) { gpg_error_t err, err2, tmperr; app_t app, last_app; int any_reselect = 0; /* Always make sure that the current app for this connection has * been selected and is at the top of the list. */ if ((err = maybe_switch_app (ctrl, card, NULL))) ; else if (!card->app->fnc.learn_status) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { err = write_learn_status_core (card, card->app, ctrl, flags); if (!err && card->app->fnc.reselect && (flags & APP_LEARN_FLAG_MULTI)) { /* The current app has the reselect feature so that we can * loop over all other apps which are capable of a reselect * and finally reselect the first app again. Note that we * did the learn for the currently selected card above. */ app = last_app = card->app; for (app = app->next; app && !err; app = app->next) if (app->fnc.reselect) { if (last_app && last_app->fnc.prep_reselect) { tmperr = last_app->fnc.prep_reselect (last_app, ctrl); if (tmperr) log_info ("slot %d, app %s:" " preparing re-select from %s failed: %s\n", card->slot, xstrapptype (app), xstrapptype (last_app), gpg_strerror (tmperr)); } any_reselect = 1; err = app->fnc.reselect (app, ctrl); if (!err) { last_app = app; err = write_learn_status_core (NULL, app, ctrl, flags); } } app = card->app; if (any_reselect) { if (last_app && last_app->fnc.prep_reselect) { tmperr = last_app->fnc.prep_reselect (last_app, ctrl); if (tmperr) log_info ("slot %d, app %s:" " preparing re-select from %s failed: %s\n", card->slot, xstrapptype (app), xstrapptype (last_app), gpg_strerror (tmperr)); } err2 = app->fnc.reselect (app, ctrl); if (err2) { log_error ("error re-selecting '%s': %s\n", strapptype(app->apptype), gpg_strerror (err2)); if (!err) err = err2; } } } } return err; } /* Read the certificate with id CERTID (as returned by learn_status in the CERTINFO status lines) and return it in the freshly allocated buffer put into CERT and the length of the certificate put into CERTLEN. */ gpg_error_t app_readcert (card_t card, ctrl_t ctrl, const char *certid, unsigned char **cert, size_t *certlen) { gpg_error_t err; if ((err = maybe_switch_app (ctrl, card, certid))) ; else if (!card->app->fnc.readcert) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling readcert(%s)\n", card->slot, xstrapptype (card->app), certid); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.readcert (card->app, certid, cert, certlen); } return err; } /* Read the key with ID KEYID. On success a canonical encoded * S-expression with the public key will get stored at PK and its * length (for assertions) at PKLEN; the caller must release that * buffer. On error NULL will be stored at PK and PKLEN and an error * code returned. If the key is not required NULL may be passed for * PK; this makes sense if the APP_READKEY_FLAG_INFO has also been set. * * This function might not be supported by all applications. */ gpg_error_t app_readkey (card_t card, ctrl_t ctrl, const char *keyid, unsigned int flags, unsigned char **pk, size_t *pklen) { gpg_error_t err; if (pk) *pk = NULL; if (pklen) *pklen = 0; if (!keyid) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyid))) ; else if (!card->app->fnc.readkey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling readkey(%s)\n", card->slot, xstrapptype (card->app), keyid); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.readkey (card->app, ctrl, keyid, flags, pk, pklen); } return err; } /* Perform a GETATTR operation. */ gpg_error_t app_getattr (card_t card, ctrl_t ctrl, const char *name) { gpg_error_t err; if (!name || !*name) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, NULL))) ; else if (name && !strcmp (name, "CARDTYPE")) { send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype)); } else if (name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", strapptype (card->app->apptype)); } else if (name && !strcmp (name, "SERIALNO")) { char *serial; serial = app_get_serialno (card->app); if (!serial) err = gpg_error (GPG_ERR_INV_VALUE); else { send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); } } else if (!card->app->fnc.getattr) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling getattr(%s)\n", card->slot, xstrapptype (card->app), name); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.getattr (card->app, ctrl, name); } return err; } /* Perform a SETATTR operation. */ gpg_error_t app_setattr (card_t card, ctrl_t ctrl, const char *name, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *value, size_t valuelen) { gpg_error_t err; if (!name || !*name || !value) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, NULL))) ; else if (!card->app->fnc.setattr) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling setattr(%s)\n", card->slot, xstrapptype (card->app), name); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.setattr (card->app, ctrl, name, pincb, pincb_arg, value, valuelen); } return err; } /* Create the signature and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_sign (card_t card, ctrl_t ctrl, const char *keyidstr, int hashalgo, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyidstr))) ; else if (!card->app->fnc.sign) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling sign(%s)\n", card->slot, xstrapptype (card->app), keyidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.sign (card->app, ctrl, keyidstr, hashalgo, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); } if (opt.verbose) log_info ("operation sign result: %s\n", gpg_strerror (err)); return err; } /* Create the signature using the INTERNAL AUTHENTICATE command and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_auth (card_t card, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen ) { gpg_error_t err; if (!outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyidstr))) ; else if (!card->app->fnc.auth) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (card->app->apptype != APPTYPE_OPENPGP && (!indata || !indatalen)) return gpg_error (GPG_ERR_INV_VALUE); if (DBG_APP) log_debug ("slot %d app %s: calling auth(%s)\n", card->slot, xstrapptype (card->app), keyidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.auth (card->app, ctrl, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen); } if (opt.verbose) log_info ("operation auth result: %s\n", gpg_strerror (err)); return err; } /* Decrypt the data in INDATA and return the allocated result in OUTDATA. If a PIN is required the PINCB will be used to ask for the PIN; it should return the PIN in an allocated buffer and put it into PIN. */ gpg_error_t app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const void *indata, size_t indatalen, unsigned char **outdata, size_t *outdatalen, unsigned int *r_info) { gpg_error_t err; *r_info = 0; if (!indata || !indatalen || !outdata || !outdatalen || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyidstr))) ; else if (!card->app->fnc.decipher) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling decipher(%s)\n", card->slot, xstrapptype (card->app), keyidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.decipher (card->app, ctrl, keyidstr, pincb, pincb_arg, indata, indatalen, outdata, outdatalen, r_info); } if (opt.verbose) log_info ("operation decipher result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITECERT operation. */ gpg_error_t app_writecert (card_t card, ctrl_t ctrl, const char *certidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *data, size_t datalen) { gpg_error_t err; if (!certidstr || !*certidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, certidstr))) ; else if (!card->app->fnc.writecert) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling writecert(%s)\n", card->slot, xstrapptype (card->app), certidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.writecert (card->app, ctrl, certidstr, pincb, pincb_arg, data, datalen); } if (opt.verbose) log_info ("operation writecert result: %s\n", gpg_strerror (err)); return err; } /* Perform the WRITEKEY operation. */ gpg_error_t app_writekey (card_t card, ctrl_t ctrl, const char *keyidstr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg, const unsigned char *keydata, size_t keydatalen) { gpg_error_t err; if (!keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keyidstr))) ; else if (!card->app->fnc.writekey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling writekey(%s)\n", card->slot, xstrapptype (card->app), keyidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.writekey (card->app, ctrl, keyidstr, flags, pincb, pincb_arg, keydata, keydatalen); } if (opt.verbose) log_info ("operation writekey result: %s\n", gpg_strerror (err)); return err; } /* Perform a GENKEY operation. */ gpg_error_t app_genkey (card_t card, ctrl_t ctrl, const char *keynostr, const char *keytype, unsigned int flags, time_t createtime, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!keynostr || !*keynostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, keynostr))) ; else if (!card->app->fnc.genkey) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling genkey(%s)\n", card->slot, xstrapptype (card->app), keynostr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.genkey (card->app, ctrl, keynostr, keytype, flags, createtime, pincb, pincb_arg); } if (opt.verbose) log_info ("operation genkey result: %s\n", gpg_strerror (err)); return err; } /* Perform a GET CHALLENGE operation. This function is special as it directly accesses the card without any application specific wrapper. */ gpg_error_t app_get_challenge (card_t card, ctrl_t ctrl, size_t nbytes, unsigned char *buffer) { (void)ctrl; if (!nbytes || !buffer) return gpg_error (GPG_ERR_INV_VALUE); return iso7816_get_challenge (card->slot, nbytes, buffer); } /* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */ gpg_error_t app_change_pin (card_t card, ctrl_t ctrl, const char *chvnostr, unsigned int flags, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!chvnostr || !*chvnostr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, NULL))) ; else if (!card->app->fnc.change_pin) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling change_pin(%s)\n", card->slot, xstrapptype (card->app), chvnostr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.change_pin (card->app, ctrl, chvnostr, flags, pincb, pincb_arg); } if (opt.verbose) log_info ("operation change_pin result: %s\n", gpg_strerror (err)); return err; } /* Perform a VERIFY operation without doing anything else. This may be used to initialize a the PIN cache for long lasting other operations. Its use is highly application dependent. */ gpg_error_t app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr, gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg) { gpg_error_t err; if (!keyidstr || !*keyidstr || !pincb) return gpg_error (GPG_ERR_INV_VALUE); if ((err = maybe_switch_app (ctrl, card, NULL))) ; else if (!card->app->fnc.check_pin) err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); else { if (DBG_APP) log_debug ("slot %d app %s: calling check_pin(%s)\n", card->slot, xstrapptype (card->app), keyidstr); if (card->app->need_reset) err = gpg_error (GPG_ERR_CARD_RESET); else err = card->app->fnc.check_pin (card->app, ctrl, keyidstr, pincb, pincb_arg); } if (opt.verbose) log_info ("operation check_pin result: %s\n", gpg_strerror (err)); return err; } - +#ifndef HAVE_W32_SYSTEM static void -setup_env (struct spawn_cb_arg *sca) +setup_env (void *arg) { -#ifdef HAVE_W32_SYSTEM - (void)sca; /* Not supported on Windows. */ -#else - char *v = sca->arg; + char *v = arg; putenv (v); -#endif } +#endif static void report_change (int slot, int old_status, int cur_status) { char *homestr, *envstr; char *fname; char templ[50]; estream_t fp; snprintf (templ, sizeof templ, "reader_%d.status", slot); fname = make_filename (gnupg_homedir (), templ, NULL ); fp = es_fopen (fname, "w"); if (fp) { es_fprintf (fp, "%s\n", (cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); es_fclose (fp); } xfree (fname); homestr = make_filename (gnupg_homedir (), NULL); if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) log_error ("out of core while building environment\n"); else { gpg_error_t err; const char *args[9]; char numbuf1[30], numbuf2[30], numbuf3[30]; + gnupg_spawn_actions_t act = NULL; sprintf (numbuf1, "%d", slot); sprintf (numbuf2, "0x%04X", old_status); sprintf (numbuf3, "0x%04X", cur_status); args[0] = "--reader-port"; args[1] = numbuf1; args[2] = "--old-code"; args[3] = numbuf2; args[4] = "--new-code"; args[5] = numbuf3; args[6] = "--status"; args[7] = ((cur_status & 1)? "USABLE": (cur_status & 4)? "ACTIVE": (cur_status & 2)? "PRESENT": "NOCARD"); args[8] = NULL; fname = make_filename (gnupg_homedir (), "scd-event", NULL); - err = gnupg_process_spawn (fname, args, - GNUPG_PROCESS_DETACHED, - setup_env, envstr, NULL); + err = gnupg_spawn_actions_new (&act); + if (!err) + { +#ifndef HAVE_W32_SYSTEM + gnupg_spawn_actions_set_atfork (act, setup_env, envstr); +#endif + err = gnupg_process_spawn (fname, args, GNUPG_PROCESS_DETACHED, + act, NULL); + gnupg_spawn_actions_release (act); + } if (err && gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("failed to run event handler '%s': %s\n", fname, gpg_strerror (err)); xfree (fname); xfree (envstr); } xfree (homestr); } int scd_update_reader_status_file (void) { card_t card, card_next; int periodical_check_needed = 0; int reported = 0; card_list_w_lock (); for (card = card_top; card; card = card_next) { int sw; unsigned int status; lock_card (card, NULL); card_next = card->next; if (card->reset_requested) { /* Here is the post-processing of RESET request. */ status = 0; card->reset_requested = 0; } else { sw = apdu_get_status (card->slot, 0, &status); if (sw == SW_HOST_NO_READER) { /* Most likely the _reader_ has been unplugged. */ status = 0; } else if (sw) { /* Get status failed. Ignore that. */ if (card->periodical_check_needed) periodical_check_needed = 1; unlock_card (card); continue; } } if (card->card_status != status) { report_change (card->slot, card->card_status, status); send_client_notifications (card, status == 0); reported++; if (status == 0) { if (DBG_APP) log_debug ("Removal of a card: %d\n", card->slot); pincache_put (NULL, card->slot, NULL, NULL, NULL, 0); apdu_close_reader (card->slot); deallocate_card (card); } else { card->card_status = status; if (card->periodical_check_needed) periodical_check_needed = 1; unlock_card (card); } } else { if (card->periodical_check_needed) periodical_check_needed = 1; unlock_card (card); } } if (reported) card_list_signal (); card_list_w_unlock (); return periodical_check_needed; } /* This function must be called once to initialize this module. This has to be done before a second thread is spawned. We can't do the static initialization because Pth emulation code might not be able to do a static init; in particular, it is not possible for W32. */ gpg_error_t initialize_module_command (void) { gpg_error_t err; card_list_lock.num_readers_active = 0; card_list_lock.num_writers_waiting = 0; card_list_lock.writer_active = 0; if (npth_mutex_init (&card_list_lock.lock, NULL)) { err = gpg_error_from_syserror (); log_error ("app: error initializing mutex: %s\n", gpg_strerror (err)); return err; } err = npth_cond_init (&card_list_lock.cond, NULL); if (err) { err = gpg_error_from_syserror (); log_error ("npth_cond_init failed: %s\n", gpg_strerror (err)); return err; } err = npth_cond_init (&card_list_lock.notify_cond, NULL); if (err) { err = gpg_error_from_syserror (); log_error ("npth_cond_init failed: %s\n", gpg_strerror (err)); return err; } return apdu_init (); } /* Sort helper for app_send_card_list. */ static int compare_card_list_items (const void *arg_a, const void *arg_b) { const card_t a = *(const card_t *)arg_a; const card_t b = *(const card_t *)arg_b; return a->slot - b->slot; } /* Helper for send_card_and_app_list and app_switch_active_app. */ static gpg_error_t send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl) { gpg_error_t err; app_t a; char *serial; char *p; membuf_t mb; int any = 0; serial = card_get_serialno (card); if (!serial) return 0; /* Oops. */ if (with_apps) { /* Note that in case the additional applications have not yet been * added to the card context (which is commonly done by means of * "SERIALNO --all", we do that here. */ err = select_all_additional_applications_internal (ctrl, card); if (err) { xfree (serial); return err; } init_membuf (&mb, 256); put_membuf_str (&mb, serial); for (a = card->app; a; a = a->next) { if (!a->fnc.with_keygrip || a->need_reset) continue; any = 1; put_membuf (&mb, " ", 1); put_membuf_str (&mb, xstrapptype (a)); } if (!any && card->app) { /* No card app supports the with_keygrip function. Use the * main app as fallback. */ put_membuf (&mb, " ", 1); put_membuf_str (&mb, xstrapptype (card->app)); } put_membuf (&mb, "", 1); p = get_membuf (&mb, NULL); if (!p) { err = gpg_error_from_syserror (); xfree (serial); return err; } send_status_direct (ctrl, "SERIALNO", p); xfree (p); } else send_status_direct (ctrl, "SERIALNO", serial); xfree (serial); return 0; } /* Common code for app_send_card_list and app_send_active_apps. */ static gpg_error_t send_card_and_app_list (ctrl_t ctrl, card_t wantcard, int with_apps) { gpg_error_t err; card_t c; card_t *cardlist = NULL; int n, ncardlist; card_list_r_lock (); for (n=0, c = card_top; c; c = c->next) n++; if (!n) { err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); goto leave; } cardlist = xtrycalloc (n, sizeof *cardlist); if (!cardlist) { err = gpg_error_from_syserror (); goto leave; } for (ncardlist=0, c = card_top; c; c = c->next) cardlist[ncardlist++] = c; qsort (cardlist, ncardlist, sizeof *cardlist, compare_card_list_items); for (n=0; n < ncardlist; n++) { if (wantcard && wantcard != cardlist[n]) continue; err = send_serialno_and_app_status (cardlist[n], with_apps, ctrl); if (err) goto leave; } err = 0; leave: card_list_r_unlock (); xfree (cardlist); return err; } /* Send status lines with the serialno of all inserted cards. */ gpg_error_t app_send_card_list (ctrl_t ctrl) { return send_card_and_app_list (ctrl, NULL, 0); } /* Send status lines with the serialno and appname of the current card * or of all cards if CARD is NULL. */ gpg_error_t app_send_active_apps (card_t card, ctrl_t ctrl) { return send_card_and_app_list (ctrl, card, 1); } /* Switch to APPNAME and print a respective status line with that app * listed first. If APPNAME is NULL or the empty string no switching * is done but the status line is printed anyway. */ gpg_error_t app_switch_active_app (card_t card, ctrl_t ctrl, const char *appname) { gpg_error_t err; apptype_t apptype; /* Note that in case the additional applications have not yet been * added to the card context (which is commonly done by means of * "SERIALNO --all", we do that here. */ err = select_all_additional_applications_internal (ctrl, card); if (err) goto leave; if (appname && *appname) { apptype = apptype_from_name (appname); if (!apptype) { err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } ctrl->current_apptype = apptype; err = maybe_switch_app (ctrl, card, NULL); if (err) goto leave; } /* Print the status line. */ err = send_serialno_and_app_status (card, 1, ctrl); leave: return err; } /* Execute an action for each app. ACTION can be one of: * * - KEYGRIP_ACTION_SEND_DATA * * If KEYGRIP_STR matches a public key of any active application * send information as LF terminated data lines about the public * key. The format of these lines is * <keygrip> T <serialno> <idstr> * If a match was found a pointer to the matching application is * returned. With the KEYGRIP_STR given as NULL, lines for all * keys (with CAPABILITY) will be send and the return value is * GPG_ERR_TRUE. * * - KEYGRIP_ACTION_WRITE_STATUS * * Same as KEYGRIP_ACTION_SEND_DATA but uses status lines instead * of data lines. * * - KEYGRIP_ACTION_LOOKUP * * Returns a pointer to the application matching KEYGRIP_STR but * does not emit any status or data lines. If no key with that * keygrip is available or KEYGRIP_STR is NULL, GPG_ERR_NOT_FOUND * is returned. */ card_t app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str, int capability) { card_t card; card_list_r_lock (); card = do_with_keygrip (ctrl, action, keygrip_str, capability); card_list_r_unlock (); return card; } diff --git a/tests/gpgscm/ffi.c b/tests/gpgscm/ffi.c index 36b0b98d2..510dc4088 100644 --- a/tests/gpgscm/ffi.c +++ b/tests/gpgscm/ffi.c @@ -1,1752 +1,1761 @@ /* FFI interface for TinySCHEME. * * Copyright (C) 2016 g10 code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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/>. */ #include <config.h> #include <assert.h> #include <ctype.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <gpg-error.h> #include <limits.h> #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #if HAVE_LIBREADLINE #define GNUPG_LIBREADLINE_H_INCLUDED #include <readline/readline.h> #include <readline/history.h> #endif #include "../../common/util.h" #ifdef HAVE_W32_SYSTEM #define NEED_STRUCT_SPAWN_CB_ARG #endif #include "../../common/exechelp.h" #include "../../common/sysutils.h" #ifdef HAVE_W32_SYSTEM #include <windows.h> #endif #include "private.h" #include "ffi.h" #include "ffi-private.h" /* For use in nice error messages. */ static const char * ordinal_suffix (int n) { switch (n) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; default: return "th"; } assert (! "reached"); } int ffi_bool_value (scheme *sc, pointer p) { return ! (p == sc->F); } static pointer do_logand (scheme *sc, pointer args) { FFI_PROLOG (); unsigned int v, acc = ~0; while (args != sc->NIL) { FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args); acc &= v; } FFI_RETURN_INT (sc, acc); } static pointer do_logior (scheme *sc, pointer args) { FFI_PROLOG (); unsigned int v, acc = 0; while (args != sc->NIL) { FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args); acc |= v; } FFI_RETURN_INT (sc, acc); } static pointer do_logxor (scheme *sc, pointer args) { FFI_PROLOG (); unsigned int v, acc = 0; while (args != sc->NIL) { FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args); acc ^= v; } FFI_RETURN_INT (sc, acc); } static pointer do_lognot (scheme *sc, pointer args) { FFI_PROLOG (); unsigned int v; FFI_ARG_OR_RETURN (sc, unsigned int, v, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_INT (sc, ~v); } /* User interface. */ static pointer do_flush_stdio (scheme *sc, pointer args) { FFI_PROLOG (); FFI_ARGS_DONE_OR_RETURN (sc, args); fflush (stdout); fflush (stderr); FFI_RETURN (sc); } int use_libreadline; /* Read a string, and return a pointer to it. Returns NULL on EOF. */ char * rl_gets (const char *prompt) { static char *line = NULL; char *p; xfree (line); #if HAVE_LIBREADLINE { line = readline (prompt); if (line && *line) add_history (line); } #else { size_t max_size = 0xff; printf ("%s", prompt); fflush (stdout); line = xtrymalloc (max_size); if (line != NULL) fgets (line, max_size, stdin); } #endif /* Strip trailing whitespace. */ if (line && strlen (line) > 0) for (p = &line[strlen (line) - 1]; isspace (*p); p--) *p = 0; return line; } static pointer do_prompt (scheme *sc, pointer args) { FFI_PROLOG (); const char *prompt; const char *line; FFI_ARG_OR_RETURN (sc, const char *, prompt, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); line = rl_gets (prompt); if (! line) FFI_RETURN_POINTER (sc, sc->EOF_OBJ); FFI_RETURN_STRING (sc, line); } static pointer do_sleep (scheme *sc, pointer args) { FFI_PROLOG (); unsigned int seconds; FFI_ARG_OR_RETURN (sc, unsigned int, seconds, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); sleep (seconds); FFI_RETURN (sc); } static pointer do_usleep (scheme *sc, pointer args) { FFI_PROLOG (); useconds_t microseconds; FFI_ARG_OR_RETURN (sc, useconds_t, microseconds, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); usleep (microseconds); FFI_RETURN (sc); } static pointer do_chdir (scheme *sc, pointer args) { FFI_PROLOG (); char *name; FFI_ARG_OR_RETURN (sc, char *, name, path, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (chdir (name)) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static pointer do_strerror (scheme *sc, pointer args) { FFI_PROLOG (); int error; FFI_ARG_OR_RETURN (sc, int, error, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_STRING (sc, gpg_strerror (error)); } static pointer do_getenv (scheme *sc, pointer args) { FFI_PROLOG (); char *name; char *value; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); value = getenv (name); FFI_RETURN_STRING (sc, value ? value : ""); } static pointer do_setenv (scheme *sc, pointer args) { FFI_PROLOG (); char *name; char *value; int overwrite; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARG_OR_RETURN (sc, char *, value, string, args); FFI_ARG_OR_RETURN (sc, int, overwrite, bool, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (gnupg_setenv (name, value, overwrite)) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static pointer do_exit (scheme *sc, pointer args) { FFI_PROLOG (); int retcode; FFI_ARG_OR_RETURN (sc, int, retcode, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); exit (retcode); } /* XXX: use gnupgs variant b/c mode as string */ static pointer do_open (scheme *sc, pointer args) { FFI_PROLOG (); int fd; char *pathname; int flags; mode_t mode = 0; FFI_ARG_OR_RETURN (sc, char *, pathname, path, args); FFI_ARG_OR_RETURN (sc, int, flags, number, args); if (args != sc->NIL) FFI_ARG_OR_RETURN (sc, mode_t, mode, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); fd = open (pathname, flags, mode); if (fd == -1) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN_INT (sc, fd); } static pointer do_fdopen (scheme *sc, pointer args) { FFI_PROLOG (); FILE *stream; int fd; char *mode; int kind; FFI_ARG_OR_RETURN (sc, int, fd, number, args); FFI_ARG_OR_RETURN (sc, char *, mode, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); stream = fdopen (fd, mode); if (stream == NULL) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); if (setvbuf (stream, NULL, _IONBF, 0) != 0) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); kind = 0; if (strchr (mode, 'r')) kind |= port_input; if (strchr (mode, 'w')) kind |= port_output; FFI_RETURN_POINTER (sc, sc->vptr->mk_port_from_file (sc, stream, kind)); } static pointer do_close (scheme *sc, pointer args) { FFI_PROLOG (); int fd; FFI_ARG_OR_RETURN (sc, int, fd, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_ERR (sc, close (fd) == 0 ? 0 : gpg_error_from_syserror ()); } static pointer do_seek (scheme *sc, pointer args) { FFI_PROLOG (); int fd; off_t offset; int whence; FFI_ARG_OR_RETURN (sc, int, fd, number, args); FFI_ARG_OR_RETURN (sc, off_t, offset, number, args); FFI_ARG_OR_RETURN (sc, int, whence, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_ERR (sc, lseek (fd, offset, whence) == (off_t) -1 ? gpg_error_from_syserror () : 0); } static pointer do_get_temp_path (scheme *sc, pointer args) { FFI_PROLOG (); #ifdef HAVE_W32_SYSTEM char buffer[MAX_PATH+1]; #endif FFI_ARGS_DONE_OR_RETURN (sc, args); #ifdef HAVE_W32_SYSTEM if (GetTempPath (MAX_PATH+1, buffer) == 0) FFI_RETURN_STRING (sc, "/temp"); else { size_t len = strlen (buffer); buffer[len-1] = 0; } FFI_RETURN_STRING (sc, buffer); #else FFI_RETURN_STRING (sc, "/tmp"); #endif } static pointer do_mkdtemp (scheme *sc, pointer args) { FFI_PROLOG (); char *template; #ifdef PATH_MAX char buffer[PATH_MAX]; #else char buffer[1024]; #endif char *name; FFI_ARG_OR_RETURN (sc, char *, template, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (strlen (template) > sizeof buffer - 1) FFI_RETURN_ERR (sc, EINVAL); strncpy (buffer, template, sizeof buffer); name = gnupg_mkdtemp (buffer); if (name == NULL) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN_STRING (sc, name); } static pointer do_unlink (scheme *sc, pointer args) { FFI_PROLOG (); char *name; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (unlink (name) == -1) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static gpg_error_t unlink_recursively (const char *name) { gpg_error_t err = 0; struct stat st; if (stat (name, &st) == -1) return gpg_error_from_syserror (); if (S_ISDIR (st.st_mode)) { DIR *dir; struct dirent *dent; dir = opendir (name); if (dir == NULL) return gpg_error_from_syserror (); while ((dent = readdir (dir))) { char *child; if (strcmp (dent->d_name, ".") == 0 || strcmp (dent->d_name, "..") == 0) continue; child = xtryasprintf ("%s/%s", name, dent->d_name); if (child == NULL) { err = gpg_error_from_syserror (); goto leave; } err = unlink_recursively (child); xfree (child); if (err == gpg_error_from_errno (ENOENT)) err = 0; if (err) goto leave; } leave: closedir (dir); if (! err) rmdir (name); return err; } else if (unlink (name) == -1) return gpg_error_from_syserror (); return 0; } static pointer do_unlink_recursively (scheme *sc, pointer args) { FFI_PROLOG (); char *name; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); err = unlink_recursively (name); FFI_RETURN (sc); } static pointer do_rename (scheme *sc, pointer args) { FFI_PROLOG (); char *old; char *new; FFI_ARG_OR_RETURN (sc, char *, old, string, args); FFI_ARG_OR_RETURN (sc, char *, new, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (rename (old, new) == -1) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static pointer do_getcwd (scheme *sc, pointer args) { FFI_PROLOG (); pointer result; char *cwd; FFI_ARGS_DONE_OR_RETURN (sc, args); cwd = gnupg_getcwd (); if (cwd == NULL) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); result = sc->vptr->mk_string (sc, cwd); xfree (cwd); FFI_RETURN_POINTER (sc, result); } static pointer do_mkdir (scheme *sc, pointer args) { FFI_PROLOG (); char *name; char *mode; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARG_OR_RETURN (sc, char *, mode, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (gnupg_mkdir (name, mode) == -1) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static pointer do_rmdir (scheme *sc, pointer args) { FFI_PROLOG (); char *name; FFI_ARG_OR_RETURN (sc, char *, name, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (rmdir (name) == -1) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); FFI_RETURN (sc); } static pointer do_get_isotime (scheme *sc, pointer args) { FFI_PROLOG (); gnupg_isotime_t timebuf; FFI_ARGS_DONE_OR_RETURN (sc, args); gnupg_get_isotime (timebuf); FFI_RETURN_STRING (sc, timebuf); } static pointer do_get_time (scheme *sc, pointer args) { FFI_PROLOG (); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_INT (sc, gnupg_get_time ()); } static pointer do_getpid (scheme *sc, pointer args) { FFI_PROLOG (); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_INT (sc, getpid ()); } static pointer do_srandom (scheme *sc, pointer args) { FFI_PROLOG (); int seed; FFI_ARG_OR_RETURN (sc, int, seed, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); srand (seed); FFI_RETURN (sc); } static int random_scaled (int scale) { int v; #ifdef HAVE_RAND v = rand (); #else v = random (); #endif #ifndef RAND_MAX /* for SunOS */ #define RAND_MAX 32767 #endif return ((int) (1 + (int) ((float) scale * v / (RAND_MAX + 1.0))) - 1); } static pointer do_random (scheme *sc, pointer args) { FFI_PROLOG (); int scale; FFI_ARG_OR_RETURN (sc, int, scale, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_INT (sc, random_scaled (scale)); } static pointer do_make_random_string (scheme *sc, pointer args) { FFI_PROLOG (); int size; pointer chunk; char *p; FFI_ARG_OR_RETURN (sc, int, size, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); if (size < 0) return ffi_sprintf (sc, "size must be positive"); chunk = sc->vptr->mk_counted_string (sc, NULL, size); if (sc->no_memory) FFI_RETURN_ERR (sc, ENOMEM); for (p = sc->vptr->string_value (chunk); size; p++, size--) *p = (char) random_scaled (256); FFI_RETURN_POINTER (sc, chunk); } /* estream functions. */ struct es_object_box { estream_t stream; int closed; }; static void es_object_finalize (scheme *sc, void *data) { struct es_object_box *box = data; (void) sc; if (! box->closed) es_fclose (box->stream); xfree (box); } static void es_object_to_string (scheme *sc, char *out, size_t size, void *data) { struct es_object_box *box = data; (void) sc; snprintf (out, size, "#estream %p", box->stream); } static struct foreign_object_vtable es_object_vtable = { es_object_finalize, es_object_to_string, }; #if 0 static pointer es_wrap (scheme *sc, estream_t stream) { struct es_object_box *box = xmalloc (sizeof *box); if (box == NULL) return sc->NIL; box->stream = stream; box->closed = 0; return sc->vptr->mk_foreign_object (sc, &es_object_vtable, box); } #endif static struct es_object_box * es_unwrap (scheme *sc, pointer object) { (void) sc; if (! is_foreign_object (object)) return NULL; if (sc->vptr->get_foreign_object_vtable (object) != &es_object_vtable) return NULL; return sc->vptr->get_foreign_object_data (object); } #define CONVERSION_estream(SC, X) es_unwrap (SC, X) #define IS_A_estream(SC, X) es_unwrap (SC, X) static pointer do_es_fclose (scheme *sc, pointer args) { FFI_PROLOG (); struct es_object_box *box; FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args); FFI_ARGS_DONE_OR_RETURN (sc, args); err = es_fclose (box->stream); if (! err) box->closed = 1; FFI_RETURN (sc); } static pointer do_es_read (scheme *sc, pointer args) { FFI_PROLOG (); struct es_object_box *box; size_t bytes_to_read; pointer result; void *buffer; size_t bytes_read; FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args); FFI_ARG_OR_RETURN (sc, size_t, bytes_to_read, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); buffer = xtrymalloc (bytes_to_read); if (buffer == NULL) FFI_RETURN_ERR (sc, ENOMEM); err = es_read (box->stream, buffer, bytes_to_read, &bytes_read); if (err) FFI_RETURN_ERR (sc, err); result = sc->vptr->mk_counted_string (sc, buffer, bytes_read); xfree (buffer); FFI_RETURN_POINTER (sc, result); } static pointer do_es_feof (scheme *sc, pointer args) { FFI_PROLOG (); struct es_object_box *box; FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_POINTER (sc, es_feof (box->stream) ? sc->T : sc->F); } static pointer do_es_write (scheme *sc, pointer args) { FFI_PROLOG (); struct es_object_box *box; const char *buffer; size_t bytes_to_write, bytes_written; FFI_ARG_OR_RETURN (sc, struct es_object_box *, box, estream, args); /* XXX how to get the length of the string buffer? scheme strings may contain \0. */ FFI_ARG_OR_RETURN (sc, const char *, buffer, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); bytes_to_write = strlen (buffer); while (bytes_to_write > 0) { err = es_write (box->stream, buffer, bytes_to_write, &bytes_written); if (err) break; bytes_to_write -= bytes_written; buffer += bytes_written; } FFI_RETURN (sc); } /* Process handling. */ struct proc_object_box { gnupg_process_t proc; }; static void proc_object_finalize (scheme *sc, void *data) { struct proc_object_box *box = data; (void) sc; if (!box->proc) gnupg_process_release (box->proc); xfree (box); } static void proc_object_to_string (scheme *sc, char *out, size_t size, void *data) { struct proc_object_box *box = data; (void) sc; snprintf (out, size, "#proc %p", box->proc); } static struct foreign_object_vtable proc_object_vtable = { proc_object_finalize, proc_object_to_string, }; static pointer proc_wrap (scheme *sc, gnupg_process_t proc) { struct proc_object_box *box = xmalloc (sizeof *box); if (box == NULL) return sc->NIL; box->proc = proc; return sc->vptr->mk_foreign_object (sc, &proc_object_vtable, box); } static struct proc_object_box * proc_unwrap (scheme *sc, pointer object) { (void) sc; if (! is_foreign_object (object)) return NULL; if (sc->vptr->get_foreign_object_vtable (object) != &proc_object_vtable) return NULL; return sc->vptr->get_foreign_object_data (object); } #define CONVERSION_proc(SC, X) proc_unwrap (SC, X) #define IS_A_proc(SC, X) proc_unwrap (SC, X) #define SPAWN_IO_BUFSIZE 4096 #ifdef HAVE_W32_SYSTEM struct rfp { HANDLE hd; char *buf; size_t len; off_t off; }; static DWORD __attribute__((stdcall)) read_from_pipe (void *arg) { struct rfp *rfp = arg; DWORD bytes_read; if (rfp->hd == INVALID_HANDLE_VALUE) goto errout; while (1) { if (!ReadFile (rfp->hd, rfp->buf + rfp->off, rfp->len - rfp->off, &bytes_read, NULL)) { DWORD ec = GetLastError (); if (ec == ERROR_BROKEN_PIPE) { CloseHandle (rfp->hd); rfp->hd = INVALID_HANDLE_VALUE; break; } goto errout; } if (bytes_read == 0) /* It may occur, when it writes WriteFile with zero-byte on the other end of the pipe. */ continue; else { rfp->off += bytes_read; if (rfp->off == rfp->len) { rfp->len += SPAWN_IO_BUFSIZE; rfp->buf = xtryrealloc (rfp->buf, rfp->len); if (rfp->buf == NULL) goto errout; } } } return 0; errout: if (rfp->hd != INVALID_HANDLE_VALUE) { CloseHandle (rfp->hd); rfp->hd = INVALID_HANDLE_VALUE; } xfree (rfp->buf); rfp->buf = NULL; return 1; } #endif static pointer do_process_spawn_io (scheme *sc, pointer args) { FFI_PROLOG (); pointer arguments; char *a_input; char **argv; size_t len; unsigned int flags; gnupg_process_t proc = NULL; estream_t infp; #ifdef HAVE_W32_SYSTEM HANDLE out_hd, err_hd; #else int out_fd, err_fd; #endif char *out_string = NULL; char *err_string = NULL; size_t out_len = SPAWN_IO_BUFSIZE; size_t err_len = SPAWN_IO_BUFSIZE; off_t out_off = 0; off_t err_off = 0; int retcode = -1; pointer p0, p1, p2; FFI_ARG_OR_RETURN (sc, pointer, arguments, list, args); FFI_ARG_OR_RETURN (sc, char *, a_input, string, args); flags = (GNUPG_PROCESS_STDIN_PIPE | GNUPG_PROCESS_STDOUT_PIPE | GNUPG_PROCESS_STDERR_PIPE); FFI_ARGS_DONE_OR_RETURN (sc, args); err = ffi_list2argv (sc, arguments, &argv, &len); if (err == gpg_error (GPG_ERR_INV_VALUE)) return ffi_sprintf (sc, "%luth element of first argument is " "neither string nor symbol", (unsigned long) len); if (err) FFI_RETURN_ERR (sc, err); if (verbose > 1) { char **p; fprintf (stderr, "Executing:"); for (p = argv; *p; p++) fprintf (stderr, " '%s'", *p); fprintf (stderr, "\n"); } err = gnupg_process_spawn (argv[0], (const char **) &argv[1], - flags, NULL, NULL, &proc); + flags, NULL, &proc); err = gnupg_process_get_streams (proc, 0, &infp, NULL, NULL); err = es_write (infp, a_input, strlen (a_input), NULL); es_fclose (infp); if (err) { gnupg_process_release (proc); xfree (argv); FFI_RETURN_ERR (sc, err); } #ifdef HAVE_W32_SYSTEM err = gnupg_process_ctl (proc, GNUPG_PROCESS_GET_HANDLES, NULL, &out_hd, &err_hd); #else err = gnupg_process_get_fds (proc, 0, NULL, &out_fd, &err_fd); #endif if (err) { gnupg_process_release (proc); xfree (argv); FFI_RETURN_ERR (sc, err); } out_string = xtrymalloc (out_len); if (out_string == NULL) goto errout; err_string = xtrymalloc (err_len); if (err_string == NULL) goto errout; #ifdef HAVE_W32_SYSTEM { HANDLE h_thread_rfp_err; struct rfp rfp_out; struct rfp rfp_err; DWORD thread_exit_code; rfp_err.hd = err_hd; rfp_err.buf = err_string; rfp_err.len = err_len; rfp_err.off = 0; err_hd = INVALID_HANDLE_VALUE; err_string = NULL; h_thread_rfp_err = CreateThread (NULL, 0, read_from_pipe, (void *)&rfp_err, 0, NULL); if (h_thread_rfp_err == NULL) { xfree (rfp_err.buf); CloseHandle (rfp_err.hd); goto errout; } rfp_out.hd = out_hd; rfp_out.buf = out_string; rfp_out.len = out_len; rfp_out.off = 0; out_hd = INVALID_HANDLE_VALUE; out_string = NULL; if (read_from_pipe (&rfp_out)) { CloseHandle (h_thread_rfp_err); xfree (rfp_err.buf); goto errout; } out_string = rfp_out.buf; out_off = rfp_out.off; WaitForSingleObject (h_thread_rfp_err, INFINITE); GetExitCodeThread (h_thread_rfp_err, &thread_exit_code); CloseHandle (h_thread_rfp_err); if (thread_exit_code) goto errout; err_string = rfp_err.buf; err_off = rfp_err.off; } #else { fd_set read_fdset; ssize_t bytes_read; while (1) { int nfd; int ret; FD_ZERO (&read_fdset); if (out_fd >= 0) FD_SET (out_fd, &read_fdset); if (err_fd >= 0) FD_SET (err_fd, &read_fdset); if (out_fd > err_fd) nfd = out_fd; else nfd = err_fd; if (nfd == -1) break; ret = select (nfd+1, &read_fdset, NULL, NULL, NULL); if (ret < 0) break; if (FD_ISSET (out_fd, &read_fdset)) { bytes_read = read (out_fd, out_string + out_off, out_len - out_off); if (bytes_read == 0) { close (out_fd); out_fd = -1; } else if (bytes_read < 0) goto errout; else { out_off += bytes_read; if (out_off == out_len) { out_len += SPAWN_IO_BUFSIZE; out_string = xtryrealloc (out_string, out_len); if (out_string == NULL) goto errout; } } } if (FD_ISSET (err_fd, &read_fdset)) { bytes_read = read (err_fd, err_string + err_off, err_len - err_off); if (bytes_read == 0) { close (err_fd); err_fd = -1; } else if (bytes_read < 0) goto errout; else { err_off += bytes_read; if (err_off == err_len) { err_len += SPAWN_IO_BUFSIZE; err_string = xtryrealloc (err_string, err_len); if (err_string == NULL) goto errout; } } } } } #endif err = gnupg_process_wait (proc, 1); if (!err) err = gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &retcode); gnupg_process_release (proc); xfree (argv); p0 = sc->vptr->mk_integer (sc, (unsigned long)retcode); p1 = sc->vptr->mk_counted_string (sc, out_string, out_off); p2 = sc->vptr->mk_counted_string (sc, err_string, err_off); xfree (out_string); xfree (err_string); FFI_RETURN_POINTER (sc, _cons (sc, p0, _cons (sc, p1, _cons (sc, p2, sc->NIL, 1), 1), 1)); errout: xfree (out_string); xfree (err_string); #ifdef HAVE_W32_SYSTEM if (out_hd != INVALID_HANDLE_VALUE) CloseHandle (out_hd); if (err_hd != INVALID_HANDLE_VALUE) CloseHandle (err_hd); #else if (out_fd >= 0) close (out_fd); if (err_fd >= 0) close (err_fd); #endif gnupg_process_release (proc); xfree (argv); FFI_RETURN_ERR (sc, err); } -static void -setup_std_fds (struct spawn_cb_arg *sca) -{ - int *std_fds = sca->arg; - -#ifdef HAVE_W32_SYSTEM - sca->hd[0] = std_fds[0] == -1? - INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle (std_fds[0]); - sca->hd[1] = std_fds[1] == -1? - INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle (std_fds[1]); - sca->hd[2] = std_fds[2] == -1? - INVALID_HANDLE_VALUE : (HANDLE)_get_osfhandle (std_fds[2]); -#else - sca->fds[0] = std_fds[0]; - sca->fds[1] = std_fds[1]; - sca->fds[2] = std_fds[2]; -#endif -} - static pointer do_process_spawn_fd (scheme *sc, pointer args) { FFI_PROLOG (); pointer arguments; char **argv; size_t len; int std_fds[3]; gnupg_process_t proc = NULL; + gnupg_spawn_actions_t act = NULL; FFI_ARG_OR_RETURN (sc, pointer, arguments, list, args); FFI_ARG_OR_RETURN (sc, int, std_fds[0], number, args); FFI_ARG_OR_RETURN (sc, int, std_fds[1], number, args); FFI_ARG_OR_RETURN (sc, int, std_fds[2], number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); err = ffi_list2argv (sc, arguments, &argv, &len); if (err == gpg_error (GPG_ERR_INV_VALUE)) return ffi_sprintf (sc, "%luth element of first argument is " "neither string nor symbol", (unsigned long) len); if (err) FFI_RETURN_ERR (sc, err); if (verbose > 1) { char **p; fprintf (stderr, "Executing:"); for (p = argv; *p; p++) fprintf (stderr, " '%s'", *p); fprintf (stderr, " (%d %d %d)\n", std_fds[0], std_fds[1], std_fds[2]); } - err = gnupg_process_spawn (argv[0], (const char **) &argv[1], - 0, setup_std_fds, std_fds, &proc); + err = gnupg_spawn_actions_new (&act); + if (err) + { + FFI_RETURN_ERR (sc, err); + } +#ifdef HAVE_W32_SYSTEM + { + HANDLE std_in, std_out, std_err; + + if (std_fds[0] == -1) + std_in = INVALID_HANDLE_VALUE; + else + std_in = (HANDLE)_get_osfhandle (std_fds[0]); + if (std_fds[1] == -1) + std_out = INVALID_HANDLE_VALUE; + else + std_out = (HANDLE)_get_osfhandle (std_fds[1]); + if (std_fds[2] == -1) + std_err = INVALID_HANDLE_VALUE; + else + std_err = (HANDLE)_get_osfhandle (std_fds[2]); + + gnupg_spawn_actions_set_redirect (act, std_in, std_out, std_err); + } +#else + gnupg_spawn_actions_set_redirect (act, std_fds[0], std_fds[1], std_fds[2]); +#endif + err = gnupg_process_spawn (argv[0], (const char **)&argv[1], 0, act, &proc); + gnupg_spawn_actions_release (act); xfree (argv); FFI_RETURN_POINTER (sc, proc_wrap (sc, proc)); } static pointer do_process_wait (scheme *sc, pointer args) { FFI_PROLOG (); struct proc_object_box *box; int hang; int retcode = -1; FFI_ARG_OR_RETURN (sc, struct proc_object_box *, box, proc, args); FFI_ARG_OR_RETURN (sc, int, hang, bool, args); FFI_ARGS_DONE_OR_RETURN (sc, args); err = gnupg_process_wait (box->proc, hang); if (!err) err = gnupg_process_ctl (box->proc, GNUPG_PROCESS_GET_EXIT_ID, &retcode); if (err == GPG_ERR_TIMEOUT) err = 0; FFI_RETURN_INT (sc, retcode); } static pointer do_pipe (scheme *sc, pointer args) { FFI_PROLOG (); int filedes[2]; FFI_ARGS_DONE_OR_RETURN (sc, args); err = gnupg_create_pipe (filedes); #define IMC(A, B) \ _cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1) FFI_RETURN_POINTER (sc, IMC (filedes[0], IMC (filedes[1], sc->NIL))); #undef IMC } static pointer do_inbound_pipe (scheme *sc, pointer args) { FFI_PROLOG (); int filedes[2]; FFI_ARGS_DONE_OR_RETURN (sc, args); err = gnupg_create_inbound_pipe (filedes, NULL, 0); #define IMC(A, B) \ _cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1) FFI_RETURN_POINTER (sc, IMC (filedes[0], IMC (filedes[1], sc->NIL))); #undef IMC } static pointer do_outbound_pipe (scheme *sc, pointer args) { FFI_PROLOG (); int filedes[2]; FFI_ARGS_DONE_OR_RETURN (sc, args); err = gnupg_create_outbound_pipe (filedes, NULL, 0); #define IMC(A, B) \ _cons (sc, sc->vptr->mk_integer (sc, (unsigned long) (A)), (B), 1) FFI_RETURN_POINTER (sc, IMC (filedes[0], IMC (filedes[1], sc->NIL))); #undef IMC } /* Test helper functions. */ static pointer do_file_equal (scheme *sc, pointer args) { FFI_PROLOG (); pointer result = sc->F; char *a_name, *b_name; int binary; const char *mode; FILE *a_stream = NULL, *b_stream = NULL; struct stat a_stat, b_stat; #define BUFFER_SIZE 1024 char a_buf[BUFFER_SIZE], b_buf[BUFFER_SIZE]; #undef BUFFER_SIZE size_t chunk; FFI_ARG_OR_RETURN (sc, char *, a_name, string, args); FFI_ARG_OR_RETURN (sc, char *, b_name, string, args); FFI_ARG_OR_RETURN (sc, int, binary, bool, args); FFI_ARGS_DONE_OR_RETURN (sc, args); mode = binary ? "rb" : "r"; a_stream = fopen (a_name, mode); if (a_stream == NULL) goto errout; b_stream = fopen (b_name, mode); if (b_stream == NULL) goto errout; if (fstat (fileno (a_stream), &a_stat) < 0) goto errout; if (fstat (fileno (b_stream), &b_stat) < 0) goto errout; if (binary && a_stat.st_size != b_stat.st_size) { if (verbose) fprintf (stderr, "Files %s and %s differ in size %lu != %lu\n", a_name, b_name, (unsigned long) a_stat.st_size, (unsigned long) b_stat.st_size); goto out; } while (! feof (a_stream)) { chunk = sizeof a_buf; chunk = fread (a_buf, 1, chunk, a_stream); if (chunk == 0 && ferror (a_stream)) goto errout; /* some error */ if (fread (b_buf, 1, chunk, b_stream) < chunk) { if (feof (b_stream)) goto out; /* short read */ goto errout; /* some error */ } if (chunk > 0 && memcmp (a_buf, b_buf, chunk) != 0) goto out; } fread (b_buf, 1, 1, b_stream); if (! feof (b_stream)) goto out; /* b is longer */ /* They match. */ result = sc->T; out: if (a_stream) fclose (a_stream); if (b_stream) fclose (b_stream); FFI_RETURN_POINTER (sc, result); errout: err = gpg_error_from_syserror (); goto out; } static pointer do_splice (scheme *sc, pointer args) { FFI_PROLOG (); int source; char buffer[1024]; ssize_t bytes_read; pointer sinks, sink; FFI_ARG_OR_RETURN (sc, int, source, number, args); sinks = args; if (sinks == sc->NIL) return ffi_sprintf (sc, "need at least one sink"); for (sink = sinks; sink != sc->NIL; sink = pair_cdr (sink), ffi_arg_index++) if (! sc->vptr->is_number (pair_car (sink))) return ffi_sprintf (sc, "%d%s argument is not a number", ffi_arg_index, ordinal_suffix (ffi_arg_index)); while (1) { bytes_read = read (source, buffer, sizeof buffer); if (bytes_read == 0) break; if (bytes_read < 0) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); for (sink = sinks; sink != sc->NIL; sink = pair_cdr (sink)) { int fd = sc->vptr->ivalue (pair_car (sink)); char *p = buffer; ssize_t left = bytes_read; while (left) { ssize_t written = write (fd, p, left); if (written < 0) FFI_RETURN_ERR (sc, gpg_error_from_syserror ()); assert (written <= left); left -= written; p += written; } } } FFI_RETURN (sc); } static pointer do_string_index (scheme *sc, pointer args) { FFI_PROLOG (); char *haystack; char needle; ssize_t offset = 0; char *position; FFI_ARG_OR_RETURN (sc, char *, haystack, string, args); FFI_ARG_OR_RETURN (sc, char, needle, character, args); if (args != sc->NIL) { FFI_ARG_OR_RETURN (sc, ssize_t, offset, number, args); if (offset < 0) return ffi_sprintf (sc, "offset must be positive"); if (offset > strlen (haystack)) return ffi_sprintf (sc, "offset exceeds haystack"); } FFI_ARGS_DONE_OR_RETURN (sc, args); position = strchr (haystack+offset, needle); if (position) FFI_RETURN_INT (sc, position - haystack); else FFI_RETURN_POINTER (sc, sc->F); } static pointer do_string_rindex (scheme *sc, pointer args) { FFI_PROLOG (); char *haystack; char needle; ssize_t offset = 0; char *position; FFI_ARG_OR_RETURN (sc, char *, haystack, string, args); FFI_ARG_OR_RETURN (sc, char, needle, character, args); if (args != sc->NIL) { FFI_ARG_OR_RETURN (sc, ssize_t, offset, number, args); if (offset < 0) return ffi_sprintf (sc, "offset must be positive"); if (offset > strlen (haystack)) return ffi_sprintf (sc, "offset exceeds haystack"); } FFI_ARGS_DONE_OR_RETURN (sc, args); position = strrchr (haystack+offset, needle); if (position) FFI_RETURN_INT (sc, position - haystack); else FFI_RETURN_POINTER (sc, sc->F); } static pointer do_string_contains (scheme *sc, pointer args) { FFI_PROLOG (); char *haystack; char *needle; FFI_ARG_OR_RETURN (sc, char *, haystack, string, args); FFI_ARG_OR_RETURN (sc, char *, needle, string, args); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_POINTER (sc, strstr (haystack, needle) ? sc->T : sc->F); } static pointer do_get_verbose (scheme *sc, pointer args) { FFI_PROLOG (); FFI_ARGS_DONE_OR_RETURN (sc, args); FFI_RETURN_INT (sc, verbose); } static pointer do_set_verbose (scheme *sc, pointer args) { FFI_PROLOG (); int new_verbosity, old; FFI_ARG_OR_RETURN (sc, int, new_verbosity, number, args); FFI_ARGS_DONE_OR_RETURN (sc, args); old = verbose; verbose = new_verbosity; FFI_RETURN_INT (sc, old); } gpg_error_t ffi_list2argv (scheme *sc, pointer list, char ***argv, size_t *len) { int i; *len = sc->vptr->list_length (sc, list); *argv = xtrycalloc (*len + 1, sizeof **argv); if (*argv == NULL) return gpg_error_from_syserror (); for (i = 0; sc->vptr->is_pair (list); list = sc->vptr->pair_cdr (list)) { if (sc->vptr->is_string (sc->vptr->pair_car (list))) (*argv)[i++] = sc->vptr->string_value (sc->vptr->pair_car (list)); else if (sc->vptr->is_symbol (sc->vptr->pair_car (list))) (*argv)[i++] = sc->vptr->symname (sc->vptr->pair_car (list)); else { xfree (*argv); *argv = NULL; *len = i; return gpg_error (GPG_ERR_INV_VALUE); } } (*argv)[i] = NULL; return 0; } gpg_error_t ffi_list2intv (scheme *sc, pointer list, int **intv, size_t *len) { int i; *len = sc->vptr->list_length (sc, list); *intv = xtrycalloc (*len, sizeof **intv); if (*intv == NULL) return gpg_error_from_syserror (); for (i = 0; sc->vptr->is_pair (list); list = sc->vptr->pair_cdr (list)) { if (sc->vptr->is_number (sc->vptr->pair_car (list))) (*intv)[i++] = sc->vptr->ivalue (sc->vptr->pair_car (list)); else { xfree (*intv); *intv = NULL; *len = i; return gpg_error (GPG_ERR_INV_VALUE); } } return 0; } char * ffi_schemify_name (const char *s, int macro) { /* Fixme: We should use xtrystrdup and return NULL. However, this * requires a lot more changes. Simply returning S as done * originally is not an option. */ char *n = xstrdup (s), *p; /* if (n == NULL) */ /* return s; */ for (p = n; *p; p++) { *p = (char) tolower (*p); /* We convert _ to - in identifiers. We allow, however, for function names to start with a leading _. The functions in this namespace are not yet finalized and might change or vanish without warning. Use them with care. */ if (! macro && p != n && *p == '_') *p = '-'; } return n; } pointer ffi_sprintf (scheme *sc, const char *format, ...) { pointer result; va_list listp; char *expression; int size, written; va_start (listp, format); size = vsnprintf (NULL, 0, format, listp); va_end (listp); expression = xtrymalloc (size + 1); if (expression == NULL) return NULL; va_start (listp, format); written = vsnprintf (expression, size + 1, format, listp); va_end (listp); assert (size == written); result = sc->vptr->mk_string (sc, expression); xfree (expression); return result; } void ffi_scheme_eval (scheme *sc, const char *format, ...) { va_list listp; char *expression; int size, written; va_start (listp, format); size = vsnprintf (NULL, 0, format, listp); va_end (listp); expression = xtrymalloc (size + 1); if (expression == NULL) return; va_start (listp, format); written = vsnprintf (expression, size + 1, format, listp); va_end (listp); assert (size == written); sc->vptr->load_string (sc, expression); xfree (expression); } gpg_error_t ffi_init (scheme *sc, const char *argv0, const char *scriptname, int argc, const char **argv) { int i; pointer args = sc->NIL; /* bitwise arithmetic */ ffi_define_function (sc, logand); ffi_define_function (sc, logior); ffi_define_function (sc, logxor); ffi_define_function (sc, lognot); /* libc. */ ffi_define_constant (sc, O_RDONLY); ffi_define_constant (sc, O_WRONLY); ffi_define_constant (sc, O_RDWR); ffi_define_constant (sc, O_CREAT); ffi_define_constant (sc, O_APPEND); #ifndef O_BINARY # define O_BINARY 0 #endif #ifndef O_TEXT # define O_TEXT 0 #endif ffi_define_constant (sc, O_BINARY); ffi_define_constant (sc, O_TEXT); ffi_define_constant (sc, STDIN_FILENO); ffi_define_constant (sc, STDOUT_FILENO); ffi_define_constant (sc, STDERR_FILENO); ffi_define_constant (sc, SEEK_SET); ffi_define_constant (sc, SEEK_CUR); ffi_define_constant (sc, SEEK_END); ffi_define_function (sc, sleep); ffi_define_function (sc, usleep); ffi_define_function (sc, chdir); ffi_define_function (sc, strerror); ffi_define_function (sc, getenv); ffi_define_function (sc, setenv); ffi_define_function_name (sc, "_exit", exit); ffi_define_function (sc, open); ffi_define_function (sc, fdopen); ffi_define_function (sc, close); ffi_define_function (sc, seek); ffi_define_function (sc, get_temp_path); ffi_define_function_name (sc, "_mkdtemp", mkdtemp); ffi_define_function (sc, unlink); ffi_define_function (sc, unlink_recursively); ffi_define_function (sc, rename); ffi_define_function (sc, getcwd); ffi_define_function (sc, mkdir); ffi_define_function (sc, rmdir); ffi_define_function (sc, get_isotime); ffi_define_function (sc, get_time); ffi_define_function (sc, getpid); /* Random numbers. */ ffi_define_function (sc, srandom); ffi_define_function (sc, random); ffi_define_function (sc, make_random_string); /* Process management. */ ffi_define_function (sc, pipe); ffi_define_function (sc, inbound_pipe); ffi_define_function (sc, outbound_pipe); ffi_define_function (sc, process_spawn_io); ffi_define_function (sc, process_spawn_fd); ffi_define_function (sc, process_wait); /* estream functions. */ ffi_define_function_name (sc, "es-fclose", es_fclose); ffi_define_function_name (sc, "es-read", es_read); ffi_define_function_name (sc, "es-feof", es_feof); ffi_define_function_name (sc, "es-write", es_write); /* Test helper functions. */ ffi_define_function (sc, file_equal); ffi_define_function (sc, splice); ffi_define_function (sc, string_index); ffi_define_function (sc, string_rindex); ffi_define_function_name (sc, "string-contains?", string_contains); /* User interface. */ ffi_define_function (sc, flush_stdio); ffi_define_function (sc, prompt); /* Configuration. */ ffi_define_function_name (sc, "*verbose*", get_verbose); ffi_define_function_name (sc, "*set-verbose!*", set_verbose); ffi_define (sc, "*argv0*", sc->vptr->mk_string (sc, argv0)); ffi_define (sc, "*scriptname*", sc->vptr->mk_string (sc, scriptname)); for (i = argc - 1; i >= 0; i--) { pointer value = sc->vptr->mk_string (sc, argv[i]); args = (sc->vptr->cons) (sc, value, args); } ffi_define (sc, "*args*", args); #if _WIN32 ffi_define (sc, "*pathsep*", sc->vptr->mk_character (sc, ';')); #else ffi_define (sc, "*pathsep*", sc->vptr->mk_character (sc, ':')); #endif ffi_define (sc, "*win32*", #if _WIN32 sc->T #else sc->F #endif ); ffi_define (sc, "*maintainer-mode*", #if MAINTAINER_MODE sc->T #else sc->F #endif ); ffi_define (sc, "*run-all-tests*", #if RUN_ALL_TESTS sc->T #else sc->F #endif ); ffi_define (sc, "*stdin*", sc->vptr->mk_port_from_file (sc, stdin, port_input)); ffi_define (sc, "*stdout*", sc->vptr->mk_port_from_file (sc, stdout, port_output)); ffi_define (sc, "*stderr*", sc->vptr->mk_port_from_file (sc, stderr, port_output)); return 0; } diff --git a/tools/gpg-card.c b/tools/gpg-card.c index 8b3a3082b..3d66033bf 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,4420 +1,4420 @@ /* gpg-card.c - An interactive tool to work with cards. * Copyright (C) 2019--2022 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 General Public License as published by * the Free Software Foundation; either version 3 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 Lesser 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://gnu.org/licenses/>. * SPDX-License-Identifier: GPL-3.0-or-later */ #include <config.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #ifdef HAVE_LIBREADLINE # define GNUPG_LIBREADLINE_H_INCLUDED # include <readline/readline.h> #endif /*HAVE_LIBREADLINE*/ #define INCLUDED_BY_MAIN_MODULE 1 #include "../common/util.h" #include "../common/status.h" #include "../common/i18n.h" #include "../common/init.h" #include "../common/sysutils.h" #include "../common/asshelp.h" #include "../common/userids.h" #include "../common/ccparray.h" #include "../common/exectool.h" #include "../common/exechelp.h" #include "../common/ttyio.h" #include "../common/server-help.h" #include "../common/openpgpdefs.h" #include "../common/tlv.h" #include "../common/comopt.h" #include "gpg-card.h" #define CONTROL_D ('D' - 'A' + 1) #define HISTORYNAME ".gpg-card_history" /* Constants to identify the commands and options. */ enum opt_values { aNull = 0, oQuiet = 'q', oVerbose = 'v', oDebug = 500, oGpgProgram, oGpgsmProgram, oStatusFD, oWithColons, oNoAutostart, oAgentProgram, oDisplay, oTTYname, oTTYtype, oXauthority, oLCctype, oLCmessages, oNoKeyLookup, oNoHistory, oChUid, oDummy }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (301, ("@\nOptions:\n ")), ARGPARSE_s_n (oVerbose, "verbose", ("verbose")), ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")), ARGPARSE_s_s (oDebug, "debug", "@"), ARGPARSE_s_s (oGpgProgram, "gpg", "@"), ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), ARGPARSE_s_n (oWithColons, "with-colons", "@"), ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"), ARGPARSE_s_s (oAgentProgram, "agent-program", "@"), ARGPARSE_s_s (oDisplay, "display", "@"), ARGPARSE_s_s (oTTYname, "ttyname", "@"), ARGPARSE_s_s (oTTYtype, "ttytype", "@"), ARGPARSE_s_s (oXauthority, "xauthority", "@"), ARGPARSE_s_s (oLCctype, "lc-ctype", "@"), ARGPARSE_s_s (oLCmessages, "lc-messages","@"), ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup", "use --no-key-lookup for \"list\""), ARGPARSE_s_n (oNoHistory,"no-history", "do not use the command history file"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end () }; /* The list of supported debug flags. */ static struct debug_flags_s debug_flags [] = { { DBG_IPC_VALUE , "ipc" }, { DBG_EXTPROG_VALUE, "extprog" }, { 0, NULL } }; /* An object to create lists of labels and keyrefs. */ struct keyinfolabel_s { const char *label; const char *keyref; }; typedef struct keyinfolabel_s *keyinfolabel_t; /* Helper for --chuid. */ static const char *changeuser; /* Limit of size of data we read from a file for certain commands. */ #define MAX_GET_DATA_FROM_FILE 16384 /* Constants for OpenPGP cards. */ #define OPENPGP_USER_PIN_DEFAULT "123456" #define OPENPGP_ADMIN_PIN_DEFAULT "12345678" #define OPENPGP_KDF_DATA_LENGTH_MIN 90 #define OPENPGP_KDF_DATA_LENGTH_MAX 110 /* Local prototypes. */ static void show_keysize_warning (void); static gpg_error_t dispatch_command (card_info_t info, const char *command); static void interactive_loop (void); #ifdef HAVE_LIBREADLINE static char **command_completion (const char *text, int start, int end); #endif /*HAVE_LIBREADLINE*/ /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "gpg-card"; break; case 12: p = "@GNUPG@"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = ("Usage: gpg-card" " [options] [{[--] command [args]}] (-h for help)"); break; case 41: p = ("Syntax: gpg-card" " [options] [command [args] {-- command [args]}]\n\n" "Tool to manage cards and tokens. Without a command an interactive\n" "mode is used. Use command \"help\" to list all commands."); break; default: p = NULL; break; } return p; } static void set_opt_session_env (const char *name, const char *value) { gpg_error_t err; err = session_env_setenv (opt.session_env, name, value); if (err) log_fatal ("error setting session environment: %s\n", gpg_strerror (err)); } /* Command line parsing. */ static void parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts) { while (gpgrt_argparse (NULL, pargs, popts)) { switch (pargs->r_opt) { case oQuiet: opt.quiet = 1; break; case oVerbose: opt.verbose++; break; case oDebug: if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags)) { pargs->r_opt = ARGPARSE_INVALID_ARG; pargs->err = ARGPARSE_PRINT_ERROR; } break; case oGpgProgram: opt.gpg_program = make_filename (pargs->r.ret_str, NULL); break; case oGpgsmProgram: opt.gpgsm_program = make_filename (pargs->r.ret_str, NULL); break; case oAgentProgram: opt.agent_program = make_filename (pargs->r.ret_str, NULL); break; case oStatusFD: gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1)); break; case oWithColons: opt.with_colons = 1; break; case oNoAutostart: opt.autostart = 0; break; case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break; case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break; case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break; case oXauthority: set_opt_session_env ("XAUTHORITY", pargs->r.ret_str); break; case oLCctype: opt.lc_ctype = pargs->r.ret_str; break; case oLCmessages: opt.lc_messages = pargs->r.ret_str; break; case oNoKeyLookup: opt.no_key_lookup = 1; break; case oNoHistory: opt.no_history = 1; break; case oChUid: changeuser = pargs->r.ret_str; break; default: pargs->err = 2; break; } } } /* gpg-card main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; char **command_list = NULL; int cmdidx; char *command; gnupg_reopen_std ("gpg-card"); gpgrt_set_strusage (my_strusage); gnupg_rl_initialize (); log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); setup_libassuan_logging (&opt.debug, NULL); /* Setup default options. */ opt.autostart = 1; opt.session_env = session_env_new (); if (!opt.session_env) log_fatal ("error allocating session environment block: %s\n", gpg_strerror (gpg_error_from_syserror ())); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; parse_arguments (&pargs, opts); gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (changeuser && gnupg_chuid (changeuser, 0)) log_inc_errorcount (); /* Force later termination. */ if (log_get_errorcount (0)) exit (2); /* Process common component options. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); if (parse_comopt (GNUPG_MODULE_NAME_CARD, opt.debug)) { gnupg_status_printf (STATUS_FAILURE, "option-parser %u", gpg_error (GPG_ERR_GENERAL)); exit(2); } if (comopt.no_autostart) opt.autostart = 0; /* Set defaults for non given options. */ if (!opt.gpg_program) opt.gpg_program = xstrdup (gnupg_module_name (GNUPG_MODULE_NAME_GPG)); if (!opt.gpgsm_program) opt.gpgsm_program = xstrdup (gnupg_module_name (GNUPG_MODULE_NAME_GPGSM)); /* Now build the list of commands. We guess the size of the array * by assuming each item is a complete command. Obviously this will * be rarely the case, but it is less code to allocate a possible * too large array. */ command_list = xcalloc (argc+1, sizeof *command_list); cmdidx = 0; command = NULL; while (argc) { for ( ; argc && strcmp (*argv, "--"); argc--, argv++) { if (!command) command = xstrdup (*argv); else { char *tmp = xstrconcat (command, " ", *argv, NULL); xfree (command); command = tmp; } } if (argc) { /* Skip the double dash. */ argc--; argv++; } if (command) { command_list[cmdidx++] = command; command = NULL; } } opt.interactive = !cmdidx; if (!opt.interactive) opt.no_history = 1; if (opt.interactive) { interactive_loop (); err = 0; } else { struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; err = 0; for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++) { err = dispatch_command (info, command); if (err) break; } if (gpg_err_code (err) == GPG_ERR_EOF) err = 0; /* This was a "quit". */ else if (command && !opt.quiet) log_info ("stopped at command '%s'\n", command); } flush_keyblock_cache (); if (command_list) { for (cmdidx=0; command_list[cmdidx]; cmdidx++) xfree (command_list[cmdidx]); xfree (command_list); } if (err) gnupg_status_printf (STATUS_FAILURE, "- %u", err); else if (log_get_errorcount (0)) gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL); else gnupg_status_printf (STATUS_SUCCESS, NULL); return log_get_errorcount (0)? 1:0; } /* Return S or the string "[none]" if S is NULL. */ static GPGRT_INLINE const char * nullnone (const char *s) { return s? s: "[none]"; } /* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters. * On error return an error code and stores NULL at R_BUFFER; on * success returns 0 and stores the number of bytes read at R_BUFLEN * and the address of a newly allocated buffer at R_BUFFER. A * complementary nul byte is always appended to the data but not * counted; this allows one to pass NULL for R-BUFFER and consider the * returned data as a string. */ static gpg_error_t get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen) { gpg_error_t err; estream_t fp; char *data; int n; *r_buffer = NULL; if (r_buflen) *r_buflen = 0; fp = es_fopen (fname, "rb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); return err; } data = xtrymalloc (MAX_GET_DATA_FROM_FILE); if (!data) { err = gpg_error_from_syserror (); log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err)); es_fclose (fp); return err; } n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp); es_fclose (fp); if (n < 0) { err = gpg_error_from_syserror (); tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); xfree (data); return err; } data[n] = 0; *r_buffer = data; if (r_buflen) *r_buflen = n; return 0; } /* Fixup the ENODEV error from scdaemon which we may see after * removing a card due to scdaemon scanning for readers with cards. * We also map the CAERD REMOVED error to the more useful CARD_NOT * PRESENT. */ static gpg_error_t fixup_scd_errors (gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) err = gpg_error (GPG_ERR_CARD_NOT_PRESENT); return err; } /* Set the card removed flag from INFO depending on ERR. This does * not clear the flag. */ static gpg_error_t maybe_set_card_removed (card_info_t info, gpg_error_t err) { if ((gpg_err_code (err) == GPG_ERR_ENODEV || gpg_err_code (err) == GPG_ERR_CARD_REMOVED) && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) info->card_removed = 1; return err; } /* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on * success. */ static gpg_error_t put_data_to_file (const char *fname, const void *buffer, size_t length) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "wb"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err)); return err; } if (length && es_fwrite (buffer, length, 1, fp) != 1) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); es_fclose (fp); return err; } if (es_fclose (fp)) { err = gpg_error_from_syserror (); log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err)); return err; } return 0; } /* Return a malloced string with the number opf the menu PROMPT. * Control-D is mapped to "Q". */ static char * get_selection (const char *prompt) { char *answer; tty_printf ("\n"); tty_printf ("%s", prompt); tty_printf ("\n"); answer = tty_get (_("Your selection? ")); tty_kill_prompt (); if (*answer == CONTROL_D) strcpy (answer, "q"); return answer; } /* Simply prints TEXT to the output. Returns 0 as a convenience. * This is a separate function so that it can be extended to run * less(1) or so. The extra arguments are int values terminated by a * 0 to indicate card application types supported with this command. * If none are given (just the final 0), this is a general * command. */ static gpg_error_t print_help (const char *text, ...) { estream_t fp; va_list arg_ptr; int value; int any = 0; fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "%s\n", text); va_start (arg_ptr, text); while ((value = va_arg (arg_ptr, int))) { if (!any) tty_fprintf (fp, "[Supported by: "); tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value)); any = 1; } if (any) tty_fprintf (fp, "]\n"); va_end (arg_ptr); return 0; } /* Print an (OpenPGP) fingerprint. */ static void print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen) { int i; if (fpr) { for (i=0; i < fprlen ; i++, fpr++) tty_fprintf (fp, "%02X", *fpr); } else tty_fprintf (fp, " [none]"); tty_fprintf (fp, "\n"); } /* Print the keygrip GRP. */ static void print_keygrip (estream_t fp, const unsigned char *grp, int with_lf) { int i; for (i=0; i < 20 ; i++, grp++) tty_fprintf (fp, "%02X", *grp); if (with_lf) tty_fprintf (fp, "\n"); } /* Print a string but avoid printing control characters. */ static void print_string (estream_t fp, const char *text, const char *name) { tty_fprintf (fp, "%s", text); /* FIXME: tty_printf_utf8_string2 eats everything after and including an @ - e.g. when printing an url. */ if (name && *name) { if (fp) print_utf8_buffer2 (fp, name, strlen (name), '\n'); else tty_print_utf8_string2 (NULL, name, strlen (name), 0); } else tty_fprintf (fp, _("[not set]")); tty_fprintf (fp, "\n"); } /* Print an ISO formatted name or "[not set]". */ static void print_isoname (estream_t fp, const char *name) { if (name && *name) { char *p, *given, *buf; buf = xstrdup (name); given = strstr (buf, "<<"); for (p=buf; *p; p++) if (*p == '<') *p = ' '; if (given && given[2]) { *given = 0; given += 2; if (fp) print_utf8_buffer2 (fp, given, strlen (given), '\n'); else tty_print_utf8_string2 (NULL, given, strlen (given), 0); if (*buf) tty_fprintf (fp, " "); } if (fp) print_utf8_buffer2 (fp, buf, strlen (buf), '\n'); else tty_print_utf8_string2 (NULL, buf, strlen (buf), 0); xfree (buf); } else { tty_fprintf (fp, _("[not set]")); } tty_fprintf (fp, "\n"); } /* Return true if the buffer MEM of length memlen consists only of zeroes. */ static int mem_is_zero (const char *mem, unsigned int memlen) { int i; for (i=0; i < memlen && !mem[i]; i++) ; return (i == memlen); } /* Helper to list a single keyref. LABEL_KEYREF is a fallback key * reference if no info is available; it may be NULL. */ static void list_one_kinfo (card_info_t info, key_info_t kinfo, const char *label_keyref, estream_t fp, int no_key_lookup, int create_shadow) { gpg_error_t err; key_info_t firstkinfo = info->kinfo; keyblock_t keyblock = NULL; keyblock_t kb; pubkey_t pubkey; userid_t uid; key_info_t ki; const char *s; gcry_sexp_t s_pkey; int any; if (firstkinfo && kinfo) { tty_fprintf (fp, " "); if (mem_is_zero (kinfo->grip, sizeof kinfo->grip)) { tty_fprintf (fp, "[none]\n"); tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); goto leave; } print_keygrip (fp, kinfo->grip, 1); tty_fprintf (fp, " keyref .....: %s", kinfo->keyref); if (kinfo->usage) { any = 0; tty_fprintf (fp, " ("); if ((kinfo->usage & GCRY_PK_USAGE_SIGN)) { tty_fprintf (fp, "sign"); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_CERT)) { tty_fprintf (fp, "%scert", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_AUTH)) { tty_fprintf (fp, "%sauth", any?",":""); any=1; } if ((kinfo->usage & GCRY_PK_USAGE_ENCR)) { tty_fprintf (fp, "%sencr", any?",":""); any=1; } tty_fprintf (fp, ")"); } tty_fprintf (fp, "\n"); if (kinfo->label) tty_fprintf (fp, " label ......: %s\n", kinfo->label); if (!(err = scd_readkey (kinfo->keyref, create_shadow, &s_pkey))) { char *tmp = pubkey_algo_string (s_pkey, NULL); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (tmp)); xfree (tmp); gcry_sexp_release (s_pkey); s_pkey = NULL; } else { maybe_set_card_removed (info, err); tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } if (kinfo->fprlen && kinfo->created) { tty_fprintf (fp, " stored fpr .: "); print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen); tty_fprintf (fp, " created ....: %s\n", isotimestamp (kinfo->created)); } if (no_key_lookup) err = 0; else err = get_matching_keys (kinfo->grip, (GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS), &keyblock); if (err) { if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY) tty_fprintf (fp, " used for ...: [%s]\n", gpg_strerror (err)); goto leave; } for (kb = keyblock; kb; kb = kb->next) { tty_fprintf (fp, " used for ...: %s\n", kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" : kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?"); pubkey = kb->keys; if (kb->protocol == GNUPG_PROTOCOL_OPENPGP) { /* If this is not the primary key print the primary * key's fingerprint or a reference to it. */ tty_fprintf (fp, " main key .: "); for (ki=firstkinfo; ki; ki = ki->next) if (pubkey->grip_valid && !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN)) break; if (ki) { /* Fixme: Replace mapping by a table lookup. */ if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) s = "this"; else if (!strcmp (ki->keyref, "OPENPGP.1")) s = "Signature key"; else if (!strcmp (ki->keyref, "OPENPGP.2")) s = "Encryption key"; else if (!strcmp (ki->keyref, "OPENPGP.3")) s = "Authentication key"; else s = NULL; if (s) tty_fprintf (fp, "<%s>\n", s); else tty_fprintf (fp, "<Key %s>\n", ki->keyref); } else /* Print the primary key as fallback. */ print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); } if (kb->protocol == GNUPG_PROTOCOL_OPENPGP || kb->protocol == GNUPG_PROTOCOL_CMS) { /* Find the primary or subkey of that key. */ for (; pubkey; pubkey = pubkey->next) if (pubkey->grip_valid && !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN)) break; if (pubkey) { tty_fprintf (fp, " fpr ......: "); print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen); tty_fprintf (fp, " created ..: %s\n", isotimestamp (pubkey->created)); } } for (uid = kb->uids; uid; uid = uid->next) { print_string (fp, " user id ..: ", uid->value); } } } else { tty_fprintf (fp, " [none]\n"); if (label_keyref) tty_fprintf (fp, " keyref .....: %s\n", label_keyref); if (kinfo) tty_fprintf (fp, " algorithm ..: %s\n", nullnone (kinfo->keyalgo)); } leave: release_keyblock (keyblock); } /* Return the retired key number if KEYREF is for a retired key; 0 if * not. */ static int piv_keyref_is_retired (const char *keyref) { if (!strncmp (keyref, "PIV.8", 5) && keyref[5] >= '2' && hexdigitp (keyref + 5)) return xtoi_1 (keyref+5) - 1; else if (!strncmp (keyref, "PIV.9", 5) && keyref[5] >= '0' && keyref[5] <= '5') return atoi_1 (keyref+5) + 15; else return 0; } /* List all keyinfo in INFO using the list of LABELS. */ static void list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp, int no_key_lookup, int create_shadow) { key_info_t kinfo; int idx, i, j; int rn; /* Print the keyinfo. We first print those we known and then all * remaining item. */ for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) kinfo->xflag = 0; if (labels) { for (idx=0; labels[idx].label; idx++) { tty_fprintf (fp, "%s", labels[idx].label); kinfo = find_kinfo (info, labels[idx].keyref); list_one_kinfo (info, kinfo, labels[idx].keyref, fp, no_key_lookup, create_shadow); if (kinfo) kinfo->xflag = 1; } } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { if (kinfo->xflag) continue; if (info->apptype == APP_TYPE_PIV && (rn = piv_keyref_is_retired (kinfo->keyref))) tty_fprintf (fp, "Key retired %2d ...", rn); else { tty_fprintf (fp, "Key %s", kinfo->keyref); for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1) tty_fprintf (fp, j? ".":" "); } tty_fprintf (fp, ":"); list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup, create_shadow); } } static void list_retry_counter (card_info_t info, estream_t fp) { const char *s; int i; if (info->chvlabels) tty_fprintf (fp, "PIN labels .......: %s\n", info->chvlabels); tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo) && i < info->nchvinfo; i++) { if (info->chvinfo[i] >= 0) tty_fprintf (fp, " %d", info->chvinfo[i]); else { switch (info->chvinfo[i]) { case -1: s = "[error]"; break; case -2: s = "-"; break; /* No such PIN or info not available. */ case -3: s = "[blocked]"; break; case -4: s = "[nullpin]"; break; case -5: s = "[verified]"; break; default: s = "[?]"; break; } tty_fprintf (fp, " %s", s); } } tty_fprintf (fp, "\n"); } /* List OpenPGP card specific data. */ static void list_openpgp (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "Signature key ....:", "OPENPGP.1" }, { "Encryption key....:", "OPENPGP.2" }, { "Authentication key:", "OPENPGP.3" }, { NULL, NULL } }; if (info->apptype != APP_TYPE_OPENPGP) { tty_fprintf (fp, "invalid OpenPGP card\n"); return; } tty_fprintf (fp, "Name of cardholder: "); print_isoname (fp, info->disp_name); print_string (fp, "Language prefs ...: ", info->disp_lang); tty_fprintf (fp, "Salutation .......: %s\n", info->disp_sex == 1? _("Mr."): info->disp_sex == 2? _("Ms.") : ""); print_string (fp, "URL of public key : ", info->pubkey_url); print_string (fp, "Login data .......: ", info->login_data); if (info->private_do[0]) print_string (fp, "Private DO 1 .....: ", info->private_do[0]); if (info->private_do[1]) print_string (fp, "Private DO 2 .....: ", info->private_do[1]); if (info->private_do[2]) print_string (fp, "Private DO 3 .....: ", info->private_do[2]); if (info->private_do[3]) print_string (fp, "Private DO 4 .....: ", info->private_do[3]); if (info->cafpr1len) { tty_fprintf (fp, "CA fingerprint %d .:", 1); print_shax_fpr (fp, info->cafpr1, info->cafpr1len); } if (info->cafpr2len) { tty_fprintf (fp, "CA fingerprint %d .:", 2); print_shax_fpr (fp, info->cafpr2, info->cafpr2len); } if (info->cafpr3len) { tty_fprintf (fp, "CA fingerprint %d .:", 3); print_shax_fpr (fp, info->cafpr3, info->cafpr3len); } tty_fprintf (fp, "Signature PIN ....: %s\n", info->chv1_cached? _("not forced"): _("forced")); tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n", info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]); list_retry_counter (info, fp); tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter); tty_fprintf (fp, "Capabilities .....:"); if (info->extcap.ki) tty_fprintf (fp, " key-import"); if (info->extcap.aac) tty_fprintf (fp, " algo-change"); if (info->extcap.bt) tty_fprintf (fp, " button"); if (info->extcap.sm) tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo)); if (info->extcap.private_dos) tty_fprintf (fp, " priv-data"); tty_fprintf (fp, "\n"); if (info->extcap.kdf) { tty_fprintf (fp, "KDF setting ......: %s\n", info->kdf_do_enabled ? "on" : "off"); } if (info->extcap.bt) { tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n", info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off", info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off"); } list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PIV card specific data. */ static void list_piv (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { "PIV authentication:", "PIV.9A" }, { "Card authenticat. :", "PIV.9E" }, { "Digital signature :", "PIV.9C" }, { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; if (info->chvusage[0] || info->chvusage[1]) { tty_fprintf (fp, "PIN usage policy .:"); if ((info->chvusage[0] & 0x40)) tty_fprintf (fp, " app-pin"); if ((info->chvusage[0] & 0x20)) tty_fprintf (fp, " global-pin"); if ((info->chvusage[0] & 0x10)) tty_fprintf (fp, " occ"); if ((info->chvusage[0] & 0x08)) tty_fprintf (fp, " vci"); if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) tty_fprintf (fp, " pairing"); if (info->chvusage[1] == 0x10) tty_fprintf (fp, " primary:card"); else if (info->chvusage[1] == 0x20) tty_fprintf (fp, " primary:global"); tty_fprintf (fp, "\n"); } list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List Netkey card specific data. */ static void list_nks (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } /* List PKCS#15 card specific data. */ static void list_p15 (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow) { static struct keyinfolabel_s keyinfolabels[] = { { NULL, NULL } }; list_retry_counter (info, fp); list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow); } static void print_a_version (estream_t fp, const char *prefix, unsigned int value) { unsigned int a, b, c, d; a = ((value >> 24) & 0xff); b = ((value >> 16) & 0xff); c = ((value >> 8) & 0xff); d = ((value ) & 0xff); if (a) tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d); else if (b) tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d); else if (c) tty_fprintf (fp, "%s %u.%u\n", prefix, c, d); else tty_fprintf (fp, "%s %u\n", prefix, d); } /* Print all available information about the current card. With * NO_KEY_LOOKUP the sometimes expensive listing of all matching * OpenPGP and X.509 keys is not done */ static void list_card (card_info_t info, int no_key_lookup, int create_shadow) { estream_t fp = opt.interactive? NULL : es_stdout; tty_fprintf (fp, "Reader ...........: %s\n", nullnone (info->reader)); if (info->cardtype) tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); if (info->cardversion) print_a_version (fp, "Card firmware ....:", info->cardversion); tty_fprintf (fp, "Serial number ....: %s\n", nullnone (info->serialno)); tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr ? info->apptypestr:"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":""); if (info->appversion) print_a_version (fp, "Version ..........:", info->appversion); if (info->serialno && info->dispserialno && strcmp (info->serialno, info->dispserialno)) tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno); if (info->manufacturer_name && info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s (%x)\n", info->manufacturer_name, info->manufacturer_id); else if (info->manufacturer_name && !info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name); else if (info->manufacturer_id) tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id); switch (info->apptype) { case APP_TYPE_OPENPGP: list_openpgp (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_PIV: list_piv (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_NKS: list_nks (info, fp, no_key_lookup, create_shadow); break; case APP_TYPE_P15: list_p15 (info, fp, no_key_lookup, create_shadow); break; default: break; } } /* Helper for cmd_list. */ static void print_card_list (estream_t fp, card_info_t info, strlist_t cards, int only_current) { int count; strlist_t sl; size_t snlen; int star; const char *s; for (count = 0, sl = cards; sl; sl = sl->next, count++) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); star = (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)); } else star = 0; if (!only_current || star) tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d); } } /* The LIST command. This also updates INFO if needed. */ static gpg_error_t cmd_list (card_info_t info, char *argstr) { gpg_error_t err; int opt_cards, opt_apps, opt_info, opt_reread, opt_no_key_lookup; int opt_shadow; strlist_t cards = NULL; strlist_t sl; estream_t fp = opt.interactive? NULL : es_stdout; const char *cardsn = NULL; char *appstr = NULL; int count; int need_learn = 0; if (!info) return print_help ("LIST [--cards] [--apps] [--info] [--reread] [--shadow]" " [--no-key-lookup] [N] [APP]\n\n" "Show the content of the current card.\n" "With N given select and list the N-th card;\n" "with APP also given select that application.\n" "To select an APP on the current card use '-' for N.\n" "The S/N of the card may be used instead of N.\n" " --cards list available cards\n" " --apps list additional card applications\n" " --info select a card and prints its s/n\n" " --reread read infos from PCKS#15 cards again\n" " --shadow create shadow keys for all card keys\n" " --no-key-lookup do not list matching OpenPGP or X.509 keys\n" , 0); opt_cards = has_leading_option (argstr, "--cards"); opt_apps = has_leading_option (argstr, "--apps"); opt_info = has_leading_option (argstr, "--info"); opt_reread = has_leading_option (argstr, "--reread"); opt_shadow = has_leading_option (argstr, "--shadow"); opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup"); argstr = skip_options (argstr); if (opt_shadow) opt_no_key_lookup = 1; if (opt.no_key_lookup) opt_no_key_lookup = 1; if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1))) { if (*argstr == '-' && (argstr[1] || spacep (argstr+1))) argstr++; /* Keep current card. */ else { cardsn = argstr; while (hexdigitp (argstr)) argstr++; if (*argstr && !spacep (argstr)) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (*argstr) *argstr++ = 0; } while (spacep (argstr)) argstr++; if (*argstr) { appstr = argstr; while (*argstr && !spacep (argstr)) argstr++; while (spacep (argstr)) argstr++; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } } } else if (*argstr) { /* First argument needs to be a digit. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (!info->serialno || info->need_sn_cmd) { /* This is probably the first call or was explicitly requested. * We need to send a SERIALNO command to scdaemon so that our * session knows all cards. */ err = scd_serialno (NULL, NULL); if (err) goto leave; info->need_sn_cmd = 0; need_learn = 1; } if (opt_cards || opt_apps) { /* Note that with option --apps CARDS is here the list of all * apps. Format is "SERIALNO APPNAME {APPNAME}". We print the * card number in the first column. */ if (opt_apps) err = scd_applist (&cards, opt_cards); else err = scd_cardlist (&cards); if (err) goto leave; print_card_list (fp, info, cards, 0); } else { if (cardsn) { int i, cardno; err = scd_cardlist (&cards); if (err) goto leave; /* Switch to the requested card. */ for (i=0; digitp (cardsn+i); i++) ; if (i && i < 4 && !cardsn[i]) { /* Looks like an index into the card list. */ cardno = atoi (cardsn); for (count = 0, sl = cards; sl; sl = sl->next, count++) if (count == cardno) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } else /* S/N of card specified. */ { for (sl = cards; sl; sl = sl->next) if (!ascii_strcasecmp (sl->d, cardsn)) break; if (!sl) { err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } } err = scd_switchcard (sl->d); need_learn = 1; } else /* show app list. */ { err = scd_applist (&cards, 1); if (err) goto leave; } if (appstr && *appstr) { /* Switch to the requested app. */ err = scd_switchapp (appstr); if (err) goto leave; need_learn = 1; } if (need_learn) err = scd_learn (info, opt_reread); else err = 0; if (err) ; else if (opt_info) print_card_list (fp, info, cards, 1); else { size_t snlen; const char *s; /* First get the list of active cards and check whether the * current card is still in the list. If not the card has * been removed. Note that during the listing the card * remove state might also be detected but only if an access * to the scdaemon is required; it is anyway better to test * that before starting a listing. */ free_strlist (cards); err = scd_cardlist (&cards); if (err) goto leave; for (sl = cards; sl; sl = sl->next) { if (info && info->serialno) { s = strchr (sl->d, ' '); if (s) snlen = s - sl->d; else snlen = strlen (sl->d); if (strlen (info->serialno) == snlen && !memcmp (info->serialno, sl->d, snlen)) break; } } if (!sl) { info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); goto leave; } list_card (info, opt_no_key_lookup, opt_shadow); } } leave: free_strlist (cards); return err; } /* The CHECKKEYS command. */ static gpg_error_t cmd_checkkeys (card_info_t callerinfo, char *argstr) { gpg_error_t err; estream_t fp = opt.interactive? NULL : es_stdout; strlist_t cards = NULL; strlist_t sl; int opt_ondisk; int opt_delete_clear; int opt_delete_protected; int delete_count = 0; struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; key_info_t kinfo; if (!callerinfo) return print_help ("CHECKKEYS [--ondisk] [--delete-clear-copy] [--delete-protected-copy]" "\n\n" "Print a list of keys on all inserted cards. With --ondisk only\n" "keys are listed which also have a copy on disk. Missing shadow\n" "keys are created. With --delete-clear-copy, copies of keys also\n" "stored on disk without any protection will be deleted.\n" , 0); opt_ondisk = has_leading_option (argstr, "--ondisk"); opt_delete_clear = has_leading_option (argstr, "--delete-clear-copy"); opt_delete_protected = has_leading_option (argstr, "--delete-protected-copy"); argstr = skip_options (argstr); if (*argstr) { /* No args expected */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (!callerinfo->serialno) { /* This is probably the first call We need to send a SERIALNO * command to scdaemon so that our session knows all cards. */ err = scd_serialno (NULL, NULL); if (err) goto leave; } /* Get the list of all cards. */ err = scd_cardlist (&cards); if (err) goto leave; /* Loop over all cards. We use our own info buffer here. */ for (sl = cards; sl; sl = sl->next) { err = scd_switchcard (sl->d); if (err) { log_error ("Error switching to card %s: %s\n", sl->d, gpg_strerror (err)); continue; } release_card_info (info); err = scd_learn (info, 0); if (err) { log_error ("Error getting infos from card %s: %s\n", sl->d, gpg_strerror (err)); continue; } for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next) { char *infostr; err = scd_havekey_info (kinfo->grip, &infostr); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) { /* Create a shadow key and try again. */ scd_readkey (kinfo->keyref, 1, NULL); err = scd_havekey_info (kinfo->grip, &infostr); } if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND) log_error ("Error getting infos for a key: %s\n", gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) ; /* does not make sense to show this. */ else if (opt_ondisk && infostr && !strcmp (infostr, "shadowed")) ; /* Don't print this one. */ else { tty_fprintf (fp, "%s %s ", nullnone (info->serialno), app_type_string (info->apptype)); print_keygrip (fp, kinfo->grip, 0); tty_fprintf (fp, " %s %s\n", kinfo->keyref, infostr? infostr: "error"); } if (infostr && ((opt_delete_clear && !strcmp (infostr, "clear")) || (opt_delete_protected && !strcmp (infostr, "protected")))) { err = scd_delete_key (kinfo->grip, 0); if (err) log_error ("Error deleting a key copy: %s\n", gpg_strerror (err)); else delete_count++; } xfree (infostr); } } es_fflush (es_stdout); if (delete_count) log_info ("Number of deleted key copies: %d\n", delete_count); err = 0; leave: release_card_info (info); free_strlist (cards); /* Better reset to the original card. */ scd_learn (callerinfo, 0); return err; } /* The VERIFY command. */ static gpg_error_t cmd_verify (card_info_t info, char *argstr) { gpg_error_t err, err2; const char *pinref; if (!info) return print_help ("verify [chvid]", 0); if (*argstr) pinref = argstr; else if (info->apptype == APP_TYPE_OPENPGP) pinref = info->serialno; else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else return gpg_error (GPG_ERR_MISSING_VALUE); err = scd_checkpin (pinref); if (err) log_error ("verify failed: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); /* In any case update the CHV status, so that the next "list" shows * the correct retry counter values. */ err2 = scd_getattr ("CHV-STATUS", info); return err ? err : err2; } static gpg_error_t cmd_authenticate (card_info_t info, char *argstr) { gpg_error_t err; int opt_setkey; int opt_raw; char *string = NULL; char *key = NULL; size_t keylen; if (!info) return print_help ("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n" "Perform a mutual authentication either by reading the key\n" "from FILE or by taking it from the command line. Without\n" "the option --raw the key is expected to be hex encoded.\n" "To install a new administration key --setkey is used; this\n" "requires a prior authentication with the old key.", APP_TYPE_PIV, 0); if (info->apptype != APP_TYPE_PIV) { log_info ("Note: This is a PIV only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } opt_setkey = has_leading_option (argstr, "--setkey"); opt_raw = has_leading_option (argstr, "--raw"); argstr = skip_options (argstr); if (*argstr == '<') /* Read key from a file. */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &string, NULL); if (err) goto leave; } if (opt_raw) { key = string? string : xstrdup (argstr); string = NULL; keylen = strlen (key); } else { key = hex_to_buffer (string? string: argstr, &keylen); if (!key) { err = gpg_error_from_syserror (); goto leave; } } err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen); leave: if (key) { wipememory (key, keylen); xfree (key); } xfree (string); return err; } /* Helper for cmd_name to qyery a part of name. */ static char * ask_one_name (const char *prompt) { char *name; int i; for (;;) { name = tty_get (prompt); trim_spaces (name); tty_kill_prompt (); if (!*name || *name == CONTROL_D) { if (*name == CONTROL_D) tty_fprintf (NULL, "\n"); xfree (name); return NULL; } for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++) ; /* The name must be in Latin-1 and not UTF-8 - lacking the code * to ensure this we restrict it to ASCII. */ if (name[i]) tty_printf (_("Error: Only plain ASCII is currently allowed.\n")); else if (strchr (name, '<')) tty_printf (_("Error: The \"<\" character may not be used.\n")); else if (strstr (name, " ")) tty_printf (_("Error: Double spaces are not allowed.\n")); else return name; xfree (name); } } /* The NAME command. */ static gpg_error_t cmd_name (card_info_t info, const char *argstr) { gpg_error_t err; char *surname, *givenname; char *isoname, *p; if (!info) return print_help ("name [--clear]\n\n" "Set the name field of an OpenPGP card. With --clear the stored\n" "name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } again: if (!strcmp (argstr, "--clear")) isoname = xstrdup (" "); /* No real way to clear; set to space instead. */ else { surname = ask_one_name (_("Cardholder's surname: ")); givenname = ask_one_name (_("Cardholder's given name: ")); if (!surname || !givenname || (!*surname && !*givenname)) { xfree (surname); xfree (givenname); return gpg_error (GPG_ERR_CANCELED); } isoname = xstrconcat (surname, "<<", givenname, NULL); xfree (surname); xfree (givenname); for (p=isoname; *p; p++) if (*p == ' ') *p = '<'; if (strlen (isoname) > 39 ) { log_info (_("Error: Combined name too long " "(limit is %d characters).\n"), 39); xfree (isoname); goto again; } } err = scd_setattr ("DISP-NAME", isoname, strlen (isoname)); xfree (isoname); return err; } static gpg_error_t cmd_url (card_info_t info, const char *argstr) { gpg_error_t err; char *url; if (!info) return print_help ("URL [--clear]\n\n" "Set the URL data object. That data object can be used by\n" "the FETCH command to retrieve the full public key. The\n" "option --clear deletes the content of that data object.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!strcmp (argstr, "--clear")) url = xstrdup (" "); /* No real way to clear; set to space instead. */ else { url = tty_get (_("URL to retrieve public key: ")); trim_spaces (url); tty_kill_prompt (); if (!*url || *url == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr ("PUBKEY-URL", url, strlen (url)); leave: xfree (url); return err; } /* Fetch the key from the URL given on the card or try to get it from * the default keyserver. */ static gpg_error_t cmd_fetch (card_info_t info) { gpg_error_t err; key_info_t kinfo; if (!info) return print_help ("FETCH\n\n" "Retrieve a key using the URL data object or if that is missing\n" "using the fingerprint.", APP_TYPE_OPENPGP, 0); if (info->pubkey_url && *info->pubkey_url) { /* strlist_t sl = NULL; */ /* add_to_strlist (&sl, info.pubkey_url); */ /* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */ /* free_strlist (sl); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen) { /* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */ /* opt.keyserver, 0); */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */ } else err = gpg_error (GPG_ERR_NO_DATA); return err; } static gpg_error_t cmd_login (card_info_t info, char *argstr) { gpg_error_t err; char *data; size_t datalen; int use_default_pin; if (!info) return print_help ("LOGIN [--clear|--use-default-pin] [< FILE]\n\n" "Set the login data object. If FILE is given the data is\n" "is read from that file. This allows for binary data.\n" "The option --clear deletes the login data. --use-default-pin\n" "tells the card to always use the default PIN (\"123456\").", APP_TYPE_OPENPGP, 0); use_default_pin = has_leading_option (argstr, "--use-default-pin"); argstr = skip_options (argstr); if (!strcmp (argstr, "--clear")) { data = xstrdup (" "); /* kludge. */ datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else { data = tty_get (_("Login data (account name): ")); trim_spaces (data); tty_kill_prompt (); if ((!*data && !use_default_pin) || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } datalen = strlen (data); } if (use_default_pin) { char *tmpdata = xmalloc (datalen + 5); memcpy (tmpdata, data, datalen); memcpy (tmpdata+datalen, "\n\x14" "F=3", 5); xfree (data); data = tmpdata; datalen += 5; } err = scd_setattr ("LOGIN-DATA", data, datalen); leave: xfree (data); return err; } static gpg_error_t cmd_lang (card_info_t info, const char *argstr) { gpg_error_t err; char *data, *p; if (!info) return print_help ("LANG [--clear]\n\n" "Change the language info for the card. This info can be used\n" "by applications for a personalized greeting. Up to 4 two-digit\n" "language identifiers can be entered as a preference. The option\n" "--clear removes all identifiers. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); if (!strcmp (argstr, "--clear")) data = xstrdup (" "); /* Note that we need two spaces here. */ else { again: data = tty_get (_("Language preferences: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (strlen (data) > 8 || (strlen (data) & 1)) { log_info (_("Error: invalid length of preference string.\n")); xfree (data); goto again; } for (p=data; *p && *p >= 'a' && *p <= 'z'; p++) ; if (*p) { log_info (_("Error: invalid characters in preference string.\n")); xfree (data); goto again; } } err = scd_setattr ("DISP-LANG", data, strlen (data)); leave: xfree (data); return err; } static gpg_error_t cmd_salut (card_info_t info, const char *argstr) { gpg_error_t err; char *data = NULL; const char *str; if (!info) return print_help ("SALUT [--clear]\n\n" "Change the salutation info for the card. This info can be used\n" "by applications for a personalized greeting. The option --clear\n" "removes this data object. GnuPG does not use this info.", APP_TYPE_OPENPGP, 0); again: if (!strcmp (argstr, "--clear")) str = "9"; else { data = tty_get (_("Salutation (M = Mr., F = Ms., or space): ")); trim_spaces (data); tty_kill_prompt (); if (*data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (!*data) str = "9"; else if ((*data == 'M' || *data == 'm') && !data[1]) str = "1"; else if ((*data == 'F' || *data == 'f') && !data[1]) str = "2"; else { tty_printf (_("Error: invalid response.\n")); xfree (data); data = NULL; goto again; } } err = scd_setattr ("DISP-SEX", str, 1); leave: xfree (data); return err; } static gpg_error_t cmd_cafpr (card_info_t info, char *argstr) { gpg_error_t err; char *data = NULL; const char *s; int i, c; unsigned char fpr[32]; int fprlen; int fprno; int opt_clear = 0; if (!info) return print_help ("CAFPR [--clear] N\n\n" "Change the CA fingerprint number N. N must be in the\n" "range 1 to 3. The option --clear clears the specified\n" "CA fingerprint N or all of them if N is 0 or not given.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { fprno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else fprno = 0; if (opt_clear && !fprno) ; /* Okay: clear all fprs. */ else if (fprno < 1 || fprno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } again: if (opt_clear) { memset (fpr, 0, 20); fprlen = 20; } else { xfree (data); data = tty_get (_("CA fingerprint: ")); trim_spaces (data); tty_kill_prompt (); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } for (i=0, s=data; i < sizeof fpr && *s; ) { while (spacep(s)) s++; if (*s == ':') s++; while (spacep(s)) s++; c = hextobyte (s); if (c == -1) break; fpr[i++] = c; s += 2; } fprlen = i; if ((fprlen != 20 && fprlen != 32) || *s) { log_error (_("Error: invalid formatted fingerprint.\n")); goto again; } } if (!fprno) { log_assert (opt_clear); err = scd_setattr ("CA-FPR-1", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-2", fpr, fprlen); if (!err) err = scd_setattr ("CA-FPR-3", fpr, fprlen); } else err = scd_setattr (fprno==1?"CA-FPR-1": fprno==2?"CA-FPR-2": fprno==3?"CA-FPR-3":"x", fpr, fprlen); leave: xfree (data); return err; } static gpg_error_t cmd_privatedo (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; char *do_name = NULL; char *data = NULL; size_t datalen; int do_no; if (!info) return print_help ("PRIVATEDO [--clear] N [< FILE]\n\n" "Change the private data object N. N must be in the\n" "range 1 to 4. If FILE is given the data is is read\n" "from that file. The option --clear clears the data.", APP_TYPE_OPENPGP, 0); opt_clear = has_leading_option (argstr, "--clear"); argstr = skip_options (argstr); if (digitp (argstr)) { do_no = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else do_no = 0; if (do_no < 1 || do_no > 4) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } do_name = xasprintf ("PRIVATE-DO-%d", do_no); if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; } else if (*argstr) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } else { data = tty_get (_("Private DO data: ")); trim_spaces (data); tty_kill_prompt (); datalen = strlen (data); if (!*data || *data == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } err = scd_setattr (do_name, data, datalen); leave: xfree (do_name); xfree (data); return err; } static gpg_error_t cmd_writecert (card_info_t info, char *argstr) { gpg_error_t err; int opt_clear; int opt_openpgp; char *certref_buffer = NULL; char *certref; char *data = NULL; size_t datalen; estream_t key = NULL; if (!info) return print_help ("WRITECERT CERTREF '<' FILE\n" "WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n" "WRITECERT --clear CERTREF\n\n" "Write a certificate to the card under the id CERTREF.\n" "The option --clear removes the certificate from the card.\n" "The option --openpgp expects an OpenPGP keyblock and stores\n" "it encapsulated in a CMS container; the keyblock is taken\n" "from FILE or directly from the OpenPGP key with FPR", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_clear = has_leading_option (argstr, "--clear"); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } else /* Upcase the certref; prepend cardtype if needed. */ { if (!strchr (certref, '.')) certref_buffer = xstrconcat (app_type_string (info->apptype), ".", certref, NULL); else certref_buffer = xstrdup (certref); ascii_strupr (certref_buffer); certref = certref_buffer; } if (opt_clear) { data = xstrdup (" "); datalen = 1; } else if (*argstr == '<') /* Read it from a file */ { for (argstr++; spacep (argstr); argstr++) ; err = get_data_from_file (argstr, &data, &datalen); if (err) goto leave; if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----") && ascii_memistr (data, datalen, "-----END CERTIFICATE-----") && !memchr (data, 0, datalen) && !memchr (data, 1, datalen)) { gpgrt_b64state_t b64; b64 = gpgrt_b64dec_start (""); if (!b64) err = gpg_error_from_syserror (); else err = gpgrt_b64dec_proc (b64, data, datalen, &datalen); if (!err) err = gpgrt_b64dec_finish (b64); if (err) goto leave; } } else if (opt_openpgp && *argstr) { err = get_minimal_openpgp_key (&key, argstr); if (err) goto leave; if (es_fclose_snatch (key, (void*)&data, &datalen)) { err = gpg_error_from_syserror (); goto leave; } key = NULL; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_openpgp && !opt_clear) { tlv_builder_t tb; void *tmpder; size_t tmpderlen; tb = tlv_builder_new (0); if (!tb) { err = gpg_error_from_syserror (); goto leave; } tlv_builder_add_tag (tb, 0, TAG_SEQUENCE); tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10); tlv_builder_add_tag (tb, CLASS_CONTEXT, 0); tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen); tlv_builder_add_end (tb); tlv_builder_add_end (tb); err = tlv_builder_finalize (tb, &tmpder, &tmpderlen); if (err) goto leave; xfree (data); data = tmpder; datalen = tmpderlen; } err = scd_writecert (certref, data, datalen); leave: es_fclose (key); xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_readcert (card_info_t info, char *argstr) { gpg_error_t err; char *certref_buffer = NULL; char *certref; void *data = NULL; size_t datalen, dataoff; const char *fname; int opt_openpgp; if (!info) return print_help ("READCERT [--openpgp] CERTREF > FILE\n\n" "Read the certificate for key CERTREF and store it in FILE.\n" "With option \"--openpgp\" an OpenPGP keyblock is expected\n" "and stored in FILE.\n", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_openpgp = has_leading_option (argstr, "--openpgp"); argstr = skip_options (argstr); certref = argstr; if ((argstr = strchr (certref, ' '))) { *argstr++ = 0; trim_spaces (certref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = certref + strlen (certref); if (info->apptype == APP_TYPE_OPENPGP) { if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3")) certref_buffer = xstrdup ("OPENPGP.3"); else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2")) certref_buffer = xstrdup ("OPENPGP.2"); else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1")) certref_buffer = xstrdup ("OPENPGP.1"); else { err = gpg_error (GPG_ERR_INV_ID); log_error ("Error: CERTREF must be OPENPGP.N or just N" " with N being 1..3\""); goto leave; } certref = certref_buffer; } if (*argstr == '>') /* Write it to a file */ { for (argstr++; spacep (argstr); argstr++) ; fname = argstr; } else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } dataoff = 0; err = scd_readcert (certref, &data, &datalen); if (err) goto leave; if (opt_openpgp) { /* Check whether DATA contains an OpenPGP keyblock and put only * this into FILE. If the data is something different, return * an error. */ const unsigned char *p; size_t n, objlen, hdrlen; int class, tag, cons, ndef; p = data; n = datalen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons)) goto not_openpgp; /* Does not start with a sequence. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons)) goto not_openpgp; /* No Object ID. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ if (objlen != 10 || memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen)) goto not_openpgp; /* Wrong Object ID. */ p += objlen; n -= objlen; if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_CONTEXT && tag == 0 && cons)) goto not_openpgp; /* Not a [0] context tag. */ if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen)) goto not_openpgp; if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons)) goto not_openpgp; /* Not an octet string. */ if (objlen > n) goto not_openpgp; /* Inconsistent lengths. */ dataoff = p - (const unsigned char*)data; datalen = objlen; } err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen); goto leave; not_openpgp: err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); leave: xfree (data); xfree (certref_buffer); return err; } static gpg_error_t cmd_writekey (card_info_t info, char *argstr) { gpg_error_t err; int opt_force; const char *argv[2]; int argc; char *keyref_buffer = NULL; const char *keyref; const char *keygrip; if (!info) return print_help ("WRITEKEY [--force] KEYREF KEYGRIP\n\n" "Write a private key object identified by KEYGRIP to slot KEYREF.\n" "Use --force to overwrite an existing key.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); opt_force = has_leading_option (argstr, "--force"); argstr = skip_options (argstr); argc = split_fields (argstr, argv, DIM (argv)); if (argc < 2) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } /* Upcase the keyref; prepend cardtype if needed. */ keyref = argv[0]; if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; /* Get the keygrip. */ keygrip = argv[1]; if (strlen (keygrip) != 40 && !(keygrip[0] == '&' && strlen (keygrip+1) == 40)) { log_error (_("Not a valid keygrip (expecting 40 hex digits)\n")); err = gpg_error (GPG_ERR_INV_ARG); goto leave; } err = scd_writekey (keyref, opt_force, keygrip); leave: xfree (keyref_buffer); return err; } static gpg_error_t cmd_forcesig (card_info_t info) { gpg_error_t err; int newstate; if (!info) return print_help ("FORCESIG\n\n" "Toggle the forcesig flag of an OpenPGP card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } newstate = !info->chv1_cached; err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1); if (err) goto leave; /* Read it back to be sure we have the right toggle state the next * time. */ err = scd_getattr ("CHV-STATUS", info); leave: return err; } /* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at * FORCED_CHV1. */ static gpg_error_t check_pin_for_key_operation (card_info_t info, int *forced_chv1) { gpg_error_t err = 0; *forced_chv1 = !info->chv1_cached; if (*forced_chv1) { /* Switch off the forced mode so that during key generation we * don't get bothered with PIN queries for each self-signature. */ err = scd_setattr ("CHV-STATUS-1", "\x01", 1); if (err) { log_error ("error clearing forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = -1; /* Not changed. */ goto leave; } } /* Check the PIN now, so that we won't get asked later for each * binding signature. */ err = scd_checkpin (info->serialno); if (err) log_error ("error checking the PIN: %s\n", gpg_strerror (err)); leave: return err; } /* Helper for cmd_generate_openpgp. */ static void restore_forced_chv1 (int *forced_chv1) { gpg_error_t err; /* Note the possible values stored at FORCED_CHV1: * 0 - forcesig was not enabled. * 1 - forcesig was enabled - enable it again. * -1 - We have not changed anything. */ if (*forced_chv1 == 1) { /* Switch back to forced state. */ err = scd_setattr ("CHV-STATUS-1", "", 1); if (err) log_error ("error setting forced signature PIN flag: %s\n", gpg_strerror (err)); *forced_chv1 = 0; } } /* Ask whether existing keys shall be overwritten. With NULL used for * KINFO it will ask for all keys, other wise for the given key. */ static gpg_error_t ask_replace_keys (key_info_t kinfo) { gpg_error_t err; char *answer; tty_printf ("\n"); if (kinfo) log_info (_("Note: key %s is already stored on the card!\n"), kinfo->keyref); else log_info (_("Note: Keys are already stored on the card!\n")); tty_printf ("\n"); if (kinfo) answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref); else answer = tty_get (_("Replace existing keys? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; xfree (answer); return err; } /* Implementation of cmd_generate for OpenPGP cards to generate all * standard keys at once. */ static gpg_error_t generate_all_openpgp_card_keys (card_info_t info, char **algos) { gpg_error_t err; int forced_chv1 = -1; int want_backup; char *answer = NULL; key_info_t kinfo1, kinfo2, kinfo3; if (info->extcap.ki) { xfree (answer); answer = tty_get (_("Make off-card backup of encryption key? (Y/n) ")); want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/); tty_kill_prompt (); if (*answer == CONTROL_D) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } } else want_backup = 0; kinfo1 = find_kinfo (info, "OPENPGP.1"); kinfo2 = find_kinfo (info, "OPENPGP.2"); kinfo3 = find_kinfo (info, "OPENPGP.3"); if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen)) || (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen)) || (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen)) ) { err = ask_replace_keys (NULL); if (err) goto leave; } /* If no displayed name has been set, we assume that this is a fresh * card and print a hint about the default PINs. */ if (!info->disp_name || !*info->disp_name) { tty_printf ("\n"); tty_printf (_("Please note that the factory settings of the PINs are\n" " PIN = '%s' Admin PIN = '%s'\n" "You should change them using the command --change-pin\n"), OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT); tty_printf ("\n"); } err = check_pin_for_key_operation (info, &forced_chv1); if (err) goto leave; (void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */ /* FIXME: We need to divert to a function which spawns gpg which * will then create the key. This also requires new features in * gpg. We might also first create the keys on the card and then * tell gpg to use them to create the OpenPGP keyblock. */ /* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */ (void)want_backup; err = scd_genkey ("OPENPGP.1", 1, NULL, NULL); leave: restore_forced_chv1 (&forced_chv1); xfree (answer); return err; } /* Create a single key. This is a helper for cmd_generate. */ static gpg_error_t generate_key (card_info_t info, const char *keyref, int force, const char *algo) { gpg_error_t err; key_info_t kinfo; if (info->apptype == APP_TYPE_OPENPGP) { kinfo = find_kinfo (info, keyref); if (!kinfo) { err = gpg_error (GPG_ERR_INV_ID); goto leave; } if (!force && kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen)) { err = ask_replace_keys (NULL); if (err) goto leave; force = 1; } } err = scd_genkey (keyref, force, algo, NULL); leave: return err; } static gpg_error_t cmd_generate (card_info_t info, char *argstr) { static char * const valid_algos[] = { "rsa2048", "rsa3072", "rsa4096", "", "nistp256", "nistp384", "nistp521", "", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "", "ed25519", "cv25519", NULL }; gpg_error_t err; int opt_force; char *p; char **opt_algo = NULL; /* Malloced. */ char *keyref_buffer = NULL; /* Malloced. */ char *keyref; /* Points into argstr or keyref_buffer. */ int i, j; if (!info) return print_help ("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n" "Create a new key on a card.\n" "Use --force to overwrite an existing key.\n" "Use \"help\" for ALGO to get a list of known algorithms.\n" "For OpenPGP cards several algos may be given.\n" "Note that the OpenPGP key generation is done interactively\n" "unless a single ALGO or KEYREF are given.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); opt_force = has_leading_option (argstr, "--force"); err = get_option_value (argstr, "--algo", &p); if (err) goto leave; if (p) { opt_algo = strtokenize (p, "+"); if (!opt_algo) { err = gpg_error_from_syserror (); xfree (p); goto leave; } xfree (p); } argstr = skip_options (argstr); keyref = argstr; if ((argstr = strchr (keyref, ' '))) { *argstr++ = 0; trim_spaces (keyref); trim_spaces (argstr); } else /* Let argstr point to an empty string. */ argstr = keyref + strlen (keyref); if (!*keyref) keyref = NULL; if (*argstr) { /* Extra arguments found. */ err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if (opt_algo) { /* opt_algo is an array of algos. */ for (i=0; opt_algo[i]; i++) { for (j=0; valid_algos[j]; j++) if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i])) break; if (!valid_algos[j]) { int lf = 1; if (!ascii_strcasecmp (opt_algo[i], "help")) log_info ("Known algorithms:\n"); else { log_info ("Invalid algorithm '%s' given. Use one of:\n", opt_algo[i]); err = gpg_error (GPG_ERR_PUBKEY_ALGO); } for (i=0; valid_algos[i]; i++) { if (!*valid_algos[i]) lf = 1; else if (lf) { lf = 0; log_info (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } else log_printf (" %s%s", valid_algos[i], valid_algos[i+1]?",":"."); } log_printf ("\n"); show_keysize_warning (); goto leave; } } } /* Upcase the keyref; if it misses the cardtype, prepend it. */ if (keyref) { if (!strchr (keyref, '.')) keyref_buffer = xstrconcat (app_type_string (info->apptype), ".", keyref, NULL); else keyref_buffer = xstrdup (keyref); ascii_strupr (keyref_buffer); keyref = keyref_buffer; } /* Special checks. */ if ((info->cardtype && !strcmp (info->cardtype, "yubikey")) && info->cardversion >= 0x040200 && info->cardversion < 0x040305) { log_error ("On-chip key generation on this YubiKey has been blocked.\n"); log_info ("Please see <https://yubi.co/ysa201701> for details\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } /* Divert to dedicated functions. */ if (info->apptype == APP_TYPE_OPENPGP && !keyref && (!opt_algo || (opt_algo[0] && opt_algo[1]))) { /* With no algo requested or more than one algo requested and no * keyref given we create all keys. */ if (opt_force || keyref) log_info ("Note: OpenPGP key generation is interactive.\n"); err = generate_all_openpgp_card_keys (info, opt_algo); } else if (!keyref) err = gpg_error (GPG_ERR_INV_ID); else if (opt_algo && opt_algo[0] && opt_algo[1]) { log_error ("only one algorithm expected as value for --algo.\n"); err = gpg_error (GPG_ERR_INV_ARG); } else err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL); if (!err) { err = scd_learn (info, 0); if (err) log_error ("Error re-reading card: %s\n", gpg_strerror (err)); } leave: xfree (opt_algo); xfree (keyref_buffer); return err; } /* Change a PIN. */ static gpg_error_t cmd_passwd (card_info_t info, char *argstr) { gpg_error_t err = 0; char *answer = NULL; const char *pinref = NULL; int reset_mode = 0; int nullpin = 0; int menu_used = 0; if (!info) return print_help ("PASSWD [--reset|--nullpin] [PINREF]\n\n" "Change or unblock the PINs. Note that in interactive mode\n" "and without a PINREF a menu is presented for certain cards;\n" "in non-interactive and without a PINREF a default value is\n" "used for these cards. The option --reset is used with TCOS\n" "cards to reset the PIN using the PUK or vice versa; --nullpin\n" "is used for these cards to set the initial PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (has_option (argstr, "--reset")) reset_mode = 1; else if (has_option (argstr, "--nullpin")) nullpin = 1; argstr = skip_options (argstr); /* If --reset or --nullpin has been given we force non-interactive mode. */ if (*argstr || reset_mode || nullpin) { pinref = argstr; if (!*pinref) { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } } else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - unblock and set new a PIN\n" "3 - change the Admin PIN\n" "4 - set the Reset Code\n" "Q - quit\n"); if (strlen (answer) != 1) continue; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "OPENPGP.1"; else if (*answer == '2') { pinref = "OPENPGP.1"; reset_mode = 1; } else if (*answer == '3') pinref = "OPENPGP.3"; else if (*answer == '4') { pinref = "OPENPGP.2"; reset_mode = 1; } } } else if (info->apptype == APP_TYPE_OPENPGP) pinref = "OPENPGP.1"; else if (opt.interactive && info->apptype == APP_TYPE_PIV) { menu_used = 1; while (!pinref) { xfree (answer); answer = get_selection ("1 - change the PIN\n" "2 - change the PUK\n" "3 - change the Global PIN\n" "Q - quit\n"); if (strlen (answer) != 1) ; else if (*answer == 'q' || *answer == 'Q') goto leave; else if (*answer == '1') pinref = "PIV.80"; else if (*answer == '2') pinref = "PIV.81"; else if (*answer == '3') pinref = "PIV.00"; } } else if (opt.interactive && info->apptype == APP_TYPE_NKS) { int for_qualified = 0; menu_used = 1; log_assert (DIM (info->chvinfo) >= 4); /* If there is a qualified signature use a menu to select * between standard PIN and QES PINs. */ if (info->chvinfo[2] != -2 || info->chvinfo[3] != -2) { for (;;) { xfree (answer); answer = get_selection (" 1 - Standard PIN/PUK\n" " 2 - PIN/PUK for qualified signature\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) break; else if (!strcmp (answer, "2")) { for_qualified = 1; break; } } } if (info->chvinfo[for_qualified? 2 : 0] == -4) { while (!pinref) { xfree (answer); answer = get_selection ("The NullPIN is still active on this card.\n" "You need to choose and set a PIN first.\n" "\n" " 1 - Set your PIN\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; nullpin = 1; } } } else { while (!pinref) { xfree (answer); answer = get_selection (" 1 - change PIN\n" " 2 - reset PIN\n" " 3 - change PUK\n" " 4 - reset PUK\n" " Q - quit\n"); if (!ascii_strcasecmp (answer, "q")) goto leave; else if (!strcmp (answer, "1")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; } else if (!strcmp (answer, "2")) { pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH"; reset_mode = 1; } else if (!strcmp (answer, "3")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; } else if (!strcmp (answer, "4")) { pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH"; reset_mode = 1; } } } } else if (info->apptype == APP_TYPE_PIV) pinref = "PIV.80"; else { err = gpg_error (GPG_ERR_MISSING_VALUE); goto leave; } err = scd_change_pin (pinref, reset_mode, nullpin); if (err) { if (!opt.interactive && !menu_used && !opt.verbose) ; else if (gpg_err_code (err) == GPG_ERR_CANCELED && gpg_err_source (err) == GPG_ERR_SOURCE_PINENTRY) log_info ("%s\n", gpg_strerror (err)); else if (!ascii_strcasecmp (pinref, "PIV.81")) log_error ("Error changing the PUK.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_error ("Error unblocking the PIN.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_error ("Error setting the Reset Code.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_error ("Error changing the Admin PIN.\n"); else if (reset_mode) log_error ("Error resetting the PIN.\n"); else log_error ("Error changing the PIN.\n"); } else { if (!opt.interactive && !opt.verbose) ; else if (!ascii_strcasecmp (pinref, "PIV.81")) log_info ("PUK changed.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode) log_info ("PIN unblocked and new PIN set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode) log_info ("Reset Code set.\n"); else if (!ascii_strcasecmp (pinref, "OPENPGP.3")) log_info ("Admin PIN changed.\n"); else if (reset_mode) log_info ("PIN reset.\n"); else log_info ("PIN changed.\n"); /* Update the CHV status. */ err = scd_getattr ("CHV-STATUS", info); } leave: xfree (answer); return err; } static gpg_error_t cmd_unblock (card_info_t info) { gpg_error_t err = 0; if (!info) return print_help ("UNBLOCK\n\n" "Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n" "cards prior to version 2 can't use this; instead the PASSWD\n" "command can be used to set a new PIN.", 0); if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (info->apptype == APP_TYPE_OPENPGP) { if (!info->is_v2) { log_error (_("This command is only available for version 2 cards\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); } else if (!info->chvinfo[1]) { log_error (_("Reset Code not or not anymore available\n")); err = gpg_error (GPG_ERR_NO_RESET_CODE); } else { err = scd_change_pin ("OPENPGP.2", 0, 0); if (!err) log_info ("PIN changed.\n"); } } else if (info->apptype == APP_TYPE_PIV) { /* Unblock the Application PIN. */ err = scd_change_pin ("PIV.80", 1, 0); if (!err) log_info ("PIN unblocked and changed.\n"); } else { log_info ("Unblocking not supported for '%s'.\n", app_type_string (info->apptype)); err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } return err; } /* Note: On successful execution a redisplay should be scheduled. If * this function fails the card may be in an unknown state. */ static gpg_error_t cmd_factoryreset (card_info_t info) { gpg_error_t err; char *answer = NULL; int termstate = 0; int any_apdu = 0; int is_yubikey = 0; int locked = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" "Do a complete reset of some OpenPGP and PIV cards. This\n" "deletes all data and keys and resets the PINs to their default.\n" "This is mainly used by developers with scratch cards. Don't\n" "worry, you need to confirm before the command proceeds.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); /* We support the factory reset for most OpenPGP cards and Yubikeys * with the PIV application. */ if (info->apptype == APP_TYPE_OPENPGP) ; else if (info->apptype == APP_TYPE_PIV && info->cardtype && !strcmp (info->cardtype, "yubikey")) is_yubikey = 1; else return gpg_error (GPG_ERR_NOT_SUPPORTED); /* For an OpenPGP card the code below basically does the same what * this gpg-connect-agent script does: * * scd reset * scd serialno undefined * scd apdu 00 A4 04 00 06 D2 76 00 01 24 01 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40 * scd apdu 00 e6 00 00 * scd apdu 00 44 00 00 * scd reset * /echo Card has been reset to factory defaults * * For a PIV application on a Yubikey it merely issues the Yubikey * specific resset command. */ err = scd_learn (info, 0); if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE && gpg_err_source (err) == GPG_ERR_SOURCE_SCD) termstate = 1; else if (err) { log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err)); goto leave; } if (opt.interactive || opt.verbose) log_info (_("%s card no. %s detected\n"), app_type_string (info->apptype), info->dispserialno? info->dispserialno : info->serialno); if (!termstate || is_yubikey) { if (!is_yubikey) { if (!(info->status_indicator == 3 || info->status_indicator == 5)) { /* Note: We won't see status-indicator 3 here because it * is not possible to select a card application in * termination state. */ log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } } tty_printf ("\n"); log_info (_("Note: This command destroys all keys stored on the card!\n")); tty_printf ("\n"); xfree (answer); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); trim_spaces (answer); if (*answer == CONTROL_D || !answer_is_yes_no_default (answer, 0/*(default to no)*/)) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } xfree (answer); answer = tty_get (_("Really do a factory reset? (enter \"yes\") ")); tty_kill_prompt (); trim_spaces (answer); if (strcmp (answer, "yes") && strcmp (answer,_("yes"))) { err = gpg_error (GPG_ERR_CANCELED); goto leave; } if (is_yubikey) { /* If the PIV application is already selected, we only need to * send the special reset APDU after having blocked PIN and * PUK. Note that blocking the PUK is done using the * unblock PIN command. */ any_apdu = 1; for (i=0; i < 5; i++) send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 5; i++) send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "RESET RETRY COUNTER", 0xffff, NULL, NULL); err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL); if (err) goto leave; } else /* OpenPGP card. */ { any_apdu = 1; /* We need to select a card application before we can send * APDUs to the card without scdaemon doing anything on its * own. We then lock the connection so that other tools * (e.g. Kleopatra) don't try a new select. */ err = send_apdu ("lock", "locking connection ", 0, NULL, NULL); if (err) goto leave; locked = 1; err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); if (err) goto leave; err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL); if (err) goto leave; /* Select the OpenPGP application. */ err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0, NULL, NULL); if (err) goto leave; /* Do some dummy verifies with wrong PINs to set the retry * counter to zero. We can't easily use the card version 2.1 * feature of presenting the admin PIN to allow the terminate * command because there is no machinery in scdaemon to catch * the verify command and ask for the PIN when the "APDU" * command is used. * Here, the length of dummy wrong PIN is 32-byte, also * supporting authentication with KDF DO. */ for (i=0; i < 4; i++) send_apdu ("0020008120" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); for (i=0; i < 4; i++) send_apdu ("0020008320" "40404040404040404040404040404040" "40404040404040404040404040404040", "VERIFY", 0xffff, NULL, NULL); /* Send terminate datafile command. */ err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL); if (err) goto leave; } } if (!is_yubikey) { any_apdu = 1; /* Send activate datafile command. This is used without * confirmation if the card is already in termination state. */ err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL); if (err) goto leave; } /* Finally we reset the card reader once more. */ if (locked) err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL); else err = send_apdu (NULL, "RESET", 0, NULL, NULL); if (err) goto leave; /* Then, connect the card again. */ err = scd_serialno (NULL, NULL); if (!err) info->need_sn_cmd = 0; leave: if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n"); /* FIXME: We need a better solution in the case that the card is * in a termination state, i.e. the card was removed before the * activate was sent. The best solution I found with v2.1 * Zeitcontrol card was to kill scdaemon and the issue this * sequence with gpg-connect-agent: * scd reset * scd serialno undefined * scd apdu 00A4040006D27600012401 (returns error) * scd apdu 00440000 * Then kill scdaemon again and issue: * scd reset * scd serialno openpgp */ } if (locked) send_apdu ("unlock", "unlocking connection ", 0, NULL, NULL); xfree (answer); return err; } /* Generate KDF data. This is a helper for cmd_kdfsetup. */ static gpg_error_t gen_kdf_data (unsigned char *data, int single_salt) { gpg_error_t err; const unsigned char h0[] = { 0x81, 0x01, 0x03, 0x82, 0x01, 0x08, 0x83, 0x04 }; const unsigned char h1[] = { 0x84, 0x08 }; const unsigned char h2[] = { 0x85, 0x08 }; const unsigned char h3[] = { 0x86, 0x08 }; const unsigned char h4[] = { 0x87, 0x20 }; const unsigned char h5[] = { 0x88, 0x20 }; unsigned char *p, *salt_user, *salt_admin; unsigned char s2k_char; unsigned int iterations; unsigned char count_4byte[4]; p = data; s2k_char = encode_s2k_iterations (agent_get_s2k_count ()); iterations = S2K_DECODE_COUNT (s2k_char); count_4byte[0] = (iterations >> 24) & 0xff; count_4byte[1] = (iterations >> 16) & 0xff; count_4byte[2] = (iterations >> 8) & 0xff; count_4byte[3] = (iterations & 0xff); memcpy (p, h0, sizeof h0); p += sizeof h0; memcpy (p, count_4byte, sizeof count_4byte); p += sizeof count_4byte; memcpy (p, h1, sizeof h1); salt_user = (p += sizeof h1); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; if (single_salt) salt_admin = salt_user; else { memcpy (p, h2, sizeof h2); p += sizeof h2; gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; memcpy (p, h3, sizeof h3); salt_admin = (p += sizeof h3); gcry_randomize (p, 8, GCRY_STRONG_RANDOM); p += 8; } memcpy (p, h4, sizeof h4); p += sizeof h4; err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT, strlen (OPENPGP_USER_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_user, 8, iterations, 32, p); p += 32; if (!err) { memcpy (p, h5, sizeof h5); p += sizeof h5; err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT, strlen (OPENPGP_ADMIN_PIN_DEFAULT), GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, salt_admin, 8, iterations, 32, p); } return err; } static gpg_error_t cmd_kdfsetup (card_info_t info, char *argstr) { gpg_error_t err; unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX]; int single = (*argstr != 0); if (!info) return print_help ("KDF-SETUP\n\n" "Prepare the OpenPGP card KDF feature for this card.", APP_TYPE_OPENPGP, 0); if (info->apptype != APP_TYPE_OPENPGP) { log_info ("Note: This is an OpenPGP only command.\n"); return gpg_error (GPG_ERR_NOT_SUPPORTED); } if (!info->extcap.kdf) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gen_kdf_data (kdf_data, single); if (err) goto leave; err = scd_setattr ("KDF", kdf_data, single ? OPENPGP_KDF_DATA_LENGTH_MIN /* */ : OPENPGP_KDF_DATA_LENGTH_MAX); if (err) goto leave; err = scd_getattr ("KDF", info); leave: return err; } static void show_keysize_warning (void) { static int shown; if (shown) return; shown = 1; tty_printf (_("Note: There is no guarantee that the card supports the requested\n" " key type or size. If the key generation does not succeed,\n" " please check the documentation of your card to see which\n" " key types and sizes are supported.\n") ); } static gpg_error_t cmd_uif (card_info_t info, char *argstr) { gpg_error_t err; int keyno; char name[50]; unsigned char data[2]; char *answer = NULL; int opt_yes; if (!info) return print_help ("UIF N [on|off|permanent]\n\n" "Change the User Interaction Flag. N must in the range 1 to 3.", APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); if (!info->extcap.bt) { log_error (_("This command is not supported by this card\n")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } opt_yes = has_leading_option (argstr, "--yes"); argstr = skip_options (argstr); if (digitp (argstr)) { keyno = atoi (argstr); while (digitp (argstr)) argstr++; while (spacep (argstr)) argstr++; } else keyno = 0; if (keyno < 1 || keyno > 3) { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } if ( !strcmp (argstr, "off") ) data[0] = 0x00; else if ( !strcmp (argstr, "on") ) data[0] = 0x01; else if ( !strcmp (argstr, "permanent") ) data[0] = 0x02; else { err = gpg_error (GPG_ERR_INV_ARG); goto leave; } data[1] = 0x20; log_assert (keyno - 1 < DIM(info->uif)); if (info->uif[keyno-1] == 2) { log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"), "permanent"); err = gpg_error (GPG_ERR_INV_STATE); goto leave; } if (data[0] == 0x02) { if (opt.interactive) { tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); answer = tty_get (_("Continue? (y/N) ")); tty_kill_prompt (); if (*answer == CONTROL_D) err = gpg_error (GPG_ERR_CANCELED); else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/)) err = gpg_error (GPG_ERR_CANCELED); else err = 0; } else if (!opt_yes) { log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n" " can only be reverted using a factory reset!\n" ), "permanent"); log_info (_("Please use \"uif --yes %d %s\"\n"), keyno, "permanent"); err = gpg_error (GPG_ERR_CANCELED); } else err = 0; if (err) goto leave; } snprintf (name, sizeof name, "UIF-%d", keyno); err = scd_setattr (name, data, 2); if (!err) /* Read all UIF attributes again. */ err = scd_getattr ("UIF", info); leave: xfree (answer); return err; } static gpg_error_t cmd_yubikey (card_info_t info, char *argstr) { gpg_error_t err, err2; estream_t fp = opt.interactive? NULL : es_stdout; const char *words[20]; int nwords; if (!info) return print_help ("YUBIKEY <cmd> args\n\n" "Various commands pertaining to Yubikey tokens with <cmd> being:\n" "\n" " LIST \n" "\n" "List supported and enabled applications.\n" "\n" " ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" " DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n" "\n" "Enable or disable the specified or all applications on the\n" "given interface.", 0); argstr = skip_options (argstr); if (!info->cardtype || strcmp (info->cardtype, "yubikey")) { log_info ("This command can only be used with Yubikeys.\n"); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } nwords = split_fields (argstr, words, DIM (words)); if (nwords < 1) { err = gpg_error (GPG_ERR_SYNTAX); goto leave; } /* Note that we always do a learn to get a chance to the card back * into a usable state. */ err = yubikey_commands (info, fp, nwords, words); err2 = scd_learn (info, 0); if (err2) log_error ("Error re-reading card: %s\n", gpg_strerror (err2)); leave: return err; } static gpg_error_t cmd_apdu (card_info_t info, char *argstr) { gpg_error_t err; estream_t fp = opt.interactive? NULL : es_stdout; int with_atr; int handle_more; const char *s; const char *exlenstr; int exlenstrlen; char *options = NULL; unsigned int sw; unsigned char *result = NULL; size_t i, j, resultlen; if (!info) return print_help ("APDU [--more] [--exlen[=N]] <hexstring>\n" "\n" "Send an APDU to the current card. This command bypasses the high\n" "level functions and sends the data directly to the card. HEXSTRING\n" "is expected to be a proper APDU.\n" "\n" "Using the option \"--more\" handles the card status word MORE_DATA\n" "(61xx) and concatenates all responses to one block.\n" "\n" "Using the option \"--exlen\" the returned APDU may use extended\n" "length up to N bytes. If N is not given a default value is used.\n", 0); if (has_option (argstr, "--dump-atr")) with_atr = 2; else with_atr = has_option (argstr, "--atr"); handle_more = has_option (argstr, "--more"); exlenstr = has_option_name (argstr, "--exlen"); exlenstrlen = 0; if (exlenstr) { for (s=exlenstr; *s && !spacep (s); s++) exlenstrlen++; } argstr = skip_options (argstr); if (with_atr || handle_more || exlenstr) options = xasprintf ("%s%s%s%.*s", with_atr == 2? " --dump-atr": with_atr? " --data-atr":"", handle_more?" --more":"", exlenstr?" --exlen=":"", exlenstrlen, exlenstr?exlenstr:""); err = scd_apdu (argstr, options, &sw, &result, &resultlen); if (err) goto leave; if (!with_atr) { if (opt.interactive || opt.verbose) { char *p = scd_apdu_strerror (sw); log_info ("Statusword: 0x%04x (%s)\n", sw, p? p: "?"); xfree (p); } else log_info ("Statusword: 0x%04x\n", sw); } for (i=0; i < resultlen; ) { size_t save_i = i; tty_fprintf (fp, "D[%04X] ", (unsigned int)i); for (j=0; j < 16 ; j++, i++) { if (j == 8) tty_fprintf (fp, " "); if (i < resultlen) tty_fprintf (fp, " %02X", result[i]); else tty_fprintf (fp, " "); } tty_fprintf (fp, " "); i = save_i; for (j=0; j < 16; j++, i++) { unsigned int c = result[i]; if ( i >= resultlen ) tty_fprintf (fp, " "); else if (isascii (c) && isprint (c) && !iscntrl (c)) tty_fprintf (fp, "%c", c); else tty_fprintf (fp, "."); } tty_fprintf (fp, "\n"); } leave: xfree (result); xfree (options); return err; } static gpg_error_t cmd_gpg (card_info_t info, char *argstr, int use_gpgsm) { gpg_error_t err; char **argarray; ccparray_t ccp; const char **argv = NULL; gnupg_process_t proc; int i; if (!info) return print_help ("GPG[SM] <commands_and_options>\n" "\n" "Run gpg/gpgsm directly from this shell.\n", 0); /* Fixme: We need to write and use a version of strtokenize which * takes care of shell-style quoting. */ argarray = strtokenize (argstr, " \t\n\v"); if (!argarray) { err = gpg_error_from_syserror (); goto leave; } ccparray_init (&ccp, 0); for (i=0; argarray[i]; i++) ccparray_put (&ccp, argarray[i]); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } err = gnupg_process_spawn (use_gpgsm? opt.gpgsm_program:opt.gpg_program, argv, (GNUPG_PROCESS_STDOUT_KEEP | GNUPG_PROCESS_STDERR_KEEP), - NULL, NULL, &proc); + NULL, &proc); if (!err) { err = gnupg_process_wait (proc, 1); gnupg_process_release (proc); } leave: xfree (argv); xfree (argarray); return err; } static gpg_error_t cmd_history (card_info_t info, char *argstr) { int opt_list, opt_clear; opt_list = has_option (argstr, "--list"); opt_clear = has_option (argstr, "--clear"); if (!info || !(opt_list || opt_clear)) return print_help ("HISTORY --list\n" " List the command history\n" "HISTORY --clear\n" " Clear the command history", 0); if (opt_list) tty_printf ("Sorry, history listing not yet possible\n"); if (opt_clear) tty_read_history (NULL, 0); return 0; } /* Data used by the command parser. This needs to be outside of the * function scope to allow readline based command completion. */ enum cmdids { cmdNOP = 0, cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY, cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR, cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT, cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP, cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY, cmdCHECKKEYS, cmdINVCMD }; static struct { const char *name; enum cmdids id; const char *desc; } cmds[] = { { "quit" , cmdQUIT, N_("quit this menu")}, { "q" , cmdQUIT, NULL }, { "bye" , cmdQUIT, NULL }, { "help" , cmdHELP, N_("show this help")}, { "?" , cmdHELP, NULL }, { "list" , cmdLIST, N_("list all available data")}, { "l" , cmdLIST, NULL }, { "name" , cmdNAME, N_("change card holder's name")}, { "url" , cmdURL, N_("change URL to retrieve key")}, { "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")}, { "login" , cmdLOGIN, N_("change the login name")}, { "lang" , cmdLANG, N_("change the language preferences")}, { "salutation",cmdSALUT, N_("change card holder's salutation")}, { "salut" , cmdSALUT, NULL }, { "cafpr" , cmdCAFPR , N_("change a CA fingerprint")}, { "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")}, { "generate", cmdGENERATE, N_("generate new keys")}, { "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")}, { "verify" , cmdVERIFY, N_("verify the PIN and list all data")}, { "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")}, { "authenticate",cmdAUTH, N_("authenticate to the card")}, { "auth" , cmdAUTH, NULL }, { "reset" , cmdRESET, N_("send a reset to the card daemon")}, { "factory-reset",cmdFACTRST, N_("destroy all keys and data")}, { "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")}, { "uif", cmdUIF, N_("change the User Interaction Flag")}, { "privatedo", cmdPRIVATEDO, N_("change a private data object")}, { "readcert", cmdREADCERT, N_("read a certificate from a data object")}, { "writecert", cmdWRITECERT, N_("store a certificate to a data object")}, { "writekey", cmdWRITEKEY, N_("store a private key to a data object")}, { "checkkeys", cmdCHECKKEYS, N_("run various checks on the keys")}, { "yubikey", cmdYUBIKEY, N_("Yubikey management commands")}, { "gpg", cmdGPG, NULL}, { "gpgsm", cmdGPGSM, NULL}, { "apdu", cmdAPDU, NULL}, { "history", cmdHISTORY, N_("manage the command history")}, { NULL, cmdINVCMD, NULL } }; /* The command line command dispatcher. */ static gpg_error_t dispatch_command (card_info_t info, const char *orig_command) { gpg_error_t err = 0; enum cmdids cmd; /* The command. */ char *command; /* A malloced copy of ORIG_COMMAND. */ char *argstr; /* The argument as a string. */ int i; int ignore_error; if ((ignore_error = *orig_command == '-')) orig_command++; command = xstrdup (orig_command); argstr = NULL; if ((argstr = strchr (command, ' '))) { *argstr++ = 0; trim_spaces (command); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (command, cmds[i].name )) break; cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */ /* Make sure we have valid strings for the args. They are allowed * to be modified and must thus point to a buffer. */ if (!argstr) argstr = command + strlen (command); /* For most commands we need to make sure that we have a card. */ if (!info) ; /* Help mode */ else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdINVCMD) && !info->initialized) { err = scd_learn (info, 0); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); goto leave; } } if (info) info->card_removed = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Stop processing.", 0); else { err = gpg_error (GPG_ERR_EOF); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) dispatch_command (NULL, argstr); else { es_printf ("List of commands (\"help <command>\" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); es_printf ("Prefix a command with a dash to ignore its error.\n"); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */ case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break; case cmdINVCMD: default: log_error (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ leave: /* Return GPG_ERR_EOF only if its origin was "quit". */ es_fflush (es_stdout); if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT) err = gpg_error (GPG_ERR_GENERAL); if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (err && gpg_err_code (err) != GPG_ERR_EOF) { err = fixup_scd_errors (err); if (ignore_error) { log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err)); err = 0; } else { log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } xfree (command); return err; } /* The interactive main loop. */ static void interactive_loop (void) { gpg_error_t err; char *answer = NULL; /* The input line. */ enum cmdids cmd = cmdNOP; /* The command. */ char *argstr; /* The argument as a string. */ int redisplay = 1; /* Whether to redisplay the main info. */ char *help_arg = NULL; /* Argument of the HELP command. */ struct card_info_s info_buffer = { 0 }; card_info_t info = &info_buffer; char *p; int i; char *historyname = NULL; /* In the interactive mode we do not want to print the program prefix. */ log_set_prefix (NULL, 0); if (!opt.no_history) { historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL); if (tty_read_history (historyname, 500)) log_info ("error reading '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); } for (;;) { if (help_arg) { /* Clear info to indicate helpmode */ info = NULL; } else if (!info) { /* Get out of help. */ info = &info_buffer; help_arg = NULL; redisplay = 0; } else if (redisplay) { err = cmd_list (info, ""); if (err) { err = fixup_scd_errors (err); log_error ("Error reading card: %s\n", gpg_strerror (err)); } else { tty_printf("\n"); redisplay = 0; } } if (!info) { /* Copy the pending help arg into our answer. Note that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; xfree (answer); answer = p; } else { do { xfree (answer); tty_enable_completion (command_completion); answer = tty_get (_("gpg/card> ")); tty_kill_prompt(); tty_disable_completion (); trim_spaces(answer); } while ( *answer == '#' ); } argstr = NULL; if (!*answer) cmd = cmdLIST; /* We default to the list command */ else if (*answer == CONTROL_D) cmd = cmdQUIT; else { if ((argstr = strchr (answer,' '))) { *argstr++ = 0; trim_spaces (answer); trim_spaces (argstr); } for (i=0; cmds[i].name; i++ ) if (!ascii_strcasecmp (answer, cmds[i].name )) break; cmd = cmds[i].id; } /* Make sure we have valid strings for the args. They are * allowed to be modified and must thus point to a buffer. */ if (!argstr) argstr = answer + strlen (answer); if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP || cmd == cmdHISTORY || cmd == cmdINVCMD)) { /* If redisplay is set we know that there was an error reading * the card. In this case we force a LIST command to retry. */ if (!info) ; /* In help mode. */ else if (redisplay) { cmd = cmdLIST; } else if (!info->serialno) { /* Without a serial number most commands won't work. * Catch it here. */ if (cmd == cmdRESET || cmd == cmdLIST) info->need_sn_cmd = 1; else { tty_printf ("\n"); tty_printf ("Serial number missing\n"); continue; } } } if (info) info->card_removed = 0; err = 0; switch (cmd) { case cmdNOP: if (!info) print_help ("NOP\n\n" "Dummy command.", 0); break; case cmdQUIT: if (!info) print_help ("QUIT\n\n" "Leave this tool.", 0); else { tty_printf ("\n"); goto leave; } break; case cmdHELP: if (!info) print_help ("HELP [command]\n\n" "Show all commands. With an argument show help\n" "for that command.", 0); else if (*argstr) help_arg = argstr; /* Trigger help for a command. */ else { tty_printf ("List of commands (\"help <command>\" for details):\n"); for (i=0; cmds[i].name; i++ ) if(cmds[i].desc) tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) ); } break; case cmdRESET: if (!info) print_help ("RESET\n\n" "Send a RESET to the card daemon.", 0); else { flush_keyblock_cache (); err = scd_apdu (NULL, NULL, NULL, NULL, NULL); if (!err) info->need_sn_cmd = 1; } break; case cmdLIST: err = cmd_list (info, argstr); break; case cmdVERIFY: err = cmd_verify (info, argstr); if (!err) redisplay = 1; break; case cmdAUTH: err = cmd_authenticate (info, argstr); break; case cmdNAME: err = cmd_name (info, argstr); break; case cmdURL: err = cmd_url (info, argstr); break; case cmdFETCH: err = cmd_fetch (info); break; case cmdLOGIN: err = cmd_login (info, argstr); break; case cmdLANG: err = cmd_lang (info, argstr); break; case cmdSALUT: err = cmd_salut (info, argstr); break; case cmdCAFPR: err = cmd_cafpr (info, argstr); break; case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break; case cmdWRITECERT: err = cmd_writecert (info, argstr); break; case cmdREADCERT: err = cmd_readcert (info, argstr); break; case cmdWRITEKEY: err = cmd_writekey (info, argstr); break; case cmdFORCESIG: err = cmd_forcesig (info); break; case cmdGENERATE: err = cmd_generate (info, argstr); break; case cmdPASSWD: err = cmd_passwd (info, argstr); break; case cmdUNBLOCK: err = cmd_unblock (info); break; case cmdFACTRST: err = cmd_factoryreset (info); if (!err) redisplay = 1; break; case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break; case cmdUIF: err = cmd_uif (info, argstr); break; case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break; case cmdAPDU: err = cmd_apdu (info, argstr); break; case cmdGPG: err = cmd_gpg (info, argstr, 0); break; case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break; case cmdHISTORY: err = cmd_history (info, argstr); break; case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break; case cmdINVCMD: default: tty_printf ("\n"); tty_printf (_("Invalid command (try \"help\")\n")); break; } /* End command switch. */ if (!err && info && info->card_removed) { info->card_removed = 0; info->need_sn_cmd = 1; err = gpg_error (GPG_ERR_CARD_REMOVED); } if (gpg_err_code (err) == GPG_ERR_CANCELED) tty_fprintf (NULL, "\n"); else if (err) { const char *s = "?"; for (i=0; cmds[i].name; i++ ) if (cmd == cmds[i].id) { s = cmds[i].name; break; } err = fixup_scd_errors (err); log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT) info->need_sn_cmd = 1; } } /* End of main menu loop. */ leave: if (historyname && tty_write_history (historyname)) log_info ("error writing '%s': %s\n", historyname, gpg_strerror (gpg_error_from_syserror ())); release_card_info (info); xfree (historyname); xfree (answer); } #ifdef HAVE_LIBREADLINE /* Helper function for readline's command completion. */ static char * command_generator (const char *text, int state) { static int list_index, len; const char *name; /* If this is a new word to complete, initialize now. This includes * saving the length of TEXT for efficiency, and initializing the index variable to 0. */ if (!state) { list_index = 0; len = strlen(text); } /* Return the next partial match */ while ((name = cmds[list_index].name)) { /* Only complete commands that have help text. */ if (cmds[list_index++].desc && !strncmp (name, text, len)) return strdup(name); } return NULL; } /* Second helper function for readline's command completion. */ static char ** command_completion (const char *text, int start, int end) { (void)end; /* If we are at the start of a line, we try and command-complete. * If not, just do nothing for now. The support for help completion * needs to be more smarter. */ if (!start) return rl_completion_matches (text, command_generator); else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5)) return rl_completion_matches (text, command_generator); rl_attempted_completion_over = 1; return NULL; } #endif /*HAVE_LIBREADLINE*/ diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index d6aa9d61b..b54b52c69 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -1,3628 +1,3625 @@ /* gpgconf-comp.c - Configuration utility for GnuPG. * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc. * Copyright (C) 2016 Werner Koch * Copyright (C) 2020, 2021 g10 Code GmbH * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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 GnuPG; if not, see <https://www.gnu.org/licenses/>. * SPDX-License-Identifier: GPL-3.0-or-later */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <time.h> #include <stdarg.h> #ifdef HAVE_SIGNAL_H # include <signal.h> #endif #include <ctype.h> #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN 1 # include <windows.h> #else # include <pwd.h> # include <grp.h> #endif #include "../common/util.h" #include "../common/i18n.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/status.h" #include "../common/gc-opt-flags.h" #include "gpgconf.h" #if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )) void gc_error (int status, int errnum, const char *fmt, ...) \ __attribute__ ((format (printf, 3, 4))); #endif /* Output a diagnostic message. If ERRNUM is not 0, then the output is followed by a colon, a white space, and the error string for the error number ERRNUM. In any case the output is finished by a newline. The message is prepended by the program name, a colon, and a whitespace. The output may be further formatted or redirected by the jnlib logging facility. */ void gc_error (int status, int errnum, const char *fmt, ...) { va_list arg_ptr; va_start (arg_ptr, fmt); log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr); va_end (arg_ptr); if (errnum) log_printf (": %s\n", strerror (errnum)); else log_printf ("\n"); if (status) { log_printf (NULL); log_printf ("fatal error (exit status %i)\n", status); gpgconf_failure (gpg_error_from_errno (errnum)); } } /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); #ifdef BUILD_WITH_TPM2D static void tpm2daemon_runtime_change (int killflag); #endif static void dirmngr_runtime_change (int killflag); static void keyboxd_runtime_change (int killflag); /* STRING_ARRAY is a malloced array with malloced strings. It is used * a space to store strings so that other objects may point to these * strings. It shall never be shrinked or any items changes. * STRING_ARRAY itself may be reallocated to increase the size of the * table. STRING_ARRAY_USED is the number of items currently used, * STRING_ARRAY_SIZE is the number of calloced slots. */ static char **string_array; static size_t string_array_used; static size_t string_array_size; /* Option configuration. */ /* An option might take an argument, or not. Argument types can be basic or complex. Basic types are generic and easy to validate. Complex types provide more specific information about the intended use, but can be difficult to validate. If you add to this enum, don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* Basic argument types. */ /* No argument. */ GC_ARG_TYPE_NONE = 0, /* A String argument. */ GC_ARG_TYPE_STRING = 1, /* A signed integer argument. */ GC_ARG_TYPE_INT32 = 2, /* An unsigned integer argument. */ GC_ARG_TYPE_UINT32 = 3, /* ADD NEW BASIC TYPE ENTRIES HERE. */ /* Complex argument types. */ /* A complete filename. */ GC_ARG_TYPE_FILENAME = 32, /* An LDAP server in the format HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ GC_ARG_TYPE_LDAP_SERVER = 33, /* A 40 character fingerprint. */ GC_ARG_TYPE_KEY_FPR = 34, /* A user ID or key ID or fingerprint for a certificate. */ GC_ARG_TYPE_PUB_KEY = 35, /* A user ID or key ID or fingerprint for a certificate with a key. */ GC_ARG_TYPE_SEC_KEY = 36, /* A alias list made up of a key, an equal sign and a space separated list of values. */ GC_ARG_TYPE_ALIAS_LIST = 37, /* ADD NEW COMPLEX TYPE ENTRIES HERE. */ /* The number of the above entries. */ GC_ARG_TYPE_NR } gc_arg_type_t; /* For every argument, we record some information about it in the following struct. */ static const struct { /* For every argument type exists a basic argument type that can be used as a fallback for input and validation purposes. */ gc_arg_type_t fallback; /* Human-readable name of the type. */ const char *name; } gc_arg_type[GC_ARG_TYPE_NR] = { /* The basic argument types have their own types as fallback. */ { GC_ARG_TYPE_NONE, "none" }, { GC_ARG_TYPE_STRING, "string" }, { GC_ARG_TYPE_INT32, "int32" }, { GC_ARG_TYPE_UINT32, "uint32" }, /* Reserved basic type entries for future extension. */ { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL }, /* The complex argument types have a basic type as fallback. */ { GC_ARG_TYPE_STRING, "filename" }, { GC_ARG_TYPE_STRING, "ldap server" }, { GC_ARG_TYPE_STRING, "key fpr" }, { GC_ARG_TYPE_STRING, "pub key" }, { GC_ARG_TYPE_STRING, "sec key" }, { GC_ARG_TYPE_STRING, "alias list" }, }; /* Every option has an associated expert level, than can be used to hide advanced and expert options from beginners. If you add to this list, don't forget to update GC_LEVEL below. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ typedef enum { /* The basic options should always be displayed. */ GC_LEVEL_BASIC, /* The advanced options may be hidden from beginners. */ GC_LEVEL_ADVANCED, /* The expert options should only be displayed to experts. */ GC_LEVEL_EXPERT, /* The invisible options should normally never be displayed. */ GC_LEVEL_INVISIBLE, /* The internal options are never exported, they mark options that are recorded for internal use only. */ GC_LEVEL_INTERNAL, /* ADD NEW ENTRIES HERE. */ /* The number of the above entries. */ GC_LEVEL_NR } gc_expert_level_t; /* A description for each expert level. */ static const struct { const char *name; } gc_level[] = { { "basic" }, { "advanced" }, { "expert" }, { "invisible" }, { "internal" } }; /* Option flags. The flags which are used by the components are defined by gc-opt-flags.h, included above. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ /* Some entries in the emitted option list are not options, but mark the beginning of a new group of options. These entries have the GROUP flag set. Note that this is internally also known as a header line. */ #define GC_OPT_FLAG_GROUP (1UL << 0) /* The ARG_OPT flag for an option indicates that the argument is optional. This is never set for GC_ARG_TYPE_NONE options. */ #define GC_OPT_FLAG_ARG_OPT (1UL << 1) /* The LIST flag for an option indicates that the option can occur several times. A comma separated list of arguments is used as the argument value. */ #define GC_OPT_FLAG_LIST (1UL << 2) /* The RUNTIME flag for an option indicates that the option can be changed at runtime. */ #define GC_OPT_FLAG_RUNTIME (1UL << 3) /* A human-readable description for each flag. */ static const struct { const char *name; } gc_flag[] = { { "group" }, { "optional arg" }, { "list" }, { "runtime" }, { "default" }, { "default desc" }, { "no arg desc" }, { "no change" } }; /* Each option we want to support in gpgconf has the needed * information in a static list per componenet. This struct describes * the info for a single option. */ struct known_option_s { /* If this is NULL, then this is a terminator in an array of unknown * length. Otherwise it is the name of the option described by this * entry. The name must not contain a colon. */ const char *name; /* The option flags. */ unsigned long flags; /* The expert level. */ gc_expert_level_t level; /* The complex type of the option argument; the default of 0 is used * for a standard type as returned by --dump-option-table. */ gc_arg_type_t arg_type; }; typedef struct known_option_s known_option_t; /* The known options of the GC_COMPONENT_GPG_AGENT component. */ static known_option_t known_options_gpg_agent[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, /**/ GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "check-sym-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, /**/ GC_ARG_TYPE_FILENAME }, { "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_SCDAEMON component. */ static known_option_t known_options_scdaemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT }, { "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "application-priority", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { NULL } }; #ifdef BUILD_WITH_TPM2D /* The known options of the GC_COMPONENT_TPM2DAEMON component. */ static known_option_t known_options_tpm2daemon[] = { { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, { "parent", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, { NULL } }; #endif /* The known options of the GC_COMPONENT_GPG component. */ static known_option_t known_options_gpg[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED, GC_ARG_TYPE_ALIAS_LIST}, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, /* The next items are pseudo options which we read via --gpgconf-list. * The meta information is taken from the table below. */ { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "compliance_de_vs", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "use_keyboxd", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; static const char *known_pseudo_options_gpg[] = {/* v-- ARGPARSE_TYPE_STRING */ "default_pubkey_algo:0:2:@:", /* A basic compliance check for gpg. We use gpg here but the * result is valid for all components. * v-- ARGPARSE_TYPE_INT */ "compliance_de_vs:0:1:@:", /* True is use_keyboxd is enabled. That option can be set in * common.conf but is not direcly supported by gpgconf. Thus we * only allow to read it out. * v-- ARGPARSE_TYPE_INT */ "use_keyboxd:0:1:@:", NULL }; /* The known options of the GC_COMPONENT_GPGSM component. */ static known_option_t known_options_gpgsm[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_INVISIBLE, GC_ARG_TYPE_LDAP_SERVER }, { "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, /* Pseudo option follows. See also table below. */ { "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; static const char *known_pseudo_options_gpgsm[] = {/* v-- ARGPARSE_TYPE_STRING */ "default_pubkey_algo:0:2:@:", NULL }; /* The known options of the GC_COMPONENT_DIRMNGR component. */ static known_option_t known_options_dirmngr[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ldapserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, GC_ARG_TYPE_LDAP_SERVER }, { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT }, { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED }, { NULL } }; /* The known options of the GC_COMPONENT_KEYBOXD component. */ static known_option_t known_options_keyboxd[] = { { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC }, { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, GC_ARG_TYPE_FILENAME }, { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, { NULL } }; /* The known options of the GC_COMPONENT_PINENTRY component. */ static known_option_t known_options_pinentry[] = { { NULL } }; /* Our main option info object. We copy all required information from the * gpgrt_opt_t items but convert the flags value to bit flags. */ struct gc_option_s { const char *name; /* The same as gpgrt_opt_t.long_opt. */ const char *desc; /* The same as gpgrt_opt_t.description. */ unsigned int is_header:1; /* This is a header item. */ unsigned int is_list:1; /* This is a list style option. */ unsigned int opt_arg:1; /* The option's argument is optional. */ unsigned int runtime:1; /* The option is runtime changeable. */ unsigned int gpgconf_list:1; /* Mentioned by --gpgconf-list. */ unsigned int has_default:1; /* The option has a default value. */ unsigned int def_in_desc:1; /* The default is in the descrition. */ unsigned int no_arg_desc:1; /* The argument has a default ???. */ unsigned int no_change:1; /* User shall not change the option. */ unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */ unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */ /* The expert level - copied from known_options. */ gc_expert_level_t level; /* The complex type - copied from known_options. */ gc_arg_type_t arg_type; /* The default value for this option. This is NULL if the option is not present in the component, the empty string if no default is available, and otherwise a quoted string. This is currently malloced.*/ char *default_value; /* The current value of this option. */ char *value; /* The new flags for this option. The only defined flag is actually GC_OPT_FLAG_DEFAULT, and it means that the option should be deleted. In this case, NEW_VALUE is NULL. */ unsigned long new_flags; /* The new value of this option. */ char *new_value; }; typedef struct gc_option_s gc_option_t; /* The information associated with each component. */ static struct { /* The name of the component. Some components don't have an * associated program, but are implemented directly by GPGConf. In * this case, PROGRAM is NULL. */ char *program; /* The displayed name of this component. Must not contain a colon * (':') character. */ const char *name; /* The gettext domain for the description DESC. If this is NULL, then the description is not translated. */ const char *desc_domain; /* The description of this component. */ const char *desc; /* The module name (GNUPG_MODULE_NAME_foo) as defined by * ../common/util.h. This value is used to get the actual installed * path of the program. 0 is used if no program for the component * is available. */ char module_name; /* The name for the configuration filename of this component. */ const char *option_config_filename; /* The static table of known options for this component. */ known_option_t *known_options; /* The static table of known pseudo options for this component or NULL. */ const char **known_pseudo_options; /* The runtime change callback. If KILLFLAG is true the component is killed and not just reloaded. */ void (*runtime_change) (int killflag); /* The table of known options as read from the component including * header lines and such. This is suitable to be passed to * gpgrt_argparser. Will be filled in by * retrieve_options_from_program. */ gpgrt_opt_t *opt_table; /* The full table including data from OPT_TABLE. The end of the * table is marked by NULL entry for NAME. Will be filled in by * retrieve_options_from_program. */ gc_option_t *options; } gc_component[GC_COMPONENT_NR] = { /* Note: The order of the items must match the order given in the * gc_component_id_t enumeration. The order is often used by * frontends to display the backend options thus do not change the * order without considering the user experience. */ { NULL }, /* DUMMY for GC_COMPONENT_ANY */ { GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"), GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf", known_options_gpg, known_pseudo_options_gpg }, { GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"), GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf", known_options_gpgsm, known_pseudo_options_gpgsm }, { KEYBOXD_NAME, KEYBOXD_DISP_NAME, "gnupg", N_("Public Keys"), GNUPG_MODULE_NAME_KEYBOXD, KEYBOXD_NAME ".conf", known_options_keyboxd, NULL, keyboxd_runtime_change }, { GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"), GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf", known_options_gpg_agent, NULL, gpg_agent_runtime_change }, { SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"), GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", known_options_scdaemon, NULL, scdaemon_runtime_change}, #ifdef BUILD_WITH_TPM2D { TPM2DAEMON_NAME, TPM2DAEMON_DISP_NAME, "gnupg", N_("TPM"), GNUPG_MODULE_NAME_TPM2DAEMON, TPM2DAEMON_NAME ".conf", known_options_tpm2daemon, NULL, tpm2daemon_runtime_change}, #else { NULL }, /* DUMMY to keep the table in-sync with enums */ #endif { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", known_options_dirmngr, NULL, dirmngr_runtime_change }, { "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"), GNUPG_MODULE_NAME_PINENTRY, NULL, known_options_pinentry } }; /* Structure used to collect error output of the component programs. */ struct error_line_s; typedef struct error_line_s *error_line_t; struct error_line_s { error_line_t next; /* Link to next item. */ const char *fname; /* Name of the config file (points into BUFFER). */ unsigned int lineno; /* Line number of the config file. */ const char *errtext; /* Text of the error message (points into BUFFER). */ char buffer[1]; /* Helper buffer. */ }; /* Initialization and finalization. */ static void gc_option_free (gc_option_t *o) { if (o == NULL || o->name == NULL) return; xfree (o->value); gc_option_free (o + 1); } static void gc_components_free (void) { int i; for (i = 0; i < DIM (gc_component); i++) gc_option_free (gc_component[i].options); } void gc_components_init (void) { atexit (gc_components_free); } /* Engine specific support. */ static void gpg_agent_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[5]; gnupg_process_t proc = NULL; int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "--no-autostart"; cmdidx = i; argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_process_release (proc); } static void scdaemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; gnupg_process_t proc = NULL; int i = 0; int cmdidx; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO scd_running"; argv[i++] = "/if ${! $?}"; cmdidx = i; argv[i++] = "scd killscd"; argv[i++] = "/end"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_process_release (proc); } #ifdef BUILD_WITH_TPM2D static void tpm2daemon_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[9]; gnupg_process_t proc = NULL; int i = 0; int cmdidx; (void)killflag; /* For scdaemon kill and reload are synonyms. */ /* We use "GETINFO app_running" to see whether the agent is already running and kill it only in this case. This avoids an explicit starting of the agent in case it is not yet running. There is obviously a race condition but that should not harm too much. */ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "-s"; argv[i++] = "--no-autostart"; argv[i++] = "GETINFO tpm2d_running"; argv[i++] = "/if ${! $?}"; cmdidx = i; argv[i++] = "scd killtpm2cd"; argv[i++] = "/end"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_process_release (proc); } #endif static void dirmngr_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; gnupg_process_t proc = NULL; int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i++] = "--no-autostart"; argv[i++] = "--dirmngr"; cmdidx = i; argv[i++] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR"; argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_process_release (proc); } static void keyboxd_runtime_change (int killflag) { gpg_error_t err = 0; const char *pgmname; const char *argv[6]; gnupg_process_t proc = NULL; int i = 0; int cmdidx; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); argv[i++] = "--no-autostart"; argv[i++] = "--keyboxd"; cmdidx = i; argv[i++] = killflag? "KILLKEYBOXD" : "RELOADKEYBOXD"; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } argv[i] = NULL; log_assert (i < DIM(argv)); if (!err) - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s %s': %s", pgmname, argv[cmdidx], gpg_strerror (err)); gnupg_process_release (proc); } /* Launch the gpg-agent or the dirmngr if not already running. */ gpg_error_t gc_component_launch (int component) { gpg_error_t err; const char *pgmname; const char *argv[6]; int i; gnupg_process_t proc = NULL; if (component < 0) { err = gc_component_launch (GC_COMPONENT_GPG_AGENT); if (!err) err = gc_component_launch (GC_COMPONENT_KEYBOXD); if (!err) err = gc_component_launch (GC_COMPONENT_DIRMNGR); return err; } if (!(component == GC_COMPONENT_GPG_AGENT || component == GC_COMPONENT_KEYBOXD || component == GC_COMPONENT_DIRMNGR)) { log_error ("%s\n", _("Component not suitable for launching")); gpgconf_failure (0); } if (gc_component_check_options (component, NULL, NULL)) { log_error (_("Configuration file of component %s is broken\n"), gc_component[component].name); if (!opt.quiet) log_info (_("Note: Use the command \"%s%s\" to get details.\n"), gc_component[component].program ? gc_component[component].program : gc_component[component].name, " --gpgconf-test"); gpgconf_failure (0); } pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); i = 0; if (!gnupg_default_homedir_p ()) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } if (component == GC_COMPONENT_DIRMNGR) argv[i++] = "--dirmngr"; else if (component == GC_COMPONENT_KEYBOXD) argv[i++] = "--keyboxd"; argv[i++] = "NOP"; argv[i] = NULL; log_assert (i < DIM(argv)); - err = gnupg_process_spawn (pgmname, argv, 0, NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc); if (!err) err = gnupg_process_wait (proc, 1); if (err) gc_error (0, 0, "error running '%s%s%s': %s", pgmname, component == GC_COMPONENT_DIRMNGR? " --dirmngr" : component == GC_COMPONENT_KEYBOXD? " --keyboxd":"", " NOP", gpg_strerror (err)); gnupg_process_release (proc); return err; } static void do_runtime_change (int component, int killflag) { int runtime[GC_COMPONENT_NR] = { 0 }; if (component < 0) { for (component = 0; component < GC_COMPONENT_NR; component++) runtime [component] = 1; } else { log_assert (component >= 0 && component < GC_COMPONENT_NR); runtime [component] = 1; } /* Do the restart for the selected components. */ for (component = GC_COMPONENT_NR-1; component >= 0; component--) { if (runtime[component] && gc_component[component].runtime_change) (*gc_component[component].runtime_change) (killflag); } } /* Unconditionally restart COMPONENT. */ void gc_component_kill (int component) { do_runtime_change (component, 1); } /* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */ void gc_component_reload (int component) { do_runtime_change (component, 0); } /* More or less Robust version of dgettext. It has the side effect of switching the codeset to utf-8 because this is what we want to output. In theory it is possible to keep the original code set and switch back for regular diagnostic output (redefine "_(" for that) but given the nature of this tool, being something invoked from other programs, it does not make much sense. */ static const char * my_dgettext (const char *domain, const char *msgid) { if (!msgid || !*msgid) return msgid; /* Shortcut form "" which has the PO files meta data. */ #ifdef USE_SIMPLE_GETTEXT if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; gettext_use_utf8 (1); } if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; /* FIXME: we have no dgettext, thus we can't switch. */ text = (char*)gettext (msgid); return text ? text : msgid; } else return msgid; #elif defined(ENABLE_NLS) if (domain) { static int switched_codeset; char *text; if (!switched_codeset) { switched_codeset = 1; bind_textdomain_codeset (PACKAGE_GT, "utf-8"); bindtextdomain (DIRMNGR_NAME, gnupg_localedir ()); bind_textdomain_codeset (DIRMNGR_NAME, "utf-8"); } /* Note: This is a hack to actually use the gnupg2 domain as long we are in a transition phase where gnupg 1.x and 1.9 may coexist. */ if (!strcmp (domain, "gnupg")) domain = PACKAGE_GT; text = dgettext (domain, msgid); return text ? text : msgid; } else return msgid; #else (void)domain; return msgid; #endif } /* Percent-Escape special characters. The string is valid until the next invocation of the function. */ char * gc_percent_escape (const char *src) { static char *esc_str; static int esc_str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (esc_str_len < new_len) { char *new_esc_str = xrealloc (esc_str, new_len); esc_str = new_esc_str; esc_str_len = new_len; } dst = esc_str; while (*src) { if (*src == '%') { *(dst++) = '%'; *(dst++) = '2'; *(dst++) = '5'; } else if (*src == ':') { /* The colon is used as field separator. */ *(dst++) = '%'; *(dst++) = '3'; *(dst++) = 'a'; } else if (*src == ',') { /* The comma is used as list separator. */ *(dst++) = '%'; *(dst++) = '2'; *(dst++) = 'c'; } else if (*src == '\n') { /* The newline is problematic in a line-based format. */ *(dst++) = '%'; *(dst++) = '0'; *(dst++) = 'a'; } else *(dst++) = *(src); src++; } *dst = '\0'; return esc_str; } /* Percent-Deescape special characters. The string is valid until the next invocation of the function. */ static char * percent_deescape (const char *src) { static char *str; static int str_len; int new_len = 3 * strlen (src) + 1; char *dst; if (str_len < new_len) { char *new_str = xrealloc (str, new_len); str = new_str; str_len = new_len; } dst = str; while (*src) { if (*src == '%') { int val = hextobyte (src + 1); if (val < 0) gc_error (1, 0, "malformed end of string %s", src); *(dst++) = (char) val; src += 3; } else *(dst++) = *(src++); } *dst = '\0'; return str; } /* List all components that are available. */ void gc_component_list_components (estream_t out) { gc_component_id_t component; const char *desc; const char *pgmname; for (component = 0; component < GC_COMPONENT_NR; component++) { if (!gc_component[component].program) continue; if (gc_component[component].module_name) pgmname = gnupg_module_name (gc_component[component].module_name); else pgmname = ""; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, gc_percent_escape (desc)); es_fprintf (out, "%s\n", gc_percent_escape (pgmname)); } } static int all_digits_p (const char *p, size_t len) { if (!len) return 0; /* No. */ for (; len; len--, p++) if (!isascii (*p) || !isdigit (*p)) return 0; /* No. */ return 1; /* Yes. */ } /* Collect all error lines from stream FP. Only lines prefixed with TAG are considered. Returns a list of error line items (which may be empty). There is no error return. */ static error_line_t collect_error_output (estream_t fp, const char *tag) { char buffer[1024]; char *p, *p2, *p3; int c, cont_line; unsigned int pos; error_line_t eitem, errlines, *errlines_tail; size_t taglen = strlen (tag); errlines = NULL; errlines_tail = &errlines; pos = 0; cont_line = 0; while ((c=es_getc (fp)) != EOF) { buffer[pos++] = c; if (pos >= sizeof buffer - 5 || c == '\n') { buffer[pos - (c == '\n')] = 0; if (cont_line) ; /*Ignore continuations of previous line. */ else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') { /* "gpgsm: foo:4: bla" */ /* Yep, we are interested in this line. */ p = buffer + taglen + 1; while (*p == ' ' || *p == '\t') p++; trim_trailing_spaces (p); /* Get rid of extra CRs. */ if (!*p) ; /* Empty lines are ignored. */ else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':')) && all_digits_p (p2+1, p3 - (p2+1))) { /* Line in standard compiler format. */ p3++; while (*p3 == ' ' || *p3 == '\t') p3++; eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = eitem->buffer; eitem->buffer[p2-p] = 0; eitem->errtext = eitem->buffer + (p3 - p); /* (we already checked that there are only ascii digits followed by a colon) */ eitem->lineno = 0; for (p2++; isdigit (*p2); p2++) eitem->lineno = eitem->lineno*10 + (*p2 - '0'); *errlines_tail = eitem; errlines_tail = &eitem->next; } else { /* Other error output. */ eitem = xmalloc (sizeof *eitem + strlen (p)); eitem->next = NULL; strcpy (eitem->buffer, p); eitem->fname = NULL; eitem->errtext = eitem->buffer; eitem->lineno = 0; *errlines_tail = eitem; errlines_tail = &eitem->next; } } pos = 0; /* If this was not a complete line mark that we are in a continuation. */ cont_line = (c != '\n'); } } /* We ignore error lines not terminated by a LF. */ return errlines; } /* Check the options of a single component. If CONF_FILE is NULL the * standard config file is used. If OUT is not NULL the output is * written to that stream. Returns 0 if everything is OK. */ int gc_component_check_options (int component, estream_t out, const char *conf_file) { gpg_error_t err; unsigned int result; const char *pgmname; const char *argv[6]; int i; gnupg_process_t proc; estream_t errfp; error_line_t errlines; log_assert (component >= 0 && component < GC_COMPONENT_NR); if (!gc_component[component].program) return 0; if (!gc_component[component].module_name) return 0; pgmname = gnupg_module_name (gc_component[component].module_name); i = 0; if (!gnupg_default_homedir_p () && component != GC_COMPONENT_PINENTRY) { argv[i++] = "--homedir"; argv[i++] = gnupg_homedir (); } if (conf_file) { argv[i++] = "--options"; argv[i++] = conf_file; } if (component == GC_COMPONENT_PINENTRY) argv[i++] = "--version"; else argv[i++] = "--gpgconf-test"; argv[i] = NULL; log_assert (i < DIM(argv)); result = 0; errlines = NULL; - err = gnupg_process_spawn (pgmname, argv, - GNUPG_PROCESS_STDERR_PIPE, - NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDERR_PIPE, + NULL, &proc); if (err) result |= 1; /* Program could not be run. */ else { gnupg_process_get_streams (proc, 0, NULL, NULL, &errfp); errlines = collect_error_output (errfp, gc_component[component].name); if (!gnupg_process_wait (proc, 1)) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode == -1) result |= 1; /* Program could not be run or it terminated abnormally. */ else if (exitcode) result |= 2; /* Program returned an error. */ } gnupg_process_release (proc); es_fclose (errfp); } /* If the program could not be run, we can't tell whether the config file is good. */ if (result & 1) result |= 2; if (out) { const char *desc; error_line_t errptr; desc = gc_component[component].desc; desc = my_dgettext (gc_component[component].desc_domain, desc); es_fprintf (out, "%s:%s:", gc_component[component].program, gc_percent_escape (desc)); es_fputs (gc_percent_escape (pgmname), out); es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2)); for (errptr = errlines; errptr; errptr = errptr->next) { if (errptr != errlines) es_fputs ("\n:::::", out); /* Continuation line. */ if (errptr->fname) es_fputs (gc_percent_escape (errptr->fname), out); es_putc (':', out); if (errptr->fname) es_fprintf (out, "%u", errptr->lineno); es_putc (':', out); es_fputs (gc_percent_escape (errptr->errtext), out); es_putc (':', out); } es_putc ('\n', out); } while (errlines) { error_line_t tmp = errlines->next; xfree (errlines); errlines = tmp; } return result; } /* Check all components that are available. */ void gc_check_programs (estream_t out) { gc_component_id_t component; for (component = 0; component < GC_COMPONENT_NR; component++) gc_component_check_options (component, out, NULL); } /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name) { gc_component_id_t idx; for (idx = 0; idx < GC_COMPONENT_NR; idx++) { if (gc_component[idx].program && !strcmp (name, gc_component[idx].program)) return idx; } return -1; } /* List the option OPTION. */ static void list_one_option (gc_component_id_t component, const gc_option_t *option, estream_t out) { const char *desc = NULL; char *arg_name = NULL; unsigned long flags; const char *desc_domain = gc_component[component].desc_domain; /* Don't show options with the ignore attribute. */ if (option->attr_ignore && !option->attr_force) return; if (option->desc) { desc = my_dgettext (desc_domain, option->desc); if (*desc == '|') { const char *arg_tail = strchr (&desc[1], '|'); if (arg_tail) { int arg_len = arg_tail - &desc[1]; arg_name = xmalloc (arg_len + 1); memcpy (arg_name, &desc[1], arg_len); arg_name[arg_len] = '\0'; desc = arg_tail + 1; } } } /* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY FIELDS. */ /* The name field. */ es_fprintf (out, "%s", option->name); /* The flags field. */ flags = 0; if (option->is_header) flags |= GC_OPT_FLAG_GROUP; if (option->is_list) flags |= GC_OPT_FLAG_LIST; if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME; if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT; if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC; if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC; if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE; if (option->attr_force) flags |= GC_OPT_FLAG_NO_CHANGE; es_fprintf (out, ":%lu", flags); if (opt.verbose) { es_putc (' ', out); if (!flags) es_fprintf (out, "none"); else { unsigned long flag = 0; unsigned long first = 1; while (flags) { if (flags & 1) { if (first) first = 0; else es_putc (',', out); es_fprintf (out, "%s", gc_flag[flag].name); } flags >>= 1; flag++; } } } /* The level field. */ es_fprintf (out, ":%u", option->level); if (opt.verbose) es_fprintf (out, " %s", gc_level[option->level].name); /* The description field. */ es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : ""); /* The type field. */ es_fprintf (out, ":%u", option->arg_type); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[option->arg_type].name); /* The alternate type field. */ es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); if (opt.verbose) es_fprintf (out, " %s", gc_arg_type[gc_arg_type[option->arg_type].fallback].name); /* The argument name field. */ es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : ""); xfree (arg_name); /* The default value field. */ es_fprintf (out, ":%s", option->default_value ? option->default_value : ""); /* The default argument field. This was never used and is thus empty. */ es_fprintf (out, ":"); /* The value field. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list && option->value) { /* The special format "1,1,1,1,...,1" is converted to a number here. */ es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2)); } else es_fprintf (out, ":%s", option->value ? option->value : ""); /* ADD NEW FIELDS HERE. */ es_putc ('\n', out); } /* List all options of the component COMPONENT. */ void gc_component_list_options (int component, estream_t out) { const gc_option_t *option = gc_component[component].options; for ( ; option && option->name; option++) { /* Do not output unknown or internal options. */ if (!option->is_header && option->level == GC_LEVEL_INTERNAL) continue; if (option->is_header) { const gc_option_t *group_option = option + 1; gc_expert_level_t level = GC_LEVEL_NR; /* The manual states that the group level is always the minimum of the levels of all contained options. Due to different active options, and because it is hard to maintain manually, we calculate it here. The value in the global static table is ignored. */ for ( ; group_option->name; group_option++) { if (group_option->is_header) break; if (group_option->level < level) level = group_option->level; } /* Check if group is empty. */ if (level != GC_LEVEL_NR) { gc_option_t opt_copy; /* Fix up the group level. */ opt_copy = *option; opt_copy.level = level; list_one_option (component, &opt_copy, out); } } else list_one_option (component, option, out); } } /* Return true if the option NAME is known and that we want it as * gpgconf managed option. */ static known_option_t * is_known_option (gc_component_id_t component, const char *name) { known_option_t *option = gc_component[component].known_options; if (option) { for (; option->name; option++) if (!strcmp (option->name, name)) break; } return (option && option->name)? option : NULL; } /* Find the option NAME in component COMPONENT. Returns pointer to * the option descriptor or NULL if not found. */ static gc_option_t * find_option (gc_component_id_t component, const char *name) { gc_option_t *option = gc_component[component].options; if (option) { for (; option->name; option++) { if (!option->is_header && !strcmp (option->name, name)) return option; } } return NULL; } struct read_line_wrapper_parm_s { const char *pgmname; estream_t fp; char *line; size_t line_len; const char **extra_lines; int extra_lines_idx; char *extra_line_buffer; }; /* Helper for retrieve_options_from_program. */ static ssize_t read_line_wrapper (struct read_line_wrapper_parm_s *parm) { ssize_t length; const char *extra_line; if (parm->fp) { length = es_read_line (parm->fp, &parm->line, &parm->line_len, NULL); if (length > 0) return length; if (length < 0 || es_ferror (parm->fp)) gc_error (1, errno, "error reading from %s", parm->pgmname); if (es_fclose (parm->fp)) gc_error (1, errno, "error closing %s", parm->pgmname); /* EOF seen. */ parm->fp = NULL; } /* Return the made up lines. */ if (!parm->extra_lines || !(extra_line = parm->extra_lines[parm->extra_lines_idx])) return -1; /* This is really the EOF. */ parm->extra_lines_idx++; xfree (parm->extra_line_buffer); parm->extra_line_buffer = xstrdup (extra_line); return strlen (parm->extra_line_buffer); } /* Retrieve the options for the component COMPONENT. With * ONLY_INSTALLED set components which are not installed are silently * ignored. */ static void retrieve_options_from_program (gc_component_id_t component, int only_installed) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; gnupg_process_t proc; known_option_t *known_option; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; const char *config_name; gpgrt_argparse_t pargs; int dummy_argc; char *twopartconfig_name = NULL; gpgrt_opt_t *opt_table = NULL; /* A malloced option table. */ size_t opt_table_used = 0; /* Its current length. */ size_t opt_table_size = 0; /* Its allocated length. */ gc_option_t *opt_info = NULL; /* A malloced options table. */ size_t opt_info_used = 0; /* Its current length. */ size_t opt_info_size = 0; /* Its allocated length. */ int i; struct read_line_wrapper_parm_s read_line_parm; int pseudo_count; pgmname = (gc_component[component].module_name ? gnupg_module_name (gc_component[component].module_name) : gc_component[component].program ); if (only_installed && gnupg_access (pgmname, X_OK)) { return; /* The component is not installed. */ } /* First we need to read the option table from the program. */ argv[0] = "--dump-option-table"; argv[1] = NULL; - err = gnupg_process_spawn (pgmname, argv, - GNUPG_PROCESS_STDOUT_PIPE, - NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE, + NULL, &proc); if (err) { gc_error (1, 0, "could not gather option table from '%s': %s", pgmname, gpg_strerror (err)); } gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL); read_line_parm.pgmname = pgmname; read_line_parm.fp = outfp; read_line_parm.line = line; read_line_parm.line_len = line_len = 0; read_line_parm.extra_line_buffer = NULL; read_line_parm.extra_lines = gc_component[component].known_pseudo_options; read_line_parm.extra_lines_idx = 0; pseudo_count = 0; while ((length = read_line_wrapper (&read_line_parm)) > 0) { const char *fields[4]; const char *optname, *optdesc; unsigned int optflags; int short_opt; gc_arg_type_t arg_type; int pseudo = 0; if (read_line_parm.extra_line_buffer) { line = read_line_parm.extra_line_buffer; pseudo = 1; pseudo_count++; } else line = read_line_parm.line; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; if (split_fields_colon (line, fields, DIM (fields)) < 4) { gc_error (0,0, "WARNING: invalid line in option table of '%s'\n", pgmname); continue; } optname = fields[0]; short_opt = atoi (fields[1]); if (short_opt < 1 && !pseudo) { gc_error (0,0, "WARNING: bad short option in option table of '%s'\n", pgmname); continue; } optflags = strtoul (fields[2], NULL, 10); if ((optflags & ARGPARSE_OPT_HEADER)) known_option = NULL; /* We want all header-only options. */ else if ((known_option = is_known_option (component, optname))) ; /* Yes we want this one. */ else continue; /* No need to store this option description. */ /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_table_used + 1 >= opt_table_size) { /* Note that this also does the initial allocation. */ opt_table_size += 128; opt_table = xreallocarray (opt_table, opt_table_used, opt_table_size, sizeof *opt_table); } /* The +1 here is to make sure that we will have a zero item at * the end of the table. */ if (opt_info_used + 1 >= opt_info_size) { /* Note that this also does the initial allocation. */ opt_info_size += 128; opt_info = xreallocarray (opt_info, opt_info_used, opt_info_size, sizeof *opt_info); } /* The +1 here accounts for the two items we are going to add to * the global string table. */ if (string_array_used + 1 >= string_array_size) { string_array_size += 256; string_array = xreallocarray (string_array, string_array_used, string_array_size, sizeof *string_array); } optname = string_array[string_array_used++] = xstrdup (fields[0]); optdesc = string_array[string_array_used++] = xstrdup (fields[3]); /* Create an option table which can then be supplied to * gpgrt_parser. Unfortunately there is no private pointer in * the public option table struct so that we can't add extra * data we need here. Thus we need to build up another table * for such info and for ease of use we also copy the tehre the * data from the option table. It is not possible to use the * known_option_s for this because that one does not carry * header lines and it might also be problematic to use such * static tables for caching options and default values. */ if (!pseudo) { opt_table[opt_table_used].long_opt = optname; opt_table[opt_table_used].short_opt = short_opt; opt_table[opt_table_used].description = optdesc; opt_table[opt_table_used].flags = optflags; opt_table_used++; } /* Note that as per argparser specs the opt_table uses "@" to * specifify an empty description. In the DESC script of * options (opt_info_t) we want to have a real empty string. */ opt_info[opt_info_used].name = optname; if (*optdesc == '@' && !optdesc[1]) opt_info[opt_info_used].desc = optdesc+1; else opt_info[opt_info_used].desc = optdesc; /* Unfortunately we need to remap the types. */ switch ((optflags & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break; case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break; case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break; default: arg_type = GC_ARG_TYPE_NONE; break; } opt_info[opt_info_used].arg_type = arg_type; if (pseudo) /* Pseudo options are always no_change. */ opt_info[opt_info_used].no_change = 1; if ((optflags & ARGPARSE_OPT_HEADER)) opt_info[opt_info_used].is_header = 1; if (known_option) { if ((known_option->flags & GC_OPT_FLAG_LIST)) opt_info[opt_info_used].is_list = 1; /* FIXME: The next can also be taken from opt_table->flags. * We need to check the code whether both specifications match. */ if ((known_option->flags & GC_OPT_FLAG_ARG_OPT)) opt_info[opt_info_used].opt_arg = 1; if ((known_option->flags & GC_OPT_FLAG_RUNTIME)) opt_info[opt_info_used].runtime = 1; opt_info[opt_info_used].level = known_option->level; /* Override the received argtype by a complex type. */ if (known_option->arg_type) opt_info[opt_info_used].arg_type = known_option->arg_type; } opt_info_used++; } xfree (read_line_parm.extra_line_buffer); line = read_line_parm.line; line_len = read_line_parm.line_len; log_assert (opt_table_used + pseudo_count == opt_info_used); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); /* Make the gpgrt option table and the internal option table available. */ gc_component[component].opt_table = opt_table; gc_component[component].options = opt_info; /* Now read the default options. */ argv[0] = "--gpgconf-list"; argv[1] = NULL; - err = gnupg_process_spawn (pgmname, argv, - GNUPG_PROCESS_STDOUT_PIPE, - NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE, + NULL, &proc); if (err) { gc_error (1, 0, "could not gather active options from '%s': %s", pgmname, gpg_strerror (err)); } gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL); while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *default_value = NULL; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s from %s", line, pgmname); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s from %s", line, pgmname); linep = end; } /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; if ((flags & GC_OPT_FLAG_DEFAULT)) default_value = linep; linep = end; } /* Look up the option in the component and install the configuration data. */ option = find_option (component, line); if (option) { if (option->gpgconf_list) gc_error (1, errno, "option %s returned twice from \"%s --gpgconf-list\"", line, pgmname); option->gpgconf_list = 1; if ((flags & GC_OPT_FLAG_DEFAULT)) option->has_default = 1; if ((flags & GC_OPT_FLAG_DEF_DESC)) option->def_in_desc = 1; if ((flags & GC_OPT_FLAG_NO_ARG_DESC)) option->no_arg_desc = 1; if ((flags & GC_OPT_FLAG_NO_CHANGE)) option->no_change = 1; if (default_value && *default_value) option->default_value = xstrdup (default_value); } } if (length < 0 || es_ferror (outfp)) gc_error (1, errno, "error reading from %s", pgmname); if (es_fclose (outfp)) gc_error (1, errno, "error closing %s", pgmname); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) gc_error (1, 0, "running %s failed (exitcode=%d): %s", pgmname, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); /* At this point, we can parse the configuration file. */ config_name = gc_component[component].option_config_filename; if (!config_name) gc_error (1, 0, "name of config file for %s is not known\n", pgmname); if (!gnupg_default_homedir_p ()) { /* This is not the default homedir. We need to take an absolute * config name for the user config file; gpgrt_argparser * fortunately supports this. */ char *tmp = make_filename (gnupg_homedir (), config_name, NULL); twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL); xfree (tmp); config_name = twopartconfig_name; } memset (&pargs, 0, sizeof pargs); dummy_argc = 0; pargs.argc = &dummy_argc; pargs.flags = (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_SYS | ARGPARSE_FLAG_USER | ARGPARSE_FLAG_WITHATTR); if (opt.verbose) pargs.flags |= ARGPARSE_FLAG_VERBOSE; while (gpgrt_argparser (&pargs, opt_table, config_name)) { char *opt_value; if (pargs.r_opt == ARGPARSE_CONFFILE) { /* log_debug ("current conffile='%s'\n", */ /* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */ continue; } if ((pargs.r_type & ARGPARSE_OPT_IGNORE)) continue; /* We only have the short option. Search in the option table * for the long option name. */ for (i=0; opt_table[i].short_opt; i++) if (opt_table[i].short_opt == pargs.r_opt) break; if (!opt_table[i].short_opt || !opt_table[i].long_opt) continue; /* No or only a short option - ignore. */ /* Look up the option from the config file in our list of * supported options. */ option= find_option (component, opt_table[i].long_opt); if (!option) continue; /* We don't want to handle this option. */ /* Set the force and ignore attributes. The idea is that there * is no way to clear them again, thus we set them when first * encountered. */ if ((pargs.r_type & ARGPARSE_ATTR_FORCE)) option->attr_force = 1; if ((pargs.r_type & ARGPARSE_ATTR_IGNORE)) option->attr_ignore = 1; /* If an option has been ignored, there is no need to return * that option with gpgconf --list-options. */ if (option->attr_ignore) continue; switch ((pargs.r_type & ARGPARSE_TYPE_MASK)) { case ARGPARSE_TYPE_INT: opt_value = xasprintf ("%d", pargs.r.ret_int); break; case ARGPARSE_TYPE_LONG: opt_value = xasprintf ("%ld", pargs.r.ret_long); break; case ARGPARSE_TYPE_ULONG: opt_value = xasprintf ("%lu", pargs.r.ret_ulong); break; case ARGPARSE_TYPE_STRING: if (!pargs.r.ret_str) opt_value = xstrdup ("\"(none)"); /* We should not see this. */ else opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str)); break; default: /* ARGPARSE_TYPE_NONE or any unknown type. */ opt_value = xstrdup ("1"); /* Make sure we have some value. */ break; } /* Now enter the value read from the config file into the table. */ if (!option->is_list) { xfree (option->value); option->value = opt_value; } else if (!option->value) /* LIST but first item. */ option->value = opt_value; else { char *old = option->value; option->value = xstrconcat (old, ",", opt_value, NULL); xfree (old); xfree (opt_value); } } xfree (line); xfree (twopartconfig_name); } /* Retrieve the currently active options and their defaults for this component. Using -1 for component will retrieve all options from all installed components. */ void gc_component_retrieve_options (int component) { int process_all = 0; if (component == -1) { process_all = 1; component = 0; } do { if (component == GC_COMPONENT_PINENTRY) continue; /* Skip this dummy component. */ if (gc_component[component].program) retrieve_options_from_program (component, process_all); } while (process_all && ++component < GC_COMPONENT_NR); } /* Perform a simple validity check based on the type. Return in * NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of * type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode * is used. */ static void option_check_validity (gc_component_id_t component, gc_option_t *option, unsigned long flags, char *new_value, unsigned long *new_value_nr, int verbatim) { char *arg; (void)component; if (option->new_flags || option->new_value) gc_error (1, 0, "option %s already changed", option->name); if (flags & GC_OPT_FLAG_DEFAULT) { if (*new_value) gc_error (1, 0, "argument %s provided for deleted option %s", new_value, option->name); return; } /* GC_ARG_TYPE_NONE options have special list treatment. */ if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { char *tail; gpg_err_set_errno (0); *new_value_nr = strtoul (new_value, &tail, 0); if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*tail) gc_error (1, 0, "garbage after argument for option %s", option->name); if (!option->is_list) { if (*new_value_nr != 1) gc_error (1, 0, "argument for non-list option %s of type 0 " "(none) must be 1", option->name); } else { if (*new_value_nr == 0) gc_error (1, 0, "argument for option %s of type 0 (none) " "must be positive", option->name); } return; } arg = new_value; do { if (*arg == '\0' || (*arg == ',' && !verbatim)) { if (!option->opt_arg) gc_error (1, 0, "argument required for option %s", option->name); if (*arg == ',' && !verbatim && !option->is_list) gc_error (1, 0, "list found for non-list option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { if (*arg != '"' && !verbatim) gc_error (1, 0, "string argument for option %s must begin " "with a quote (\") character", option->name); /* FIXME: We do not allow empty string arguments for now, as we do not quote arguments in configuration files, and thus no argument is indistinguishable from the empty string. */ if (arg[1] == '\0' || (arg[1] == ',' && !verbatim)) gc_error (1, 0, "empty string argument for option %s is " "currently not allowed. Please report this!", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) { long res; gpg_err_set_errno (0); res = strtol (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32) { unsigned long res; gpg_err_set_errno (0); res = strtoul (arg, &arg, 0); (void) res; if (errno) gc_error (1, errno, "invalid argument for option %s", option->name); if (*arg != '\0' && (*arg != ',' || verbatim)) gc_error (1, 0, "garbage after argument for option %s", option->name); } arg = verbatim? strchr (arg, ',') : NULL; if (arg) arg++; } while (arg && *arg); } #ifdef HAVE_W32_SYSTEM int copy_file (const char *src_name, const char *dst_name) { #define BUF_LEN 4096 char buffer[BUF_LEN]; int len; gpgrt_stream_t src; gpgrt_stream_t dst; src = gpgrt_fopen (src_name, "r"); if (src == NULL) return -1; dst = gpgrt_fopen (dst_name, "w"); if (dst == NULL) { int saved_err = errno; gpgrt_fclose (src); gpg_err_set_errno (saved_err); return -1; } do { int written; len = gpgrt_fread (buffer, 1, BUF_LEN, src); if (len == 0) break; written = gpgrt_fwrite (buffer, 1, len, dst); if (written != len) break; } while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst)); if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src)) { int saved_errno = errno; gpgrt_fclose (src); gpgrt_fclose (dst); unlink (dst_name); gpg_err_set_errno (saved_errno); return -1; } if (gpgrt_fclose (dst)) gc_error (1, errno, "error closing %s", dst_name); if (gpgrt_fclose (src)) gc_error (1, errno, "error closing %s", src_name); return 0; } #endif /* HAVE_W32_SYSTEM */ /* Create and verify the new configuration file for the specified * component. Returns 0 on success and -1 on error. If * VERBATIM is set the profile mode is used. This function may store * pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and * ORIG_FILENAMEP. Those must be freed by the caller. The strings * refer to three versions of the configuration file: * * SRC_FILENAME: The updated configuration is written to this file. * DEST_FILENAME: Name of the configuration file read by the * component. * ORIG_FILENAME: A backup of the previous configuration file. * * To apply the configuration change, rename SRC_FILENAME to * DEST_FILENAME. To revert to the previous configuration, rename * ORIG_FILENAME to DEST_FILENAME. */ static int change_options_program (gc_component_id_t component, char **src_filenamep, char **dest_filenamep, char **orig_filenamep, int verbatim) { static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###"; /* True if we are within the marker in the config file. */ int in_marker = 0; gc_option_t *option; char *line = NULL; size_t line_len; ssize_t length; int res; int fd; gpgrt_stream_t src_file = NULL; gpgrt_stream_t dest_file = NULL; char *src_filename; char *dest_filename; char *orig_filename; /* Special hack for gpg, see below. */ int utf8strings_seen = 0; /* FIXME. Throughout the function, do better error reporting. */ if (!gc_component[component].option_config_filename) gc_error (1, 0, "name of config file for %s is not known\n", gc_component[component].name); dest_filename = make_absfilename (gnupg_homedir (), gc_component[component].option_config_filename, NULL); src_filename = xasprintf ("%s.%s.%i.new", dest_filename, GPGCONF_NAME, (int)getpid ()); orig_filename = xasprintf ("%s.%s.%i.bak", dest_filename, GPGCONF_NAME, (int)getpid ()); #ifdef HAVE_W32_SYSTEM res = copy_file (dest_filename, orig_filename); #else res = link (dest_filename, orig_filename); #endif if (res < 0 && errno != ENOENT) { xfree (dest_filename); xfree (src_filename); xfree (orig_filename); return -1; } if (res < 0) { xfree (orig_filename); orig_filename = NULL; } /* We now initialize the return strings, so the caller can do the cleanup for us. */ *src_filenamep = src_filename; *dest_filenamep = dest_filename; *orig_filenamep = orig_filename; /* Use open() so that we can use O_EXCL. * FIXME: gpgrt has an x flag for quite some time now - use that. */ fd = gnupg_open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); if (fd < 0) return -1; src_file = gpgrt_fdopen (fd, "w"); res = errno; if (!src_file) { gpg_err_set_errno (res); return -1; } /* Only if ORIG_FILENAME is not NULL did the configuration file exist already. In this case, we will copy its content into the new configuration file, changing it to our liking in the process. */ if (orig_filename) { dest_file = gpgrt_fopen (dest_filename, "r"); if (!dest_file) goto change_one_err; while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { int disable = 0; char *start; if (!strncmp (marker, line, sizeof (marker) - 1)) { if (!in_marker) in_marker = 1; else break; } else if (component == GC_COMPONENT_GPG && in_marker && ! strcmp ("utf8-strings\n", line)) { /* Strip duplicated entries. */ if (utf8strings_seen) disable = 1; else utf8strings_seen = 1; } start = line; while (*start == ' ' || *start == '\t') start++; if (*start && *start != '\r' && *start != '\n' && *start != '#') { char *end; char saved_end; end = start; while (*end && *end != ' ' && *end != '\t' && *end != '\r' && *end != '\n' && *end != '#') end++; saved_end = *end; *end = '\0'; option = find_option (component, start); *end = saved_end; if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) || option->new_value)) disable = 1; } if (disable) { if (!in_marker) { gpgrt_fprintf (src_file, "# %s disabled this option here at %s\n", GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# %s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } else { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } if (!in_marker) { /* There was no marker. This is the first time we edit the file. We add our own marker at the end of the file and proceed. Note that we first write a newline, this guards us against files which lack the newline at the end of the last line, while it doesn't hurt us in all other cases. */ gpgrt_fprintf (src_file, "\n%s\n", marker); if (gpgrt_ferror (src_file)) goto change_one_err; } /* At this point, we have copied everything up to the end marker into the new file, except for the options we are going to change. Now, dump the changed options (except for those we are going to revert to their default), and write the end marker, possibly followed by the rest of the original file. */ /* We have to turn on UTF8 strings for GnuPG. */ if (component == GC_COMPONENT_GPG && ! utf8strings_seen) gpgrt_fprintf (src_file, "utf8-strings\n"); option = gc_component[component].options; for ( ; option->name; option++) { if (!option->is_header && option->new_value) { char *arg = option->new_value; do { if (*arg == '\0' || *arg == ',') { gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) { log_assert (*arg == '1'); gpgrt_fprintf (src_file, "%s\n", option->name); if (gpgrt_ferror (src_file)) goto change_one_err; arg++; } else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) { char *end; if (!verbatim) { log_assert (*arg == '"'); arg++; end = strchr (arg, ','); if (end) *end = '\0'; } else end = NULL; gpgrt_fprintf (src_file, "%s %s\n", option->name, verbatim? arg : percent_deescape (arg)); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } else { char *end; end = strchr (arg, ','); if (end) *end = '\0'; gpgrt_fprintf (src_file, "%s %s\n", option->name, arg); if (gpgrt_ferror (src_file)) goto change_one_err; if (end) *end = ','; arg = end; } log_assert (arg == NULL || *arg == '\0' || *arg == ','); if (arg && *arg == ',') arg++; } while (arg && *arg); } } gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); if (gpgrt_ferror (src_file)) goto change_one_err; if (!in_marker) { gpgrt_fprintf (src_file, "# %s edited this configuration file.\n", GPGCONF_DISP_NAME); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# It will disable options before this marked " "block, but it will\n"); if (gpgrt_ferror (src_file)) goto change_one_err; gpgrt_fprintf (src_file, "# never change anything below these lines.\n"); if (gpgrt_ferror (src_file)) goto change_one_err; } if (dest_file) { while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0) { gpgrt_fprintf (src_file, "%s", line); if (gpgrt_ferror (src_file)) goto change_one_err; } if (length < 0 || gpgrt_ferror (dest_file)) goto change_one_err; } xfree (line); line = NULL; res = gpgrt_fclose (src_file); if (res) { res = errno; close (fd); if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } close (fd); if (dest_file) { res = gpgrt_fclose (dest_file); if (res) return -1; } return 0; change_one_err: xfree (line); res = errno; if (src_file) { gpgrt_fclose (src_file); close (fd); } if (dest_file) gpgrt_fclose (dest_file); gpg_err_set_errno (res); return -1; } /* Common code for gc_component_change_options and * gc_process_gpgconf_conf. If VERBATIM is set the profile parsing * mode is used. */ static void change_one_value (gc_component_id_t component, gc_option_t *option, int *r_runtime, unsigned long flags, char *new_value, int verbatim) { unsigned long new_value_nr = 0; option_check_validity (component, option, flags, new_value, &new_value_nr, verbatim); if (option->runtime) *r_runtime = 1; option->new_flags = flags; if (!(flags & GC_OPT_FLAG_DEFAULT)) { if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE && option->is_list) { char *str; /* We convert the number to a list of 1's for convenient list handling. */ log_assert (new_value_nr > 0); option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1); str = option->new_value; *(str++) = '1'; while (--new_value_nr > 0) { *(str++) = ','; *(str++) = '1'; } *(str++) = '\0'; } else option->new_value = xstrdup (new_value); } } /* Read the modifications from IN and apply them. If IN is NULL the modifications are expected to already have been set to the global table. If VERBATIM is set the profile mode is used. */ void gc_component_change_options (int component, estream_t in, estream_t out, int verbatim) { int err = 0; int block = 0; int runtime = 0; char *src_filename = NULL; char *dest_filename = NULL; char *orig_filename = NULL; gc_option_t *option; char *line = NULL; size_t line_len = 0; ssize_t length; if (component == GC_COMPONENT_PINENTRY) return; /* Dummy component for now. */ if (in) { /* Read options from the file IN. */ while ((length = es_read_line (in, &line, &line_len, NULL)) > 0) { char *linep; unsigned long flags = 0; char *new_value = ""; /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; linep = strchr (line, ':'); if (linep) *(linep++) = '\0'; /* Extract additional flags. Default to none. */ if (linep) { char *end; char *tail; end = strchr (linep, ':'); if (end) *(end++) = '\0'; gpg_err_set_errno (0); flags = strtoul (linep, &tail, 0); if (errno) gc_error (1, errno, "malformed flags in option %s", line); if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) gc_error (1, 0, "garbage after flags in option %s", line); linep = end; } /* Don't allow setting of the no change flag. */ flags &= ~GC_OPT_FLAG_NO_CHANGE; /* Extract default value, if present. Default to empty if not. */ if (linep) { char *end; end = strchr (linep, ':'); if (end) *(end++) = '\0'; new_value = linep; linep = end; } option = find_option (component, line); if (!option) gc_error (1, 0, "unknown option %s", line); if (option->no_change) { gc_error (0, 0, "ignoring new value for option %s", option->name); continue; } change_one_value (component, option, &runtime, flags, new_value, 0); } if (length < 0 || gpgrt_ferror (in)) gc_error (1, errno, "error reading stream 'in'"); } /* Now that we have collected and locally verified the changes, write them out to new configuration files, verify them externally, and then commit them. */ option = gc_component[component].options; while (option && option->name) { /* Go on if there is nothing to do. */ if (src_filename || !(option->new_flags || option->new_value)) { option++; continue; } if (gc_component[component].program) { err = change_options_program (component, &src_filename, &dest_filename, &orig_filename, verbatim); if (! err) { /* External verification. */ err = gc_component_check_options (component, out, src_filename); if (err) { gc_error (0, 0, _("External verification of component %s failed"), gc_component[component].name); gpg_err_set_errno (EINVAL); } } } if (err) break; option++; } /* We are trying to atomically commit all changes. Unfortunately, we cannot rely on gnupg_rename_file to manage the signals for us, doing so would require us to pass NULL as BLOCK to any subsequent call to it. Instead, we just manage the signal handling manually. */ block = 1; gnupg_block_all_signals (); if (!err && !opt.dry_run) { if (src_filename) { /* FIXME: Make a verification here. */ log_assert (dest_filename); if (orig_filename) err = gnupg_rename_file (src_filename, dest_filename, NULL); else { #ifdef HAVE_W32_SYSTEM /* We skip the unlink if we expect the file not to be * there. */ err = gnupg_rename_file (src_filename, dest_filename, NULL); #else /* HAVE_W32_SYSTEM */ /* This is a bit safer than rename() because we expect * DEST_FILENAME not to be there. If it happens to be * there, this will fail. */ err = link (src_filename, dest_filename); if (!err) err = unlink (src_filename); #endif /* !HAVE_W32_SYSTEM */ } if (!err) { xfree (src_filename); src_filename = NULL; } } } if (err || opt.dry_run) { int saved_errno = errno; /* An error occurred or a dry-run is requested. */ if (src_filename) { /* The change was not yet committed. */ unlink (src_filename); if (orig_filename) unlink (orig_filename); } else { /* The changes were already committed. FIXME: This is a tad dangerous, as we don't know if we don't overwrite a version of the file that is even newer than the one we just installed. */ if (orig_filename) gnupg_rename_file (orig_filename, dest_filename, NULL); else unlink (dest_filename); } if (err) gc_error (1, saved_errno, "could not commit changes"); /* Fall-through for dry run. */ goto leave; } /* If it all worked, notify the daemons of the changes. */ if (opt.runtime) do_runtime_change (component, 0); /* Move the per-process backup file into its place. */ if (orig_filename) { char *backup_filename; log_assert (dest_filename); backup_filename = xasprintf ("%s.%s.bak", dest_filename, GPGCONF_NAME); gnupg_rename_file (orig_filename, backup_filename, NULL); xfree (backup_filename); } leave: if (block) gnupg_unblock_all_signals (); xfree (line); xfree (src_filename); xfree (dest_filename); xfree (orig_filename); } /* Check whether USER matches the current user or one of its group. This function may change USER. Returns true is there is a match. */ static int key_matches_user_or_group (char *user) { char *group; if (*user == '*' && user[1] == 0) return 1; /* A single asterisk matches all users. */ group = strchr (user, ':'); if (group) *group++ = 0; #ifdef HAVE_W32_SYSTEM /* Under Windows we don't support groups. */ if (group && *group) gc_error (0, 0, _("Note that group specifications are ignored\n")); if (*user) { static char *my_name; if (!my_name) { char tmp[1]; DWORD size = 1; GetUserNameA (tmp, &size); my_name = xmalloc (size); if (!GetUserNameA (my_name, &size)) gc_error (1,0, "error getting current user name: %s", w32_strerror (-1)); } if (!strcmp (user, my_name)) return 1; /* Found. */ } #else /*!HAVE_W32_SYSTEM*/ /* First check whether the user matches. */ if (*user) { static char *my_name; if (!my_name) { struct passwd *pw = getpwuid ( getuid () ); if (!pw) gc_error (1, errno, "getpwuid failed for current user"); my_name = xstrdup (pw->pw_name); } if (!strcmp (user, my_name)) return 1; /* Found. */ } /* If that failed, check whether a group matches. */ if (group && *group) { static char *my_group; static char **my_supgroups; int n; if (!my_group) { struct group *gr = getgrgid ( getgid () ); if (!gr) gc_error (1, errno, "getgrgid failed for current user"); my_group = xstrdup (gr->gr_name); } if (!strcmp (group, my_group)) return 1; /* Found. */ if (!my_supgroups) { int ngids; gid_t *gids; ngids = getgroups (0, NULL); gids = xcalloc (ngids+1, sizeof *gids); ngids = getgroups (ngids, gids); if (ngids < 0) gc_error (1, errno, "getgroups failed for current user"); my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups); for (n=0; n < ngids; n++) { struct group *gr = getgrgid ( gids[n] ); if (!gr) gc_error (1, errno, "getgrgid failed for supplementary group"); my_supgroups[n] = xstrdup (gr->gr_name); } xfree (gids); } for (n=0; my_supgroups[n]; n++) if (!strcmp (group, my_supgroups[n])) return 1; /* Found. */ } #endif /*!HAVE_W32_SYSTEM*/ return 0; /* No match. */ } /* Read and process the global configuration file for gpgconf. This optional file is used to update our internal tables at runtime and may also be used to set new default values. If FNAME is NULL the default name will be used. With UPDATE set to true the internal tables are actually updated; if not set, only a syntax check is done. If DEFAULTS is true the global options are written to the configuration files. If LISTFP is set, no changes are done but the configuration file is printed to LISTFP in a colon separated format. Returns 0 on success or if the config file is not present; -1 is returned on error. */ int gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults, estream_t listfp) { int result = 0; char *line = NULL; size_t line_len = 0; ssize_t length; gpgrt_stream_t config; int lineno = 0; int in_rule = 0; int got_match = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id; char *fname; if (fname_arg) fname = xstrdup (fname_arg); else fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf", NULL); config = gpgrt_fopen (fname, "r"); if (!config) { /* Do not print an error if the file is not available, except when running in syntax check mode. */ if (errno != ENOENT || !update) { gc_error (0, errno, "can't open global config file '%s'", fname); result = -1; } xfree (fname); return result; } while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0) { char *key, *compname, *option, *flags, *value; char *empty; gc_option_t *option_info = NULL; char *p; int is_continuation; lineno++; key = line; while (*key == ' ' || *key == '\t') key++; if (!*key || *key == '#' || *key == '\r' || *key == '\n') continue; is_continuation = (key != line); /* Parse the key field. */ if (!is_continuation && got_match) break; /* Finish after the first match. */ else if (!is_continuation) { in_rule = 0; for (p=key+1; *p && !strchr (" \t\r\n", *p); p++) ; if (!*p) { gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno); result = -1; gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing rule", GPG_ERR_SYNTAX, fname, lineno); continue; } *p++ = 0; compname = p; } else if (!in_rule) { gc_error (0, 0, "continuation but no rule at '%s', line %d", fname, lineno); result = -1; continue; } else { compname = key; key = NULL; } in_rule = 1; /* Parse the component. */ while (*compname == ' ' || *compname == '\t') compname++; for (p=compname; *p && !strchr (" \t\r\n", *p); p++) ; if (p == compname) { gc_error (0, 0, "missing component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " " missing component", GPG_ERR_NO_NAME, fname, lineno); result = -1; continue; } empty = p; *p++ = 0; option = p; component_id = gc_component_find (compname); if (component_id < 0) { gc_error (0, 0, "unknown component at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown component", GPG_ERR_UNKNOWN_NAME, fname, lineno); result = -1; } /* Parse the option name. */ while (*option == ' ' || *option == '\t') option++; for (p=option; *p && !strchr (" \t\r\n", *p); p++) ; if (p == option) { gc_error (0, 0, "missing option at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "missing option", GPG_ERR_INV_NAME, fname, lineno); result = -1; continue; } *p++ = 0; flags = p; if ( component_id != -1) { /* We need to make sure that we got the option list for the * component. */ if (!gc_component[component_id].options) gc_component_retrieve_options (component_id); option_info = find_option (component_id, option); if (!option_info) { gc_error (0, 0, "unknown option '%s' at '%s', line %d", option, fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "unknown option", GPG_ERR_UNKNOWN_OPTION, fname, lineno); result = -1; } } /* Parse the optional flags. */ while (*flags == ' ' || *flags == '\t') flags++; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { gc_error (0, 0, "syntax error in rule at '%s', line %d", fname, lineno); gpgconf_write_status (STATUS_WARNING, "gpgconf.conf %d file '%s' line %d " "syntax error in rule", GPG_ERR_SYNTAX, fname, lineno); result = -1; continue; } *p++ = 0; value = p; } else /* No flags given. */ { value = flags; flags = NULL; } /* Parse the optional value. */ while (*value == ' ' || *value == '\t') value++; for (p=value; *p && !strchr ("\r\n", *p); p++) ; if (p == value) value = empty; /* No value given; let it point to an empty string. */ else { /* Strip trailing white space. */ *p = 0; for (p--; p > value && (*p == ' ' || *p == '\t'); p--) *p = 0; } /* Check flag combinations. */ if (!flags) ; else if (!strcmp (flags, "default")) { if (*value) { gc_error (0, 0, "flag \"default\" may not be combined " "with a value at '%s', line %d", fname, lineno); result = -1; } } else if (!strcmp (flags, "change")) ; else if (!strcmp (flags, "no-change")) ; else { gc_error (0, 0, "unknown flag at '%s', line %d", fname, lineno); result = -1; } /* In list mode we print out all records. */ if (listfp && !result) { /* If this is a new ruleset, print a key record. */ if (!is_continuation) { char *group = strchr (key, ':'); if (group) { *group++ = 0; if ((p = strchr (group, ':'))) *p = 0; /* We better strip any extra stuff. */ } es_fprintf (listfp, "k:%s:", gc_percent_escape (key)); es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):""); } /* All other lines are rule records. */ es_fprintf (listfp, "r:::%s:%s:%s:", gc_component[component_id].name, option_info->name? option_info->name : "", flags? flags : ""); if (value != empty) es_fprintf (listfp, "\"%s", gc_percent_escape (value)); es_putc ('\n', listfp); } /* Check whether the key matches but do this only if we are not running in syntax check mode. */ if ( update && !result && !listfp && (got_match || (key && key_matches_user_or_group (key))) ) { int newflags = 0; got_match = 1; /* Apply the flags from gpgconf.conf. */ if (!flags) ; else if (!strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; else if (!strcmp (flags, "no-change")) option_info->no_change = 1; else if (!strcmp (flags, "change")) option_info->no_change = 0; if (defaults) { /* Here we explicitly allow updating the value again. */ if (newflags) { option_info->new_flags = 0; } if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (component_id, option_info, runtime, newflags, value, 0); } } } if (length < 0 || gpgrt_ferror (config)) { gc_error (0, errno, "error reading from '%s'", fname); result = -1; } if (gpgrt_fclose (config)) gc_error (0, errno, "error closing '%s'", fname); xfree (line); /* If it all worked, process the options. */ if (!result && update && defaults && !listfp) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 0); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname); return result; } /* * Apply the profile FNAME to all known configure files. */ gpg_error_t gc_apply_profile (const char *fname) { gpg_error_t err; char *fname_buffer = NULL; char *line = NULL; size_t line_len = 0; ssize_t length; estream_t fp; int lineno = 0; int runtime[GC_COMPONENT_NR] = { 0 }; int component_id = -1; int skip_section = 0; int error_count = 0; int newflags; if (!fname) fname = "-"; if (!(!strcmp (fname, "-") || strchr (fname, '/') #ifdef HAVE_W32_SYSTEM || strchr (fname, '\\') #endif || strchr (fname, '.'))) { /* FNAME looks like a standard profile name. Check whether one * is installed and use that instead of the given file name. */ fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S, fname, ".prf", NULL); if (!gnupg_access (fname_buffer, F_OK)) fname = fname_buffer; } fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); log_error ("can't open '%s': %s\n", fname, gpg_strerror (err)); return err; } if (opt.verbose) log_info ("applying profile '%s'\n", fname); err = 0; while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0) { char *name, *flags, *value; gc_option_t *option_info = NULL; char *p; lineno++; name = line; while (*name == ' ' || *name == '\t') name++; if (!*name || *name == '#' || *name == '\r' || *name == '\n') continue; trim_trailing_spaces (name); /* Check whether this is a new section. */ if (*name == '[') { name++; skip_section = 0; /* New section: Get the name of the component. */ p = strchr (name, ']'); if (!p) { error_count++; log_info ("%s:%d:%d: error: syntax error in section tag\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } *p++ = 0; if (*p) log_info ("%s:%d:%d: warning: garbage after section tag\n", fname, lineno, (int)(p - line)); trim_spaces (name); component_id = gc_component_find (name); if (component_id < 0) { log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n", fname, lineno, (int)(name - line), name ); skip_section = 1; } continue; } if (skip_section) continue; if (component_id < 0) { error_count++; log_info ("%s:%d:%d: error: not in a valid section\n", fname, lineno, (int)(name - line)); skip_section = 1; continue; } /* Parse the option name. */ for (p = name; *p && !spacep (p); p++) ; *p++ = 0; value = p; option_info = find_option (component_id, name); if (!option_info) { error_count++; log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n", fname, lineno, (int)(name - line), name, gc_component[component_id].name); continue; } /* Parse the optional flags. */ trim_spaces (value); flags = value; if (*flags == '[') { flags++; p = strchr (flags, ']'); if (!p) { log_info ("%s:%d:%d: warning: invalid flag specification\n", fname, lineno, (int)(p - line)); continue; } *p++ = 0; value = p; trim_spaces (value); } else /* No flags given. */ flags = NULL; /* Set required defaults. */ if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE && !*value) value = "1"; /* Check and save this option. */ newflags = 0; if (flags && !strcmp (flags, "default")) newflags |= GC_OPT_FLAG_DEFAULT; if (newflags) option_info->new_flags = 0; if (*value) { xfree (option_info->new_value); option_info->new_value = NULL; } change_one_value (component_id, option_info, runtime, newflags, value, 1); } if (length < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); error_count++; log_error (_("%s:%u: read error: %s\n"), fname, lineno, gpg_strerror (err)); } if (es_fclose (fp)) log_error (_("error closing '%s'\n"), fname); if (error_count) log_error (_("error parsing '%s'\n"), fname); xfree (line); /* If it all worked, process the options. */ if (!err) { /* We need to switch off the runtime update, so that we can do it later all at once. */ int save_opt_runtime = opt.runtime; opt.runtime = 0; for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) { gc_component_change_options (component_id, NULL, NULL, 1); } opt.runtime = save_opt_runtime; if (opt.runtime) { for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++) if (runtime[component_id] && gc_component[component_id].runtime_change) (*gc_component[component_id].runtime_change) (0); } } xfree (fname_buffer); return err; } diff --git a/tools/gpgconf.c b/tools/gpgconf.c index ac709ae21..301c1838f 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,1826 +1,1825 @@ /* gpgconf.c - Configuration utility for GnuPG * Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc. * Copyright (C) 2016 g10 Code GmbH. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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: GPL-3.0-or-later */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define INCLUDED_BY_MAIN_MODULE 1 #include "gpgconf.h" #include "../common/i18n.h" #include "../common/sysutils.h" #include "../common/init.h" #include "../common/status.h" #include "../common/exechelp.h" #include "../common/dotlock.h" #ifdef HAVE_W32_SYSTEM #include <windows.h> #endif /* Constants to identify the commands and options. */ enum cmd_and_opt_values { aNull = 0, oDryRun = 'n', oOutput = 'o', oQuiet = 'q', oVerbose = 'v', oRuntime = 'r', oComponent = 'c', oNull = '0', aListDirs = 'L', aKill = 'K', aReload = 'R', aShowVersions = 'V', aShowConfigs = 'X', oNoVerbose = 500, oHomedir, oBuilddir, oStatusFD, oShowSocket, oChUid, aListComponents, aCheckPrograms, aListOptions, aChangeOptions, aCheckOptions, aApplyDefaults, aListConfig, aCheckConfig, aQuerySWDB, aLaunch, aCreateSocketDir, aRemoveSocketDir, aApplyProfile, aShowCodepages, aDotlockLock, aDotlockUnlock }; /* The list of commands and options. */ static gpgrt_opt_t opts[] = { ARGPARSE_group (300, N_("@Commands:\n ")), ARGPARSE_c (aListComponents, "list-components", N_("list all components")), ARGPARSE_c (aCheckPrograms, "check-programs", N_("check all programs")), ARGPARSE_c (aListOptions, "list-options", N_("|COMPONENT|list options")), ARGPARSE_c (aChangeOptions, "change-options", N_("|COMPONENT|change options")), ARGPARSE_c (aCheckOptions, "check-options", N_("|COMPONENT|check options")), ARGPARSE_c (aApplyDefaults, "apply-defaults", N_("apply global default values")), ARGPARSE_c (aApplyProfile, "apply-profile", N_("|FILE|update configuration files using FILE")), ARGPARSE_c (aListDirs, "list-dirs", N_("get the configuration directories for @GPGCONF@")), ARGPARSE_c (aListConfig, "list-config", N_("list global configuration file")), ARGPARSE_c (aCheckConfig, "check-config", N_("check global configuration file")), ARGPARSE_c (aQuerySWDB, "query-swdb", N_("query the software version database")), ARGPARSE_c (aReload, "reload", N_("reload all or a given component")), ARGPARSE_c (aLaunch, "launch", N_("launch a given component")), ARGPARSE_c (aKill, "kill", N_("kill a given component")), ARGPARSE_c (aCreateSocketDir, "create-socketdir", "@"), ARGPARSE_c (aRemoveSocketDir, "remove-socketdir", "@"), ARGPARSE_c (aShowVersions, "show-versions", ""), ARGPARSE_c (aShowConfigs, "show-configs", ""), /* hidden commands: for debugging */ ARGPARSE_c (aShowCodepages, "show-codepages", "@"), ARGPARSE_c (aDotlockLock, "lock", "@"), ARGPARSE_c (aDotlockUnlock, "unlock", "@"), ARGPARSE_header (NULL, N_("@\nOptions:\n ")), ARGPARSE_s_s (oOutput, "output", N_("use as output file")), ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")), ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")), ARGPARSE_s_n (oRuntime, "runtime", N_("activate changes at runtime, if possible")), ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")), /* hidden options */ ARGPARSE_s_s (oHomedir, "homedir", "@"), ARGPARSE_s_s (oBuilddir, "build-prefix", "@"), ARGPARSE_s_n (oNull, "null", "@"), ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"), ARGPARSE_s_n (oShowSocket, "show-socket", "@"), ARGPARSE_s_s (oChUid, "chuid", "@"), ARGPARSE_end () }; #define CUTLINE_FMT \ "--8<---------------cut here---------------%s------------->8---\n" /* The stream to output the status information. Status Output is disabled if * this is NULL. */ static estream_t statusfp; static void show_versions (estream_t fp); static void show_configs (estream_t fp); /* Print usage information and provide strings for help. */ static const char * my_strusage( int level ) { const char *p; switch (level) { case 9: p = "GPL-3.0-or-later"; break; case 11: p = "@GPGCONF@ (@GNUPG@)"; break; case 13: p = VERSION; break; case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; case 17: p = PRINTABLE_OS_NAME; break; case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; case 1: case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)"); break; case 41: p = _("Syntax: @GPGCONF@ [options]\n" "Manage configuration options for tools of the @GNUPG@ system\n"); break; default: p = NULL; break; } return p; } /* Return the fp for the output. This is usually stdout unless --output has been used. In the latter case this function opens that file. */ static estream_t get_outfp (estream_t *fp) { if (!*fp) { if (opt.outfile) { *fp = es_fopen (opt.outfile, "w"); if (!*fp) gc_error (1, errno, "can not open '%s'", opt.outfile); } else *fp = es_stdout; } return *fp; } /* Set the status FD. */ static void set_status_fd (int fd) { static int last_fd = -1; if (fd != -1 && last_fd == fd) return; if (statusfp && statusfp != es_stdout && statusfp != es_stderr) es_fclose (statusfp); statusfp = NULL; if (fd == -1) return; if (fd == 1) statusfp = es_stdout; else if (fd == 2) statusfp = es_stderr; else statusfp = es_fdopen (fd, "w"); if (!statusfp) { log_fatal ("can't open fd %d for status output: %s\n", fd, gpg_strerror (gpg_error_from_syserror ())); } last_fd = fd; } /* Write a status line with code NO followed by the output of the * printf style FORMAT. The caller needs to make sure that LFs and * CRs are not printed. */ void gpgconf_write_status (int no, const char *format, ...) { va_list arg_ptr; if (!statusfp) return; /* Not enabled. */ es_fputs ("[GNUPG:] ", statusfp); es_fputs (get_status_string (no), statusfp); if (format) { es_putc (' ', statusfp); va_start (arg_ptr, format); es_vfprintf (statusfp, format, arg_ptr); va_end (arg_ptr); } es_putc ('\n', statusfp); } static void list_dirs (estream_t fp, char **names, int show_config_mode) { static struct { const char *name; /* If NULL only a file check will be done. */ const char *(*fnc)(void); const char *extra; } list[] = { { "sysconfdir", gnupg_sysconfdir, NULL }, { "bindir", gnupg_bindir, NULL }, { "libexecdir", gnupg_libexecdir, NULL }, { "libdir", gnupg_libdir, NULL }, { "datadir", gnupg_datadir, NULL }, { "localedir", gnupg_localedir, NULL }, { "socketdir", gnupg_socketdir, NULL }, { "dirmngr-socket", dirmngr_socket_name, NULL,}, { "keyboxd-socket", keyboxd_socket_name, NULL,}, { "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME }, { "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME }, { "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME }, { "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME }, { NULL, gnupg_socketdir, "S.uiserver" }, { "homedir", gnupg_homedir, NULL } }; int idx, j; char *tmp; const char *s; gpg_error_t err; if (show_config_mode) es_fprintf (fp, "#+begin_example\n"); for (idx = 0; idx < DIM (list); idx++) { s = list[idx].fnc (); if (list[idx].extra) { tmp = make_filename (s, list[idx].extra, NULL); s = tmp; } else tmp = NULL; if (!list[idx].name) ; else if (!names) es_fprintf (fp, "%s%s:%s\n", show_config_mode? " ":"", list[idx].name, gc_percent_escape (s)); else { for (j=0; names[j]; j++) if (!strcmp (names[j], list[idx].name)) { if (show_config_mode) es_fputs (" ", fp); es_fputs (s, fp); es_putc (opt.null? '\0':'\n', fp); } } /* In show config mode check that the socket files are accessible. */ if (list[idx].extra && show_config_mode) { estream_t tmpfp; tmpfp = es_fopen (s, "rb"); if (tmpfp) es_fclose (tmpfp); /* All fine - we can read that file. */ else if ((err=gpg_error_from_syserror ()) == GPG_ERR_ENOENT || err == GPG_ERR_ENXIO) ; /* No such file/ No such device or address - this is okay. */ else es_fprintf (fp, "# Warning: error reading existing file '%s': %s\n", s, gpg_strerror (err)); } xfree (tmp); } if (show_config_mode) es_fprintf (fp, "#+end_example\n"); #ifdef HAVE_W32_SYSTEM tmp = read_w32_registry_string (NULL, gnupg_registry_dir (), "HomeDir"); if (tmp) { int hkcu = 0; int hklm = 0; xfree (tmp); if ((tmp = read_w32_registry_string ("HKEY_CURRENT_USER", gnupg_registry_dir (), "HomeDir"))) { xfree (tmp); hkcu = 1; } if ((tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE", gnupg_registry_dir (), "HomeDir"))) { xfree (tmp); hklm = 1; } es_fflush (fp); if (show_config_mode) es_fprintf (fp, "\n" "Note: homedir taken from registry key %s%s\\%s:%s\n" "\n", hkcu?"HKCU":"", hklm?"HKLM":"", gnupg_registry_dir (), "HomeDir"); else log_info ("Warning: homedir taken from registry key (%s:%s) in%s%s\n", gnupg_registry_dir (), "HomeDir", hkcu?" HKCU":"", hklm?" HKLM":""); } else if ((tmp = read_w32_registry_string (NULL, gnupg_registry_dir (), NULL))) { xfree (tmp); es_fflush (fp); if (show_config_mode) es_fprintf (fp, "\n" "Note: registry %s without value in HKCU or HKLM\n" "\n", GNUPG_REGISTRY_DIR); else log_info ("Warning: registry key (%s) without value in HKCU or HKLM\n", gnupg_registry_dir ()); } #else /*!HAVE_W32_SYSTEM*/ (void)show_config_mode; #endif /*!HAVE_W32_SYSTEM*/ } /* Check whether NAME is valid argument for query_swdb(). Valid names * start with a letter and contain only alphanumeric characters or an * underscore. */ static int valid_swdb_name_p (const char *name) { if (!name || !*name || !alphap (name)) return 0; for (name++; *name; name++) if (!alnump (name) && *name != '_') return 0; return 1; } /* Query the SWDB file. If necessary and possible this functions asks * the dirmngr to load an updated version of that file. The caller * needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and * optional the currently installed version in CURRENT_VERSION. The * output written to OUT is a colon delimited line with these fields: * * name :: The name of the package * curvers:: The installed version if given. * status :: This value tells the status of the software package * '-' :: No information available * (error or CURRENT_VERSION not given) * '?' :: Unknown NAME * 'u' :: Update available * 'c' :: The version is Current * 'n' :: The current version is already Newer than the * available one. * urgency :: If the value is greater than zero an urgent update is required. * error :: 0 on success or an gpg_err_code_t * Common codes seen: * GPG_ERR_TOO_OLD :: The SWDB file is to old to be used. * GPG_ERR_ENOENT :: The SWDB file is not available. * GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file. * filedate:: Date of the swdb file (yyyymmddThhmmss) * verified:: Date we checked the validity of the file (yyyyymmddThhmmss) * version :: The version string from the swdb. * reldate :: Release date of that version (yyyymmddThhmmss) * size :: Size of the package in bytes. * hash :: SHA-2 hash of the package. * */ static void query_swdb (estream_t out, const char *name, const char *current_version) { gpg_error_t err; const char *search_name; char *fname = NULL; estream_t fp = NULL; char *line = NULL; char *self_version = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; const char *fields[2]; char *p; gnupg_isotime_t filedate = {0}; gnupg_isotime_t verified = {0}; char *value_ver = NULL; gnupg_isotime_t value_date = {0}; char *value_size = NULL; char *value_sha2 = NULL; unsigned long value_size_ul = 0; int status, i; if (!valid_swdb_name_p (name)) { log_error ("error in package name '%s': %s\n", name, gpg_strerror (GPG_ERR_INV_NAME)); goto leave; } if (!strcmp (name, "gnupg")) search_name = GNUPG_SWDB_TAG; else if (!strcmp (name, "gnupg1")) search_name = "gnupg1"; else search_name = name; if (!current_version && !strcmp (name, "gnupg")) { /* Use our own version but string a possible beta string. */ self_version = xstrdup (PACKAGE_VERSION); p = strchr (self_version, '-'); if (p) *p = 0; current_version = self_version; } if (current_version && (strchr (current_version, ':') || compare_version_strings (current_version, NULL))) { log_error ("error in version string '%s': %s\n", current_version, gpg_strerror (GPG_ERR_INV_ARG)); goto leave; } fname = make_filename (gnupg_homedir (), "swdb.lst", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Note that the parser uses the first occurrence of a matching * values and ignores possible duplicated values. */ maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } /* Strip newline and carriage return, if present. */ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) line[--len] = '\0'; if (split_fields (line, fields, DIM (fields)) < DIM(fields)) continue; /* Skip empty lines and names w/o a value. */ if (*fields[0] == '#') continue; /* Skip comments. */ /* Record the meta data. */ if (!*filedate && !strcmp (fields[0], ".filedate")) { string2isotime (filedate, fields[1]); continue; } if (!*verified && !strcmp (fields[0], ".verified")) { string2isotime (verified, fields[1]); continue; } /* Tokenize the name. */ p = strrchr (fields[0], '_'); if (!p) continue; /* Name w/o an underscore. */ *p++ = 0; /* Wait for the requested name. */ if (!strcmp (fields[0], search_name)) { if (!strcmp (p, "ver") && !value_ver) value_ver = xstrdup (fields[1]); else if (!strcmp (p, "date") && !*value_date) string2isotime (value_date, fields[1]); else if (!strcmp (p, "size") && !value_size) value_size = xstrdup (fields[1]); else if (!strcmp (p, "sha2") && !value_sha2) value_sha2 = xstrdup (fields[1]); } } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); goto leave; } if (!*filedate || !*verified) { err = gpg_error (GPG_ERR_INV_TIME); es_fprintf (out, "%s:%s:-::%u:::::::\n", name, current_version? current_version : "", gpg_err_code (err)); goto leave; } if (!value_ver) { es_fprintf (out, "%s:%s:?:::::::::\n", name, current_version? current_version : ""); goto leave; } if (value_size) { gpg_err_set_errno (0); value_size_ul = strtoul (value_size, &p, 10); if (errno) value_size_ul = 0; else if (*p == 'k') value_size_ul *= 1024; } err = 0; status = '-'; if (compare_version_strings (value_ver, NULL)) err = gpg_error (GPG_ERR_INV_VALUE); else if (!current_version) ; else if (!(i = compare_version_strings (value_ver, current_version))) status = 'c'; else if (i > 0) status = 'u'; else status = 'n'; es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n", name, current_version? current_version : "", status, err, filedate, verified, value_ver, value_date, value_size_ul, value_sha2? value_sha2 : ""); leave: xfree (value_ver); xfree (value_size); xfree (value_sha2); xfree (line); es_fclose (fp); xfree (fname); xfree (self_version); } #if !defined(HAVE_W32_SYSTEM) /* dotlock tool to handle dotlock by command line DO_LOCK: 1 for to lock, 0 for unlock FILENAME: filename for the dotlock */ static void dotlock_tool (int do_lock, const char *filename) { dotlock_t h; unsigned int flags = DOTLOCK_LOCK_BY_PARENT; if (!do_lock) flags |= DOTLOCK_LOCKED; h = dotlock_create (filename, flags); if (!h) { if (do_lock) log_error ("error creating the lock file\n"); else log_error ("no lock file found\n"); return; } if (do_lock) { if (dotlock_take (h, 0)) log_error ("error taking the lock\n"); } else dotlock_release (h); dotlock_destroy (h); } #endif /* gpgconf main. */ int main (int argc, char **argv) { gpg_error_t err; gpgrt_argparse_t pargs; const char *fname; int no_more_options = 0; enum cmd_and_opt_values cmd = 0; estream_t outfp = NULL; int show_socket = 0; const char *changeuser = NULL; early_system_init (); gnupg_reopen_std (GPGCONF_NAME); gpgrt_set_strusage (my_strusage); log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY); /* Make sure that our subsystems are ready. */ i18n_init(); init_common_subsystems (&argc, &argv); gc_components_init (); /* Parse the command line. */ pargs.argc = &argc; pargs.argv = &argv; pargs.flags = ARGPARSE_FLAG_KEEP; while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts)) { switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oRuntime: opt.runtime = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break; case oNull: opt.null = 1; break; case oStatusFD: set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1)); break; case oShowSocket: show_socket = 1; break; case oChUid: changeuser = pargs.r.ret_str; break; case aListDirs: case aListComponents: case aCheckPrograms: case aListOptions: case aChangeOptions: case aCheckOptions: case aApplyDefaults: case aApplyProfile: case aListConfig: case aCheckConfig: case aQuerySWDB: case aReload: case aLaunch: case aKill: case aCreateSocketDir: case aRemoveSocketDir: case aShowVersions: case aShowConfigs: case aShowCodepages: case aDotlockLock: case aDotlockUnlock: cmd = pargs.r_opt; break; default: pargs.err = 2; break; } } gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ if (log_get_errorcount (0)) gpgconf_failure (GPG_ERR_USER_2); /* Print a warning if an argument looks like an option. */ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) { int i; for (i=0; i < argc; i++) if (argv[i][0] == '-' && argv[i][1] == '-') log_info (_("Note: '%s' is not considered an option\n"), argv[i]); } fname = argc ? *argv : NULL; /* If requested switch to the requested user or die. */ if (changeuser && (err = gnupg_chuid (changeuser, 0))) gpgconf_failure (err); /* Set the configuraton directories for use by gpgrt_argparser. We * don't have a configuration file for this program but we have code * which reads the component's config files. */ gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); switch (cmd) { case aListComponents: default: /* List all components. */ gc_component_list_components (get_outfp (&outfp)); break; case aCheckPrograms: /* Check all programs. */ gc_check_programs (get_outfp (&outfp)); break; case aListOptions: case aChangeOptions: case aCheckOptions: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else { int idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } if (cmd == aCheckOptions) gc_component_check_options (idx, get_outfp (&outfp), NULL); else { gc_component_retrieve_options (idx); if (gc_process_gpgconf_conf (NULL, 1, 0, NULL)) gpgconf_failure (0); if (cmd == aListOptions) gc_component_list_options (idx, get_outfp (&outfp)); else if (cmd == aChangeOptions) gc_component_change_options (idx, es_stdin, get_outfp (&outfp), 0); } } break; case aLaunch: case aKill: if (!fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("Need one component argument"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } else if (!strcmp (fname, "all")) { if (cmd == aLaunch) { if (gc_component_launch (-1)) gpgconf_failure (0); } else { gc_component_kill (-1); } } else { /* Launch/Kill a given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else if (cmd == aLaunch) { err = gc_component_launch (idx); if (show_socket) { char *names[2]; if (idx == GC_COMPONENT_GPG_AGENT) names[0] = "agent-socket"; else if (idx == GC_COMPONENT_DIRMNGR) names[0] = "dirmngr-socket"; else if (idx == GC_COMPONENT_KEYBOXD) names[0] = "keyboxd-socket"; else names[0] = NULL; names[1] = NULL; get_outfp (&outfp); list_dirs (outfp, names, 0); } if (err) gpgconf_failure (0); } else { /* We don't error out if the kill failed because this command should do nothing if the component is not running. */ gc_component_kill (idx); } } break; case aReload: if (!fname || !strcmp (fname, "all")) { /* Reload all. */ gc_component_reload (-1); } else { /* Reload given component. */ int idx; idx = gc_component_find (fname); if (idx < 0) { es_fputs (_("Component not found"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (0); } else { gc_component_reload (idx); } } break; case aListConfig: if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp))) gpgconf_failure (0); break; case aCheckConfig: if (gc_process_gpgconf_conf (fname, 0, 0, NULL)) gpgconf_failure (0); break; case aApplyDefaults: if (fname) { es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME); es_putc ('\n', es_stderr); es_fputs (_("No argument allowed"), es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_USER_2); } if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); gc_component_retrieve_options (-1); if (gc_process_gpgconf_conf (NULL, 1, 1, NULL)) gpgconf_failure (0); break; case aApplyProfile: if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK)) gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet); gc_component_retrieve_options (-1); if (gc_apply_profile (fname)) gpgconf_failure (0); break; case aListDirs: /* Show the system configuration directories for gpgconf. */ get_outfp (&outfp); list_dirs (outfp, argc? argv : NULL, 0); break; case aQuerySWDB: /* Query the software version database. */ if (!fname || argc > 2) { es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n", GPGCONF_NAME); gpgconf_failure (GPG_ERR_USER_2); } get_outfp (&outfp); query_swdb (outfp, fname, argc > 1? argv[1] : NULL); break; case aCreateSocketDir: { char *socketdir; unsigned int flags; /* Make sure that the top /run/user/UID/gnupg dir has been * created. */ gnupg_socketdir (); /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 64) && !opt.dry_run) { /* No sub dir - create it. */ if (gnupg_mkdir (socketdir, "-rwx")) gc_error (1, errno, "error creating '%s'", socketdir); /* Try again. */ xfree (socketdir); socketdir = _gnupg_socketdir_internal (1, &flags); } /* Give some info. */ if ( (flags & ~32) || opt.verbose || opt.dry_run) { log_info ("socketdir is '%s'\n", socketdir); if ((flags & 1)) log_info ("\tgeneral error\n"); if ((flags & 2)) log_info ("\tno /run/user dir\n"); if ((flags & 4)) log_info ("\tbad permissions\n"); if ((flags & 8)) log_info ("\tbad permissions (subdir)\n"); if ((flags & 16)) log_info ("\tmkdir failed\n"); if ((flags & 32)) log_info ("\tnon-default homedir\n"); if ((flags & 64)) log_info ("\tno such subdir\n"); if ((flags & 128)) log_info ("\tusing homedir as fallback\n"); } if ((flags & ~32) && !opt.dry_run) gc_error (1, 0, "error creating socket directory"); xfree (socketdir); } break; case aRemoveSocketDir: { char *socketdir; unsigned int flags; /* Check the /var/run dir. */ socketdir = _gnupg_socketdir_internal (1, &flags); if ((flags & 128)) log_info ("ignoring request to remove non /run/user socket dir\n"); else if (opt.dry_run) ; else if (gnupg_rmdir (socketdir)) { /* If the director is not empty we first try to delete * socket files. */ err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY || gpg_err_code (err) == GPG_ERR_EEXIST) { static const char * const names[] = { GPG_AGENT_SOCK_NAME, GPG_AGENT_EXTRA_SOCK_NAME, GPG_AGENT_BROWSER_SOCK_NAME, GPG_AGENT_SSH_SOCK_NAME, SCDAEMON_SOCK_NAME, KEYBOXD_SOCK_NAME, DIRMNGR_SOCK_NAME, TPM2DAEMON_SOCK_NAME }; int i; char *p; for (i=0; i < DIM(names); i++) { p = strconcat (socketdir , "/", names[i], NULL); if (p) gnupg_remove (p); xfree (p); } if (gnupg_rmdir (socketdir)) { err = gpg_error_from_syserror (); gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } } else if (gpg_err_code (err) == GPG_ERR_ENOENT) gc_error (0, 0, "warning: removing '%s' failed: %s", socketdir, gpg_strerror (err)); else gc_error (1, 0, "error removing '%s': %s", socketdir, gpg_strerror (err)); } xfree (socketdir); } break; case aShowVersions: { get_outfp (&outfp); show_versions (outfp); } break; case aShowConfigs: { get_outfp (&outfp); show_configs (outfp); } break; case aShowCodepages: #ifdef HAVE_W32_SYSTEM { get_outfp (&outfp); if (GetConsoleCP () != GetConsoleOutputCP ()) es_fprintf (outfp, "Console: CP%u/CP%u\n", GetConsoleCP (), GetConsoleOutputCP ()); else es_fprintf (outfp, "Console: CP%u\n", GetConsoleCP ()); es_fprintf (outfp, "ANSI: CP%u\n", GetACP ()); es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ()); } #endif break; case aDotlockLock: case aDotlockUnlock: #if !defined(HAVE_W32_SYSTEM) if (!fname) { es_fprintf (es_stderr, "usage: %s --%slock NAME", GPGCONF_NAME, cmd==aDotlockUnlock?"un":""); es_putc ('\n', es_stderr); es_fputs ("Need name of file protected by the lock", es_stderr); es_putc ('\n', es_stderr); gpgconf_failure (GPG_ERR_SYNTAX); } else { char *filename; /* Keybox pubring.db lock is under public-keys.d. */ if (!strcmp (fname, "pubring.db")) fname = "public-keys.d/pubring.db"; filename = make_absfilename (gnupg_homedir (), fname, NULL); dotlock_tool (cmd == aDotlockLock, filename); xfree (filename); } #endif break; } if (outfp != es_stdout) if (es_fclose (outfp)) gc_error (1, errno, "error closing '%s'", opt.outfile); if (log_get_errorcount (0)) gpgconf_failure (0); else gpgconf_write_status (STATUS_SUCCESS, NULL); return 0; } void gpgconf_failure (gpg_error_t err) { log_flush (); if (!err) err = gpg_error (GPG_ERR_GENERAL); gpgconf_write_status (STATUS_FAILURE, "- %u", gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err); exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1); } /* Parse the revision part from the extended version blurb. */ static const char * get_revision_from_blurb (const char *blurb, int *r_len) { const char *s = blurb? blurb : ""; int n; for (; *s; s++) if (*s == '\n' && s[1] == '(') break; if (s) { s += 2; for (n=0; s[n] && s[n] != ' '; n++) ; } else { s = "?"; n = 1; } *r_len = n; return s; } static void show_version_gnupg (estream_t fp, const char *prefix) { char *fname, *p, *p0; size_t n; estream_t verfp; char *line = NULL; size_t line_len = 0; ssize_t length; es_fprintf (fp, "%s%sGnuPG %s (%s)\n%s%s\n", prefix, *prefix?"":"* ", gpgrt_strusage (13), BUILD_REVISION, prefix, gpgrt_strusage (17)); /* Show the GnuPG VS-Desktop version in --show-configs mode */ if (prefix && *prefix) { fname = make_filename (gnupg_bindir (), NULL); n = strlen (fname); if (n > 10 && (!ascii_strcasecmp (fname + n - 10, "/GnuPG/bin") || !ascii_strcasecmp (fname + n - 10, "\\GnuPG\\bin"))) { /* Append VERSION to the ../../ directory. Note that VERSION * is only 7 bytes and thus fits. */ strcpy (fname + n - 9, "VERSION"); verfp = es_fopen (fname, "r"); if (!verfp) es_fprintf (fp, "%s[VERSION file not found]\n", prefix); else { int lnr = 0; p0 = NULL; while ((length = es_read_line (verfp, &line, &line_len, NULL))>0) { lnr++; trim_spaces (line); if (lnr == 1 && *line != '[') { /* Old file format where we look only at the * first line. */ p0 = line; break; } else if (!strncmp (line, "version=", 8)) { p0 = line + 8; break; } } if (length < 0 || es_ferror (verfp)) es_fprintf (fp, "%s[VERSION file read error]\n", prefix); else if (p0) { for (p=p0; *p; p++) if (*p < ' ' || *p > '~' || *p == '[') *p = '?'; es_fprintf (fp, "%s%s\n", prefix, p0); } else es_fprintf (fp, "%s[VERSION file is empty]\n", prefix); es_fclose (verfp); } } xfree (fname); } xfree (line); #ifdef HAVE_W32_SYSTEM { OSVERSIONINFO osvi = { sizeof (osvi) }; GetVersionEx (&osvi); es_fprintf (fp, "%sWindows %lu.%lu build %lu%s%s%s\n", prefix, (unsigned long)osvi.dwMajorVersion, (unsigned long)osvi.dwMinorVersion, (unsigned long)osvi.dwBuildNumber, *osvi.szCSDVersion? " (":"", osvi.szCSDVersion, *osvi.szCSDVersion? ")":"" ); } #endif /*HAVE_W32_SYSTEM*/ } static void show_version_libgcrypt (estream_t fp) { const char *s; int n; s = get_revision_from_blurb (gcry_check_version ("\x01\x01"), &n); es_fprintf (fp, "* Libgcrypt %s (%.*s)\n", gcry_check_version (NULL), n, s); s = gcry_get_config (0, NULL); if (s) es_fputs (s, fp); } static void show_version_gpgrt (estream_t fp) { const char *s; int n; s = get_revision_from_blurb (gpg_error_check_version ("\x01\x01"), &n); es_fprintf (fp, "* GpgRT %s (%.*s)\n", gpg_error_check_version (NULL), n, s); } /* Printing version information for other libraries is problematic * because we don't want to link gpgconf to all these libraries. The * best solution is delegating this to dirmngr which uses libassuan, * libksba, libnpth and ntbtls anyway. */ static void show_versions_via_dirmngr (estream_t fp) { gpg_error_t err; const char *pgmname; const char *argv[2]; estream_t outfp; gnupg_process_t proc; char *line = NULL; size_t line_len = 0; ssize_t length; pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); argv[0] = "--gpgconf-versions"; argv[1] = NULL; - err = gnupg_process_spawn (pgmname, argv, - GNUPG_PROCESS_STDOUT_PIPE, - NULL, NULL, &proc); + err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE, + NULL, &proc); if (err) { log_error ("error spawning %s: %s", pgmname, gpg_strerror (err)); es_fprintf (fp, "[error: can't get further info]\n"); return; } gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL); while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0) { /* Strip newline and carriage return, if present. */ while (length > 0 && (line[length - 1] == '\n' || line[length - 1] == '\r')) line[--length] = '\0'; es_fprintf (fp, "%s\n", line); } if (length < 0 || es_ferror (outfp)) { err = gpg_error_from_syserror (); log_error ("error reading from %s: %s\n", pgmname, gpg_strerror (err)); } if (es_fclose (outfp)) { err = gpg_error_from_syserror (); log_error ("error closing output stream of %s: %s\n", pgmname, gpg_strerror (err)); } err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); log_error ("running %s failed (exitcode=%d): %s\n", pgmname, exitcode, gpg_strerror (err)); es_fprintf (fp, "[error: can't get further info]\n"); } gnupg_process_release (proc); xfree (line); } /* Show all kind of version information. */ static void show_versions (estream_t fp) { show_version_gnupg (fp, ""); es_fputc ('\n', fp); show_version_libgcrypt (fp); es_fputc ('\n', fp); show_version_gpgrt (fp); es_fputc ('\n', fp); show_versions_via_dirmngr (fp); } /* Copy data from file SRC to DST. Returns 0 on success or an error * code on failure. If LISTP is not NULL, that strlist is updated * with the variable or registry key names detected. Flag bit 0 * indicates a registry entry. */ static gpg_error_t my_copy_file (estream_t src, estream_t dst, strlist_t *listp) { gpg_error_t err; char *line = NULL; size_t line_len = 0; ssize_t length; int written; while ((length = es_read_line (src, &line, &line_len, NULL)) > 0) { /* Prefix each line with two spaces but use a comma if the line * starts with a special org-mode character. */ if (*line == '*' || (*line == '#' && line[1] == '+')) es_fputc (',', dst); else es_fputc (' ', dst); es_fputc (' ', dst); written = gpgrt_fwrite (line, 1, length, dst); if (written != length) return gpg_error_from_syserror (); trim_spaces (line); if (*line == '[' && listp) { char **tokens; char *p; for (p=line+1; *p; p++) if (*p != ' ' && *p != '\t') break; if (*p && p[strlen (p)-1] == ']') p[strlen (p)-1] = 0; tokens = strtokenize (p, " \t"); if (!tokens) { err = gpg_error_from_syserror (); log_error ("strtokenize failed: %s\n", gpg_strerror (err)); return err; } /* Check whether we have a getreg or getenv statement and * store the third token to later retrieval. */ if (tokens[0] && tokens[1] && tokens[2] && (!strcmp (tokens[0], "getreg") || !strcmp (tokens[0], "getenv"))) { int isreg = (tokens[0][3] == 'r'); strlist_t sl = *listp; for (sl = *listp; sl; sl = sl->next) if (!strcmp (sl->d, tokens[2]) && (sl->flags & 1) == isreg) break; if (!sl) /* Not yet in the respective list. */ { sl = add_to_strlist (listp, tokens[2]); if (isreg) sl->flags = 1; } } xfree (tokens); } } if (length < 0 || es_ferror (src)) return gpg_error_from_syserror (); if (gpgrt_fflush (dst)) return gpg_error_from_syserror (); return 0; } /* Helper for show_configs */ static void show_configs_one_file (const char *fname, int global, estream_t outfp, strlist_t *listp) { gpg_error_t err; estream_t fp; fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) es_fprintf (outfp, "** %s config \"%s\": %s\n", global? "global":"local", fname, gpg_strerror (err)); } else { es_fprintf (outfp, "** %s config \"%s\"\n#+begin_src\n", global? "global":"local", fname); err = my_copy_file (fp, outfp, listp); es_fprintf (outfp, "\n#+end_src\n"); if (err) log_error ("Error copying file \"%s\": %s\n", fname, gpg_strerror (err)); es_fclose (fp); } } #ifdef HAVE_W32_SYSTEM /* Print registry entries relevant to the GnuPG system and related * software. */ static void show_other_registry_entries (estream_t outfp) { static struct { int group; const char *name; unsigned int prependregkey:1; } names[] = { { 1, "HKLM\\Software\\Gpg4win:Install Directory" }, { 1, "HKLM\\Software\\Gpg4win:Desktop-Version" }, { 1, "HKLM\\Software\\Gpg4win:VS-Desktop-Version" }, { 1, ":HomeDir", 1 }, { 1, ":DefaultLogFile", 1 }, { 2, "\\Software\\Microsoft\\Office\\Outlook\\Addins\\GNU.GpgOL" ":LoadBehavior" }, { 2, "HKCU\\Software\\Microsoft\\Office\\16.0\\Outlook\\Options\\Mail:" "ReadAsPlain" }, { 2, "HKCU\\Software\\Policies\\Microsoft\\Office\\16.0\\Outlook\\" "Options\\Mail:ReadAsPlain" }, { 3, "logFile" }, { 3, "enableDebug" }, { 3, "searchSmimeServers" }, { 3, "smimeInsecureReplyAllowed" }, { 3, "enableSmime" }, { 3, "preferSmime" }, { 3, "encryptDefault" }, { 3, "signDefault" }, { 3, "inlinePGP" }, { 3, "replyCrypt" }, { 3, "autoresolve" }, { 3, "autoretrieve" }, { 3, "automation" }, { 3, "autosecure" }, { 3, "autotrust" }, { 3, "autoencryptUntrusted" }, { 3, "autoimport" }, { 3, "splitBCCMails" }, { 3, "combinedOpsEnabled" }, { 3, "encryptSubject" }, { 0, NULL } }; int idx; int group = 0; char *namebuf = NULL; const char *name; int from_hklm; for (idx=0; (name = names[idx].name); idx++) { char *value; if (names[idx].group == 3) { xfree (namebuf); namebuf = xstrconcat ("\\Software\\GNU\\GpgOL", ":", names[idx].name, NULL); name = namebuf; } else if (names[idx].prependregkey) { xfree (namebuf); namebuf = xstrconcat ("\\", gnupg_registry_dir (), names[idx].name, NULL); name = namebuf; } value = read_w32_reg_string (name, &from_hklm); if (!value) continue; if (names[idx].group != group) { group = names[idx].group; es_fprintf (outfp, "\n%s related:\n", group == 1 ? "GnuPG Desktop" : group == 2 ? "Outlook" : group == 3 ? "\\Software\\GNU\\GpgOL" : "System" ); } if (group == 3) es_fprintf (outfp, " %s=%s%s\n", names[idx].name, value, from_hklm? " [hklm]":""); else es_fprintf (outfp, " %s\n ->%s<-%s\n", name, value, from_hklm? " [hklm]":""); xfree (value); } xfree (namebuf); } /* Print registry entries take from a configuration file. */ static void show_registry_entries_from_file (estream_t outfp) { gpg_error_t err; char *fname; estream_t fp; char *line = NULL; size_t length_of_line = 0; size_t maxlen; ssize_t len; char *value = NULL; int from_hklm; int any = 0; fname = make_filename (gnupg_datadir (), "gpgconf.rnames", NULL); fp = es_fopen (fname, "r"); if (!fp) { err = gpg_error_from_syserror (); if (gpg_err_code (err) != GPG_ERR_ENOENT) log_error ("error opening '%s': %s\n", fname, gpg_strerror (err)); goto leave; } maxlen = 2048; /* Set limit. */ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) { if (!maxlen) { err = gpg_error (GPG_ERR_LINE_TOO_LONG); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); goto leave; } trim_spaces (line); if (*line == '#') continue; xfree (value); value = read_w32_reg_string (line, &from_hklm); if (!value) continue; if (!any) { any = 1; es_fprintf (outfp, "Taken from gpgconf.rnames:\n"); } es_fprintf (outfp, " %s\n ->%s<-%s\n", line, value, from_hklm? " [hklm]":""); } if (len < 0 || es_ferror (fp)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", fname, gpg_strerror (err)); } leave: xfree (value); xfree (line); es_fclose (fp); xfree (fname); } #endif /*HAVE_W32_SYSTEM*/ /* Show all config files. */ static void show_configs (estream_t outfp) { static const char *names[] = { "common.conf", "gpg-agent.conf", "scdaemon.conf", "dirmngr.conf", "gpg.conf", "gpgsm.conf" }; static const char *envvars[] = { "PATH", "http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "LD_LIBRARY_PATH", "LD_PRELOAD", "LD_AUDIT", "LD_ORIGIN_PATH" }; gpg_error_t err; int idx; char *fname; gnupg_dir_t dir; gnupg_dirent_t dir_entry; size_t n; int any, anywarn; strlist_t list = NULL; strlist_t sl; const char *s; int got_gpgconfconf = 0; es_fprintf (outfp, "# gpgconf -X invoked %s%*s-*- org -*-\n\n", isotimestamp (time (NULL)), 28, ""); es_fprintf (outfp, "* General information\n"); es_fprintf (outfp, "** Versions\n"); show_version_gnupg (outfp, " "); es_fprintf (outfp, " Libgcrypt %s\n", gcry_check_version (NULL)); es_fprintf (outfp, " GpgRT %s\n", gpg_error_check_version (NULL)); #ifdef HAVE_W32_SYSTEM es_fprintf (outfp, " Codepages:"); if (GetConsoleCP () != GetConsoleOutputCP ()) es_fprintf (outfp, " %u/%u", GetConsoleCP (), GetConsoleOutputCP ()); else es_fprintf (outfp, " %u", GetConsoleCP ()); es_fprintf (outfp, " %u", GetACP ()); es_fprintf (outfp, " %u\n", GetOEMCP ()); #endif es_fprintf (outfp, "\n\n"); es_fprintf (outfp, "** Directories\n"); list_dirs (outfp, NULL, 1); es_fprintf (outfp, "\n"); es_fprintf (outfp, "** Environment\n#+begin_example\n"); for (idx=0; idx < DIM(envvars); idx++) if ((s = getenv (envvars[idx]))) es_fprintf (outfp, "%s=%s\n", envvars[idx], s); es_fprintf (outfp, "#+end_example\n"); es_fprintf (outfp, "* Config files\n"); fname = make_filename (gnupg_sysconfdir (), "gpgconf.conf", NULL); if (!gnupg_access (fname, F_OK)) { got_gpgconfconf = 1; show_configs_one_file (fname, 1, outfp, &list); es_fprintf (outfp, "\n"); } xfree (fname); for (idx = 0; idx < DIM (names); idx++) { fname = make_filename (gnupg_sysconfdir (), names[idx], NULL); show_configs_one_file (fname, 1, outfp, &list); xfree (fname); fname = make_filename (gnupg_homedir (), names[idx], NULL); show_configs_one_file (fname, 0, outfp, &list); xfree (fname); es_fprintf (outfp, "\n"); } /* Print the encountered registry values and envvars. */ es_fprintf (outfp, "* Other info\n"); if (list) { any = 0; for (sl = list; sl; sl = sl->next) if (!(sl->flags & 1)) { if (!any) { any = 1; es_fprintf (outfp, "** List of encountered environment variables\n" "#+begin_example\n"); } if ((s = getenv (sl->d))) es_fprintf (outfp, " %-12s ->%s<-\n", sl->d, s); else es_fprintf (outfp, " %-12s [not set]\n", sl->d); } if (any) es_fprintf (outfp, "#+end_example\n"); } #ifdef HAVE_W32_SYSTEM es_fprintf (outfp, "** Registry entries\n"); es_fprintf (outfp, "#+begin_example\n"); any = 0; if (list) { for (sl = list; sl; sl = sl->next) if ((sl->flags & 1)) { char *p; int from_hklm; if (!any) { any = 1; es_fprintf (outfp, "Encountered in config files:\n"); } if ((p = read_w32_reg_string (sl->d, &from_hklm))) es_fprintf (outfp, " %s ->%s<-%s\n", sl->d, p, from_hklm? " [hklm]":""); else es_fprintf (outfp, " %s [not set]\n", sl->d); xfree (p); } } show_other_registry_entries (outfp); show_registry_entries_from_file (outfp); es_fprintf (outfp, "#+end_example\n"); #endif /*HAVE_W32_SYSTEM*/ free_strlist (list); /* Additional warning. */ anywarn = 0; if (got_gpgconfconf) { anywarn = 1; es_fprintf (outfp, "* Warnings\n"); es_fprintf (outfp, "- Legacy config file \"gpgconf.conf\" found\n"); } /* Check for uncommon files in the home directory. */ dir = gnupg_opendir (gnupg_homedir ()); if (!dir) { err = gpg_error_from_syserror (); log_error ("error reading directory \"%s\": %s\n", gnupg_homedir (), gpg_strerror (err)); return; } any = 0; while ((dir_entry = gnupg_readdir (dir))) { for (idx = 0; idx < DIM (names); idx++) { n = strlen (names[idx]); if (!ascii_strncasecmp (dir_entry->d_name, names[idx], n) && dir_entry->d_name[n] == '-' && ascii_strncasecmp (dir_entry->d_name, "gpg.conf-1", 10)) { if (!anywarn) { anywarn = 1; es_fprintf (outfp, "* Warnings\n"); } if (!any) { any = 1; es_fprintf (outfp, "- Suspicious files in \"%s\":\n", gnupg_homedir ()); } es_fprintf (outfp, " - %s\n", dir_entry->d_name); } } } gnupg_closedir (dir); es_fprintf (outfp, "# eof #\n"); } diff --git a/tools/gpgtar-create.c b/tools/gpgtar-create.c index 7af5a2ede..76eb47449 100644 --- a/tools/gpgtar-create.c +++ b/tools/gpgtar-create.c @@ -1,1401 +1,1414 @@ /* gpgtar-create.c - Create a TAR archive * Copyright (C) 2016-2017, 2019-2023 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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: GPL-3.0-or-later */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #include <unistd.h> #ifdef HAVE_W32_SYSTEM # define WIN32_LEAN_AND_MEAN # include <windows.h> #else /*!HAVE_W32_SYSTEM*/ # include <pwd.h> # include <grp.h> #endif /*!HAVE_W32_SYSTEM*/ #include "../common/i18n.h" #include <gpg-error.h> #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "../common/membuf.h" #include "gpgtar.h" #ifndef HAVE_LSTAT #define lstat(a,b) gnupg_stat ((a), (b)) #endif /* Number of files to be write. */ static unsigned long global_total_files; /* Count the number of written file and thus headers. Extended * headers are not counted. */ static unsigned long global_written_files; /* Total data expected to be written. */ static unsigned long long global_total_data; /* Number of data bytes written so far. */ static unsigned long long global_written_data; /* Object to control the file scanning. */ struct scanctrl_s; typedef struct scanctrl_s *scanctrl_t; struct scanctrl_s { tar_header_t flist; tar_header_t *flist_tail; unsigned long file_count; int nestlevel; }; /* See ../g10/progress.c:write_status_progress for some background. */ static void write_progress (int countmode, unsigned long long current, unsigned long long total_arg) { char units[] = "BKMGTPEZY?"; int unitidx = 0; uint64_t total = total_arg; if (!opt.status_stream) return; /* Not enabled. */ if (countmode) { if (total && current > total) current = total; } else if (total) /* Size mode: This may use units. */ { if (current > total) current = total; while (total > 1024*1024) { total /= 1024; current /= 1024; unitidx++; } } else /* Size mode */ { while (current > 1024*1024) { current /= 1024; unitidx++; } } if (unitidx > sizeof units - 1) unitidx = sizeof units - 1; if (countmode) es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar c %zu %zu\n", (size_t)current, (size_t)total); else es_fprintf (opt.status_stream, "[GNUPG:] PROGRESS gpgtar s %zu %zu %c%s\n", (size_t)current, (size_t)total, units[unitidx], unitidx? "iB" : ""); } /* On Windows convert name to UTF8 and return it; caller must release * the result. On Unix or if ALREADY_UTF8 is set, this function is a * mere xtrystrcopy. On failure NULL is returned and ERRNO set. */ static char * name_to_utf8 (const char *name, int already_utf8) { #ifdef HAVE_W32_SYSTEM wchar_t *wstring; char *result; if (already_utf8) result = xtrystrdup (name); else { wstring = native_to_wchar (name); if (!wstring) return NULL; result = wchar_to_utf8 (wstring); xfree (wstring); } return result; #else /*!HAVE_W32_SYSTEM */ (void)already_utf8; return xtrystrdup (name); #endif /*!HAVE_W32_SYSTEM */ } /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the W32 version. */ #ifdef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_w32 (tar_header_t hdr) { char *p; wchar_t *wfname; WIN32_FILE_ATTRIBUTE_DATA fad; DWORD attr; for (p=hdr->name; *p; p++) if (*p == '/') *p = '\\'; wfname = gpgrt_fname_to_wchar (hdr->name); for (p=hdr->name; *p; p++) if (*p == '\\') *p = '/'; if (!wfname) { log_error ("error converting '%s': %s\n", hdr->name, w32_strerror (-1)); return gpg_error_from_syserror (); } if (!GetFileAttributesExW (wfname, GetFileExInfoStandard, &fad)) { log_error ("error stat-ing '%s': %s\n", hdr->name, w32_strerror (-1)); xfree (wfname); return gpg_error_from_syserror (); } xfree (wfname); attr = fad.dwFileAttributes; if ((attr & FILE_ATTRIBUTE_NORMAL)) hdr->typeflag = TF_REGULAR; else if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->typeflag = TF_DIRECTORY; else if ((attr & FILE_ATTRIBUTE_DEVICE)) hdr->typeflag = TF_NOTSUP; else if ((attr & (FILE_ATTRIBUTE_OFFLINE | FILE_ATTRIBUTE_TEMPORARY))) hdr->typeflag = TF_NOTSUP; else hdr->typeflag = TF_REGULAR; /* Map some attributes to USTAR defined mode bits. */ hdr->mode = 0640; /* User may read and write, group only read. */ if ((attr & FILE_ATTRIBUTE_DIRECTORY)) hdr->mode |= 0110; /* Dirs are user and group executable. */ if ((attr & FILE_ATTRIBUTE_READONLY)) hdr->mode &= ~0200; /* Clear the user write bit. */ if ((attr & FILE_ATTRIBUTE_HIDDEN)) hdr->mode &= ~0707; /* Clear all user and other bits. */ if ((attr & FILE_ATTRIBUTE_SYSTEM)) hdr->mode |= 0004; /* Make it readable by other. */ /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = (fad.nFileSizeHigh * ((unsigned long long)MAXDWORD+1) + fad.nFileSizeLow); hdr->mtime = (((unsigned long long)fad.ftLastWriteTime.dwHighDateTime << 32) | fad.ftLastWriteTime.dwLowDateTime); if (!hdr->mtime) hdr->mtime = (((unsigned long long)fad.ftCreationTime.dwHighDateTime << 32) | fad.ftCreationTime.dwLowDateTime); hdr->mtime -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */ hdr->mtime /= 10000000; /* Convert from 0.1us to seconds. */ return 0; } #endif /*HAVE_W32_SYSTEM*/ /* Given a fresh header object HDR with only the name field set, try to gather all available info. This is the POSIX version. */ #ifndef HAVE_W32_SYSTEM static gpg_error_t fillup_entry_posix (tar_header_t hdr) { gpg_error_t err; struct stat sbuf; if (lstat (hdr->name, &sbuf)) { err = gpg_error_from_syserror (); log_error ("error stat-ing '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } if (S_ISREG (sbuf.st_mode)) hdr->typeflag = TF_REGULAR; else if (S_ISDIR (sbuf.st_mode)) hdr->typeflag = TF_DIRECTORY; else if (S_ISCHR (sbuf.st_mode)) hdr->typeflag = TF_CHARDEV; else if (S_ISBLK (sbuf.st_mode)) hdr->typeflag = TF_BLOCKDEV; else if (S_ISFIFO (sbuf.st_mode)) hdr->typeflag = TF_FIFO; else if (S_ISLNK (sbuf.st_mode)) hdr->typeflag = TF_SYMLINK; else hdr->typeflag = TF_NOTSUP; /* FIXME: Save DEV and INO? */ /* Set the USTAR defined mode bits using the system macros. */ if (sbuf.st_mode & S_IRUSR) hdr->mode |= 0400; if (sbuf.st_mode & S_IWUSR) hdr->mode |= 0200; if (sbuf.st_mode & S_IXUSR) hdr->mode |= 0100; if (sbuf.st_mode & S_IRGRP) hdr->mode |= 0040; if (sbuf.st_mode & S_IWGRP) hdr->mode |= 0020; if (sbuf.st_mode & S_IXGRP) hdr->mode |= 0010; if (sbuf.st_mode & S_IROTH) hdr->mode |= 0004; if (sbuf.st_mode & S_IWOTH) hdr->mode |= 0002; if (sbuf.st_mode & S_IXOTH) hdr->mode |= 0001; #ifdef S_IXUID if (sbuf.st_mode & S_IXUID) hdr->mode |= 04000; #endif #ifdef S_IXGID if (sbuf.st_mode & S_IXGID) hdr->mode |= 02000; #endif #ifdef S_ISVTX if (sbuf.st_mode & S_ISVTX) hdr->mode |= 01000; #endif hdr->nlink = sbuf.st_nlink; hdr->uid = sbuf.st_uid; hdr->gid = sbuf.st_gid; /* Only set the size for a regular file. */ if (hdr->typeflag == TF_REGULAR) hdr->size = sbuf.st_size; hdr->mtime = sbuf.st_mtime; return 0; } #endif /*!HAVE_W32_SYSTEM*/ /* Add a new entry. The name of a directory entry is ENTRYNAME; if that is NULL, DNAME is the name of the directory itself. Under Windows ENTRYNAME shall have backslashes replaced by standard slashes. */ static gpg_error_t add_entry (const char *dname, const char *entryname, scanctrl_t scanctrl) { gpg_error_t err; tar_header_t hdr; char *p; size_t dnamelen = strlen (dname); log_assert (dnamelen); hdr = xtrycalloc (1, sizeof *hdr + dnamelen + 1 + (entryname? strlen (entryname) : 0) + 1); if (!hdr) return gpg_error_from_syserror (); p = stpcpy (hdr->name, dname); if (entryname) { if (dname[dnamelen-1] != '/') *p++ = '/'; strcpy (p, entryname); } else { if (hdr->name[dnamelen-1] == '/') hdr->name[dnamelen-1] = 0; } #ifdef HAVE_DOSISH_SYSTEM err = fillup_entry_w32 (hdr); #else err = fillup_entry_posix (hdr); #endif if (err) xfree (hdr); else { /* FIXME: We don't have the extended info yet available so we * can't print them. */ if (opt.verbose) gpgtar_print_header (hdr, NULL, log_get_stream ()); *scanctrl->flist_tail = hdr; scanctrl->flist_tail = &hdr->next; scanctrl->file_count++; /* Print a progress line during scnanning in increments of 5000 * and not of 100 as we doing during write: Scanning is of * course much faster. */ if (!(scanctrl->file_count % 5000)) write_progress (1, scanctrl->file_count, 0); } return 0; } static gpg_error_t scan_directory (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; #ifdef HAVE_W32_SYSTEM /* Note that we introduced gnupg_opendir only after we had deployed * this code and thus we don't change it for now. */ WIN32_FIND_DATAW fi; HANDLE hd = INVALID_HANDLE_VALUE; char *p; if (!*dname) return 0; /* An empty directory name has no entries. */ { char *fname; wchar_t *wfname; fname = xtrymalloc (strlen (dname) + 2 + 2 + 1); if (!fname) { err = gpg_error_from_syserror (); goto leave; } if (!strcmp (dname, "/")) strcpy (fname, "/*"); /* Trailing slash is not allowed. */ else if (!strcmp (dname, ".")) strcpy (fname, "*"); else if (*dname && dname[strlen (dname)-1] == '/') strcpy (stpcpy (fname, dname), "*"); else if (*dname && dname[strlen (dname)-1] != '*') strcpy (stpcpy (fname, dname), "/*"); else strcpy (fname, dname); for (p=fname; *p; p++) if (*p == '/') *p = '\\'; wfname = gpgrt_fname_to_wchar (fname); xfree (fname); if (!wfname) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); goto leave; } hd = FindFirstFileW (wfname, &fi); if (hd == INVALID_HANDLE_VALUE) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); xfree (wfname); goto leave; } xfree (wfname); } do { char *fname = wchar_to_utf8 (fi.cFileName); if (!fname) { err = gpg_error_from_syserror (); log_error ("error converting filename: %s\n", w32_strerror (-1)); break; } for (p=fname; *p; p++) if (*p == '\\') *p = '/'; if (!strcmp (fname, "." ) || !strcmp (fname, "..")) err = 0; /* Skip self and parent dir entry. */ else if (!strncmp (dname, "./", 2) && dname[2]) err = add_entry (dname+2, fname, scanctrl); else err = add_entry (dname, fname, scanctrl); xfree (fname); } while (!err && FindNextFileW (hd, &fi)); if (err) ; else if (GetLastError () == ERROR_NO_MORE_FILES) err = 0; else { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, w32_strerror (-1)); } leave: if (hd != INVALID_HANDLE_VALUE) FindClose (hd); #else /*!HAVE_W32_SYSTEM*/ DIR *dir; struct dirent *de; if (!*dname) return 0; /* An empty directory name has no entries. */ dir = opendir (dname); if (!dir) { err = gpg_error_from_syserror (); log_error (_("error reading directory '%s': %s\n"), dname, gpg_strerror (err)); return err; } while ((de = readdir (dir))) { if (!strcmp (de->d_name, "." ) || !strcmp (de->d_name, "..")) continue; /* Skip self and parent dir entry. */ err = add_entry (dname, de->d_name, scanctrl); if (err) goto leave; } leave: closedir (dir); #endif /*!HAVE_W32_SYSTEM*/ return err; } static gpg_error_t scan_recursive (const char *dname, scanctrl_t scanctrl) { gpg_error_t err = 0; tar_header_t hdr, *start_tail, *stop_tail; if (scanctrl->nestlevel > 200) { log_error ("directories too deeply nested\n"); return gpg_error (GPG_ERR_RESOURCE_LIMIT); } scanctrl->nestlevel++; log_assert (scanctrl->flist_tail); start_tail = scanctrl->flist_tail; scan_directory (dname, scanctrl); stop_tail = scanctrl->flist_tail; hdr = *start_tail; for (; hdr && hdr != *stop_tail; hdr = hdr->next) if (hdr->typeflag == TF_DIRECTORY) { if (opt.verbose > 1) log_info ("scanning directory '%s'\n", hdr->name); scan_recursive (hdr->name, scanctrl); } scanctrl->nestlevel--; return err; } /* Returns true if PATTERN is acceptable. */ static int pattern_valid_p (const char *pattern) { if (!*pattern) return 0; if (*pattern == '.' && pattern[1] == '.') return 0; if (*pattern == '/' #ifdef HAVE_DOSISH_SYSTEM || *pattern == '\\' #endif ) return 0; /* Absolute filenames are not supported. */ #ifdef HAVE_DRIVE_LETTERS if (((*pattern >= 'a' && *pattern <= 'z') || (*pattern >= 'A' && *pattern <= 'Z')) && pattern[1] == ':') return 0; /* Drive letter are not allowed either. */ #endif /*HAVE_DRIVE_LETTERS*/ return 1; /* Okay. */ } static void store_xoctal (char *buffer, size_t length, unsigned long long value) { char *p, *pend; size_t n; unsigned long long v; log_assert (length > 1); v = value; n = length; p = pend = buffer + length; *--p = 0; /* Nul byte. */ n--; do { *--p = '0' + (v % 8); v /= 8; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = '0'; } else /* Does not fit into the field. Store as binary number. */ { v = value; n = length; p = pend = buffer + length; do { *--p = v; v /= 256; n--; } while (v && n); if (!v) { /* Pad. */ for ( ; n; n--) *--p = 0; if (*p & 0x80) BUG (); *p |= 0x80; /* Set binary flag. */ } else BUG (); } } static void store_uname (char *buffer, size_t length, unsigned long uid) { static int initialized; static unsigned long lastuid; static char lastuname[32]; if (!initialized || uid != lastuid) { #ifdef HAVE_W32_SYSTEM mem2str (lastuname, uid? "user":"root", sizeof lastuname); #else struct passwd *pw = getpwuid (uid); lastuid = uid; initialized = 1; if (pw) mem2str (lastuname, pw->pw_name, sizeof lastuname); else { log_info ("failed to get name for uid %lu\n", uid); *lastuname = 0; } #endif } mem2str (buffer, lastuname, length); } static void store_gname (char *buffer, size_t length, unsigned long gid) { static int initialized; static unsigned long lastgid; static char lastgname[32]; if (!initialized || gid != lastgid) { #ifdef HAVE_W32_SYSTEM mem2str (lastgname, gid? "users":"root", sizeof lastgname); #else struct group *gr = getgrgid (gid); lastgid = gid; initialized = 1; if (gr) mem2str (lastgname, gr->gr_name, sizeof lastgname); else { log_info ("failed to get name for gid %lu\n", gid); *lastgname = 0; } #endif } mem2str (buffer, lastgname, length); } static void compute_checksum (void *record) { struct ustar_raw_header *raw = record; unsigned long chksum = 0; unsigned char *p; size_t n; memset (raw->checksum, ' ', sizeof raw->checksum); p = record; for (n=0; n < RECORDSIZE; n++) chksum += *p++; store_xoctal (raw->checksum, sizeof raw->checksum - 1, chksum); raw->checksum[7] = ' '; } /* Read a symlink without truncating it. Caller must release the * returned buffer. Returns NULL on error. */ #ifndef HAVE_W32_SYSTEM static char * myreadlink (const char *name) { char *buffer; size_t size; int nread; for (size = 1024; size <= 65536; size *= 2) { buffer = xtrymalloc (size); if (!buffer) return NULL; nread = readlink (name, buffer, size - 1); if (nread < 0) { xfree (buffer); return NULL; } if (nread < size - 1) { buffer[nread] = 0; return buffer; /* Got it. */ } xfree (buffer); } gpg_err_set_errno (ERANGE); return NULL; } #endif /*Unix*/ /* Build a header. If the filename or the link name ist too long * allocate an exthdr and use a replacement file name in RECORD. * Caller should always release R_EXTHDR; this function initializes it * to point to NULL. */ static gpg_error_t build_header (void *record, tar_header_t hdr, strlist_t *r_exthdr) { gpg_error_t err; struct ustar_raw_header *raw = record; size_t namelen, n; strlist_t sl; memset (record, 0, RECORDSIZE); *r_exthdr = NULL; /* Store name and prefix. */ namelen = strlen (hdr->name); if (namelen < sizeof raw->name) memcpy (raw->name, hdr->name, namelen); else { n = (namelen < sizeof raw->prefix)? namelen : sizeof raw->prefix; for (n--; n ; n--) if (hdr->name[n] == '/') break; if (namelen - n < sizeof raw->name) { /* Note that the N is < sizeof prefix and that the delimiting slash is not stored. */ memcpy (raw->prefix, hdr->name, n); memcpy (raw->name, hdr->name+n+1, namelen - n); } else { /* Too long - prepare extended header. */ sl = add_to_strlist_try (r_exthdr, hdr->name); if (!sl) { err = gpg_error_from_syserror (); log_error ("error storing file '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl->flags = 1; /* Mark as path */ /* The name we use is not POSIX compliant but because we * expect that (for security issues) a tarball will anyway * be extracted to a unique new directory, a simple counter * will do. To ease testing we also put in the PID. The * count is bumped after the header has been written. */ snprintf (raw->name, sizeof raw->name-1, "_@paxheader.%u.%lu", (unsigned int)getpid(), global_written_files + 1); } } store_xoctal (raw->mode, sizeof raw->mode, hdr->mode); store_xoctal (raw->uid, sizeof raw->uid, hdr->uid); store_xoctal (raw->gid, sizeof raw->gid, hdr->gid); store_xoctal (raw->size, sizeof raw->size, hdr->size); store_xoctal (raw->mtime, sizeof raw->mtime, hdr->mtime); switch (hdr->typeflag) { case TF_REGULAR: raw->typeflag[0] = '0'; break; case TF_HARDLINK: raw->typeflag[0] = '1'; break; case TF_SYMLINK: raw->typeflag[0] = '2'; break; case TF_CHARDEV: raw->typeflag[0] = '3'; break; case TF_BLOCKDEV: raw->typeflag[0] = '4'; break; case TF_DIRECTORY: raw->typeflag[0] = '5'; break; case TF_FIFO: raw->typeflag[0] = '6'; break; default: return gpg_error (GPG_ERR_NOT_SUPPORTED); } memcpy (raw->magic, "ustar", 6); raw->version[0] = '0'; raw->version[1] = '0'; store_uname (raw->uname, sizeof raw->uname, hdr->uid); store_gname (raw->gname, sizeof raw->gname, hdr->gid); #ifndef HAVE_W32_SYSTEM if (hdr->typeflag == TF_SYMLINK) { int nread; char *p; nread = readlink (hdr->name, raw->linkname, sizeof raw->linkname -1); if (nread < 0) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } raw->linkname[nread] = 0; if (nread == sizeof raw->linkname -1) { /* Truncated - read again and store as extended header. */ p = myreadlink (hdr->name); if (!p) { err = gpg_error_from_syserror (); log_error ("error reading symlink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl = add_to_strlist_try (r_exthdr, p); xfree (p); if (!sl) { err = gpg_error_from_syserror (); log_error ("error storing syslink '%s': %s\n", hdr->name, gpg_strerror (err)); return err; } sl->flags = 2; /* Mark as linkpath */ } } #endif /*!HAVE_W32_SYSTEM*/ compute_checksum (record); return 0; } /* Add an extended header record (NAME,VALUE) to the buffer MB. */ static void add_extended_header_record (membuf_t *mb, const char *name, const char *value) { size_t n, n0, n1; char numbuf[35]; size_t valuelen; /* To avoid looping in most cases, we guess the initial value. */ valuelen = strlen (value); n1 = valuelen > 95? 3 : 2; do { n0 = n1; /* (3 for the space before name, the '=', and the LF.) */ n = n0 + strlen (name) + valuelen + 3; snprintf (numbuf, sizeof numbuf, "%zu", n); n1 = strlen (numbuf); } while (n0 != n1); put_membuf_str (mb, numbuf); put_membuf (mb, " ", 1); put_membuf_str (mb, name); put_membuf (mb, "=", 1); put_membuf (mb, value, valuelen); put_membuf (mb, "\n", 1); } /* Write the extended header specified by EXTHDR to STREAM. */ static gpg_error_t write_extended_header (estream_t stream, const void *record, strlist_t exthdr) { gpg_error_t err = 0; struct ustar_raw_header raw; strlist_t sl; membuf_t mb; char *buffer, *p; size_t buflen; init_membuf (&mb, 2*RECORDSIZE); for (sl=exthdr; sl; sl = sl->next) { if (sl->flags == 1) add_extended_header_record (&mb, "path", sl->d); else if (sl->flags == 2) add_extended_header_record (&mb, "linkpath", sl->d); } buffer = get_membuf (&mb, &buflen); if (!buffer) { err = gpg_error_from_syserror (); log_error ("error building extended header: %s\n", gpg_strerror (err)); goto leave; } /* We copy the header from the standard header record, so that an * extracted extended header (using a non-pax aware software) is * written with the same properties as the original file. The real * entry will overwrite it anyway. Of course we adjust the size and * the type. */ memcpy (&raw, record, RECORDSIZE); store_xoctal (raw.size, sizeof raw.size, buflen); raw.typeflag[0] = 'x'; /* Mark as extended header. */ compute_checksum (&raw); err = write_record (stream, &raw); if (err) goto leave; for (p = buffer; buflen >= RECORDSIZE; p += RECORDSIZE, buflen -= RECORDSIZE) { err = write_record (stream, p); if (err) goto leave; } if (buflen) { /* Reuse RAW for builidng the last record. */ memcpy (&raw, p, buflen); memset ((char*)&raw+buflen, 0, RECORDSIZE - buflen); err = write_record (stream, &raw); if (err) goto leave; } leave: xfree (buffer); return err; } static gpg_error_t write_file (estream_t stream, tar_header_t hdr, unsigned int *skipped_open) { gpg_error_t err; char record[RECORDSIZE]; estream_t infp; size_t nread, nbytes; strlist_t exthdr = NULL; int any; err = build_header (record, hdr, &exthdr); if (err) { if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) { log_info ("silently skipping unsupported file '%s'\n", hdr->name); err = 0; } return err; } if (hdr->typeflag == TF_REGULAR) { infp = es_fopen (hdr->name, "rb,sysopen"); if (!infp) { err = gpg_error_from_syserror (); log_info ("can't open '%s': %s - skipped\n", hdr->name, gpg_strerror (err)); ++*skipped_open; if (!*skipped_open) /* Protect against overflow. */ --*skipped_open; return 0; } } else infp = NULL; if (exthdr && (err = write_extended_header (stream, record, exthdr))) goto leave; err = write_record (stream, record); if (err) goto leave; global_written_files++; if (!(global_written_files % 100)) write_progress (1, global_written_files, global_total_files); if (hdr->typeflag == TF_REGULAR) { hdr->nrecords = (hdr->size + RECORDSIZE-1)/RECORDSIZE; any = 0; while (hdr->nrecords--) { nbytes = hdr->nrecords? RECORDSIZE : (hdr->size % RECORDSIZE); if (!nbytes) nbytes = RECORDSIZE; nread = es_fread (record, 1, nbytes, infp); if (nread != nbytes) { err = gpg_error_from_syserror (); log_error ("error reading file '%s': %s%s\n", hdr->name, gpg_strerror (err), any? " (file shrunk?)":""); goto leave; } else if (nbytes < RECORDSIZE) memset (record + nbytes, 0, RECORDSIZE - nbytes); any = 1; err = write_record (stream, record); if (err) goto leave; global_written_data += nbytes; if (!((global_written_data/nbytes) % (2048*100))) write_progress (0, global_written_data, global_total_data); } nread = es_fread (record, 1, 1, infp); if (nread) log_info ("note: file '%s' has grown\n", hdr->name); } leave: if (err) es_fclose (infp); else if ((err = es_fclose (infp))) log_error ("error closing file '%s': %s\n", hdr->name, gpg_strerror (err)); free_strlist (exthdr); return err; } static gpg_error_t write_eof_mark (estream_t stream) { gpg_error_t err; char record[RECORDSIZE]; memset (record, 0, sizeof record); err = write_record (stream, record); if (!err) err = write_record (stream, record); return err; } /* Create a new tarball using the names in the array INPATTERN. If INPATTERN is NULL take the pattern as null terminated strings from stdin or from the file specified by FILES_FROM. If NULL_NAMES is set the filenames in such a file are delimited by a binary Nul and not by a LF. */ gpg_error_t gpgtar_create (char **inpattern, const char *files_from, int null_names, int encrypt, int sign) { gpg_error_t err = 0; struct scanctrl_s scanctrl_buffer; scanctrl_t scanctrl = &scanctrl_buffer; tar_header_t hdr, *start_tail; estream_t files_from_stream = NULL; estream_t outstream = NULL; int eof_seen = 0; gnupg_process_t proc = NULL; unsigned int skipped_open = 0; memset (scanctrl, 0, sizeof *scanctrl); scanctrl->flist_tail = &scanctrl->flist; if (!inpattern) { if (!files_from || !strcmp (files_from, "-")) { files_from = "-"; files_from_stream = es_stdin; if (null_names) es_set_binary (es_stdin); } else if (!(files_from_stream=es_fopen (files_from, null_names? "rb":"r"))) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", files_from, gpg_strerror (err)); return err; } } if (opt.directory && gnupg_chdir (opt.directory)) { err = gpg_error_from_syserror (); log_error ("chdir to '%s' failed: %s\n", opt.directory, gpg_strerror (err)); return err; } while (!eof_seen) { char *pat, *p; int skip_this = 0; if (inpattern) { const char *pattern = *inpattern; if (!pattern) break; /* End of array. */ inpattern++; if (!*pattern) continue; pat = name_to_utf8 (pattern, 0); } else /* Read Nul or LF delimited pattern from files_from_stream. */ { int c; char namebuf[4096]; size_t n = 0; for (;;) { if ((c = es_getc (files_from_stream)) == EOF) { if (es_ferror (files_from_stream)) { err = gpg_error_from_syserror (); log_error ("error reading '%s': %s\n", files_from, gpg_strerror (err)); goto leave; } c = null_names ? 0 : '\n'; eof_seen = 1; } if (n >= sizeof namebuf - 1) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename too long"); } } else namebuf[n++] = c; if (null_names) { if (!c) { namebuf[n] = 0; break; } } else /* Shall be LF delimited. */ { if (!c) { if (!skip_this) { skip_this = 1; log_error ("error reading '%s': %s\n", files_from, "filename with embedded Nul"); } } else if ( c == '\n' ) { namebuf[n] = 0; ascii_trim_spaces (namebuf); n = strlen (namebuf); break; } } } if (skip_this || n < 2) continue; pat = name_to_utf8 (namebuf, opt.utf8strings); } if (!pat) { err = gpg_error_from_syserror (); log_error ("memory allocation problem: %s\n", gpg_strerror (err)); goto leave; } for (p=pat; *p; p++) if (*p == '\\') *p = '/'; if (opt.verbose > 1) log_info ("scanning '%s'\n", pat); start_tail = scanctrl->flist_tail; if (skip_this || !pattern_valid_p (pat)) log_error ("skipping invalid name '%s'\n", pat); else if (!add_entry (pat, NULL, scanctrl) && *start_tail && ((*start_tail)->typeflag & TF_DIRECTORY)) scan_recursive (pat, scanctrl); xfree (pat); } if (files_from_stream && files_from_stream != es_stdin) es_fclose (files_from_stream); global_total_files = global_total_data = 0; global_written_files = global_written_data = 0; for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { global_total_files++; global_total_data += hdr->size; } write_progress (1, 0, global_total_files); write_progress (0, 0, global_total_data); if (encrypt || sign) { strlist_t arg; ccparray_t ccp; #ifdef HAVE_W32_SYSTEM HANDLE except[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; #else int except[2] = { -1, -1 }; #endif const char **argv; + gnupg_spawn_actions_t act = NULL; /* '--encrypt' may be combined with '--symmetric', but 'encrypt' * is set either way. Clear it if no recipients are specified. */ if (opt.symmetric && opt.recipients == NULL) encrypt = 0; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.answer_yes) ccparray_put (&ccp, "--yes"); if (opt.answer_no) ccparray_put (&ccp, "--no"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); if (opt.status_fd) { static char tmpbuf[40]; es_syshd_t hd; snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); es_syshd (opt.status_stream, &hd); #ifdef HAVE_W32_SYSTEM except[0] = hd.u.handle; #else except[0] = hd.u.fd; #endif } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, opt.outfile? opt.outfile : "-"); if (encrypt) ccparray_put (&ccp, "--encrypt"); if (sign) ccparray_put (&ccp, "--sign"); if (opt.user) { ccparray_put (&ccp, "--local-user"); ccparray_put (&ccp, opt.user); } if (opt.symmetric) ccparray_put (&ccp, "--symmetric"); for (arg = opt.recipients; arg; arg = arg->next) { ccparray_put (&ccp, "--recipient"); ccparray_put (&ccp, arg->d); } if (opt.no_compress) ccparray_put (&ccp, "-z0"); for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } + err = gnupg_spawn_actions_new (&act); + if (err) + { + xfree (argv); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + gnupg_spawn_actions_set_inherit_handles (act, except); +#else + gnupg_spawn_actions_set_inherit_fds (act, except); +#endif err = gnupg_process_spawn (opt.gpg_program, argv, (GNUPG_PROCESS_STDIN_PIPE | GNUPG_PROCESS_STDOUT_KEEP - | GNUPG_PROCESS_STDERR_KEEP), - gnupg_spawn_helper, except, &proc); + | GNUPG_PROCESS_STDERR_KEEP), act, &proc); + gnupg_spawn_actions_release (act); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, &outstream, NULL, NULL); es_set_binary (outstream); } else if (opt.outfile) /* No crypto */ { if (!strcmp (opt.outfile, "-")) outstream = es_stdout; else outstream = es_fopen (opt.outfile, "wb,sysopen"); if (!outstream) { err = gpg_error_from_syserror (); goto leave; } if (outstream == es_stdout) es_set_binary (es_stdout); } else /* Also no crypto. */ { outstream = es_stdout; es_set_binary (outstream); } skipped_open = 0; for (hdr = scanctrl->flist; hdr; hdr = hdr->next) { err = write_file (outstream, hdr, &skipped_open); if (err) goto leave; } err = write_eof_mark (outstream); if (err) goto leave; write_progress (1, global_written_files, global_total_files); write_progress (0, global_written_data, global_total_data); if (proc) { err = es_fclose (outstream); outstream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } if (skipped_open) { log_info ("number of skipped files: %u\n", skipped_open); log_error ("exiting with failure status due to previous errors\n"); } leave: if (!err) { gpg_error_t first_err; if (outstream != es_stdout) first_err = es_fclose (outstream); else first_err = es_fflush (outstream); outstream = NULL; if (! err) err = first_err; } if (err) { log_error ("creating tarball '%s' failed: %s\n", opt.outfile ? opt.outfile : "-", gpg_strerror (err)); if (outstream && outstream != es_stdout) es_fclose (outstream); if (opt.outfile) gnupg_remove (opt.outfile); } scanctrl->flist_tail = NULL; while ( (hdr = scanctrl->flist) ) { scanctrl->flist = hdr->next; xfree (hdr); } return err; } diff --git a/tools/gpgtar-extract.c b/tools/gpgtar-extract.c index 87113b054..e93ffdc37 100644 --- a/tools/gpgtar-extract.c +++ b/tools/gpgtar-extract.c @@ -1,544 +1,557 @@ /* gpgtar-extract.c - Extract from a TAR archive * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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: GPL-3.0-or-later */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> #include "../common/i18n.h" #include <gpg-error.h> #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" #include "gpgtar.h" static gpg_error_t check_suspicious_name (const char *name, tarinfo_t info) { size_t n; n = strlen (name); #ifdef HAVE_DOSISH_SYSTEM if (strchr (name, '\\')) { log_error ("filename '%s' contains a backslash - " "can't extract on this system\n", name); info->skipped_badname++; return gpg_error (GPG_ERR_INV_NAME); } #endif /*HAVE_DOSISH_SYSTEM*/ if (!n || strstr (name, "//") || strstr (name, "/../") || !strncmp (name, "../", 3) || (n >= 3 && !strcmp (name+n-3, "/.." ))) { log_error ("filename '%s' has suspicious parts - not extracting\n", name); info->skipped_suspicious++; return gpg_error (GPG_ERR_INV_NAME); } return 0; } static gpg_error_t extract_regular (estream_t stream, const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; char record[RECORDSIZE]; size_t n, nbytes, nwritten; char *fname_buffer = NULL; const char *fname; estream_t outfp = NULL; strlist_t sl; fname = hdr->name; for (sl = exthdr; sl; sl = sl->next) if (sl->flags == 1) fname = sl->d; err = check_suspicious_name (fname, info); if (err) goto leave; fname_buffer = strconcat (dirname, "/", fname, NULL); if (!fname_buffer) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } fname = fname_buffer; if (opt.dry_run) outfp = es_fopen ("/dev/null", "wb"); else outfp = es_fopen (fname, "wb,sysopen"); if (!outfp) { err = gpg_error_from_syserror (); log_error ("error creating '%s': %s\n", fname, gpg_strerror (err)); goto leave; } for (n=0; n < hdr->nrecords;) { err = read_record (stream, record); if (err) goto leave; info->nblocks++; n++; if (n < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) nbytes = RECORDSIZE; else nbytes = (hdr->size % RECORDSIZE); nwritten = es_fwrite (record, 1, nbytes, outfp); if (nwritten != nbytes) { err = gpg_error_from_syserror (); log_error ("error writing '%s': %s\n", fname, gpg_strerror (err)); goto leave; } } /* Fixme: Set permissions etc. */ leave: if (!err) { if (opt.verbose) log_info ("extracted '%s'\n", fname); info->nextracted++; } es_fclose (outfp); if (err && fname && outfp) { if (gnupg_remove (fname)) log_error ("error removing incomplete file '%s': %s\n", fname, gpg_strerror (gpg_error_from_syserror ())); } xfree (fname_buffer); return err; } static gpg_error_t extract_directory (const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; const char *name; char *fname = NULL; strlist_t sl; name = hdr->name; for (sl = exthdr; sl; sl = sl->next) if (sl->flags == 1) name = sl->d; err = check_suspicious_name (name, info); if (err) goto leave; fname = strconcat (dirname, "/", name, NULL); if (!fname) { err = gpg_error_from_syserror (); log_error ("error creating filename: %s\n", gpg_strerror (err)); goto leave; } /* Remove a possible trailing slash. */ if (fname[strlen (fname)-1] == '/') fname[strlen (fname)-1] = 0; if (! opt.dry_run && gnupg_mkdir (fname, "-rwx------")) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EEXIST) { /* Ignore existing directories while extracting. */ err = 0; } if (gpg_err_code (err) == GPG_ERR_ENOENT) { /* Try to create the directory with parents but keep the original error code in case of a failure. */ int rc = 0; char *p; size_t prefixlen; /* (PREFIXLEN is the length of the new directory we use to * extract the tarball.) */ prefixlen = strlen (dirname) + 1; for (p = fname+prefixlen; (p = strchr (p, '/')); p++) { *p = 0; rc = gnupg_mkdir (fname, "-rwx------"); if (gpg_err_code (rc) == GPG_ERR_EEXIST) rc = 0; *p = '/'; if (rc) break; } if (!rc && !gnupg_mkdir (fname, "-rwx------")) err = 0; } if (err) log_error ("error creating directory '%s': %s\n", fname, gpg_strerror (err)); } leave: if (!err && opt.verbose) log_info ("created '%s/'\n", fname); xfree (fname); return err; } static gpg_error_t extract (estream_t stream, const char *dirname, tarinfo_t info, tar_header_t hdr, strlist_t exthdr) { gpg_error_t err; size_t n; if (hdr->typeflag == TF_REGULAR || hdr->typeflag == TF_UNKNOWN) err = extract_regular (stream, dirname, info, hdr, exthdr); else if (hdr->typeflag == TF_DIRECTORY) err = extract_directory (dirname, info, hdr, exthdr); else { char record[RECORDSIZE]; log_info ("unsupported file type %d for '%s' - skipped\n", (int)hdr->typeflag, hdr->name); if (hdr->typeflag == TF_SYMLINK) info->skipped_symlinks++; else if (hdr->typeflag == TF_HARDLINK) info->skipped_hardlinks++; else info->skipped_other++; for (err = 0, n=0; !err && n < hdr->nrecords; n++) { err = read_record (stream, record); if (!err) info->nblocks++; } } return err; } /* Create a new directory to be used for extracting the tarball. Returns the name of the directory which must be freed by the caller. In case of an error a diagnostic is printed and NULL returned. */ static char * create_directory (const char *dirprefix) { gpg_error_t err = 0; char *prefix_buffer = NULL; char *dirname = NULL; size_t n; int idx; /* Remove common suffixes. */ n = strlen (dirprefix); if (n > 4 && (!compare_filenames (dirprefix + n - 4, EXTSEP_S GPGEXT_GPG) || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pgp") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "asc") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "pem") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7m") || !compare_filenames (dirprefix + n - 4, EXTSEP_S "p7e"))) { prefix_buffer = xtrystrdup (dirprefix); if (!prefix_buffer) { err = gpg_error_from_syserror (); goto leave; } prefix_buffer[n-4] = 0; dirprefix = prefix_buffer; } for (idx=1; idx < 5000; idx++) { xfree (dirname); dirname = xtryasprintf ("%s_%d_", dirprefix, idx); if (!dirname) { err = gpg_error_from_syserror (); goto leave; } if (!gnupg_mkdir (dirname, "-rwx------")) goto leave; /* Ready. */ if (errno != EEXIST && errno != ENOTDIR) { err = gpg_error_from_syserror (); goto leave; } } err = gpg_error (GPG_ERR_LIMIT_REACHED); leave: if (err) { log_error ("error creating an extract directory: %s\n", gpg_strerror (err)); xfree (dirname); dirname = NULL; } xfree (prefix_buffer); return dirname; } gpg_error_t gpgtar_extract (const char *filename, int decrypt) { gpg_error_t err; estream_t stream = NULL; tar_header_t header = NULL; strlist_t extheader = NULL; const char *dirprefix = NULL; char *dirname = NULL; struct tarinfo_s tarinfo_buffer; tarinfo_t tarinfo = &tarinfo_buffer; gnupg_process_t proc; char *logfilename = NULL; unsigned long long notextracted; memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (opt.directory) dirname = xtrystrdup (opt.directory); else { if (opt.filename) { dirprefix = strrchr (opt.filename, '/'); if (dirprefix) dirprefix++; else dirprefix = opt.filename; } else if (filename) { dirprefix = strrchr (filename, '/'); if (dirprefix) dirprefix++; else dirprefix = filename; } if (!dirprefix || !*dirprefix) dirprefix = "GPGARCH"; dirname = create_directory (dirprefix); if (!dirname) { err = gpg_error (GPG_ERR_GENERAL); goto leave; } } if (opt.verbose) log_info ("extracting to '%s/'\n", dirname); if (decrypt) { strlist_t arg; ccparray_t ccp; #ifdef HAVE_W32_SYSTEM HANDLE except[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; #else int except[2] = { -1, -1 }; #endif const char **argv; + gnupg_spawn_actions_t act = NULL; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); if (opt.status_fd) { static char tmpbuf[40]; es_syshd_t hd; snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); es_syshd (opt.status_stream, &hd); #ifdef HAVE_W32_SYSTEM except[0] = hd.u.handle; #else except[0] = hd.u.fd; #endif } if (opt.with_log) { ccparray_put (&ccp, "--log-file"); logfilename = xstrconcat (dirname, ".log", NULL); ccparray_put (&ccp, logfilename); } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, "-"); ccparray_put (&ccp, "--decrypt"); for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); if (filename) { ccparray_put (&ccp, "--"); ccparray_put (&ccp, filename); } ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } + err = gnupg_spawn_actions_new (&act); + if (err) + { + xfree (argv); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + gnupg_spawn_actions_set_inherit_handles (act, except); +#else + gnupg_spawn_actions_set_inherit_fds (act, except); +#endif err = gnupg_process_spawn (opt.gpg_program, argv, ((filename ? 0 : GNUPG_PROCESS_STDIN_KEEP) - | GNUPG_PROCESS_STDOUT_PIPE), - gnupg_spawn_helper, except, &proc); + | GNUPG_PROCESS_STDOUT_PIPE), act, &proc); + gnupg_spawn_actions_release (act); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, NULL, &stream, NULL); es_set_binary (stream); } else if (filename) { if (!strcmp (filename, "-")) stream = es_stdin; else stream = es_fopen (filename, "rb,sysopen"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); return err; } if (stream == es_stdin) es_set_binary (es_stdin); } else { stream = es_stdin; es_set_binary (es_stdin); } for (;;) { err = gpgtar_read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; err = extract (stream, dirname, tarinfo, header, extheader); if (err) goto leave; free_strlist (extheader); extheader = NULL; xfree (header); header = NULL; } if (proc) { err = es_fclose (stream); stream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); if (exitcode) log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } leave: notextracted = tarinfo->skipped_badname; notextracted += tarinfo->skipped_suspicious; notextracted += tarinfo->skipped_symlinks; notextracted += tarinfo->skipped_hardlinks; notextracted += tarinfo->skipped_other; if (opt.status_stream) es_fprintf (opt.status_stream, "[GNUPG:] GPGTAR_EXTRACT" " %llu %llu %lu %lu %lu %lu %lu\n", tarinfo->nextracted, notextracted, tarinfo->skipped_badname, tarinfo->skipped_suspicious, tarinfo->skipped_symlinks, tarinfo->skipped_hardlinks, tarinfo->skipped_other); if (notextracted && !opt.quiet) { log_info ("Number of files not extracted: %llu\n", notextracted); if (tarinfo->skipped_badname) log_info (" invalid name: %lu\n", tarinfo->skipped_badname); if (tarinfo->skipped_suspicious) log_info (" suspicious name: %lu\n", tarinfo->skipped_suspicious); if (tarinfo->skipped_symlinks) log_info (" symlink: %lu\n", tarinfo->skipped_symlinks); if (tarinfo->skipped_hardlinks) log_info (" hardlink: %lu\n", tarinfo->skipped_hardlinks); if (tarinfo->skipped_other) log_info (" other reason: %lu\n", tarinfo->skipped_other); } free_strlist (extheader); xfree (header); xfree (dirname); xfree (logfilename); if (stream != es_stdin) es_fclose (stream); return err; } diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c index 0c5e474f3..55d9246af 100644 --- a/tools/gpgtar-list.c +++ b/tools/gpgtar-list.c @@ -1,604 +1,617 @@ /* gpgtar-list.c - List a TAR archive * Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH * Copyright (C) 2010, 2012, 2013 Werner Koch * Copyright (C) 2010 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * GnuPG 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: GPL-3.0-or-later */ #include <config.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "../common/i18n.h" #include <gpg-error.h> #include "gpgtar.h" #include "../common/exechelp.h" #include "../common/sysutils.h" #include "../common/ccparray.h" static unsigned long long parse_xoctal (const void *data, size_t length, const char *filename) { const unsigned char *p = data; unsigned long long value; if (!length) value = 0; else if ( (*p & 0x80)) { /* Binary format. */ value = (*p++ & 0x7f); while (--length) { value <<= 8; value |= *p++; } } else { /* Octal format */ value = 0; /* Skip leading spaces and zeroes. */ for (; length && (*p == ' ' || *p == '0'); length--, p++) ; for (; length && *p; length--, p++) { if (*p >= '0' && *p <= '7') { value <<= 3; value += (*p - '0'); } else { log_error ("%s: invalid octal number encountered - assuming 0\n", filename); value = 0; break; } } } return value; } static tar_header_t parse_header (const void *record, const char *filename, tarinfo_t info) { const struct ustar_raw_header *raw = record; size_t n, namelen, prefixlen; tar_header_t header; int use_prefix; int anyerror = 0; info->headerblock = info->nblocks - 1; use_prefix = (!memcmp (raw->magic, "ustar", 5) && (raw->magic[5] == ' ' || !raw->magic[5])); for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++) ; if (namelen == sizeof raw->name) { log_info ("%s: warning: name not terminated by a nul\n", filename); anyerror = 1; } for (n=namelen+1; n < sizeof raw->name; n++) if (raw->name[n]) { log_info ("%s: warning: garbage after name\n", filename); anyerror = 1; break; } if (use_prefix && raw->prefix[0]) { for (prefixlen=0; (prefixlen < sizeof raw->prefix && raw->prefix[prefixlen]); prefixlen++) ; if (prefixlen == sizeof raw->prefix) log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n", filename, info->headerblock); for (n=prefixlen+1; n < sizeof raw->prefix; n++) if (raw->prefix[n]) { log_info ("%s: warning: garbage after prefix\n", filename); anyerror = 1; break; } } else prefixlen = 0; header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen); if (!header) { log_error ("%s: error allocating header: %s\n", filename, gpg_strerror (gpg_error_from_syserror ())); return NULL; } if (prefixlen) { n = prefixlen; memcpy (header->name, raw->prefix, n); if (raw->prefix[n-1] != '/') header->name[n++] = '/'; } else n = 0; memcpy (header->name+n, raw->name, namelen); header->name[n+namelen] = 0; header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename); header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename); header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename); header->size = parse_xoctal (raw->size, sizeof raw->size, filename); header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename); /* checksum = */ switch (raw->typeflag[0]) { case '0': header->typeflag = TF_REGULAR; break; case '1': header->typeflag = TF_HARDLINK; break; case '2': header->typeflag = TF_SYMLINK; break; case '3': header->typeflag = TF_CHARDEV; break; case '4': header->typeflag = TF_BLOCKDEV; break; case '5': header->typeflag = TF_DIRECTORY; break; case '6': header->typeflag = TF_FIFO; break; case '7': header->typeflag = TF_RESERVED; break; case 'g': header->typeflag = TF_GEXTHDR; break; case 'x': header->typeflag = TF_EXTHDR; break; default: header->typeflag = TF_UNKNOWN; break; } /* Compute the number of data records following this header. */ if (header->typeflag == TF_REGULAR || header->typeflag == TF_EXTHDR || header->typeflag == TF_UNKNOWN) header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE; else header->nrecords = 0; if (anyerror) { log_info ("%s: header block %llu is corrupt" " (size=%llu type=%d nrec=%llu)\n", filename, info->headerblock, header->size, header->typeflag, header->nrecords); /* log_printhex (record, RECORDSIZE, " "); */ } return header; } /* Parse the extended header. This funcion may modify BUFFER. */ static gpg_error_t parse_extended_header (const char *fname, char *buffer, size_t buflen, strlist_t *r_exthdr) { unsigned int reclen; unsigned char *p, *record; strlist_t sl; while (buflen) { record = buffer; /* Remember begin of record. */ reclen = 0; for (p = buffer; buflen && digitp (p); buflen--, p++) { reclen *= 10; reclen += (*p - '0'); } if (!buflen || *p != ' ') { log_error ("%s: malformed record length in extended header\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } p++; /* Skip space. */ buflen--; if (buflen + (p-record) < reclen) { log_error ("%s: extended header record larger" " than total extended header data\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } if (reclen < (p-record)+2 || record[reclen-1] != '\n') { log_error ("%s: malformed extended header record\n", fname); return gpg_error (GPG_ERR_INV_RECORD); } record[reclen-1] = 0; /* For convenience change LF to a Nul. */ reclen -= (p-record); /* P points to the begin of the keyword and RECLEN is the * remaining length of the record excluding the LF. */ if (memchr (p, 0, reclen-1) && (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9))) { log_error ("%s: extended header record has an embedded nul" " - ignoring\n", fname); } else if (!strncmp (p, "path=", 5)) { sl = add_to_strlist_try (r_exthdr, p+5); if (!sl) return gpg_error_from_syserror (); sl->flags = 1; /* Mark as path */ } else if (!strncmp (p, "linkpath=", 9)) { sl = add_to_strlist_try (r_exthdr, p+9); if (!sl) return gpg_error_from_syserror (); sl->flags = 2; /* Mark as linkpath */ } buffer = p + reclen; buflen -= reclen; } return 0; } /* Read the next block, assuming it is a tar header. Returns a header * object on success in R_HEADER, or an error. If the stream is * consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case * of an error an error message is printed. If the header is an * extended header, a string list is allocated and stored at * R_EXTHEADER; the caller should provide a pointer to NULL. Such an * extended header is fully processed here and the returned R_HEADER * has then the next regular header. */ static gpg_error_t read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header, strlist_t *r_extheader) { gpg_error_t err; char record[RECORDSIZE]; int i; tar_header_t hdr; char *buffer; size_t buflen, nrec; err = read_record (stream, record); if (err) return err; info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i == RECORDSIZE) { /* All zero header - check whether it is the first part of an end of archive mark. */ err = read_record (stream, record); if (err) return err; info->nblocks++; for (i=0; i < RECORDSIZE && !record[i]; i++) ; if (i != RECORDSIZE) log_info ("%s: warning: skipping empty header\n", es_fname_get (stream)); else { /* End of archive - FIXME: we might want to check for garbage. */ *r_header = NULL; return 0; } } *r_header = parse_header (record, es_fname_get (stream), info); if (!*r_header) return gpg_error_from_syserror (); hdr = *r_header; if (hdr->typeflag != TF_EXTHDR || !r_extheader) return 0; /* Read the extended header. */ if (!hdr->nrecords) { /* More than 64k for an extedned header is surely too large. */ log_info ("%s: warning: empty extended header\n", es_fname_get (stream)); return 0; } if (hdr->nrecords > 65536 / RECORDSIZE) { /* More than 64k for an extedned header is surely too large. */ log_error ("%s: extended header too large - skipping\n", es_fname_get (stream)); return 0; } buffer = xtrymalloc (hdr->nrecords * RECORDSIZE); if (!buffer) { err = gpg_error_from_syserror (); log_error ("%s: error allocating space for extended header: %s\n", es_fname_get (stream), gpg_strerror (err)); return err; } buflen = 0; for (nrec=0; nrec < hdr->nrecords;) { err = read_record (stream, buffer + buflen); if (err) { xfree (buffer); return err; } info->nblocks++; nrec++; if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE))) buflen += RECORDSIZE; else buflen += (hdr->size % RECORDSIZE); } err = parse_extended_header (es_fname_get (stream), buffer, buflen, r_extheader); if (err) { free_strlist (*r_extheader); *r_extheader = NULL; } xfree (buffer); /* Now tha the extedned header has been read, we read the next * header without allowing an extended header. */ return read_header (stream, info, r_header, NULL); } /* Skip the data records according to HEADER. Prints an error message on error and return -1. */ static int skip_data (estream_t stream, tarinfo_t info, tar_header_t header) { char record[RECORDSIZE]; unsigned long long n; for (n=0; n < header->nrecords; n++) { if (read_record (stream, record)) return -1; info->nblocks++; } return 0; } static void print_header (tar_header_t header, strlist_t extheader, estream_t out) { unsigned long mask; char modestr[10+1]; int i; strlist_t sl; const char *name, *linkname; *modestr = '?'; switch (header->typeflag) { case TF_REGULAR: *modestr = '-'; break; case TF_HARDLINK: *modestr = 'h'; break; case TF_SYMLINK: *modestr = 'l'; break; case TF_CHARDEV: *modestr = 'c'; break; case TF_BLOCKDEV: *modestr = 'b'; break; case TF_DIRECTORY:*modestr = 'd'; break; case TF_FIFO: *modestr = 'f'; break; case TF_RESERVED: *modestr = '='; break; case TF_EXTHDR: break; case TF_GEXTHDR: break; case TF_UNKNOWN: break; case TF_NOTSUP: break; } for (mask = 0400, i = 0; i < 9; i++, mask >>= 1) modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-'; if ((header->typeflag & 04000)) modestr[3] = modestr[3] == 'x'? 's':'S'; if ((header->typeflag & 02000)) modestr[6] = modestr[6] == 'x'? 's':'S'; if ((header->typeflag & 01000)) modestr[9] = modestr[9] == 'x'? 't':'T'; modestr[10] = 0; /* FIXME: We do not parse the linkname unless its part of an * extended header. */ name = header->name; linkname = header->typeflag == TF_SYMLINK? "?" : NULL; for (sl = extheader; sl; sl = sl->next) { if (sl->flags == 1) name = sl->d; else if (sl->flags == 2) linkname = sl->d; } es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n", modestr, header->nlink, header->uid, header->gid, header->size, isotimestamp (header->mtime), name, linkname? " -> " : "", linkname? linkname : ""); } /* List the tarball FILENAME or, if FILENAME is NULL, the tarball read from stdin. */ gpg_error_t gpgtar_list (const char *filename, int decrypt) { gpg_error_t err; estream_t stream = NULL; tar_header_t header = NULL; strlist_t extheader = NULL; struct tarinfo_s tarinfo_buffer; tarinfo_t tarinfo = &tarinfo_buffer; gnupg_process_t proc = NULL; memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer); if (decrypt) { strlist_t arg; ccparray_t ccp; #ifdef HAVE_W32_SYSTEM HANDLE except[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE }; #else int except[2] = { -1, -1 }; #endif const char **argv; + gnupg_spawn_actions_t act = NULL; ccparray_init (&ccp, 0); if (opt.batch) ccparray_put (&ccp, "--batch"); if (opt.require_compliance) ccparray_put (&ccp, "--require-compliance"); if (opt.status_fd) { static char tmpbuf[40]; es_syshd_t hd; snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd); ccparray_put (&ccp, tmpbuf); es_syshd (opt.status_stream, &hd); #ifdef HAVE_W32_SYSTEM except[0] = hd.u.handle; #else except[0] = hd.u.fd; #endif } ccparray_put (&ccp, "--output"); ccparray_put (&ccp, "-"); ccparray_put (&ccp, "--decrypt"); for (arg = opt.gpg_arguments; arg; arg = arg->next) ccparray_put (&ccp, arg->d); if (filename) { ccparray_put (&ccp, "--"); ccparray_put (&ccp, filename); } ccparray_put (&ccp, NULL); argv = ccparray_get (&ccp, NULL); if (!argv) { err = gpg_error_from_syserror (); goto leave; } + err = gnupg_spawn_actions_new (&act); + if (err) + { + xfree (argv); + goto leave; + } + +#ifdef HAVE_W32_SYSTEM + gnupg_spawn_actions_set_inherit_handles (act, except); +#else + gnupg_spawn_actions_set_inherit_fds (act, except); +#endif err = gnupg_process_spawn (opt.gpg_program, argv, ((filename ? 0 : GNUPG_PROCESS_STDIN_KEEP) - | GNUPG_PROCESS_STDOUT_PIPE), - gnupg_spawn_helper, except, &proc); + | GNUPG_PROCESS_STDOUT_PIPE), act, &proc); + gnupg_spawn_actions_release (act); xfree (argv); if (err) goto leave; gnupg_process_get_streams (proc, 0, NULL, &stream, NULL); es_set_binary (stream); } else if (filename) /* No decryption requested. */ { if (!strcmp (filename, "-")) stream = es_stdin; else stream = es_fopen (filename, "rb,sysopen"); if (!stream) { err = gpg_error_from_syserror (); log_error ("error opening '%s': %s\n", filename, gpg_strerror (err)); goto leave; } if (stream == es_stdin) es_set_binary (es_stdin); } else { stream = es_stdin; es_set_binary (es_stdin); } for (;;) { err = read_header (stream, tarinfo, &header, &extheader); if (err || header == NULL) goto leave; print_header (header, extheader, es_stdout); if (skip_data (stream, tarinfo, header)) goto leave; free_strlist (extheader); extheader = NULL; xfree (header); header = NULL; } if (proc) { err = es_fclose (stream); stream = NULL; if (err) log_error ("error closing pipe: %s\n", gpg_strerror (err)); err = gnupg_process_wait (proc, 1); if (!err) { int exitcode; gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode); log_error ("running %s failed (exitcode=%d): %s", opt.gpg_program, exitcode, gpg_strerror (err)); } gnupg_process_release (proc); proc = NULL; } leave: free_strlist (extheader); xfree (header); if (stream != es_stdin) es_fclose (stream); return err; } gpg_error_t gpgtar_read_header (estream_t stream, tarinfo_t info, tar_header_t *r_header, strlist_t *r_extheader) { return read_header (stream, info, r_header, r_extheader); } void gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out) { if (header && out) print_header (header, extheader, out); }