diff --git a/tools/ChangeLog b/tools/ChangeLog index 33ed0fc8b..a382d05a5 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,11 @@ +2004-01-29 Marcus Brinkmann + + * gpgconf-list.c: File removed. + * README.gpgconf: New file. + * gpgconf-comp.c: New file. + * Makefile.am (gpgconf_SOURCES): Remove gpgconf-list.c, add + gpgconf-comp.c. + 2004-01-16 Werner Koch * watchgnupg.c (main): Need to use FD_ISSET for the client diff --git a/tools/Makefile.am b/tools/Makefile.am index fb2d929bf..09ba633f4 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -28,7 +28,7 @@ AM_CFLAGS = -I$(top_srcdir)/common -I$(top_srcdir)/intl @GPG_ERROR_CFLAGS@ bin_PROGRAMS = gpgconf -gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-list.c no-libgcrypt.c +gpgconf_SOURCES = gpgconf.c gpgconf.h gpgconf-comp.c no-libgcrypt.c gpgconf_LDADD = ../jnlib/libjnlib.a ../common/libcommon.a @INTLLIBS@ diff --git a/tools/README.gpgconf b/tools/README.gpgconf new file mode 100644 index 000000000..c0e5511c1 --- /dev/null +++ b/tools/README.gpgconf @@ -0,0 +1,322 @@ +============ + GPG Conf +============ + +CONCEPT +======= + +gpgconf provides access to the configuration of one or more components +of the GnuPG system. These components correspond more or less to the +programs that exist in the GnuPG framework, like GnuPG, GPGSM, +DirMngr, etc. But this is not a strict one-to-one relationship. Not +all configuration options are available through GPGConf. GPGConf +provides a generic and abstract method to access the most important +configuration options that can feasibly be controlled via such a +mechanism. + +GPGConf can be used to gather and change the options available in each +component, and can also provide their default values. GPGConf will +give detailed type information that can be used to restrict the user's +input without making an attempt to commit the changes. + +GPGConf provides the backend of a configuration editor. The +configuration editor would usually be a graphical user interface +program, that allows to display the current options, their default +values, and allows the user to make changes to the options. These +changes can then be made active with GPGConf again. Such a program +that uses GPGConf in this way will be called 'GUI' throughout this +document. + + +Format Conventions +================== + +Some lines in the output of GPGConf contain a list of colon-separated +fields. The following conventions apply: + +The GUI program is required to strip off trailing newline and/or carriage +return characters from the output. + +GPGConf will never leave out fields. If a certain version documents a +certain field, this field will always be present in all GPGConf +versions from that time on. + +Future versions of GPGConf might append fields to the list. New +fields will always be separated from the previously last field by a +colon separator. The GUI should be prepared to parse the last field +it knows about up until a colon or end of line. + +Not all fields are defined under all conditions. You are required to +ignore the content of undefined fields. + +Some fields contain strings that are not escaped in any way. Such +fields are described to be used "verbatim". These fields will never +contain a colon character (for obvious reasons). No de-escaping or +other formatting is required to use the field content. This is for +easy parsing of the output, when it is known that the content can +never contain any special characters. + +Some fields contain strings that are described to be +"percent-escaped". Such strings need to be de-escaped before their +content can be presented to the user. A percent-escaped string is +de-escaped by replacing all occurences of %XY by the byte that has the +hexadecimal value XY. X and Y are from the set { '0'..'9', 'a'..'f' }. + +Some fields contain strings that are described to be "localised". Such +strings are translated to the active language and formatted in the +active character set. + +Some fields contain an unsigned number. This number will always fit +into a 32-bit unsigned integer variable. The number may be followed +by a space, followed by a human readable description of that value. +You should ignore everything in the field that follows the number. + +Some fields contain a signed number. This number will always fit into +a 32-bit signed integer variable. The number may be followed by a +space, followed by a human readable description of that value. You +should ignore everything in the field that follows the number. + +Some fields contain an option argument. The format of an option +argument depends on the type of the option and on some flags: + +The simplest case is that the option does not take an argument at all +(TYPE is 0). Then the option argument is either empty if the option +is not set, or an unsigned number that specifies how often the option +occurs. If the LIST flag is not set, then the only valid number is 1. + +If the option takes a number argument (ALT-TYPE is 2 or 3), and it can +only occur once (LIST flag is not set), then the option argument is +either empty if the option is not set, or it is a number. A number is +a string that begins with an optional minus character, followed by one +or more digits. The number must fit into an integer variable +(unsigned or signed, depending on ALT-TYPE). + +If the option takes a number argument and it can occur more than once, +then the option argument is either empty, or it is a comma-separated +list of numbers as described above. + +If the option takes a string argument (ALT-TYPE is 1), and it can only +occur once (LIST flag is not set) then the option argument is either +empty if the option is not set, or it starts with a double quote +character (") followed by a percent-escaped string that is the +argument value. Note that there is only a leading double quote +character, no trailing one. The double quote character is only needed +to be able to differentiate between no value and the empty string as +value. + +If the option takes a string argument and it can occur more than once, +then the option argument is either empty or it starts with a double +quote character (") followed by a comma-separated list of +percent-escaped strings. Obviously any commas in the individual +strings must be percent-escaped. + + +FIXME: Document the active language and active character set. Allow +to change it via the command line? + + +Components +========== + +A component is a set of configuration options that semantically belong +together. Furthermore, several changes to a component can be made in +an atomic way with a single operation. The GUI could for example +provide a menu with one entry for each component, or a window with one +tabulator sheet per component. + +The following interface is provided to list the available components: + +Command --list-components +------------------------- + +Outputs a list of all components available, one per line. The format +of each line is: + +NAME:DESCRIPTION + +NAME + +This field contains a name tag of the component. The name tag is used +to specify the component in all communication with GPGConf. The name +tag is to be used verbatim. It is not in any escaped format. + +DESCRIPTION + +The string in this field contains a human-readable description of the +component. It can be displayed to the user of the GUI for +informational purposes. It is percent-escaped and localized. + +Example: +$ gpgconf --list-components +gpg-agent:GPG Agent +dirmngr:CRL Manager + + +OPTIONS +======= + +Every component contains one or more options. Options may belong to a +group. The following command lists all options and the groups they +belong to: + +Command --list-options COMPONENT +-------------------------------- + +Lists all options (and the groups they belong to) in the component +COMPONENT. COMPONENT is the string in the field NAME in the +output of the --list-components command. + +There is one line for each option and each group. First come all +options that are not in any group. Then comes a line describing a +group. Then come all options that belong into each group. Then comes +the next group and so on. + +The format of each line is: + +NAME:FLAGS:LEVEL:DESCRIPTION:TYPE:ALT-TYPE:ARGNAME:DEFAULT:VALUE + +NAME + +This field contains a name tag for the group or option. The name tag +is used to specify the group or option in all communication with +GPGConf. The name tag is to be used verbatim. It is not in any +escaped format. + +FLAGS + +The flags field contains an unsigned number. Its value is the +OR-wise combination of the following flag values: + + 1 group If this flag is set, this is a line describing + a group and not an option. + O 2 optional arg If this flag is set, the argument is optional. + O 4 list If this flag is set, the option can be given + multiple times. + O 8 runtime If this flag is set, the option can be changed + at runtime. + +Flags marked with a 'O' are only defined for options (ie, if the GROUP +flag is not set). + +LEVEL + +This field is defined for options and for groups. It contains an +unsigned number that specifies the expert level under which this group +or option should be displayed. The following expert levels are +defined for options (they have analogous meaning for groups): + + 0 basic This option should always be offered to the user. + 1 advanced This option may be offered to advanced users. + 2 expert This option should only be offered to expert users. + 3 invisible This option should normally never be displayed, + not even to expert users. + 4 internal This option is for internal use only. Ignore it. + +The level of a group will always be the lowest level of all options it +contains. + +DESCRIPTION + +This field is defined for options and groups. The string in this +field contains a human-readable description of the option or group. +It can be displayed to the user of the GUI for informational purposes. +It is percent-escaped and localized. + +TYPE + +This field is only defined for options. It contains an unsigned +number that specifies the type of the option's argument, if any. +The following types are defined: + + 0 none No argument allowed. + 1 string An unformatted string. + 2 int32 A signed integer number. + 3 uint32 An unsigned integer number. + 4 pathname A string that describes the pathname of a file. + The file does not necessarily need to exist. + 5 ldap server A string that describes an LDAP server in the format + HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. + +More types will be added in the future. Please see the ALT-TYPE field +for information on how to cope with unknown types. + +ALT-TYPE + +This field is identical to TYPE, except that only the types 0 to 3 are +allowed. The GUI is expected to present the user the option in the +format specified by TYPE. But if the argument type TYPE is not +supported by the GUI, it can still display the option in the more +generic basic type ALT-TYPE. The GUI must support the basic types 0 +to 3 to be able to display all options. + +ARGNAME + +This field is only defined for options with an argument type TYPE that +is not 0. In this case it may contain a percent-escaped and localised +string that gives a short name for the argument. The field may also +be empty, though, in which case a short name is not known. + +DEFAULT + +This field is defined only for options. Its format is that of an +option argument (see section Format Conventions for details). If the +default value is empty, then no default is known. Otherwise, the +value specifies the default value for this option. Note that this +field is also meaningful if the option itself does not take a real +argument. + +VALUE + +This field is defined only for options. Its format is that of an +option argument. If it is empty, then the option is not explicitely +set in the current configuration, and the default applies (if any). +Otherwise, it contains the current value of the option. Note that +this field is also meaningful if the option itself does not take a +real argument. + + +CHANGING OPTIONS +================ + +To change the options for a component, you must provide them in the +following format: + +NAME:NEW-VALUE + +NAME + +This is the name of the option to change. + +NEW-VALUE + +The new value for the option. The format is that of an option +argument. If it is empty (or the field is omitted), the option will +be deleted, so that the default value is used. Otherwise, the option +will be set to the specified value. + +Option --runtime +---------------- + +If this option is set, the changes will take effect at run-time, as +far as this is possible. Otherwise, they will take effect at the next +start of the respective backend programs. + + +BACKENDS +======== + +Backends should support the following commands: + +Command --gpgconf-list +---------------------- + +List the location of the configuration file, and all default values of +all options. The location of the configuration file must be an +absolute pathname. + +Example: +$ dirmngr --gpgconf-list +gpgconf-config-file:/mnt/marcus/.gnupg/dirmngr.conf +ldapservers-file:/mnt/marcus/.gnupg/dirmngr_ldapservers.conf +add-servers: +max-replies:10 diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c new file mode 100644 index 000000000..0e0ae5d7c --- /dev/null +++ b/tools/gpgconf-comp.c @@ -0,0 +1,1321 @@ +/* gpgconf-comp.c - Configuration utility for GnuPG. + Copyright (C) 2003 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 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, + Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + +#if HAVE_CONFIG_H +#include +#endif +#include +#include +#include +/* FIXME use gettext.h */ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gpgconf.h" + + +/* TODO: + Portability - Add gnulib replacements for getline, error, etc. + Backend: File backend must be able to write out changes !!! + Components: Add more components and their options. + Robustness: Do more validation. Call programs to do validation for us. +*/ + + +/* 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 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 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, NULL, NULL }, /* GC_BACKEND_ANY dummy entry. */ + { "GnuPG", "gpg", "gpgconf-config-file" }, + { "GPGSM", "gpgsm", "gpgconf-config-file" }, + { "GPG Agent", "gpg-agent", "gpgconf-config-file" }, + { "DirMngr", "dirmngr", "gpgconf-config-file" }, + { "DirMngr LDAP Server List", 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, + + + /* Complex argument types. */ + + /* A complete pathname. */ + GC_ARG_TYPE_PATHNAME = 4, + + /* An LDAP server in the format + HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */ + GC_ARG_TYPE_LDAP_SERVER = 5, + + /* A 40 character fingerprint. */ + GC_ARG_TYPE_KEY_FPR = 6, + + /* ADD NEW 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" }, + + /* 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 0 +/* 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 (1 << 0) +/* The ARG_OPT flag for an option indicates that the argument is + optional. */ +#define GC_OPT_FLAG_ARG_OPT (1 << 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 (1 << 2) +/* The RUNTIME flag for an option indicates that the option can be + changed at runtime. */ +#define GC_OPT_FLAG_RUNTIME (1 << 3) + +/* A human-readable description for each flag. */ +static struct +{ + const char *name; +} gc_flag[] = + { + { "group" }, + { "optional arg" }, + { "list" }, + { "runtime" } + }; + + +/* 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 int 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. */ + 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 '|'. */ + 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. */ + + /* 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 current value of this option. */ + char *value; + + /* 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[] = + { + 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-config-file", GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR }, + + { "Monitor", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + NULL, "Options controlling the diagnostic output" }, + { "verbose", GC_OPT_FLAG_NONE, 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, + NULL, "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, + NULL, "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, + "dirmngr", "Options useful for debugging" }, + { "debug", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED, + "dirmngr", "|FLAGS|set the debugging FLAGS", + GC_ARG_TYPE_UINT32, GC_BACKEND_DIRMNGR }, + { "debug-all", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED, + "dirmngr", "set all debugging flags", + GC_ARG_TYPE_NONE, 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", "|FILE|write 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, + NULL, "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 }, + + { "LDAP", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + NULL, "Configuration of LDAP servers to use" }, + { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC, + "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_LIST, GC_LEVEL_BASIC, + NULL, "LDAP server list", + GC_ARG_TYPE_LDAP_SERVER, GC_BACKEND_DIRMNGR_LDAP_SERVER_LIST }, + + { "CRL", + GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, + NULL, "Configuration of the CRL" }, + { "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 }, + + 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 GPG Agent. */ + GC_COMPONENT_GPG_AGENT, + + /* 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-agent", NULL, "GPG Agent", gc_options_gpg_agent }, + { "dirmngr", NULL, "CRL Manager", gc_options_dirmngr } + }; + + +/* Robust version of dgettext. */ +static const char * +my_dgettext (const char *domain, const char *msgid) +{ + if (domain) + { + char *text = dgettext (domain, msgid); + return text ? text : msgid; + } + else + 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) + error (1, 1, "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; +} + + + +/* 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 all options of the component COMPONENT. */ +void +gc_component_list_options (int component, FILE *out) +{ + const gc_option_t *option = gc_component[component].options; + + while (option->name) + { + const char *desc = NULL; + char *arg_name = NULL; + + /* Do not output unknown or internal options. */ + if (!option->default_value || option->level == GC_LEVEL_INTERNAL) + { + option++; + continue; + } + + 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 = malloc (arg_len + 1); + if (!arg_name) + error (1, 1, "Can not build argument name"); + 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, ":%u", option->flags); + if (opt.verbose) + { + putc (' ', out); + + if (!option->flags) + fprintf (out, "none"); + else + { + unsigned int flags = option->flags; + unsigned int flag = 0; + unsigned int 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) + free (arg_name); + + /* The default value field. */ + fprintf (out, ":%s", option->default_value ? option->default_value : ""); + + /* The value field. */ + fprintf (out, ":%s", option->value ? option->value : ""); + + /* ADD NEW FIELDS HERE. */ + + putc ('\n', 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; + gc_option_t *option = find_option + (component, gc_backend[backend].option_config_filename, GC_BACKEND_ANY); + assert (option); + + if (!option->default_value) + 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) + pathname = option->value; + else + pathname = option->default_value; + + if (*pathname != '/') + 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 *output; + + asprintf (&cmd_line, "%s --gpgconf-list", gc_backend[backend].program); + if (!cmd_line) + error (1, 1, "Can not construct command line"); + + output = popen (cmd_line, "r"); + if (!output) + error (1, 1, "Could not gather active options from %s", cmd_line); + + while ((length = getline (&line, &line_len, output)) > 0) + { + gc_option_t *option; + char *default_value; + char *value; + + /* Strip newline and carriage return, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + + /* Extract default value and value, if present. Default to + empty if not. */ + default_value = strchr (line, ':'); + if (!default_value) + { + default_value = ""; + value = ""; + } + else + { + *(default_value++) = '\0'; + value = strchr (default_value, ':'); + if (!value) + value = ""; + else + { + char *end; + + *(value++) = '\0'; + end = strchr (value, ':'); + if (end) + *end = '\0'; + } + } + + /* Look up the option in the component and install the + configuration data. */ + option = find_option (component, line, backend); + if (option) + { + if (option->default_value) + error (1, 1, "Option %s returned twice from %s", + line, cmd_line); + option->default_value = strdup (default_value); + option->value = strdup (value); + if (!option->default_value || !option->value) + error (1, 1, "Could not store options"); + } + } + if (ferror (output)) + error (1, 1, "Error reading from %s", cmd_line); + if (fclose (output) && ferror (output)) + error (1, 1, "Error closing %s", cmd_line); + free (cmd_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; + + list_option = find_option (component, + gc_backend[backend].option_name, GC_BACKEND_ANY); + assert (list_option); + + list_pathname = get_config_pathname (component, backend); + + list_file = fopen (list_pathname, "r"); + if (ferror (list_file)) + error (1, 1, "Can not open list file %s", list_pathname); + + list = strdup ("\""); + if (!list) + error (1, 1, "Can not allocate initial list string"); + + while ((length = getline (&line, &line_len, list_file)) > 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! Use realloc and really + append. */ + if (list) + { + asprintf (&new_list, "%s,%s", list, percent_escape (start)); + free (list); + list = new_list; + } + if (!list) + error (1, 1, "Can not construct list"); + } + if (ferror (list_file)) + error (1, 1, "Can not read list file %s", list_pathname); + list_option->default_value = ""; + list_option->value = list; +} + + +/* 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. */ +static void +option_check_validity (gc_option_t *option, const char *new_value) +{ + if (option->new_value) + error (1, 0, "Option %s already changed", option->name); + + if (!*new_value) + return; + + /* FIXME. Verify that lists are lists, numbers are numbers, strings + are strings, etc. */ +} + + +/* 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) +{ + /* FIXME. */ + assert (!"Not implemented."); + 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; +#define LINE_LEN 4096 + char line[LINE_LEN]; + 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 = strdup (get_config_pathname (component, backend)); + if (!dest_filename) + return -1; + asprintf (&src_filename, "%s.gpgconf.%i.new", dest_filename, getpid ()); + if (!src_filename) + return -1; + asprintf (&orig_filename, "%s.gpgconf.%i.bak", dest_filename, getpid ()); + if (!orig_filename) + return -1; + + res = link (dest_filename, orig_filename); + if (res < 0 && errno != ENOENT) + return -1; + if (res < 0) + { + free (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 (fgets (line, LINE_LEN, dest_file)) + { + int length; + int disable = 0; + char *start; + char *end; + + line[LINE_LEN - 1] = '\0'; + length = strlen (line); + if (length == LINE_LEN - 1) + { + /* FIXME */ + errno = ENAMETOOLONG; + goto change_one_err; + } + + 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 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_value) + disable = 1; + } + if (disable) + { + if (!in_marker) + { + fprintf (src_file, + "# GPGConf disabled this option here at FIXME\n"); + 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 (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. */ + option = gc_component[component].options; + while (option->name) + { + if (!(option->flags & GC_OPT_FLAG_GROUP) + && option->backend == backend + && option->new_value + && *option->new_value) + { + if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING) + fprintf (src_file, "%s %s\n", option->name, &option->new_value[1]); + else if (option->arg_type == GC_ARG_TYPE_NONE) + fprintf (src_file, "%s\n", option->name); + else + fprintf (src_file, "%s %s\n", option->name, option->new_value); + if (ferror (src_file)) + goto change_one_err; + } + option++; + } + { + time_t cur_time = time (NULL); + + /* asctime() returns a string that ends with a newline + character! */ + fprintf (src_file, "%s %s", marker, asctime (localtime (&cur_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 (fgets (line, LINE_LEN, dest_file)) + { + int length; + + line[LINE_LEN - 1] = '\0'; + length = strlen (line); + if (length == LINE_LEN - 1) + { + /* FIXME */ + errno = ENAMETOOLONG; + goto change_one_err; + } + fprintf (src_file, "%s", line); + if (ferror (src_file)) + goto change_one_err; + } + if (ferror (dest_file)) + goto change_one_err; + } + 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: + 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; + 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++) + { + src_pathname[backend] = NULL; + dest_pathname[backend] = NULL; + orig_pathname[backend] = NULL; + } + + while ((length = getline (&line, &line_len, in)) > 0) + { + char *value; + + /* Strip newline and carriage return, if present. */ + while (length > 0 + && (line[length - 1] == '\n' || line[length - 1] == '\r')) + line[--length] = '\0'; + + value = strchr (line, ':'); + if (!value) + value = ""; + else + { + char *end; + + *(value++) = '\0'; + end = strchr (value, ':'); + if (end) + *end = '\0'; + } + + option = find_option (component, line, GC_BACKEND_ANY); + if (!option) + error (1, 0, "Unknown option %s", line); + + option_check_validity (option, value); + option->new_value = strdup (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_value) + { + option++; + continue; + } + + if (gc_backend[option->backend].program) + err = change_options_program (component, option->backend, + &src_pathname[component], + &dest_pathname[component], + &orig_pathname[component]); + else + err = change_options_file (component, option->backend, + &src_pathname[component], + &dest_pathname[component], + &orig_pathname[component]); + + if (err) + break; + + option++; + } + if (!err) + { + int i; + + for (i = 0; i < GC_COMPONENT_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 + { + /* 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]); + } + if (err) + break; + src_pathname[i] = NULL; + } + } + } + + if (err) + { + int i; + int res = errno; + + /* An error occured. */ + for (i = 0; i < GC_COMPONENT_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]); + } + } + errno = res; + error (1, 1, "Could not commit changes"); + } +} diff --git a/tools/gpgconf-list.c b/tools/gpgconf-list.c deleted file mode 100644 index e774d2bf4..000000000 --- a/tools/gpgconf-list.c +++ /dev/null @@ -1,60 +0,0 @@ -/* gpgconf-list.c - Print list of options. - * Copyright (C) 2003 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA - */ - -#include -#include -#include -#include - -#include "gpgconf.h" -#include "i18n.h" - -/* Format of the colon delimited listing is: - - Area: gpg, gpgsm, gpg-agent, scdaemon, dirmngr, "G" or empty for unspecified. - Option name: Name of the option - Expert level: Expertnesslevel of option: 0 - basic - Immediately Change: "1" is the option is immediatley changeable - (e.g. through SIGHUP) - Option index: Instance number of the option value to build lists. - Option value: Current value of the option - -*/ - - -/* List global options, i.er. those which are commonly required and - may affect more than one program. */ -static void -list_global_options (void) -{ - - -} - - - -void -gpgconf_list_standard_options (void) -{ - - list_global_options (); - - -} diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 6f4c1fbbb..f261a2c0a 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -33,12 +33,16 @@ enum cmd_and_opt_values aNull = 0, oDryRun = 'n', oOutput = 'o', - oQuiet = 'q', + oQuiet = 'q', oVerbose = 'v', + oComponent = 'c', oNoVerbose = 500, oHomedir, - - aDummy + + aListComponents, + aListOptions, + aChangeOptions, + }; @@ -47,16 +51,19 @@ static ARGPARSE_OPTS opts[] = { { 300, NULL, 0, N_("@Commands:\n ") }, + { aListComponents, "list-components", 256, N_("list all components") }, + { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, + { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, + { 301, NULL, 0, N_("@\nOptions:\n ") }, - { oOutput, "output", 2, N_("use as output file")}, + { oOutput, "output", 2, N_("use as output file") }, { oVerbose, "verbose", 0, N_("verbose") }, - { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oQuiet, "quiet", 0, N_("quiet") }, { oDryRun, "dry-run", 0, N_("do not make any changes") }, - + /* hidden options */ { oNoVerbose, "no-verbose", 0, "@"}, - { oHomedir, "homedir", 2, "@" }, /* defaults to "~/.gnupg" */ {0} }; @@ -124,16 +131,6 @@ main (int argc, char **argv) i18n_init(); - /* Setup the default homedir. */ -#ifdef __MINGW32__ - opt.homedir = read_w32_registry_string ( NULL, - "Software\\GNU\\GnuPG", "HomeDir" ); -#else - opt.homedir = getenv ("GNUPGHOME"); -#endif - if (!opt.homedir || !*opt.homedir ) - opt.homedir = GNUPG_DEFAULT_HOMEDIR; - /* Patrse the command line. */ pargs.argc = &argc; pargs.argv = &argv; @@ -143,14 +140,17 @@ main (int argc, char **argv) switch (pargs.r_opt) { case oOutput: opt.outfile = pargs.r.ret_str; break; - - case oQuiet: opt.quiet = 1; break; + case oQuiet: opt.quiet = 1; break; case oDryRun: opt.dry_run = 1; break; case oVerbose: opt.verbose++; break; case oNoVerbose: opt.verbose = 0; break; - case oHomedir: opt.homedir = pargs.r.ret_str; break; - case aDummy: break; + case aListComponents: + case aListOptions: + case aChangeOptions: + cmd = pargs.r_opt; + break; + default: pargs.err = 2; break; } } @@ -158,14 +158,40 @@ main (int argc, char **argv) if (log_get_errorcount (0)) exit (2); - fname = argc? *argv : NULL; + fname = argc ? *argv : NULL; switch (cmd) { + case aListComponents: default: - /* List all standard options. */ - gpgconf_list_standard_options (); + /* List all components. */ + gc_component_list_components (stdout); break; + + case aListOptions: + case aChangeOptions: + if (!fname) + { + fputs (N_("usage: gpgconf [options] "), stderr); + fputs (N_("Need one component argument"), stderr); + putc ('\n',stderr); + exit (2); + } + else + { + int idx = gc_component_find (fname); + if (idx < 0) + { + fputs (N_("Component not found"), stderr); + putc ('\n', stderr); + exit (1); + } + gc_component_retrieve_options (idx); + if (cmd == aListOptions) + gc_component_list_options (idx, stdout); + else + gc_component_change_options (idx, stdin); + } } return 0; diff --git a/tools/gpgconf.h b/tools/gpgconf.h index dd0ec2cc2..6ccc0d1dd 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -26,16 +26,31 @@ /* We keep all global options in the structure OPT. */ struct { int verbose; /* Verbosity level. */ - int quiet; /* Be as quiet as possible. */ + int quiet; /* Be extra quiet. */ int dry_run; /* Don't change any persistent data. */ - const char *homedir; /* Configuration directory name. */ char *outfile; /* Name of output file. */ + + int component; /* The active component. */ } opt; -/*-- gpgconf-list.c --*/ -void gpgconf_list_standard_options (void); +/*-- gpgconf-comp.c --*/ +/* List all components that are available. */ +void gc_component_list_components (FILE *out); +/* Find the component with the name NAME. Returns -1 if not + found. */ +int gc_component_find (const char *name); + +/* Retrieve the currently active options and their defaults from all + involved backends for this component. */ +void gc_component_retrieve_options (int component); + +/* List all options of the component COMPONENT. */ +void gc_component_list_options (int component, FILE *out); + +/* Read the modifications from IN and apply them. */ +void gc_component_change_options (int component, FILE *in); #endif /*GPGCONF_H*/