summaryrefslogtreecommitdiffstats
path: root/tools/gpgconf-comp.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/gpgconf-comp.c')
-rw-r--r--tools/gpgconf-comp.c3462
1 files changed, 3462 insertions, 0 deletions
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
new file mode 100644
index 0000000..c9bdfeb
--- /dev/null
+++ b/tools/gpgconf-comp.c
@@ -0,0 +1,3462 @@
+/* gpgconf-comp.c - Configuration utility for GnuPG.
+ * Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
+ * Copyright (C) 2016 Werner Koch
+ * Copyright (C) 2020-2022 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/>.
+ */
+
+#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"
+
+/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
+ returns a plain filename without escaping. As long as we have not
+ fixed that we need to use gpg2. */
+#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
+#define GPGNAME "gpg2"
+#else
+#define GPGNAME GPG_NAME
+#endif
+
+
+
+#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_LOG_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);
+static void dirmngr_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 },
+ { "disable-scdaemon", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME,
+ 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 },
+ { "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 },
+ { "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 }
+ };
+
+
+/* The known options of the GC_COMPONENT_GPG component. */
+static known_option_t known_options_gpg[] =
+ {
+ { "verbose", GC_OPT_FLAG_LIST, 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 },
+ { "no-auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
+ { "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
+ { "no-auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
+ { "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
+ { "no-include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
+ { "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 is a pseudo option 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 },
+
+ { 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:@:",
+ 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 },
+ { "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 },
+ { "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 },
+ { "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_ADVANCED },
+ { "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_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; /* Has been announced in 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. */
+ gnupg_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 },
+
+ { 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},
+
+ { 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];
+ pid_t pid = (pid_t)(-1);
+ int i = 0;
+
+ 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++] = killflag? "KILLAGENT" : "RELOADAGENT";
+ argv[i++] = NULL;
+
+ if (!err)
+ err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
+ if (!err)
+ err = gnupg_wait_process (pgmname, pid, 1, NULL);
+ if (err)
+ gc_error (0, 0, "error running '%s %s': %s",
+ pgmname, argv[1], gpg_strerror (err));
+ gnupg_release_process (pid);
+}
+
+
+static void
+scdaemon_runtime_change (int killflag)
+{
+ gpg_error_t err = 0;
+ const char *pgmname;
+ const char *argv[9];
+ pid_t pid = (pid_t)(-1);
+ int i = 0;
+
+ (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 ${! $?}";
+ argv[i++] = "scd killscd";
+ argv[i++] = "/end";
+ argv[i++] = NULL;
+
+ if (!err)
+ err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
+ if (!err)
+ err = gnupg_wait_process (pgmname, pid, 1, NULL);
+ if (err)
+ gc_error (0, 0, "error running '%s %s': %s",
+ pgmname, argv[4], gpg_strerror (err));
+ gnupg_release_process (pid);
+}
+
+
+static void
+dirmngr_runtime_change (int killflag)
+{
+ gpg_error_t err = 0;
+ const char *pgmname;
+ const char *argv[6];
+ pid_t pid = (pid_t)(-1);
+ 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_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
+ if (!err)
+ err = gnupg_wait_process (pgmname, pid, 1, NULL);
+ if (err)
+ gc_error (0, 0, "error running '%s %s': %s",
+ pgmname, argv[cmdidx], gpg_strerror (err));
+ gnupg_release_process (pid);
+}
+
+
+/* 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;
+ pid_t pid;
+
+ if (component < 0)
+ {
+ err = gc_component_launch (GC_COMPONENT_GPG_AGENT);
+ if (!err)
+ err = gc_component_launch (GC_COMPONENT_DIRMNGR);
+ return err;
+ }
+
+ if (!(component == GC_COMPONENT_GPG_AGENT
+ || 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].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";
+ argv[i++] = "NOP";
+ argv[i] = NULL;
+ log_assert (i < DIM(argv));
+
+ err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
+ if (!err)
+ err = gnupg_wait_process (pgmname, pid, 1, NULL);
+ if (err)
+ gc_error (0, 0, "error running '%s%s%s': %s",
+ pgmname,
+ component == GC_COMPONENT_DIRMNGR? " --dirmngr":"",
+ " NOP",
+ gpg_strerror (err));
+ gnupg_release_process (pid);
+ 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 disgnostic output (redefine "_(" for that)
+ but given the natur of this tool, being something invoked from
+ other pograms, 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 = realloc (esc_str, new_len);
+ if (!new_esc_str)
+ gc_error (1, errno, "can not escape string");
+ 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 = realloc (str, new_len);
+ if (!new_str)
+ gc_error (1, errno, "can not deescape string");
+ 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;
+ pid_t pid;
+ int exitcode;
+ 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;
+
+ result = 0;
+ errlines = NULL;
+ err = gnupg_spawn_process (pgmname, argv, NULL, NULL, 0,
+ NULL, NULL, &errfp, &pid);
+ if (err)
+ result |= 1; /* Program could not be run. */
+ else
+ {
+ errlines = collect_error_output (errfp,
+ gc_component[component].name);
+ if (gnupg_wait_process (pgmname, pid, 1, &exitcode))
+ {
+ if (exitcode == -1)
+ result |= 1; /* Program could not be run or it
+ terminated abnormally. */
+ result |= 2; /* Program returned an error. */
+ }
+ gnupg_release_process (pid);
+ 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[4];
+ estream_t outfp;
+ int exitcode;
+ pid_t pid;
+ known_option_t *known_option;
+ gc_option_t *option;
+ char *line = NULL;
+ size_t line_len;
+ ssize_t length;
+ const char *config_name;
+ gnupg_argparse_t pargs;
+ int dummy_argc;
+ char *twopartconfig_name = NULL;
+ gnupg_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_spawn_process (pgmname, argv, NULL, NULL, 0,
+ NULL, &outfp, NULL, &pid);
+ if (err)
+ {
+ gc_error (1, 0, "could not gather option table from '%s': %s",
+ pgmname, gpg_strerror (err));
+ }
+
+ 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)
+ {
+ char *fields[4];
+ 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);
+ }
+ string_array[string_array_used++] = optname = xstrdup (fields[0]);
+ string_array[string_array_used++] = optdesc = 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;
+ /* Same here. */
+ 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_wait_process (pgmname, pid, 1, &exitcode);
+ if (err)
+ gc_error (1, 0, "running %s failed (exitcode=%d): %s",
+ pgmname, exitcode, gpg_strerror (err));
+ gnupg_release_process (pid);
+
+ /* 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_spawn_process (pgmname, argv, NULL, NULL, 0,
+ NULL, &outfp, NULL, &pid);
+ if (err)
+ {
+ gc_error (1, 0, "could not gather active options from '%s': %s",
+ pgmname, gpg_strerror (err));
+ }
+
+ 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_wait_process (pgmname, pid, 1, &exitcode);
+ if (err)
+ gc_error (1, 0, "running %s failed (exitcode=%d): %s",
+ pgmname, exitcode, gpg_strerror (err));
+ gnupg_release_process (pid);
+
+
+ /* 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 (gnupg_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 of 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"));
+#ifndef HAVE_W32CE_SYSTEM
+ 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. */
+ }
+#endif /*HAVE_W32CE_SYSTEM*/
+#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 not 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;
+}