diff options
Diffstat (limited to '')
-rw-r--r-- | tools/gpgconf-comp.c | 2499 |
1 files changed, 2499 insertions, 0 deletions
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c new file mode 100644 index 000000000..04a61a193 --- /dev/null +++ b/tools/gpgconf-comp.c @@ -0,0 +1,2499 @@ +/* gpgconf-comp.c - Configuration utility for GnuPG. + * Copyright (C) 2004 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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, write to the Free Software Foundation, + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#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 <assert.h> +#include <errno.h> +#include <time.h> +#include <stdarg.h> +#include <signal.h> + +/* For log_logv(), asctimestamp(), gnupg_get_time (). */ +#define JNLIB_NEED_LOG_LOGV +#include "util.h" +#include "i18n.h" + +#include "gpgconf.h" + + + +/* TODO: + Components: Add more components and their options. + Robustness: Do more validation. Call programs to do validation for us. + Don't use popen, as this will not tell us if the program had a + non-zero exit code. + Add options to change backend binary path. + Extract binary path for some backends from gpgsm/gpg config. +*/ + + +#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 (JNLIB_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); + exit (status); + } +} + + +/* Forward declaration. */ +void gpg_agent_runtime_change (void); + +/* Backend configuration. Backends are used to decide how the default + and current value of an option can be determined, and how the + option can be changed. To every option in every component belongs + exactly one backend that controls and determines the option. Some + backends are programs from the GPG system. Others might be + implemented by GPGConf itself. If you change this enum, don't + forget to update GC_BACKEND below. */ +typedef enum + { + /* Any backend, used for find_option (). */ + GC_BACKEND_ANY, + + /* The Gnu Privacy Guard. */ + GC_BACKEND_GPG, + + /* The Gnu Privacy Guard for S/MIME. */ + GC_BACKEND_GPGSM, + + /* The GPG Agent. */ + GC_BACKEND_GPG_AGENT, + + /* The GnuPG SCDaemon. */ + GC_BACKEND_SCDAEMON, + + /* The Aegypten directory manager. */ + GC_BACKEND_DIRMNGR, + + /* The LDAP server list file for the Aegypten director manager. */ + GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST, + + /* The number of the above entries. */ + GC_BACKEND_NR + } gc_backend_t; + + +/* To be able to implement generic algorithms for the various + backends, we collect all information about them in this struct. */ +static struct +{ + /* The name of the backend. */ + const char *name; + + /* The name of the program that acts as the backend. Some backends + don't have an associated program, but are implemented directly by + GPGConf. In this case, PROGRAM is NULL. */ + char *program; + + /* The runtime change callback. */ + void (*runtime_change) (void); + + /* The option name for the configuration filename of this backend. + This must be an absolute pathname. It can be an option from a + different backend (but then ordering of the options might + matter). */ + const char *option_config_filename; + + /* If this is a file backend rather than a program backend, then + this is the name of the option associated with the file. */ + const char *option_name; +} gc_backend[GC_BACKEND_NR] = + { + { NULL }, /* GC_BACKEND_ANY dummy entry. */ + { "GnuPG", "gpg", NULL, "gpgconf-gpg.conf" }, + { "GPGSM", "gpgsm", NULL, "gpgconf-gpgsm.conf" }, + { "GPG Agent", "gpg-agent", gpg_agent_runtime_change, + "gpgconf-gpg-agent.conf" }, + { "SCDaemon", "scdaemon", NULL, "gpgconf-scdaemon.conf" }, + { "DirMngr", "dirmngr", NULL, "gpgconf-dirmngr.conf" }, + { "DirMngr LDAP Server List", NULL, NULL, "ldapserverlist-file", + "LDAP Server" }, + }; + + +/* 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 pathname. */ + GC_ARG_TYPE_PATHNAME = 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, + + /* 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 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, "pathname" }, + { GC_ARG_TYPE_STRING, "ldap server" }, + { GC_ARG_TYPE_STRING, "key fpr" }, + }; + + +/* 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 struct +{ + const char *name; +} gc_level[] = + { + { "basic" }, + { "advanced" }, + { "expert" }, + { "invisible" }, + { "internal" } + }; + + +/* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING + FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */ +#define GC_OPT_FLAG_NONE 0UL +/* Some entries in the option list are not options, but mark the + beginning of a new group of options. These entries have the GROUP + flag set. */ +#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) + +/* The following flags are incorporated from the backend. */ +/* The DEFAULT flag for an option indicates that the option has a + default value. */ +#define GC_OPT_FLAG_DEFAULT (1UL << 4) +/* The DEF_DESC flag for an option indicates that the option has a + default, which is described by the value of the default field. */ +#define GC_OPT_FLAG_DEF_DESC (1UL << 5) +/* The NO_ARG_DESC flag for an option indicates that the argument has + a default, which is described by the value of the ARGDEF field. */ +#define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) + +/* A human-readable description for each flag. */ +static struct +{ + const char *name; +} gc_flag[] = + { + { "group" }, + { "optional arg" }, + { "list" }, + { "runtime" }, + { "default" }, + { "default desc" }, + { "no arg desc" } + }; + + +/* To each option, or group marker, the information in the GC_OPTION + struct is provided. If you change this, don't forget to update the + option list of each component. */ +struct gc_option +{ + /* If this is NULL, then this is a terminator in an array of unknown + length. Otherwise, if this entry is a group marker (see FLAGS), + then this is the name of the group described by this entry. + 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. If the GROUP flag is set, then this entry is a + group marker, not an option, and only the fields LEVEL, + DESC_DOMAIN and DESC are valid. In all other cases, this entry + describes a new option and all fields are valid. */ + unsigned long flags; + + /* The expert level. This field is valid for options and groups. A + group has the expert level of the lowest-level option in the + group. */ + gc_expert_level_t level; + + /* A gettext domain in which the following description can be found. + If this is NULL, then DESC is not translated. Valid for groups + and options. + + Note that we try to keep the description of groups within the + gnupg domain. + + IMPORTANT: If you add a new domain please make sure to add a code + set switching call to the function my_dgettext further below. */ + const char *desc_domain; + + /* A gettext description for this group or option. If it starts + with a '|', then the string up to the next '|' describes the + argument, and the description follows the second '|'. + + In general enclosing these description in N_() is not required + because the description should be identical to the one in the + help menu of the respective program. */ + const char *desc; + + /* The following fields are only valid for options. */ + + /* The type of the option argument. */ + gc_arg_type_t arg_type; + + /* The backend that implements this option. */ + gc_backend_t backend; + + /* The following fields are set to NULL at startup (because all + option's are declared as static variables). They are at the end + of the list so that they can be omitted from the option + declarations. */ + + /* This is true if the option is supported by this version of the + backend. */ + int active; + + /* The default value for this option. This is NULL if the option is + not present in the backend, the empty string if no default is + available, and otherwise a quoted string. */ + char *default_value; + + /* The default argument is only valid if the "optional arg" flag is + set, and specifies the default argument (value) that is used if + the argument is omitted. */ + char *default_arg; + + /* 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 gc_option_t; + +/* Use this macro to terminate an option list. */ +#define GC_OPTION_NULL { NULL } + + +/* The options of the GC_COMPONENT_GPG_AGENT component. */ +static gc_option_t gc_options_gpg_agent[] = + { + /* The configuration file to which we write the changes. */ + { "gpgconf-gpg-agent.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the diagnostic output") }, + { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, + "gnupg", "verbose", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, + "gnupg", "be somewhat more quiet", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + + { "Configuration", + GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + "gnupg", N_("Options controlling the configuration") }, + { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "|FILE|read options from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, + { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "do not use the SCdaemon", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + + { "Debug", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Options useful for debugging") }, + { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + "gnupg", "|LEVEL|set the debugging level to LEVEL", + GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT }, + { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + "gnupg", N_("|FILE|write server mode logs to FILE"), + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, + + { "Security", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the security") }, + { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, + "gnupg", "|N|expire cached PINs after N seconds", + GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, + { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, + "gnupg", "do not use the PIN cache when signing", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + "gnupg", "allow clients to mark keys as \"trusted\"", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT, + "gnupg", "do not grab keyboard and mouse", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, + + + GC_OPTION_NULL + }; + + +/* The options of the GC_COMPONENT_SCDAEMON component. */ +static gc_option_t gc_options_scdaemon[] = + { + /* The configuration file to which we write the changes. */ + { "gpgconf-scdaemon.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the diagnostic output") }, + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + "gnupg", "verbose", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "be somewhat more quiet", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + + { "Configuration", + GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + "gnupg", N_("Options controlling the configuration") }, + { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "|FILE|read options from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, + { "reader-port", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "|N|connect to reader at port N", + GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, + { "ctapi-driver", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "|NAME|use NAME as ct-API driver", + GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, + { "pcsc-driver", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "|NAME|use NAME as PC/SC driver", + GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, + { "disable-opensc", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "do not use the OpenSC layer", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + { "disable-ccid", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "do not use the internal CCID driver", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + { "disable-keypad", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "do not use a reader's keypad", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + + { "Debug", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Options useful for debugging") }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, + "gnupg", "|LEVEL|set the debugging level to LEVEL", + GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", N_("|FILE|write server mode logs to FILE"), + GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON }, + + { "Security", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the security") }, + { "allow-admin", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "allow the use of admin card commands", + GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON }, + + + GC_OPTION_NULL + }; + + +/* The options of the GC_COMPONENT_GPG component. */ +static gc_option_t gc_options_gpg[] = + { + /* The configuration file to which we write the changes. */ + { "gpgconf-gpg.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the diagnostic output") }, + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + "gnupg", "verbose", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, + { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "be somewhat more quiet", + GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, + + { "Configuration", + GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + "gnupg", N_("Options controlling the configuration") }, + { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "|FILE|read options from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, + + { "Debug", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Options useful for debugging") }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, + "gnupg", "|LEVEL|set the debugging level to LEVEL", + GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", N_("|FILE|write server mode logs to FILE"), + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG }, +/* { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */ +/* NULL, NULL, */ +/* GC_ARG_TYPE_UINT32, GC_BACKEND_GPG }, */ + + { "Keyserver", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Configuration for Keyservers") }, + { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "|URL|use keyserver at URL", + GC_ARG_TYPE_STRING, GC_BACKEND_GPG }, + { "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", N_("allow PKA lookups (DNS requests)"), + GC_ARG_TYPE_NONE, GC_BACKEND_GPG }, + + + GC_OPTION_NULL + }; + + + +/* The options of the GC_COMPONENT_GPGSM component. */ +static gc_option_t gc_options_gpgsm[] = + { + /* The configuration file to which we write the changes. */ + { "gpgconf-gpgsm.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the diagnostic output") }, + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + "gnupg", "verbose", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "be somewhat more quiet", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + + { "Configuration", + GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + "gnupg", N_("Options controlling the configuration") }, + { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "|FILE|read options from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, + { "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "use system's dirmngr if available", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + + { "Debug", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Options useful for debugging") }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, + "gnupg", "|LEVEL|set the debugging level to LEVEL", + GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", N_("|FILE|write server mode logs to FILE"), + GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_UINT32, GC_BACKEND_GPGSM }, + + { "Security", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the security") }, + { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "never consult a CRL", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", N_("do not check CRLs for root certificates"), + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "check validity using OCSP", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "gnupg", "|N|number of certificates to include", + GC_ARG_TYPE_INT32, GC_BACKEND_GPGSM }, + { "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "gnupg", "do not check certificate policies", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "gnupg", "fetch missing issuer certificates", + GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM }, + + GC_OPTION_NULL + }; + + +/* The options of the GC_COMPONENT_DIRMNGR component. */ +static gc_option_t gc_options_dirmngr[] = + { + /* The configuration file to which we write the changes. */ + { "gpgconf-dirmngr.conf", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the diagnostic output") }, + { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + "dirmngr", "verbose", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "be somewhat more quiet", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + + { "Format", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the format of the output") }, + { "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "sh-style command output", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "csh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "csh-style command output", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + + { "Configuration", + GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT, + "gnupg", N_("Options controlling the configuration") }, + { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT, + "dirmngr", "|FILE|read options from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, + + { "Debug", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Options useful for debugging") }, + { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, + "dirmngr", "|LEVEL|set the debugging level to LEVEL", + GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, + { "no-detach", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "do not detach from the console", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", N_("|FILE|write server mode logs to FILE"), + GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, + { "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, + { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, + NULL, NULL, + GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, + + { "Enforcement", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Options controlling the interactivity and enforcement") }, + { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "run without asking a user", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "force loading of outdated CRLs", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + + { "HTTP", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Configuration for HTTP servers") }, + { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "inhibit the use of HTTP", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "ignore HTTP CRL distribution points", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "|URL|redirect all HTTP requests to URL", + GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, + { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", N_("use system's HTTP proxy setting"), + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + + { "LDAP", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + "gnupg", N_("Configuration of LDAP servers to use") }, + { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "inhibit the use of LDAP", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "ignore LDAP CRL distribution points", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "|HOST|use HOST for LDAP queries", + GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, + { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "do not use fallback hosts with --ldap-proxy", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "add new servers discovered in CRL distribution points" + " to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "|N|set LDAP timeout to N seconds", + GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, + /* The following entry must not be removed, as it is required for + the GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST. */ + { "ldapserverlist-file", + GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + "dirmngr", "|FILE|read LDAP server list from FILE", + GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, + /* This entry must come after at least one entry for + GC_BACKEND_DIRMNGR in this component, so that the entry for + "ldapserverlist-file will be initialized before this one. */ + { "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC, + NULL, "LDAP server list", + GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST }, + { "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "|N|do not return more than N items in one query", + GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, + + { "OCSP", + GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED, + "gnupg", N_("Configuration for OCSP") }, + { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "dirmngr", "allow sending OCSP requests", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "ignore certificate contained OCSP service URLs", + GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR }, + { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "|URL|use OCSP responder at URL", + GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, + { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "|FPR|OCSP response signed by FPR", + GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR }, + + + GC_OPTION_NULL + }; + + +/* Component system. Each component is a set of options that can be + configured at the same time. If you change this, don't forget to + update GC_COMPONENT below. */ +typedef enum + { + /* The classic GPG for OpenPGP. */ + GC_COMPONENT_GPG, + + /* The GPG Agent. */ + GC_COMPONENT_GPG_AGENT, + + /* The Smardcard Daemon. */ + GC_COMPONENT_SCDAEMON, + + /* GPG for S/MIME. */ + GC_COMPONENT_GPGSM, + + /* The LDAP Directory Manager for CRLs. */ + GC_COMPONENT_DIRMNGR, + + /* The number of components. */ + GC_COMPONENT_NR + } gc_component_t; + + +/* The information associated with each component. */ +static struct +{ + /* The 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 for this domain. */ + const char *desc; + + /* The list of options for this component, terminated by + GC_OPTION_NULL. */ + gc_option_t *options; +} gc_component[] = + { + { "gpg", NULL, "GPG for OpenPGP", gc_options_gpg }, + { "gpg-agent", NULL, "GPG Agent", gc_options_gpg_agent }, + { "scdaemon", NULL, "Smartcard Daemon", gc_options_scdaemon }, + { "gpgsm", NULL, "GPG for S/MIME", gc_options_gpgsm }, + { "dirmngr", NULL, "Directory Manager", gc_options_dirmngr } + }; + + +/* Engine specific support. */ +void +gpg_agent_runtime_change (void) +{ +#ifndef HAVE_W32_SYSTEM + char *agent = getenv ("GPG_AGENT_INFO"); + char *pid_str; + unsigned long pid_long; + char *tail; + pid_t pid; + + if (!agent) + return; + + pid_str = strchr (agent, ':'); + if (!pid_str) + return; + + pid_str++; + errno = 0; + pid_long = strtoul (pid_str, &tail, 0); + if (errno || (*tail != ':' && *tail != '\0')) + return; + + pid = (pid_t) pid_long; + + /* Check for overflow. */ + if (pid_long != (unsigned long) pid) + return; + + /* Ignore any errors here. */ + kill (pid, SIGHUP); +#endif /*!HAVE_W32_SYSTEM*/ +} + + +/* 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 posible to keep the orginal 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) +{ +#ifdef 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", LOCALEDIR); + bind_textdomain_codeset ("dirmngr", "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 +#endif + return msgid; +} + + +/* Percent-Escape special characters. The string is valid until the + next invocation of the function. */ +static char * +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 + *(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 (FILE *out) +{ + gc_component_t idx; + + for (idx = 0; idx < GC_COMPONENT_NR; idx++) + { + const char *desc = gc_component[idx].desc; + desc = my_dgettext (gc_component[idx].desc_domain, desc); + fprintf (out, "%s:%s\n", gc_component[idx].name, percent_escape (desc)); + } +} + + +/* Find the component with the name NAME. Returns -1 if not + found. */ +int +gc_component_find (const char *name) +{ + gc_component_t idx; + + for (idx = 0; idx < GC_COMPONENT_NR; idx++) + { + if (!strcmp (name, gc_component[idx].name)) + return idx; + } + return -1; +} + + +/* List the option OPTION. */ +static void +list_one_option (const gc_option_t *option, FILE *out) +{ + const char *desc = NULL; + char *arg_name = NULL; + + if (option->desc) + { + desc = my_dgettext (option->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. */ + fprintf (out, "%s", option->name); + + /* The flags field. */ + fprintf (out, ":%lu", option->flags); + if (opt.verbose) + { + putc (' ', out); + + if (!option->flags) + fprintf (out, "none"); + else + { + unsigned long flags = option->flags; + unsigned long flag = 0; + unsigned long first = 1; + + while (flags) + { + if (flags & 1) + { + if (first) + first = 0; + else + putc (',', out); + fprintf (out, "%s", gc_flag[flag].name); + } + flags >>= 1; + flag++; + } + } + } + + /* The level field. */ + fprintf (out, ":%u", option->level); + if (opt.verbose) + fprintf (out, " %s", gc_level[option->level].name); + + /* The description field. */ + fprintf (out, ":%s", desc ? percent_escape (desc) : ""); + + /* The type field. */ + fprintf (out, ":%u", option->arg_type); + if (opt.verbose) + fprintf (out, " %s", gc_arg_type[option->arg_type].name); + + /* The alternate type field. */ + fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback); + if (opt.verbose) + fprintf (out, " %s", + gc_arg_type[gc_arg_type[option->arg_type].fallback].name); + + /* The argument name field. */ + fprintf (out, ":%s", arg_name ? percent_escape (arg_name) : ""); + if (arg_name) + xfree (arg_name); + + /* The default value field. */ + fprintf (out, ":%s", option->default_value ? option->default_value : ""); + + /* The default argument field. */ + fprintf (out, ":%s", option->default_arg ? option->default_arg : ""); + + /* The value field. */ + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE + && (option->flags & GC_OPT_FLAG_LIST) + && option->value) + /* The special format "1,1,1,1,...,1" is converted to a number + here. */ + fprintf (out, ":%u", (strlen (option->value) + 1) / 2); + else + fprintf (out, ":%s", option->value ? option->value : ""); + + /* ADD NEW FIELDS HERE. */ + + putc ('\n', out); +} + + +/* List all options of the component COMPONENT. */ +void +gc_component_list_options (int component, FILE *out) +{ + const gc_option_t *option = gc_component[component].options; + const gc_option_t *group_option = NULL; + + while (option->name) + { + /* Do not output unknown or internal options. */ + if (!(option->flags & GC_OPT_FLAG_GROUP) + && (!option->active || option->level == GC_LEVEL_INTERNAL)) + { + option++; + continue; + } + + if (option->flags & GC_OPT_FLAG_GROUP) + group_option = option; + else + { + if (group_option) + { + list_one_option (group_option, out); + group_option = NULL; + } + + list_one_option (option, out); + } + + option++; + } +} + + +/* Find the option NAME in component COMPONENT, for the backend + BACKEND. If BACKEND is GC_BACKEND_ANY, any backend will match. */ +static gc_option_t * +find_option (gc_component_t component, const char *name, + gc_backend_t backend) +{ + gc_option_t *option = gc_component[component].options; + while (option->name) + { + if (!(option->flags & GC_OPT_FLAG_GROUP) + && !strcmp (option->name, name) + && (backend == GC_BACKEND_ANY || option->backend == backend)) + break; + option++; + } + return option->name ? option : NULL; +} + + +/* Determine the configuration pathname for the component COMPONENT + and backend BACKEND. */ +static char * +get_config_pathname (gc_component_t component, gc_backend_t backend) +{ + char *pathname = NULL; + gc_option_t *option = find_option + (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); + assert (option); + assert (option->arg_type == GC_ARG_TYPE_PATHNAME); + assert (!(option->flags & GC_OPT_FLAG_LIST)); + + if (!option->active || !option->default_value) + gc_error (1, 0, "Option %s, needed by backend %s, was not initialized", + gc_backend[backend].option_config_filename, + gc_backend[backend].name); + + if (option->value && *option->value) + pathname = percent_deescape (&option->value[1]); + else if (option->default_value && *option->default_value) + pathname = percent_deescape (&option->default_value[1]); + else + pathname = ""; + +#ifdef HAVE_DOSISH_SYSTEM + if (!(pathname[0] + && pathname[1] == ':' + && (pathname[2] == '/' || pathname[2] == '\\'))) +#else + if (pathname[0] != '/') +#endif + gc_error (1, 0, "Option %s, needed by backend %s, is not absolute", + gc_backend[backend].option_config_filename, + gc_backend[backend].name); + + return pathname; +} + + +/* Retrieve the options for the component COMPONENT from backend + BACKEND, which we already know is a program-type backend. */ +static void +retrieve_options_from_program (gc_component_t component, gc_backend_t backend) +{ + char *cmd_line; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + FILE *config; + char *config_pathname; + + cmd_line = xasprintf ("%s --gpgconf-list", gc_backend[backend].program); + + config = popen (cmd_line, "r"); + if (!config) + gc_error (1, errno, "could not gather active options from %s", cmd_line); + + while ((length = read_line (config, &line, &line_len, NULL)) > 0) + { + gc_option_t *option; + 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'; + + errno = 0; + flags = strtoul (linep, &tail, 0); + if (errno) + gc_error (1, errno, "malformed flags in option %s from %s", line, cmd_line); + if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) + gc_error (1, 0, "garbage after flags in option %s from %s", line, cmd_line); + + 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, backend); + if (option) + { + if (option->active) + gc_error (1, errno, "option %s returned twice from %s", + line, cmd_line); + option->active = 1; + + option->flags |= flags; + if (default_value && *default_value) + option->default_value = xstrdup (default_value); + } + } + if (length < 0 || ferror (config)) + gc_error (1, errno, "error reading from %s", cmd_line); + if (fclose (config) && ferror (config)) + gc_error (1, errno, "error closing %s", cmd_line); + xfree (cmd_line); + + /* At this point, we can parse the configuration file. */ + config_pathname = get_config_pathname (component, backend); + + config = fopen (config_pathname, "r"); + if (!config) + gc_error (0, errno, "warning: can not open config file %s", + config_pathname); + else + { + while ((length = read_line (config, &line, &line_len, NULL)) > 0) + { + char *name; + char *value; + gc_option_t *option; + + name = line; + while (*name == ' ' || *name == '\t') + name++; + if (!*name || *name == '#' || *name == '\r' || *name == '\n') + continue; + + value = name; + while (*value && *value != ' ' && *value != '\t' + && *value != '#' && *value != '\r' && *value != '\n') + value++; + if (*value == ' ' || *value == '\t') + { + char *end; + + *(value++) = '\0'; + while (*value == ' ' || *value == '\t') + value++; + + end = value; + while (*end && *end != '#' && *end != '\r' && *end != '\n') + end++; + while (end > value && (end[-1] == ' ' || end[-1] == '\t')) + end--; + *end = '\0'; + } + else + *value = '\0'; + + /* Look up the option in the component and install the + configuration data. */ + option = find_option (component, line, backend); + if (option) + { + char *opt_value; + + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE) + { + if (*value) + gc_error (0, 0, + "warning: ignoring argument %s for option %s", + value, name); + opt_value = xstrdup ("1"); + } + else if (gc_arg_type[option->arg_type].fallback + == GC_ARG_TYPE_STRING) + opt_value = xasprintf ("\"%s", percent_escape (value)); + else + { + /* FIXME: Verify that the number is sane. */ + opt_value = xstrdup (value); + } + + /* Now enter the option into the table. */ + if (!(option->flags & GC_OPT_FLAG_LIST)) + { + if (option->value) + free (option->value); + option->value = opt_value; + } + else + { + if (!option->value) + option->value = opt_value; + else + { + char *opt_val = opt_value; + + option->value = xasprintf ("%s,%s", option->value, + opt_val); + xfree (opt_value); + } + } + } + } + + if (length < 0 || ferror (config)) + gc_error (1, errno, "error reading from %s", config_pathname); + if (fclose (config) && ferror (config)) + gc_error (1, errno, "error closing %s", config_pathname); + } + + xfree (line); +} + + +/* Retrieve the options for the component COMPONENT from backend + BACKEND, which we already know is of type file list. */ +static void +retrieve_options_from_file (gc_component_t component, gc_backend_t backend) +{ + gc_option_t *list_option; + char *list_pathname; + FILE *list_file; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + char *list = NULL; + + list_option = find_option (component, + gc_backend[backend].option_name, GC_BACKEND_ANY); + assert (list_option); + assert (!list_option->active); + + list_pathname = get_config_pathname (component, backend); + list_file = fopen (list_pathname, "r"); + if (!list_file) + gc_error (0, errno, "warning: can not open list file %s", list_pathname); + else + { + + while ((length = read_line (list_file, &line, &line_len, NULL)) > 0) + { + char *start; + char *end; + char *new_list; + + start = line; + while (*start == ' ' || *start == '\t') + start++; + if (!*start || *start == '#' || *start == '\r' || *start == '\n') + continue; + + end = start; + while (*end && *end != '#' && *end != '\r' && *end != '\n') + end++; + /* Walk back to skip trailing white spaces. Looks evil, but + works because of the conditions on START and END imposed + at this point (END is at least START + 1, and START is + not a whitespace character). */ + while (*(end - 1) == ' ' || *(end - 1) == '\t') + end--; + *end = '\0'; + /* FIXME: Oh, no! This is so lame! Should use realloc and + really append. */ + if (list) + { + new_list = xasprintf ("%s,\"%s", list, percent_escape (start)); + xfree (list); + list = new_list; + } + else + list = xasprintf ("\"%s", percent_escape (start)); + } + if (length < 0 || ferror (list_file)) + gc_error (1, errno, "can not read list file %s", list_pathname); + } + + list_option->active = 1; + list_option->value = list; + + xfree (line); +} + + +/* Retrieve the currently active options and their defaults from all + involved backends for this component. */ +void +gc_component_retrieve_options (int component) +{ + int backend_seen[GC_BACKEND_NR]; + gc_backend_t backend; + gc_option_t *option = gc_component[component].options; + + for (backend = 0; backend < GC_BACKEND_NR; backend++) + backend_seen[backend] = 0; + + while (option->name) + { + if (!(option->flags & GC_OPT_FLAG_GROUP)) + { + backend = option->backend; + + if (backend_seen[backend]) + { + option++; + continue; + } + backend_seen[backend] = 1; + + assert (backend != GC_BACKEND_ANY); + + if (gc_backend[backend].program) + retrieve_options_from_program (component, backend); + else + retrieve_options_from_file (component, backend); + } + option++; + } +} + + +/* 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. */ +static void +option_check_validity (gc_option_t *option, unsigned long flags, + char *new_value, unsigned long *new_value_nr) +{ + char *arg; + + if (!option->active) + gc_error (1, 0, "option %s not supported by backend", option->name); + + 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; + + 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->flags & GC_OPT_FLAG_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 == ',') + { + if (!(option->flags & GC_OPT_FLAG_ARG_OPT)) + gc_error (1, 0, "argument required for option %s", option->name); + + if (*arg == ',' && !(option->flags & GC_OPT_FLAG_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 != '"') + gc_error (1, 0, "string argument for option %s must begin " + "with a quote (\") character", option->name); + } + else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) + { + errno = 0; + (void) strtol (arg, &arg, 0); + + if (errno) + gc_error (1, errno, "invalid argument for option %s", + option->name); + + if (*arg != '\0' && *arg != ',') + gc_error (1, 0, "garbage after argument for option %s", + option->name); + } + else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32) + { + errno = 0; + (void) strtoul (arg, &arg, 0); + + if (errno) + gc_error (1, errno, "invalid argument for option %s", + option->name); + + if (*arg != '\0' && *arg != ',') + gc_error (1, 0, "garbage after argument for option %s", + option->name); + } + arg = strchr (arg, ','); + if (arg) + arg++; + } + while (arg && *arg); +} + + +/* Create and verify the new configuration file for the specified + backend and component. Returns 0 on success and -1 on error. */ +static int +change_options_file (gc_component_t component, gc_backend_t backend, + char **src_filenamep, char **dest_filenamep, + char **orig_filenamep) +{ + static const char marker[] = "###+++--- GPGConf ---+++###"; + /* 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; + FILE *src_file = NULL; + FILE *dest_file = NULL; + char *src_filename; + char *dest_filename; + char *orig_filename; + char *arg; + char *cur_arg = NULL; + + option = find_option (component, + gc_backend[backend].option_name, GC_BACKEND_ANY); + assert (option); + assert (option->active); + assert (gc_arg_type[option->arg_type].fallback != GC_ARG_TYPE_NONE); + + /* FIXME. Throughout the function, do better error reporting. */ + /* Note that get_config_pathname() calls percent_deescape(), so we + call this before processing the arguments. */ + dest_filename = xstrdup (get_config_pathname (component, backend)); + src_filename = xasprintf ("%s.gpgconf.%i.new", dest_filename, getpid ()); + orig_filename = xasprintf ("%s.gpgconf.%i.bak", dest_filename, getpid ()); + + arg = option->new_value; + if (arg && arg[0] == '\0') + arg = NULL; + else if (arg) + { + char *end; + + arg++; + end = strchr (arg, ','); + if (end) + *end = '\0'; + + cur_arg = percent_deescape (arg); + if (end) + { + *end = ','; + arg = end + 1; + } + else + arg = NULL; + } + +#if HAVE_W32_SYSTEM + res = 0; +#warning no backups for W32 yet - need to write a copy function +#else + res = link (dest_filename, orig_filename); +#endif + if (res < 0 && errno != ENOENT) + 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. */ + fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); + if (fd < 0) + return -1; + src_file = fdopen (fd, "w"); + res = errno; + if (!src_file) + { + 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 = fopen (dest_filename, "r"); + if (!dest_file) + goto change_file_one_err; + + while ((length = 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; + } + + start = line; + while (*start == ' ' || *start == '\t') + start++; + if (*start && *start != '\r' && *start != '\n' && *start != '#') + { + char *end; + char *endp; + char saved_end; + + endp = start; + end = endp; + + /* Search for the end of the line. */ + while (*endp && *endp != '#' && *endp != '\r' && *endp != '\n') + { + endp++; + if (*endp && *endp != ' ' && *endp != '\t' + && *endp != '\r' && *endp != '\n' && *endp != '#') + end = endp + 1; + } + saved_end = *end; + *end = '\0'; + + if ((option->new_flags & GC_OPT_FLAG_DEFAULT) + || !cur_arg || strcmp (start, cur_arg)) + disable = 1; + else + { + /* Find next argument. */ + if (arg) + { + char *arg_end; + + arg++; + arg_end = strchr (arg, ','); + if (arg_end) + *arg_end = '\0'; + + cur_arg = percent_deescape (arg); + if (arg_end) + { + *arg_end = ','; + arg = arg_end + 1; + } + else + arg = NULL; + } + else + cur_arg = NULL; + } + + *end = saved_end; + } + + if (disable) + { + if (!in_marker) + { + fprintf (src_file, + "# GPGConf disabled this option here at %s\n", + asctimestamp (gnupg_get_time ())); + if (ferror (src_file)) + goto change_file_one_err; + fprintf (src_file, "# %s", line); + if (ferror (src_file)) + goto change_file_one_err; + } + } + else + { + fprintf (src_file, "%s", line); + if (ferror (src_file)) + goto change_file_one_err; + } + } + if (length < 0 || ferror (dest_file)) + goto change_file_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. */ + fprintf (src_file, "\n%s\n", marker); + if (ferror (src_file)) + goto change_file_one_err; + } + + /* At this point, we have copied everything up to the end marker + into the new file, except for the arguments we are going to add. + Now, dump the new arguments and write the end marker, possibly + followed by the rest of the original file. */ + while (cur_arg) + { + fprintf (src_file, "%s\n", cur_arg); + + /* Find next argument. */ + if (arg) + { + char *end; + + arg++; + end = strchr (arg, ','); + if (end) + *end = '\0'; + + cur_arg = percent_deescape (arg); + if (end) + { + *end = ','; + arg = end + 1; + } + else + arg = NULL; + } + else + cur_arg = NULL; + } + + fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); + if (ferror (src_file)) + goto change_file_one_err; + + if (!in_marker) + { + fprintf (src_file, "# GPGConf edited this configuration file.\n"); + if (ferror (src_file)) + goto change_file_one_err; + fprintf (src_file, "# It will disable options before this marked " + "block, but it will\n"); + if (ferror (src_file)) + goto change_file_one_err; + fprintf (src_file, "# never change anything below these lines.\n"); + if (ferror (src_file)) + goto change_file_one_err; + } + if (dest_file) + { + while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) + { + fprintf (src_file, "%s", line); + if (ferror (src_file)) + goto change_file_one_err; + } + if (length < 0 || ferror (dest_file)) + goto change_file_one_err; + } + xfree (line); + line = NULL; + + res = fclose (src_file); + if (res) + { + res = errno; + close (fd); + if (dest_file) + fclose (dest_file); + errno = res; + return -1; + } + close (fd); + if (dest_file) + { + res = fclose (dest_file); + if (res) + return -1; + } + return 0; + + change_file_one_err: + xfree (line); + res = errno; + if (src_file) + { + fclose (src_file); + close (fd); + } + if (dest_file) + fclose (dest_file); + errno = res; + return -1; +} + + +/* Create and verify the new configuration file for the specified + backend and component. Returns 0 on success and -1 on error. */ +static int +change_options_program (gc_component_t component, gc_backend_t backend, + char **src_filenamep, char **dest_filenamep, + char **orig_filenamep) +{ + static const char marker[] = "###+++--- GPGConf ---+++###"; + /* 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; + FILE *src_file = NULL; + FILE *dest_file = NULL; + char *src_filename; + char *dest_filename; + char *orig_filename; + + /* FIXME. Throughout the function, do better error reporting. */ + dest_filename = xstrdup (get_config_pathname (component, backend)); + src_filename = xasprintf ("%s.gpgconf.%i.new", dest_filename, getpid ()); + orig_filename = xasprintf ("%s.gpgconf.%i.bak", dest_filename, getpid ()); + +#if HAVE_W32_SYSTEM + res = 0; +#warning no backups for W32 yet - need to write a copy function +#else + res = link (dest_filename, orig_filename); +#endif + if (res < 0 && errno != ENOENT) + 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. */ + fd = open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644); + if (fd < 0) + return -1; + src_file = fdopen (fd, "w"); + res = errno; + if (!src_file) + { + 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 = fopen (dest_filename, "r"); + if (!dest_file) + goto change_one_err; + + while ((length = 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; + } + + 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, backend); + *end = saved_end; + if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT) + || option->new_value)) + disable = 1; + } + if (disable) + { + if (!in_marker) + { + fprintf (src_file, + "# GPGConf disabled this option here at %s\n", + asctimestamp (gnupg_get_time ())); + if (ferror (src_file)) + goto change_one_err; + fprintf (src_file, "# %s", line); + if (ferror (src_file)) + goto change_one_err; + } + } + else + { + fprintf (src_file, "%s", line); + if (ferror (src_file)) + goto change_one_err; + } + } + if (length < 0 || 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. */ + fprintf (src_file, "\n%s\n", marker); + if (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 (backend == GC_BACKEND_GPG) + fprintf (src_file, "utf8-strings\n"); + + option = gc_component[component].options; + while (option->name) + { + if (!(option->flags & GC_OPT_FLAG_GROUP) + && option->backend == backend + && option->new_value) + { + char *arg = option->new_value; + + do + { + if (*arg == '\0' || *arg == ',') + { + fprintf (src_file, "%s\n", option->name); + if (ferror (src_file)) + goto change_one_err; + } + else if (gc_arg_type[option->arg_type].fallback + == GC_ARG_TYPE_NONE) + { + assert (*arg == '1'); + fprintf (src_file, "%s\n", option->name); + if (ferror (src_file)) + goto change_one_err; + + arg++; + } + else if (gc_arg_type[option->arg_type].fallback + == GC_ARG_TYPE_STRING) + { + char *end; + + assert (*arg == '"'); + arg++; + + end = strchr (arg, ','); + if (end) + *end = '\0'; + + fprintf (src_file, "%s %s\n", option->name, + percent_deescape (arg)); + if (ferror (src_file)) + goto change_one_err; + + if (end) + *end = ','; + arg = end; + } + else + { + char *end; + + end = strchr (arg, ','); + if (end) + *end = '\0'; + + fprintf (src_file, "%s %s\n", option->name, arg); + if (ferror (src_file)) + goto change_one_err; + + if (end) + *end = ','; + arg = end; + } + + assert (arg == NULL || *arg == '\0' || *arg == ','); + if (arg && *arg == ',') + arg++; + } + while (arg && *arg); + } + option++; + } + + fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ())); + if (ferror (src_file)) + goto change_one_err; + + if (!in_marker) + { + fprintf (src_file, "# GPGConf edited this configuration file.\n"); + if (ferror (src_file)) + goto change_one_err; + fprintf (src_file, "# It will disable options before this marked " + "block, but it will\n"); + if (ferror (src_file)) + goto change_one_err; + fprintf (src_file, "# never change anything below these lines.\n"); + if (ferror (src_file)) + goto change_one_err; + } + if (dest_file) + { + while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0) + { + fprintf (src_file, "%s", line); + if (ferror (src_file)) + goto change_one_err; + } + if (length < 0 || ferror (dest_file)) + goto change_one_err; + } + xfree (line); + line = NULL; + + res = fclose (src_file); + if (res) + { + res = errno; + close (fd); + if (dest_file) + fclose (dest_file); + errno = res; + return -1; + } + close (fd); + if (dest_file) + { + res = fclose (dest_file); + if (res) + return -1; + } + return 0; + + change_one_err: + xfree (line); + res = errno; + if (src_file) + { + fclose (src_file); + close (fd); + } + if (dest_file) + fclose (dest_file); + errno = res; + return -1; +} + + +/* Read the modifications from IN and apply them. */ +void +gc_component_change_options (int component, FILE *in) +{ + int err = 0; + int runtime[GC_BACKEND_NR]; + char *src_pathname[GC_BACKEND_NR]; + char *dest_pathname[GC_BACKEND_NR]; + char *orig_pathname[GC_BACKEND_NR]; + gc_backend_t backend; + gc_option_t *option; + char *line = NULL; + size_t line_len = 0; + ssize_t length; + + for (backend = 0; backend < GC_BACKEND_NR; backend++) + { + runtime[backend] = 0; + src_pathname[backend] = NULL; + dest_pathname[backend] = NULL; + orig_pathname[backend] = NULL; + } + + while ((length = read_line (in, &line, &line_len, NULL)) > 0) + { + char *linep; + unsigned long flags = 0; + char *new_value = ""; + unsigned long new_value_nr = 0; + + /* 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'; + + 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; + } + + /* 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, GC_BACKEND_ANY); + if (!option) + gc_error (1, 0, "unknown option %s", line); + + option_check_validity (option, flags, new_value, &new_value_nr); + + if (option->flags & GC_OPT_FLAG_RUNTIME) + runtime[option->backend] = 1; + + option->new_flags = flags; + if (!(flags & GC_OPT_FLAG_DEFAULT)) + { + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE + && (option->flags & GC_OPT_FLAG_LIST)) + { + char *str; + + /* We convert the number to a list of 1's for + convenient list handling. */ + 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); + } + } + + /* 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->name) + { + /* Go on if we have already seen this backend, or if there is + nothing to do. */ + if (src_pathname[option->backend] + || !(option->new_flags || option->new_value)) + { + option++; + continue; + } + + if (gc_backend[option->backend].program) + err = change_options_program (component, option->backend, + &src_pathname[option->backend], + &dest_pathname[option->backend], + &orig_pathname[option->backend]); + else + err = change_options_file (component, option->backend, + &src_pathname[option->backend], + &dest_pathname[option->backend], + &orig_pathname[option->backend]); + + if (err) + break; + + option++; + } + + if (!err) + { + int i; + + for (i = 0; i < GC_BACKEND_NR; i++) + { + if (src_pathname[i]) + { + /* FIXME: Make a verification here. */ + + assert (dest_pathname[i]); + + if (orig_pathname[i]) + err = rename (src_pathname[i], dest_pathname[i]); + else + { +#ifdef HAVE_W32_SYSTEM + /* FIXME: Won't work becuase W32 doesn't silently + overwrite. Fix it by creating a backup copy and + deliting the orginal file first. */ + err = rename (src_pathname[i], dest_pathname[i]); +#else /*!HAVE_W32_SYSTEM*/ + /* This is a bit safer than rename() because we + expect DEST_PATHNAME not to be there. If it + happens to be there, this will fail. */ + err = link (src_pathname[i], dest_pathname[i]); + if (!err) + unlink (src_pathname[i]); +#endif /*!HAVE_W32_SYSTEM*/ + } + if (err) + break; + src_pathname[i] = NULL; + } + } + } + + if (err) + { + int i; + int saved_errno = errno; + + /* An error occured. */ + for (i = 0; i < GC_BACKEND_NR; i++) + { + if (src_pathname[i]) + { + /* The change was not yet committed. */ + unlink (src_pathname[i]); + if (orig_pathname[i]) + unlink (orig_pathname[i]); + } + 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_pathname[i]) + rename (orig_pathname[i], dest_pathname[i]); + else + unlink (dest_pathname[i]); + } + } + gc_error (1, saved_errno, "could not commit changes"); + } + + /* If it all worked, notify the daemons of the changes. */ + if (opt.runtime) + for (backend = 0; backend < GC_BACKEND_NR; backend++) + { + if (runtime[backend] && gc_backend[backend].runtime_change) + (*gc_backend[backend].runtime_change) (); + } + + /* Move the per-process backup file into its place. */ + for (backend = 0; backend < GC_BACKEND_NR; backend++) + if (orig_pathname[backend]) + { + char *backup_pathname; + + assert (dest_pathname[backend]); + + backup_pathname = xasprintf ("%s.gpgconf.bak", dest_pathname[backend]); + rename (orig_pathname[backend], backup_pathname); + } + + xfree (line); +} |