2004-01-29 Marcus Brinkmann <marcus@g10code.de>
authorMarcus Brinkmann <mb@g10code.com>
Wed, 28 Jan 2004 23:58:18 +0000 (23:58 +0000)
committerMarcus Brinkmann <mb@g10code.com>
Wed, 28 Jan 2004 23:58:18 +0000 (23:58 +0000)
* 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.

tools/ChangeLog
tools/Makefile.am
tools/README.gpgconf [new file with mode: 0644]
tools/gpgconf-comp.c [new file with mode: 0644]
tools/gpgconf-list.c [deleted file]
tools/gpgconf.c
tools/gpgconf.h

index 33ed0fc..a382d05 100644 (file)
@@ -1,3 +1,11 @@
+2004-01-29  Marcus Brinkmann  <marcus@g10code.de>
+
+       * 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  <wk@gnupg.org>
 
        * watchgnupg.c (main): Need to use FD_ISSET for the client
index fb2d929..09ba633 100644 (file)
@@ -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 (file)
index 0000000..c0e5511
--- /dev/null
@@ -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 (file)
index 0000000..0e0ae5d
--- /dev/null
@@ -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 <config.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+/* FIXME use gettext.h */
+#include <libintl.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <errno.h>
+#include <time.h>
+
+#include <error.h>
+
+#include "gpgconf.h"
+
+\f
+/* 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.
+*/
+
+\f
+/* 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" },
+  };
+
+\f
+/* 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 }
+
+\f
+/* 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
+ };
+
+\f
+/* 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 }
+  };
+
+\f
+/* 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;
+}
+
+
+\f
+/* 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));
+    }
+}
+
+\f
+/* 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;
+}
+
+\f
+/* 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;
+}
+
+\f
+/* 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;
+}
+
+\f
+/* 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++;
+    }
+}
+
+\f
+/* 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 (file)
index e774d2b..0000000
+++ /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 <config.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#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 ();
-
-
-}
index 6f4c1fb..f261a2c 100644 (file)
@@ -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; 
index dd0ec2c..6ccc0d1 100644 (file)
 /* 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*/