Make --runtime option of gpgconf under W32 work.
[gnupg.git] / tools / gpgconf-comp.c
index ce16ce7..579025e 100644 (file)
@@ -1,21 +1,21 @@
 /* gpgconf-comp.c - Configuration utility for GnuPG.
  Copyright (C) 2004 Free Software Foundation, Inc.
-
  This file is part of GnuPG.
  GnuPG is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  GnuPG is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  General Public License for more details.
  You should have received a copy of the GNU General Public License
-   along with GnuPG; if not, write to the Free Software Foundation,
  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
* Copyright (C) 2004, 2007, 2008 Free Software Foundation, Inc.
+ *
* This file is part of GnuPG.
+ *
* GnuPG is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
+ *
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* General Public License for more details.
+ *
* You should have received a copy of the GNU General Public License
+ * along with GnuPG; if not, see <http://www.gnu.org/licenses/>.
+ */
 
 #if HAVE_CONFIG_H
 #include <config.h>
@@ -23,8 +23,6 @@
 #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 <time.h>
 #include <stdarg.h>
 #include <signal.h>
+#include <ctype.h>
+#ifdef HAVE_W32_SYSTEM
+# define WIN32_LEAN_AND_MEAN 1
+# include <windows.h>
+#else
+# include <pwd.h>
+# include <grp.h>
+#endif
 
 /* For log_logv(), asctimestamp(), gnupg_get_time ().  */
 #define JNLIB_NEED_LOG_LOGV
 #include "util.h"
+#include "i18n.h"
+#include "exechelp.h"
 
+#include "gc-opt-flags.h"
 #include "gpgconf.h"
 
+
+/* There is a problem with gpg 1.4 under Windows: --gpgconf-list
+   returns a plain filename without escaping.  As long as we have not
+   fixed that we need to use gpg2 - it might actually be better to use
+   gpg2 in any case.  */
+#ifdef HAVE_W32_SYSTEM
+#define GPGNAME "gpg2"
+#else
+#define GPGNAME "gpg"
+#endif
+
 \f
 /* TODO:
-   Portability - Add gnulib replacements for getline, etc.
-XXX Marcus: Please use the read_line code from dirmngr/src/http.c - it
-has been in use for may years and provides the ability to limit the
-length of the line and thus thwart DoS (not a issue here but at many
-other places).
-
    Components: Add more components and their options.
    Robustness: Do more validation.  Call programs to do validation for us.
-   Don't use popen, as this will not tell us if the program had a
-   non-zero exit code.
    Add options to change backend binary path.
    Extract binary path for some backends from gpgsm/gpg config.
 */
@@ -142,6 +153,12 @@ static struct
      GPGConf.  In this case, PROGRAM is NULL.  */
   char *program;
 
+  /* The module name (GNUPG_MODULE_NAME_foo) as defined by
+     ../common/util.h.  This value is used to get the actual installed
+     path of the program.  0 is used if no backedn program is
+     available. */
+  char module_name;
+
   /* The runtime change callback.  */
   void (*runtime_change) (void);
 
@@ -157,14 +174,18 @@ static struct
 } gc_backend[GC_BACKEND_NR] =
   {
     { NULL },          /* GC_BACKEND_ANY dummy entry.  */
-    { "GnuPG", "gpg", NULL, "gpgconf-gpg.conf" },
-    { "GPGSM", "gpgsm", NULL, "gpgconf-gpgsm.conf" },
-    { "GPG Agent", "gpg-agent", gpg_agent_runtime_change,
-      "gpgconf-gpg-agent.conf" },
-    { "SCDaemon", "scdaemon", NULL, "gpgconf-scdaemon.conf" },
-    { "DirMngr", "dirmngr", NULL, "gpgconf-dirmngr.conf" },
-    { "DirMngr LDAP Server List", NULL, NULL, "ldapserverlist-file",
-      "LDAP Server" },
+    { "GnuPG", GPGNAME, GNUPG_MODULE_NAME_GPG,
+      NULL, "gpgconf-gpg.conf" },
+    { "GPGSM", "gpgsm", GNUPG_MODULE_NAME_GPGSM,
+      NULL, "gpgconf-gpgsm.conf" },
+    { "GPG Agent", "gpg-agent", GNUPG_MODULE_NAME_AGENT, 
+      gpg_agent_runtime_change, "gpgconf-gpg-agent.conf" },
+    { "SCDaemon", "scdaemon", GNUPG_MODULE_NAME_SCDAEMON,
+      NULL, "gpgconf-scdaemon.conf" },
+    { "DirMngr", "dirmngr", GNUPG_MODULE_NAME_DIRMNGR,
+      NULL, "gpgconf-dirmngr.conf" },
+    { "DirMngr LDAP Server List", NULL, 0, 
+      NULL, "ldapserverlist-file", "LDAP Server" },
   };
 
 \f
@@ -207,6 +228,12 @@ typedef enum
     /* A 40 character fingerprint.  */
     GC_ARG_TYPE_KEY_FPR = 34,
 
+    /* A user ID or key ID or fingerprint for a certificate.  */
+    GC_ARG_TYPE_PUB_KEY = 35,
+
+    /* A user ID or key ID or fingerprint for a certificate with a key.  */
+    GC_ARG_TYPE_SEC_KEY = 36,
+
     /* ADD NEW COMPLEX TYPE ENTRIES HERE.  */
 
     /* The number of the above entries.  */
@@ -252,6 +279,8 @@ static struct
     { GC_ARG_TYPE_STRING, "pathname" },
     { GC_ARG_TYPE_STRING, "ldap server" },
     { GC_ARG_TYPE_STRING, "key fpr" },
+    { GC_ARG_TYPE_STRING, "pub key" },
+    { GC_ARG_TYPE_STRING, "sec key" },
   };
 
 
@@ -298,9 +327,12 @@ static struct
   };
 
 
-/* Option flags.  YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING
-   FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE.  */
-#define GC_OPT_FLAG_NONE       0UL
+/* Option flags.  The flags which are used by the backends are defined
+   by gc-opt-flags.h, included above.
+
+   YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
+   PART OF THE EXTERNAL INTERFACE.  */
+
 /* Some entries in the option list are not options, but mark the
    beginning of a new group of options.  These entries have the GROUP
    flag set.  */
@@ -312,20 +344,12 @@ static struct
    several times.  A comma separated list of arguments is used as the
    argument value.  */
 #define GC_OPT_FLAG_LIST       (1UL << 2)
-/* The RUNTIME flag for an option indicates that the option can be
-   changed at runtime.  */
-#define GC_OPT_FLAG_RUNTIME    (1UL << 3)
-
-/* The following flags are incorporated from the backend.  */
-/* The DEFAULT flag for an option indicates that the option has a
-   default value.  */
-#define GC_OPT_FLAG_DEFAULT    (1UL << 4)
-/* The DEF_DESC flag for an option indicates that the option has a
-   default, which is described by the value of the default field.  */
-#define GC_OPT_FLAG_DEF_DESC   (1UL << 5)
-/* The NO_ARG_DESC flag for an option indicates that the argument has
-   a default, which is described by the value of the ARGDEF field.  */
-#define GC_OPT_FLAG_NO_ARG_DESC        (1UL << 6)
+/* The NO_CHANGE flag for an option indicates that the user should not
+   be allowed to change this option using the standard gpgconf method.
+   Frontends using gpgconf should grey out such options, so that only
+   the current value is displayed.  */
+#define GC_OPT_FLAG_NO_CHANGE   (1UL <<7)
+
 
 /* A human-readable description for each flag.  */
 static struct
@@ -339,7 +363,8 @@ static struct
     { "runtime" },
     { "default" },
     { "default desc" },
-    { "no arg desc" }
+    { "no arg desc" },
+    { "no change" }
   };
 
 
@@ -368,12 +393,22 @@ struct gc_option
 
   /* 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.  */
+     and options.
+     
+     Note that we try to keep the description of groups within the
+     gnupg domain. 
+     
+     IMPORTANT: If you add a new domain please make sure to add a code
+     set switching call to the function my_dgettext further below.  */
   const char *desc_domain;
 
   /* A gettext description for this group or option.  If it starts
      with a '|', then the string up to the next '|' describes the
-     argument, and the description follows the second '|'.  */
+     argument, and the description follows the second '|'. 
+
+     In general enclosing these description in N_() is not required
+     because the description should be identical to the one in the
+     help menu of the respective program. */
   const char *desc;
 
   /* The following fields are only valid for options.  */
@@ -429,7 +464,7 @@ static gc_option_t gc_options_gpg_agent[] =
 
    { "Monitor",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the diagnostic output" },
+     "gnupg", N_("Options controlling the diagnostic output") },
    { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
      "gnupg", "verbose",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
@@ -442,19 +477,22 @@ static gc_option_t gc_options_gpg_agent[] =
 
    { "Configuration",
      GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
-     NULL, "Options controlling the configuration" },
+     "gnupg", N_("Options controlling the configuration") },
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", "|FILE|read options from FILE",
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT },
+   { "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", "do not use the SCdaemon",
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     "gnupg", "Options useful for debugging" },
+     "gnupg", N_("Options useful for debugging") },
    { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
      "gnupg", "|LEVEL|set the debugging level to LEVEL",
      GC_ARG_TYPE_STRING, GC_BACKEND_GPG_AGENT },
    { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
-     "gnupg", "|FILE|write logs to FILE",
+     "gnupg", N_("|FILE|write server mode logs to FILE"),
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT },
    { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
      NULL, NULL,
@@ -462,17 +500,60 @@ static gc_option_t gc_options_gpg_agent[] =
 
    { "Security",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the security" },
-   { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
-     "gnupg", "|N|expire cached PINs after N seconds",
+     "gnupg", N_("Options controlling the security") },
+   { "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_BASIC, "gnupg", 
+     "|N|expire cached PINs after N seconds",
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_ADVANCED, "gnupg",
+     N_("|N|expire SSH keys after N seconds"),
      GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
-   { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
-     "gnupg", "do not use the PIN cache when signing",
+   { "max-cache-ttl", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT, "gnupg",
+     N_("|N|set maximum PIN cache lifetime to N seconds"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("|N|set maximum SSH key lifetime to N seconds"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+   { "allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_ADVANCED, "gnupg", "allow clients to mark keys as \"trusted\"",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
    { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
      "gnupg", "do not grab keyboard and mouse",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
 
+   { "Passphrase policy",
+     GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
+     "gnupg", N_("Options enforcing a passphrase policy") },
+   { "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, 
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("do not allow to bypass the passphrase policy"),
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+   { "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_ADVANCED, "gnupg", 
+     N_("|N|set minimal required length for new passphrases to N"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("|N|require at least N non-alpha characters for a new passphrase"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT,
+     "gnupg", N_("|FILE|check new passphrases against pattern in FILE"),
+     GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG_AGENT },
+   { "max-passphrase-days", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("|N|expire the passphrase after N days"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
+   { "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, 
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("do not allow the reuse of old passphrases"),
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
 
    GC_OPTION_NULL
  };
@@ -487,7 +568,7 @@ static gc_option_t gc_options_scdaemon[] =
 
    { "Monitor",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the diagnostic output" },
+     "gnupg", N_("Options controlling the diagnostic output") },
    { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
      "gnupg", "verbose",
      GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
@@ -500,7 +581,7 @@ static gc_option_t gc_options_scdaemon[] =
 
    { "Configuration",
      GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
-     NULL, "Options controlling the configuration" },
+     "gnupg", N_("Options controlling the configuration") },
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", "|FILE|read options from FILE",
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON },
@@ -519,21 +600,23 @@ static gc_option_t gc_options_scdaemon[] =
    { "disable-ccid", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", "do not use the internal CCID driver",
      GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
-
+   { "disable-keypad", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", "do not use a reader's keypad",
+     GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     "gnupg", "Options useful for debugging" },
+     "gnupg", N_("Options useful for debugging") },
    { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
      "gnupg", "|LEVEL|set the debugging level to LEVEL",
      GC_ARG_TYPE_STRING, GC_BACKEND_SCDAEMON },
    { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
-     "gnupg", "|FILE|write logs to FILE",
+     "gnupg", N_("|FILE|write server mode logs to FILE"),
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_SCDAEMON },
 
    { "Security",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the security" },
+     "gnupg", N_("Options controlling the security") },
    { "allow-admin", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "gnupg", "allow the use of admin card commands",
      GC_ARG_TYPE_NONE, GC_BACKEND_SCDAEMON },
@@ -552,7 +635,7 @@ static gc_option_t gc_options_gpg[] =
 
    { "Monitor",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the diagnostic output" },
+     "gnupg", N_("Options controlling the diagnostic output") },
    { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
      "gnupg", "verbose",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
@@ -565,19 +648,25 @@ static gc_option_t gc_options_gpg[] =
 
    { "Configuration",
      GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
-     NULL, "Options controlling the configuration" },
+     "gnupg", N_("Options controlling the configuration") },
+   { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", N_("|NAME|use NAME as default secret key"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
+   { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", N_("|NAME|encrypt to user ID NAME as well"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", "|FILE|read options from FILE",
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     "gnupg", "Options useful for debugging" },
+     "gnupg", N_("Options useful for debugging") },
    { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
      "gnupg", "|LEVEL|set the debugging level to LEVEL",
      GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
    { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
-     "gnupg", "|FILE|write logs to FILE",
+     "gnupg", N_("|FILE|write server mode logs to FILE"),
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPG },
 /*    { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE, */
 /*      NULL, NULL, */
@@ -585,9 +674,15 @@ static gc_option_t gc_options_gpg[] =
 
    { "Keyserver",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Configuration for Keyservers" },
+     "gnupg", N_("Configuration for Keyservers") },
    { "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
-     "gnupg", "|URL|use keyserver at URL",
+     "gnupg", N_("|URL|use keyserver at URL"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
+   { "allow-pka-lookup", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", N_("allow PKA lookups (DNS requests)"),
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG },
+   { "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", N_("|MECHANISMS|use MECHANISMS to locate keys by mail address"),
      GC_ARG_TYPE_STRING, GC_BACKEND_GPG },
 
 
@@ -605,7 +700,7 @@ static gc_option_t gc_options_gpgsm[] =
 
    { "Monitor",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the diagnostic output" },
+     "gnupg", N_("Options controlling the diagnostic output") },
    { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
      "gnupg", "verbose",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
@@ -618,19 +713,34 @@ static gc_option_t gc_options_gpgsm[] =
 
    { "Configuration",
      GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
-     NULL, "Options controlling the configuration" },
+     "gnupg", N_("Options controlling the configuration") },
+   { "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", N_("|NAME|use NAME as default secret key"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
+   { "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "gnupg", N_("|NAME|encrypt to user ID NAME as well"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "gnupg", "|FILE|read options from FILE",
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM },
+   { "prefer-system-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", "use system's dirmngr if available",
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
+   { "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
+     "gnupg", N_("disable all access to the dirmngr"),
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
+   { "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", N_("|NAME|use encoding NAME for PKCS#12 passphrases"),
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     "gnupg", "Options useful for debugging" },
+     "gnupg", N_("Options useful for debugging") },
    { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
      "gnupg", "|LEVEL|set the debugging level to LEVEL",
      GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
    { "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
-     "gnupg", "|FILE|write logs to FILE",
+     "gnupg", N_("|FILE|write server mode logs to FILE"),
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_GPGSM },
    { "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
      NULL, NULL,
@@ -638,10 +748,13 @@ static gc_option_t gc_options_gpgsm[] =
 
    { "Security",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the security" },
+     "gnupg", N_("Options controlling the security") },
    { "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "gnupg", "never consult a CRL",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
+   { "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
+     "gnupg", N_("do not check CRLs for root certificates"),
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
    { "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
      "gnupg", "check validity using OCSP",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
@@ -654,6 +767,9 @@ static gc_option_t gc_options_gpgsm[] =
    { "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "gnupg", "fetch missing issuer certificates",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPGSM },
+   { "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", "|NAME|use cipher algorithm NAME",
+     GC_ARG_TYPE_STRING, GC_BACKEND_GPGSM },
 
    GC_OPTION_NULL
  };
@@ -668,7 +784,7 @@ static gc_option_t gc_options_dirmngr[] =
 
    { "Monitor",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the diagnostic output" },
+     "gnupg", N_("Options controlling the diagnostic output") },
    { "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
      "dirmngr", "verbose",
      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
@@ -681,7 +797,7 @@ static gc_option_t gc_options_dirmngr[] =
 
    { "Format",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the format of the output" },
+     "gnupg", N_("Options controlling the format of the output") },
    { "sh", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "dirmngr", "sh-style command output",
      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
@@ -691,14 +807,14 @@ static gc_option_t gc_options_dirmngr[] =
    
    { "Configuration",
      GC_OPT_FLAG_GROUP, GC_LEVEL_EXPERT,
-     NULL, "Options controlling the configuration" },
+     "gnupg", N_("Options controlling the configuration") },
    { "options", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT,
      "dirmngr", "|FILE|read options from FILE",
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR },
 
    { "Debug",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     "dirmngr", "Options useful for debugging" },
+     "gnupg", N_("Options useful for debugging") },
    { "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED,
      "dirmngr", "|LEVEL|set the debugging level to LEVEL",
      GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
@@ -706,7 +822,7 @@ static gc_option_t gc_options_dirmngr[] =
      "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",
+     "dirmngr", N_("|FILE|write server mode logs to FILE"),
      GC_ARG_TYPE_PATHNAME, GC_BACKEND_DIRMNGR },
    { "debug-wait", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE,
      NULL, NULL,
@@ -717,7 +833,7 @@ static gc_option_t gc_options_dirmngr[] =
 
    { "Enforcement",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
-     NULL, "Options controlling the interactivity and enforcement" },
+     "gnupg", N_("Options controlling the interactivity and enforcement") },
    { "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "dirmngr", "run without asking a user",
      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
@@ -725,10 +841,38 @@ static gc_option_t gc_options_dirmngr[] =
      "dirmngr", "force loading of outdated CRLs",
      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
 
+   { "HTTP",
+     GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
+     "gnupg", N_("Configuration for HTTP servers") },
+   { "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "inhibit the use of HTTP",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "ignore HTTP CRL distribution points",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "|URL|redirect all HTTP requests to URL",
+     GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
+   { "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "gnupg", N_("use system's HTTP proxy setting"),
+     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,
+     "gnupg", N_("Configuration of LDAP servers to use") },
+   { "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "inhibit the use of LDAP",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "ignore LDAP CRL distribution points",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
+     "dirmngr", "|HOST|use HOST for LDAP queries",
+     GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
+   { "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "do not use fallback hosts with --ldap-proxy",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
      "dirmngr", "add new servers discovered in CRL distribution points"
      " to serverlist", GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
    { "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
@@ -744,24 +888,23 @@ static gc_option_t gc_options_dirmngr[] =
       GC_BACKEND_DIRMNGR in this component, so that the entry for
       "ldapserverlist-file will be initialized before this one.  */
    { "LDAP Server", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
-     NULL, "LDAP server list",
+     "gnupg", N_("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 },
 
    { "OCSP",
      GC_OPT_FLAG_GROUP, GC_LEVEL_ADVANCED,
-     NULL, "Configuration for OCSP" },
+     "gnupg", N_("Configuration for OCSP") },
    { "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC,
      "dirmngr", "allow sending OCSP requests",
      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
+   { "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
+     "dirmngr", "ignore certificate contained OCSP service URLs",
+      GC_ARG_TYPE_NONE, GC_BACKEND_DIRMNGR },
    { "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
-     "dirmngr", "|URL|use OCSP responder URL",
+     "dirmngr", "|URL|use OCSP responder at URL",
      GC_ARG_TYPE_STRING, GC_BACKEND_DIRMNGR },
    { "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
      "dirmngr", "|FPR|OCSP response signed by FPR",
@@ -820,14 +963,30 @@ static struct
     { "gpg-agent", NULL, "GPG Agent", gc_options_gpg_agent },
     { "scdaemon", NULL, "Smartcard Daemon", gc_options_scdaemon },
     { "gpgsm", NULL, "GPG for S/MIME", gc_options_gpgsm },
-    { "dirmngr", NULL, "CRL Manager", gc_options_dirmngr }
+    { "dirmngr", NULL, "Directory Manager", gc_options_dirmngr }
   };
 
+
+
+/* Structure used to collect error output of the backend programs.  */
+struct error_line_s;
+typedef struct error_line_s *error_line_t;
+struct error_line_s
+{
+  error_line_t next;   /* Link to next item.  */
+  const char *fname;   /* Name of the config file (points into BUFFER).  */
+  unsigned int lineno; /* Line number of the config file.  */
+  const char *errtext; /* Text of the error message (points into BUFFER).  */
+  char buffer[1];  /* Helper buffer.  */
+};
+
+
 \f
 /* Engine specific support.  */
 void
 gpg_agent_runtime_change (void)
 {
+#ifndef HAVE_W32_SYSTEM
   char *agent = getenv ("GPG_AGENT_INFO");
   char *pid_str;
   unsigned long pid_long;
@@ -855,11 +1014,29 @@ gpg_agent_runtime_change (void)
 
   /* Ignore any errors here.  */
   kill (pid, SIGHUP);
+#else
+  gpg_error_t err;
+  const char *pgmname;
+  const char *argv[2];
+  pid_t pid;
+  
+  pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
+  argv[0] = "reloadagent";
+  argv[1] = NULL;
+  
+  err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid);
+  if (!err)
+    err = gnupg_wait_process (pgmname, pid, NULL);
+  if (err)
+    gc_error (0, 0, "error running `%s%s': %s",
+              pgmname, " reloadagent", gpg_strerror (err));
+#endif /*!HAVE_W32_SYSTEM*/
 }
 
+
 \f
-/* More or less Robust version of dgettext.  It has the sidefeect of
-   switching the codeset to utf-8 becuase this is what we want to
+/* More or less Robust version of dgettext.  It has the side effect of
+   switching the codeset to utf-8 because this is what we want to
    output.  In theory it is posible to keep the orginal code set and
    switch back for regular disgnostic output (redefine "_(" for that)
    but given the natur of this tool, being something invoked from
@@ -867,7 +1044,7 @@ gpg_agent_runtime_change (void)
 static const char *
 my_dgettext (const char *domain, const char *msgid)
 {
-#ifdef ENABLE_NLS
+#ifdef USE_SIMPLE_GETTEXT
   if (domain)
     {
       static int switched_codeset;
@@ -875,9 +1052,40 @@ my_dgettext (const char *domain, const char *msgid)
       
       if (!switched_codeset)
         {
-          bind_textdomain_codeset (PACKAGE_GT, "utf-8");
           switched_codeset = 1;
+          gettext_select_utf8 (1);
         }
+
+      if (!strcmp (domain, "gnupg"))
+        domain = PACKAGE_GT;
+
+      /* FIXME: we have no dgettext, thus we can't switch.  */
+
+      text = gettext (msgid);
+      return text ? text : msgid;
+    }
+#elif defined(ENABLE_NLS)
+  if (domain)
+    {
+      static int switched_codeset;
+      char *text;
+      
+      if (!switched_codeset)
+        {
+          switched_codeset = 1;
+          bind_textdomain_codeset (PACKAGE_GT, "utf-8");
+
+          bindtextdomain ("dirmngr", LOCALEDIR);
+          bind_textdomain_codeset ("dirmngr", "utf-8");
+   
+        }
+
+      /* Note: This is a hack to actually use the gnupg2 domain as
+         long we are in a transition phase where gnupg 1.x and 1.9 may
+         coexist. */
+      if (!strcmp (domain, "gnupg"))
+        domain = PACKAGE_GT;
+
       text = dgettext (domain, msgid);
       return text ? text : msgid;
     }
@@ -890,7 +1098,7 @@ my_dgettext (const char *domain, const char *msgid)
 /* Percent-Escape special characters.  The string is valid until the
    next invocation of the function.  */
 static char *
-percent_escape (const char *src)
+my_percent_escape (const char *src)
 {
   static char *esc_str;
   static int esc_str_len;
@@ -938,34 +1146,6 @@ percent_escape (const char *src)
 }
 
 
-/* Convert two hexadecimal digits from STR to the value they
-   represent.  Returns -1 if one of the characters is not a
-   hexadecimal digit.  */
-static int
-hextobyte (const char *str)
-{
-  int val = 0;
-  int i;
-
-#define NROFHEXDIGITS 2
-  for (i = 0; i < NROFHEXDIGITS; i++)
-    {
-      if (*str >= '0' && *str <= '9')
-       val += *str - '0';
-      else if (*str >= 'A' && *str <= 'F')
-       val += 10 + *str - 'A';
-      else if (*str >= 'a' && *str <= 'f')
-       val += 10 + *str - 'a';
-      else
-       return -1;
-      if (i < NROFHEXDIGITS - 1)
-       val *= 16;
-      str++;
-    }
-  return val;
-}
-
-
 
 /* Percent-Deescape special characters.  The string is valid until the
    next invocation of the function.  */
@@ -1011,16 +1191,286 @@ percent_deescape (const char *src)
 void
 gc_component_list_components (FILE *out)
 {
-  gc_component_t idx;
+  gc_component_t component;
+  gc_option_t *option;
+  gc_backend_t backend;
+  int backend_seen[GC_BACKEND_NR];
+  const char *desc;
+  const char *pgmname;
 
-  for (idx = 0; idx < GC_COMPONENT_NR; idx++)
+  for (component = 0; component < GC_COMPONENT_NR; component++)
+    {
+      option = gc_component[component].options;
+      if (option)
+        {
+          for (backend = 0; backend < GC_BACKEND_NR; backend++)
+            backend_seen[backend] = 0;
+
+          pgmname = "";
+          for (; option && option->name; option++)
+            {
+              if ((option->flags & GC_OPT_FLAG_GROUP))
+                continue;
+              backend = option->backend;
+              if (backend_seen[backend])
+                continue;
+              backend_seen[backend] = 1;
+              assert (backend != GC_BACKEND_ANY);
+              if (gc_backend[backend].program
+                  && !gc_backend[backend].module_name)
+                continue;
+              pgmname = gnupg_module_name (gc_backend[backend].module_name);
+              break;
+            }
+
+          desc = gc_component[component].desc;
+          desc = my_dgettext (gc_component[component].desc_domain, desc);
+          fprintf (out, "%s:%s:",
+                   gc_component[component].name,  my_percent_escape (desc));
+          fprintf (out, "%s\n",  my_percent_escape (pgmname));
+        }
+    }
+}
+
+
+\f
+static int
+all_digits_p (const char *p, size_t len)
+{
+  if (!len)
+    return 0; /* No. */
+  for (; len; len--, p++)
+    if (!isascii (*p) || !isdigit (*p))
+      return 0; /* No.  */
+  return 1; /* Yes.  */
+}
+
+
+/* Collect all error lines from file descriptor FD. Only lines
+   prefixed with TAG are considered.  Close that file descriptor
+   then.  Returns a list of error line items (which may be empty).
+   There is no error return.  */
+static error_line_t
+collect_error_output (int fd, const char *tag)
+{
+  FILE *fp;
+  char buffer[1024];
+  char *p, *p2, *p3;
+  int c, cont_line;
+  unsigned int pos;
+  error_line_t eitem, errlines, *errlines_tail;
+  size_t taglen = strlen (tag);
+
+  fp = fdopen (fd, "r");
+  if (!fp)
+    gc_error (1, errno, "can't fdopen pipe for reading");
+
+  errlines = NULL;
+  errlines_tail = &errlines;
+  pos = 0;
+  cont_line = 0;
+  while ((c=getc (fp)) != EOF)
     {
-      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));
+      buffer[pos++] = c;
+      if (pos >= sizeof buffer - 5 || c == '\n')
+        {
+          buffer[pos - (c == '\n')] = 0;
+          if (cont_line)
+            ; /*Ignore continuations of previous line. */
+          else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':') 
+            {
+              /* "gpgsm: foo:4: bla" */
+              /* Yep, we are interested in this line.  */
+              p = buffer + taglen + 1;
+              while (*p == ' ' || *p == '\t')
+                p++;
+              if (!*p)
+                ; /* Empty lines are ignored.  */
+              else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
+                        && all_digits_p (p2+1, p3 - (p2+1)))
+                {
+                  /* Line in standard compiler format.  */
+                  p3++;
+                  while (*p3 == ' ' || *p3 == '\t')
+                    p3++;
+                  eitem = xmalloc (sizeof *eitem + strlen (p));
+                  eitem->next = NULL;
+                  strcpy (eitem->buffer, p);
+                  eitem->fname = eitem->buffer;
+                  eitem->buffer[p2-p] = 0;
+                  eitem->errtext = eitem->buffer + (p3 - p);
+                  /* (we already checked that there are only ascii
+                     digits followed by a colon) */
+                  eitem->lineno = 0;
+                  for (p2++; isdigit (*p2); p2++)
+                    eitem->lineno = eitem->lineno*10 + (*p2 - '0');
+                  *errlines_tail = eitem;
+                  errlines_tail = &eitem->next;
+                }
+              else
+                {
+                  /* Other error output.  */
+                  eitem = xmalloc (sizeof *eitem + strlen (p));
+                  eitem->next = NULL;
+                  strcpy (eitem->buffer, p);
+                  eitem->fname = NULL;
+                  eitem->errtext = eitem->buffer;
+                  eitem->lineno = 0;
+                  *errlines_tail = eitem;
+                  errlines_tail = &eitem->next;
+                }
+            }
+          pos = 0;
+          /* If this was not a complete line mark that we are in a
+             continuation.  */
+          cont_line = (c != '\n');
+        }
+    }
+  
+  /* We ignore error lines not terminated by a LF.  */
+
+  fclose (fp);
+  return errlines;
+}
+
+
+/* Check the options of a single component.  Returns 0 if everything
+   is OK.  */
+int
+gc_component_check_options (int component, FILE *out, const char *conf_file)
+{
+  gpg_error_t err;
+  unsigned int result;
+  int backend_seen[GC_BACKEND_NR];
+  gc_backend_t backend;
+  gc_option_t *option;
+  const char *pgmname;
+  const char *argv[4];
+  int i;
+  pid_t pid;
+  int exitcode;
+  int filedes[2];
+  error_line_t errlines;
+
+  /* We use a temporary file to collect the error output.  It would be
+     better to use a pipe here but as of now we have no suitable
+     fucntion to create a portable pipe outside of exechelp.  Thus it
+     is easier to use the tempfile approach.  */
+
+  for (backend = 0; backend < GC_BACKEND_NR; backend++)
+    backend_seen[backend] = 0;
+
+  option = gc_component[component].options;
+  for (; option && option->name; option++)
+    {
+      if ((option->flags & GC_OPT_FLAG_GROUP))
+       continue;
+      backend = option->backend;
+      if (backend_seen[backend])
+       continue;
+      backend_seen[backend] = 1;
+      assert (backend != GC_BACKEND_ANY);
+      if (!gc_backend[backend].program)
+       continue;
+      if (!gc_backend[backend].module_name)
+       continue;
+
+      break;
+    }
+  if (! option || ! option->name)
+    return 0;
+
+  pgmname = gnupg_module_name (gc_backend[backend].module_name);
+  i = 0;
+  if (conf_file)
+    {
+      argv[i++] = "--options";
+      argv[i++] = conf_file;
+    }
+  argv[i++] = "--gpgconf-test";
+  argv[i++] = NULL;
+  
+  err = gnupg_create_inbound_pipe (filedes);
+  if (err)
+    gc_error (1, 0, _("error creating a pipe: %s\n"), 
+             gpg_strerror (err));
+  
+  result = 0;
+  errlines = NULL;
+  if (gnupg_spawn_process_fd (pgmname, argv, -1, -1, filedes[1], &pid))
+    {
+      close (filedes[0]);
+      close (filedes[1]);
+      result |= 1; /* Program could not be run.  */
+    }
+  else 
+    {
+      close (filedes[1]);
+      errlines = collect_error_output (filedes[0], 
+                                      gc_component[component].name);
+      if (gnupg_wait_process (pgmname, pid, &exitcode))
+       {
+         if (exitcode == -1)
+           result |= 1; /* Program could not be run or it
+                           terminated abnormally.  */
+         result |= 2; /* Program returned an error.  */
+       }
     }
+  
+  /* If the program could not be run, we can't tell whether
+     the config file is good.  */
+  if (result & 1)
+    result |= 2;  
+  
+  if (out)
+    {
+      const char *desc;
+      error_line_t errptr;
+
+      desc = gc_component[component].desc;
+      desc = my_dgettext (gc_component[component].desc_domain, desc);
+      fprintf (out, "%s:%s:",
+              gc_component[component].name, my_percent_escape (desc));
+      fputs (my_percent_escape (pgmname), out);
+      fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
+      for (errptr = errlines; errptr; errptr = errptr->next)
+       {
+         if (errptr != errlines)
+           fputs ("\n:::::", out); /* Continuation line.  */
+         if (errptr->fname)
+           fputs (my_percent_escape (errptr->fname), out);
+         putc (':', out);
+         if (errptr->fname)
+           fprintf (out, "%u", errptr->lineno);
+         putc (':', out);
+         fputs (my_percent_escape (errptr->errtext), out);
+         putc (':', out);
+       }
+      putc ('\n', out);
+    }
+
+  while (errlines)
+    {
+      error_line_t tmp = errlines->next;
+      xfree (errlines);
+      errlines = tmp;
+    }
+
+  return result;
 }
 
+
+/* Check all components that are available.  */
+void
+gc_check_programs (FILE *out)
+{
+  gc_component_t component;
+
+  for (component = 0; component < GC_COMPONENT_NR; component++)
+    gc_component_check_options (component, out, NULL);
+}
+
+
 \f
 /* Find the component with the name NAME.  Returns -1 if not
    found.  */
@@ -1031,7 +1481,8 @@ gc_component_find (const char *name)
 
   for (idx = 0; idx < GC_COMPONENT_NR; idx++)
     {
-      if (!strcmp (name, gc_component[idx].name))
+      if (gc_component[idx].options
+          && !strcmp (name, gc_component[idx].name))
        return idx;
     }
   return -1;
@@ -1108,7 +1559,7 @@ list_one_option (const gc_option_t *option, FILE *out)
     fprintf (out, " %s", gc_level[option->level].name);
 
   /* The description field.  */
-  fprintf (out, ":%s", desc ? percent_escape (desc) : "");
+  fprintf (out, ":%s", desc ? my_percent_escape (desc) : "");
   
   /* The type field.  */
   fprintf (out, ":%u", option->arg_type);
@@ -1122,7 +1573,7 @@ list_one_option (const gc_option_t *option, FILE *out)
             gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
 
   /* The argument name field.  */
-  fprintf (out, ":%s", arg_name ? percent_escape (arg_name) : "");
+  fprintf (out, ":%s", arg_name ? my_percent_escape (arg_name) : "");
   if (arg_name)
     xfree (arg_name);
 
@@ -1138,7 +1589,7 @@ list_one_option (const gc_option_t *option, FILE *out)
       && option->value)
     /* The special format "1,1,1,1,...,1" is converted to a number
        here.  */
-    fprintf (out, ":%u", (strlen (option->value) + 1) / 2);
+    fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
   else
     fprintf (out, ":%s", option->value ? option->value : "");
 
@@ -1153,9 +1604,8 @@ void
 gc_component_list_options (int component, FILE *out)
 {  
   const gc_option_t *option = gc_component[component].options;
-  const gc_option_t *group_option = NULL;
 
-  while (option->name)
+  while (option && option->name)
     {
       /* Do not output unknown or internal options.  */
       if (!(option->flags & GC_OPT_FLAG_GROUP)
@@ -1166,17 +1616,38 @@ gc_component_list_options (int component, FILE *out)
        }
 
       if (option->flags & GC_OPT_FLAG_GROUP)
-       group_option = option;
-      else
        {
-         if (group_option)
+         const gc_option_t *group_option = option + 1;
+         gc_expert_level_t level = GC_LEVEL_NR;
+
+         /* The manual states that the group level is always the
+            minimum of the levels of all contained options.  Due to
+            different active options, and because it is hard to
+            maintain manually, we calculate it here.  The value in
+            the global static table is ignored.  */
+         
+         while (group_option->name)
            {
-             list_one_option (group_option, out);
-             group_option = NULL;
+             if (group_option->flags & GC_OPT_FLAG_GROUP)
+               break;
+             if (group_option->level < level)
+               level = group_option->level;
+             group_option++;
            }
 
-         list_one_option (option, out);
+         /* Check if group is empty.  */
+         if (level != GC_LEVEL_NR)
+           {
+             gc_option_t opt_copy;
+
+             /* Fix up the group level.  */
+             memcpy (&opt_copy, option, sizeof (opt_copy));
+             opt_copy.level = level;
+             list_one_option (&opt_copy, out);
+           }
        }
+      else
+       list_one_option (option, out);
 
       option++;
     }
@@ -1226,7 +1697,13 @@ get_config_pathname (gc_component_t component, gc_backend_t backend)
   else
     pathname = "";
 
+#ifdef HAVE_DOSISH_SYSTEM
+  if (!(pathname[0] 
+        && pathname[1] == ':'
+        && (pathname[2] == '/' || pathname[2] == '\\')))
+#else
   if (pathname[0] != '/')
+#endif
     gc_error (1, 0, "Option %s, needed by backend %s, is not absolute",
              gc_backend[backend].option_config_filename,
              gc_backend[backend].name);
@@ -1240,20 +1717,42 @@ get_config_pathname (gc_component_t component, gc_backend_t backend)
 static void
 retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
 {
-  char *cmd_line;
+  gpg_error_t err;
+  int filedes[2];
+  const char *pgmname;
+  const char *argv[2];
+  int exitcode;
+  pid_t pid;
   char *line = NULL;
   size_t line_len = 0;
   ssize_t length;
   FILE *config;
   char *config_pathname;
 
-  cmd_line = xasprintf ("%s --gpgconf-list", gc_backend[backend].program);
+  err = gnupg_create_inbound_pipe (filedes);
+  if (err)
+    gc_error (1, 0, _("error creating a pipe: %s\n"), gpg_strerror (err));
+
+  pgmname = (gc_backend[backend].module_name 
+             ? gnupg_module_name (gc_backend[backend].module_name) 
+             : gc_backend[backend].program );
+  argv[0] = "--gpgconf-list";
+  argv[1] = NULL;
 
-  config = popen (cmd_line, "r");
+  err = gnupg_spawn_process_fd (pgmname, argv, -1, filedes[1], -1, &pid);
+  if (err)
+    {
+      close (filedes[0]);
+      close (filedes[1]);
+      gc_error (1, 0, "could not gather active options from `%s': %s",
+                pgmname, gpg_strerror (err));
+    }
+  close (filedes[1]);
+  config = fdopen (filedes[0], "r");
   if (!config)
-    gc_error (1, errno, "could not gather active options from %s", cmd_line);
+    gc_error (1, errno, "can't fdopen pipe for reading");
 
-  while ((length = getline (&line, &line_len, config)) > 0)
+  while ((length = read_line (config, &line, &line_len, NULL)) > 0)
     {
       gc_option_t *option;
       char *linep;
@@ -1282,9 +1781,11 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
          errno = 0;
          flags = strtoul (linep, &tail, 0);
          if (errno)
-           gc_error (1, errno, "malformed flags in option %s from %s", line, cmd_line);
+           gc_error (1, errno, "malformed flags in option %s from %s",
+                      line, pgmname);
          if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
-           gc_error (1, 0, "garbage after flags in option %s from %s", line, cmd_line);
+           gc_error (1, 0, "garbage after flags in option %s from %s",
+                      line, pgmname);
 
          linep = end;
        }
@@ -1312,7 +1813,7 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
        {
          if (option->active)
            gc_error (1, errno, "option %s returned twice from %s",
-                     line, cmd_line);
+                     line, pgmname);
          option->active = 1;
 
          option->flags |= flags;
@@ -1320,11 +1821,16 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
            option->default_value = xstrdup (default_value);
        }
     }
-  if (ferror (config))
-    gc_error (1, errno, "error reading from %s", cmd_line);
+  if (length < 0 || ferror (config))
+    gc_error (1, errno, "error reading from %s",pgmname);
   if (fclose (config) && ferror (config))
-    gc_error (1, errno, "error closing %s", cmd_line);
-  xfree (cmd_line);
+    gc_error (1, errno, "error closing %s", pgmname);
+
+  err = gnupg_wait_process (pgmname, pid, &exitcode);
+  if (err)
+    gc_error (1, 0, "running %s failed (exitcode=%d): %s",
+              pgmname, exitcode, gpg_strerror (err));
+
 
   /* At this point, we can parse the configuration file.  */
   config_pathname = get_config_pathname (component, backend);
@@ -1335,7 +1841,7 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
              config_pathname);
   else
     {
-      while ((length = getline (&line, &line_len, config)) > 0)
+      while ((length = read_line (config, &line, &line_len, NULL)) > 0)
        {
          char *name;
          char *value;
@@ -1386,7 +1892,7 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
                }
              else if (gc_arg_type[option->arg_type].fallback
                       == GC_ARG_TYPE_STRING)
-               opt_value = xasprintf ("\"%s", percent_escape (value));
+               opt_value = xasprintf ("\"%s", my_percent_escape (value));
              else
                {
                  /* FIXME: Verify that the number is sane.  */
@@ -1416,14 +1922,13 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend)
            }
        }
 
-      if (ferror (config))
+      if (length < 0 || ferror (config))
        gc_error (1, errno, "error reading from %s", config_pathname);
       if (fclose (config) && ferror (config))
        gc_error (1, errno, "error closing %s", config_pathname);
     }
 
-  if (line)
-    free (line);
+  xfree (line);
 }
 
 
@@ -1452,7 +1957,7 @@ retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
   else
     {
 
-      while ((length = getline (&line, &line_len, list_file)) > 0)
+      while ((length = read_line (list_file, &line, &line_len, NULL)) > 0)
        {
          char *start;
          char *end;
@@ -1478,61 +1983,79 @@ retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
             really append.  */
          if (list)
            {
-             new_list = xasprintf ("%s,\"%s", list, percent_escape (start));
+             new_list = xasprintf ("%s,\"%s", list, my_percent_escape (start));
              xfree (list);
              list = new_list;
            }
          else
-           list = xasprintf ("\"%s", percent_escape (start));
+           list = xasprintf ("\"%s", my_percent_escape (start));
        }
-      if (ferror (list_file))
+      if (length < 0 || ferror (list_file))
        gc_error (1, errno, "can not read list file %s", list_pathname);
     }
 
   list_option->active = 1;
   list_option->value = list;
 
-  if (line)
-    free (line);
+  if (list_file && fclose (list_file) && ferror (list_file))
+    gc_error (1, errno, "error closing %s", list_pathname);
+  xfree (line);
 }
 
 
 /* Retrieve the currently active options and their defaults from all
-   involved backends for this component.  */
+   involved backends for this component.  Using -1 for component will
+   retrieve all options from all components. */
 void
 gc_component_retrieve_options (int component)
 {
+  int process_all = 0;
   int backend_seen[GC_BACKEND_NR];
   gc_backend_t backend;
-  gc_option_t *option = gc_component[component].options;
+  gc_option_t *option;
 
   for (backend = 0; backend < GC_BACKEND_NR; backend++)
     backend_seen[backend] = 0;
 
-  while (option->name)
+  if (component == -1)
     {
-      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);
+      process_all = 1;
+      component = 0;
+      assert (component < GC_COMPONENT_NR);
+    }
+      
+  do
+    {
+      option = gc_component[component].options;
 
-         if (gc_backend[backend].program)
-           retrieve_options_from_program (component, backend);
-         else
-           retrieve_options_from_file (component, backend);
-       }
-      option++;
+      while (option && 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++;
+        }
     }
+  while (process_all && ++component < GC_COMPONENT_NR);
+
 }
 
+
 \f
 /* Perform a simple validity check based on the type.  Return in
    NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
@@ -1544,7 +2067,8 @@ option_check_validity (gc_option_t *option, unsigned long flags,
   char *arg;
 
   if (!option->active)
-    gc_error (1, 0, "option %s not supported by backend", option->name);
+    gc_error (1, 0, "option %s not supported by backend %s",
+              option->name, gc_backend[option->backend].name);
       
   if (option->new_flags || option->new_value)
     gc_error (1, 0, "option %s already changed", option->name);
@@ -1605,6 +2129,15 @@ option_check_validity (gc_option_t *option, unsigned long flags,
          if (*arg != '"')
            gc_error (1, 0, "string argument for option %s must begin "
                      "with a quote (\") character", option->name);
+
+         /* FIXME: We do not allow empty string arguments for now, as
+            we do not quote arguments in configuration files, and
+            thus no argument is indistinguishable from the empty
+            string.  */
+         if (arg[1] == '\0' || arg[1] == ',')
+           gc_error (1, 0, "empty string argument for option %s is "
+                     "currently not allowed.  Please report this!",
+                     option->name);
        }
       else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
        {
@@ -1639,6 +2172,61 @@ option_check_validity (gc_option_t *option, unsigned long flags,
   while (arg && *arg);
 }
 
+#ifdef HAVE_W32_SYSTEM
+int
+copy_file (const char *src_name, const char *dst_name)
+{
+#define BUF_LEN 4096
+  char buffer[BUF_LEN];
+  int len;
+  FILE *src;
+  FILE *dst;
+
+  src = fopen (src_name, "r");
+  if (src == NULL)
+    return -1;
+
+  dst = fopen (dst_name, "w");
+  if (dst == NULL)
+    {
+      int saved_err = errno;
+      fclose (src);
+      errno = saved_err;
+      return -1;
+    }
+
+  do
+    {
+      int written;
+
+      len = fread (buffer, 1, BUF_LEN, src);
+      if (len == 0)
+       break;
+      written = fwrite (buffer, 1, len, dst);
+      if (written != len)
+       break;
+    }
+  while (!feof (src) && !ferror (src) && !ferror (dst));
+
+  if (ferror (src) || ferror (dst) || !feof (src))
+    {
+      int saved_errno = errno;
+      fclose (src);
+      fclose (dst);
+      unlink (dst_name);
+      errno = saved_errno;
+      return -1;
+    }
+
+  if (fclose (dst) && ferror (dst))
+    gc_error (1, errno, "error closing %s", dst_name);
+  if (fclose (src) && ferror (src))
+    gc_error (1, errno, "error closing %s", src_name);
+
+  return 0;
+}
+#endif /* HAVE_W32_SYSTEM */
+
 
 /* Create and verify the new configuration file for the specified
    backend and component.  Returns 0 on success and -1 on error.  */
@@ -1699,7 +2287,11 @@ change_options_file (gc_component_t component, gc_backend_t backend,
        arg = NULL;
     }
 
+#ifdef HAVE_W32_SYSTEM
+  res = copy_file (dest_filename, orig_filename);
+#else
   res = link (dest_filename, orig_filename);
+#endif
   if (res < 0 && errno != ENOENT)
     return -1;
   if (res < 0)
@@ -1736,7 +2328,7 @@ change_options_file (gc_component_t component, gc_backend_t backend,
       if (!dest_file)
        goto change_file_one_err;
 
-      while ((length = getline (&line, &line_len, dest_file)) > 0)
+      while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
        {
          int disable = 0;
          char *start;
@@ -1824,7 +2416,7 @@ change_options_file (gc_component_t component, gc_backend_t backend,
                goto change_file_one_err;
            }
        }
-      if (ferror (dest_file))
+      if (length < 0 || ferror (dest_file))
        goto change_file_one_err;
     }
 
@@ -1890,17 +2482,18 @@ change_options_file (gc_component_t component, gc_backend_t backend,
     }
   if (dest_file)
     {
-      while ((length = getline (&line, &line_len, dest_file)) > 0)
+      while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
        {
          fprintf (src_file, "%s", line);
          if (ferror (src_file))
            goto change_file_one_err;
        }
-      if (ferror (dest_file))
+      if (length < 0 || ferror (dest_file))
        goto change_file_one_err;
     }
-  if (line)
-    free (line);
+  xfree (line);
+  line = NULL;
+
   res = fclose (src_file);
   if (res)
     {
@@ -1921,8 +2514,7 @@ change_options_file (gc_component_t component, gc_backend_t backend,
   return 0;
 
  change_file_one_err:
-  if (line)
-    free (line);
+  xfree (line);
   res = errno;
   if (src_file)
     {
@@ -1957,13 +2549,19 @@ change_options_program (gc_component_t component, gc_backend_t backend,
   char *src_filename;
   char *dest_filename;
   char *orig_filename;
+  /* Special hack for gpg, see below.  */
+  int utf8strings_seen = 0;
 
   /* FIXME.  Throughout the function, do better error reporting.  */
   dest_filename = xstrdup (get_config_pathname (component, backend));
   src_filename = xasprintf ("%s.gpgconf.%i.new", dest_filename, getpid ());
   orig_filename = xasprintf ("%s.gpgconf.%i.bak", dest_filename, getpid ());
 
+#ifdef HAVE_W32_SYSTEM
+  res = copy_file (dest_filename, orig_filename);
+#else
   res = link (dest_filename, orig_filename);
+#endif
   if (res < 0 && errno != ENOENT)
     return -1;
   if (res < 0)
@@ -2000,7 +2598,7 @@ change_options_program (gc_component_t component, gc_backend_t backend,
       if (!dest_file)
        goto change_one_err;
 
-      while ((length = getline (&line, &line_len, dest_file)) > 0)
+      while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
        {
          int disable = 0;
          char *start;
@@ -2012,6 +2610,15 @@ change_options_program (gc_component_t component, gc_backend_t backend,
              else
                break;
            }
+         else if (backend == GC_BACKEND_GPG && in_marker
+                  && ! strcmp ("utf8-strings\n", line))
+           {
+             /* Strip duplicated entries.  */
+             if (utf8strings_seen)
+               disable = 1;
+             else
+               utf8strings_seen = 1;
+           }
 
          start = line;
          while (*start == ' ' || *start == '\t')
@@ -2055,7 +2662,7 @@ change_options_program (gc_component_t component, gc_backend_t backend,
                goto change_one_err;
            }
        }
-      if (ferror (dest_file))
+      if (length < 0 || ferror (dest_file))
        goto change_one_err;
     }
 
@@ -2077,7 +2684,7 @@ change_options_program (gc_component_t component, gc_backend_t backend,
      followed by the rest of the original file.  */
 
   /* We have to turn on UTF8 strings for GnuPG.  */
-  if (backend == GC_BACKEND_GPG)
+  if (backend == GC_BACKEND_GPG && ! utf8strings_seen)
     fprintf (src_file, "utf8-strings\n");
 
   option = gc_component[component].options;
@@ -2173,17 +2780,18 @@ change_options_program (gc_component_t component, gc_backend_t backend,
     }
   if (dest_file)
     {
-      while ((length = getline (&line, &line_len, dest_file)) > 0)
+      while ((length = read_line (dest_file, &line, &line_len, NULL)) > 0)
        {
          fprintf (src_file, "%s", line);
          if (ferror (src_file))
            goto change_one_err;
        }
-      if (ferror (dest_file))
+      if (length < 0 || ferror (dest_file))
        goto change_one_err;
     }
-  if (line)
-    free (line);
+  xfree (line);
+  line = NULL;
+
   res = fclose (src_file);
   if (res)
     {
@@ -2204,8 +2812,7 @@ change_options_program (gc_component_t component, gc_backend_t backend,
   return 0;
 
  change_one_err:
-  if (line)
-    free (line);
+  xfree (line);
   res = errno;
   if (src_file)
     {
@@ -2219,9 +2826,51 @@ change_options_program (gc_component_t component, gc_backend_t backend,
 }
 
 
-/* Read the modifications from IN and apply them.  */
+/* Common code for gc_component_change_options and
+   gc_process_gpgconf_conf.  */
+static void
+change_one_value (gc_option_t *option, int *runtime,
+                  unsigned long flags, char *new_value)
+{
+  unsigned long new_value_nr = 0;
+
+  option_check_validity (option, flags, new_value, &new_value_nr);
+
+  if (option->flags & GC_OPT_FLAG_RUNTIME)
+    runtime[option->backend] = 1;
+
+  option->new_flags = flags;
+  if (!(flags & GC_OPT_FLAG_DEFAULT))
+    {
+      if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
+          && (option->flags & GC_OPT_FLAG_LIST))
+        {
+          char *str;
+
+          /* We convert the number to a list of 1's for convenient
+             list handling.  */
+          assert (new_value_nr > 0);
+          option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
+          str = option->new_value;
+          *(str++) = '1';
+          while (--new_value_nr > 0)
+            {
+              *(str++) = ',';
+              *(str++) = '1';
+            }
+          *(str++) = '\0';
+        }
+      else
+        option->new_value = xstrdup (new_value);
+    }
+}
+
+
+/* Read the modifications from IN and apply them.  If IN is NULL the
+   modifications are expected to already have been set to the global
+   table. */
 void
-gc_component_change_options (int component, FILE *in)
+gc_component_change_options (int component, FILE *in, FILE *out)
 {
   int err = 0;
   int runtime[GC_BACKEND_NR];
@@ -2242,97 +2891,78 @@ gc_component_change_options (int component, FILE *in)
       orig_pathname[backend] = NULL;
     }
 
-  while ((length = getline (&line, &line_len, in)) > 0)
+  if (in)
     {
-      char *linep;
-      unsigned long flags = 0;
-      char *new_value = "";
-      unsigned long new_value_nr;
-
-      /* Strip newline and carriage return, if present.  */
-      while (length > 0
-            && (line[length - 1] == '\n' || line[length - 1] == '\r'))
-       line[--length] = '\0';
-
-      linep = strchr (line, ':');
-      if (linep)
-       *(linep++) = '\0';
-
-      /* Extract additional flags.  Default to none.  */
-      if (linep)
-       {
-         char *end;
-         char *tail;
-
-         end = strchr (linep, ':');
-         if (end)
-           *(end++) = '\0';
-
-         errno = 0;
-         flags = strtoul (linep, &tail, 0);
-         if (errno)
-           gc_error (1, errno, "malformed flags in option %s", line);
-         if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
-           gc_error (1, 0, "garbage after flags in option %s", line);
-
-         linep = end;
-       }
-
-      /* Extract default value, if present.  Default to empty if
-        not.  */
-      if (linep)
-       {
-         char *end;
-
-         end = strchr (linep, ':');
-         if (end)
-           *(end++) = '\0';
-
-         new_value = linep;
-
-         linep = end;
-       }
-
-      option = find_option (component, line, GC_BACKEND_ANY);
-      if (!option)
-       gc_error (1, 0, "unknown option %s", line);
-
-      option_check_validity (option, flags, new_value, &new_value_nr);
-
-      if (option->flags & GC_OPT_FLAG_RUNTIME)
-       runtime[option->backend] = 1;
-
-      option->new_flags = flags;
-      if (!(flags & GC_OPT_FLAG_DEFAULT))
-       {
-         if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
-             && (option->flags & GC_OPT_FLAG_LIST))
-           {
-             char *str;
-
-             /* We convert the number to a list of 1's for
-                convenient list handling.  */
-             assert (new_value_nr > 0);
-             option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
-             str = option->new_value;
-             *(str++) = '1';
-             while (--new_value_nr > 0)
-               {
-                 *(str++) = ',';
-                 *(str++) = '1';
-               }
-             *(str++) = '\0';
-           }
-         else
-           option->new_value = xstrdup (new_value);
-       }
+      /* Read options from the file IN.  */
+      while ((length = read_line (in, &line, &line_len, NULL)) > 0)
+        {
+          char *linep;
+          unsigned long flags = 0;
+          char *new_value = "";
+          
+          /* Strip newline and carriage return, if present.  */
+          while (length > 0
+                 && (line[length - 1] == '\n' || line[length - 1] == '\r'))
+            line[--length] = '\0';
+          
+          linep = strchr (line, ':');
+          if (linep)
+            *(linep++) = '\0';
+          
+          /* Extract additional flags.  Default to none.  */
+          if (linep)
+            {
+              char *end;
+              char *tail;
+
+              end = strchr (linep, ':');
+              if (end)
+                *(end++) = '\0';
+              
+              errno = 0;
+              flags = strtoul (linep, &tail, 0);
+              if (errno)
+                gc_error (1, errno, "malformed flags in option %s", line);
+              if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
+                gc_error (1, 0, "garbage after flags in option %s", line);
+              
+              linep = end;
+            }
+
+          /* Don't allow setting of the no change flag.  */
+          flags &= ~GC_OPT_FLAG_NO_CHANGE;
+          
+          /* Extract default value, if present.  Default to empty if not.  */
+          if (linep)
+            {
+              char *end;
+              end = strchr (linep, ':');
+              if (end)
+                *(end++) = '\0';
+              new_value = linep;
+              linep = end;
+            }
+          
+          option = find_option (component, line, GC_BACKEND_ANY);
+          if (!option)
+            gc_error (1, 0, "unknown option %s", line);
+          
+          if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
+            {
+              gc_error (0, 0, "ignoring new value for option %s",
+                        option->name);
+              continue;
+            }
+          
+          change_one_value (option, runtime, flags, new_value);
+        }
     }
 
   /* Now that we have collected and locally verified the changes,
      write them out to new configuration files, verify them
      externally, and then commit them.  */
   option = gc_component[component].options;
-  while (option->name)
+  while (option && option->name)
     {
       /* Go on if we have already seen this backend, or if there is
         nothing to do.  */
@@ -2344,10 +2974,26 @@ gc_component_change_options (int component, FILE *in)
        }
 
       if (gc_backend[option->backend].program)
-       err = change_options_program (component, option->backend,
-                                     &src_pathname[option->backend],
-                                     &dest_pathname[option->backend],
-                                     &orig_pathname[option->backend]);
+       {
+         err = change_options_program (component, option->backend,
+                                       &src_pathname[option->backend],
+                                       &dest_pathname[option->backend],
+                                       &orig_pathname[option->backend]);
+         if (! err)
+           {
+             /* External verification.  */
+             err = gc_component_check_options (component, out,
+                                               src_pathname[option->backend]);
+             if (err)
+               {
+                 gc_error (0, 0,
+                           _("External verification of component %s failed"),
+                           gc_component[component].name);
+                 errno = EINVAL;
+               }
+           }
+
+       }
       else
        err = change_options_file (component, option->backend,
                                   &src_pathname[option->backend],
@@ -2360,7 +3006,7 @@ gc_component_change_options (int component, FILE *in)
       option++;
     }
 
-  if (!err)
+  if (! err && ! opt.dry_run)
     {
       int i;
 
@@ -2373,15 +3019,28 @@ gc_component_change_options (int component, FILE *in)
              assert (dest_pathname[i]);
 
              if (orig_pathname[i])
-               err = rename (src_pathname[i], dest_pathname[i]);
+               {
+#ifdef HAVE_W32_SYSTEM
+                 /* There is no atomic update on W32.  */
+                 err = unlink (dest_pathname[i]);
+#endif /* HAVE_W32_SYSTEM */
+                 if (!err)
+                   err = rename (src_pathname[i], dest_pathname[i]);
+               }
              else
                {
+#ifdef HAVE_W32_SYSTEM
+                 /* We skip the unlink if we expect the file not to
+                    be there.  */
+                  err = rename (src_pathname[i], dest_pathname[i]);
+#else /* HAVE_W32_SYSTEM */
                  /* This is a bit safer than rename() because we
                     expect DEST_PATHNAME not to be there.  If it
                     happens to be there, this will fail.  */
                  err = link (src_pathname[i], dest_pathname[i]);
                  if (!err)
-                   unlink (src_pathname[i]);
+                   err = unlink (src_pathname[i]);
+#endif /* !HAVE_W32_SYSTEM */
                }
              if (err)
                break;
@@ -2390,12 +3049,12 @@ gc_component_change_options (int component, FILE *in)
        }
     }
 
-  if (err)
+  if (err || opt.dry_run)
     {
       int i;
       int saved_errno = errno;
 
-      /* An error occured.  */
+      /* An error occured or a dry-run is requested.  */
       for (i = 0; i < GC_BACKEND_NR; i++)
        {
          if (src_pathname[i])
@@ -2412,12 +3071,22 @@ gc_component_change_options (int component, FILE *in)
                 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]);
+               {
+#ifdef HAVE_W32_SYSTEM
+                 /* There is no atomic update on W32.  */
+                 unlink (dest_pathname[i]);
+#endif /* HAVE_W32_SYSTEM */
+                 rename (orig_pathname[i], dest_pathname[i]);
+               }
              else
                unlink (dest_pathname[i]);
            }
        }
-      gc_error (1, saved_errno, "could not commit changes");
+      if (err)
+       gc_error (1, saved_errno, "could not commit changes");
+
+      /* Fall-through for dry run.  */
+      goto leave;
     }
 
   /* If it all worked, notify the daemons of the changes.  */
@@ -2428,6 +3097,444 @@ gc_component_change_options (int component, FILE *in)
          (*gc_backend[backend].runtime_change) ();
       }
 
-  if (line)
-    free (line);
+  /* Move the per-process backup file into its place.  */
+  for (backend = 0; backend < GC_BACKEND_NR; backend++)  
+    if (orig_pathname[backend])
+      {
+       char *backup_pathname;
+
+       assert (dest_pathname[backend]);
+
+       backup_pathname = xasprintf ("%s.gpgconf.bak", dest_pathname[backend]);
+
+#ifdef HAVE_W32_SYSTEM
+       /* There is no atomic update on W32.  */
+       unlink (backup_pathname);
+#endif /* HAVE_W32_SYSTEM */
+       rename (orig_pathname[backend], backup_pathname);
+      }
+
+ leave:
+  xfree (line);
+}
+
+
+/* Check whether USER matches the current user of one of its group.
+   This function may change USER.  Returns true is there is a
+   match.  */
+static int
+key_matches_user_or_group (char *user)
+{
+  char *group;
+
+  if (*user == '*' && user[1] == 0)
+    return 1; /* A single asterisk matches all users.  */
+
+  group = strchr (user, ':');
+  if (group)
+    *group++ = 0;
+
+#ifdef HAVE_W32_SYSTEM
+  /* Under Windows we don't support groups. */   
+  if (group && *group)
+    gc_error (0, 0, _("Note that group specifications are ignored\n"));
+  if (*user)
+    {
+      static char *my_name;
+
+      if (!my_name)
+        {
+          char tmp[1];
+          DWORD size = 1;
+
+          GetUserNameA (tmp, &size);
+          my_name = xmalloc (size);
+          if (!GetUserNameA (my_name, &size))
+            gc_error (1,0, "error getting current user name: %s",
+                      w32_strerror (-1));
+        }
+
+      if (!strcmp (user, my_name))
+        return 1; /* Found.  */
+    }
+#else /*!HAVE_W32_SYSTEM*/
+  /* First check whether the user matches.  */
+  if (*user)
+    {
+      static char *my_name;
+
+      if (!my_name)
+        {
+          struct passwd *pw = getpwuid ( getuid () );
+          if (!pw)
+            gc_error (1, errno, "getpwuid failed for current user");
+          my_name = xstrdup (pw->pw_name);
+        }
+      if (!strcmp (user, my_name))
+        return 1; /* Found.  */
+    }
+
+  /* If that failed, check whether a group matches.  */
+  if (group && *group)
+    {
+      static char *my_group;
+      static char **my_supgroups;
+      int n;
+
+      if (!my_group)
+        {
+          struct group *gr = getgrgid ( getgid () );
+          if (!gr)
+            gc_error (1, errno, "getgrgid failed for current user");
+          my_group = xstrdup (gr->gr_name);
+        }
+      if (!strcmp (group, my_group))
+        return 1; /* Found.  */
+
+      if (!my_supgroups)
+        {
+          int ngids;
+          gid_t *gids;
+
+          ngids = getgroups (0, NULL);
+          gids  = xcalloc (ngids+1, sizeof *gids);
+          ngids = getgroups (ngids, gids);
+          if (ngids < 0)
+            gc_error (1, errno, "getgroups failed for current user");
+          my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
+          for (n=0; n < ngids; n++)
+            {
+              struct group *gr = getgrgid ( gids[n] );
+              if (!gr)
+                gc_error (1, errno, "getgrgid failed for supplementary group");
+              my_supgroups[n] = xstrdup (gr->gr_name);
+            }
+          xfree (gids);
+        }
+
+      for (n=0; my_supgroups[n]; n++)
+        if (!strcmp (group, my_supgroups[n]))
+          return 1; /* Found.  */
+    }
+#endif /*!HAVE_W32_SYSTEM*/
+  return 0; /* No match.  */
+}
+
+
+
+/* Read and process the global configuration file for gpgconf.  This
+   optional file is used to update our internal tables at runtime and
+   may also be used to set new default values.  If FNAME is NULL the
+   default name will be used.  With UPDATE set to true the internal
+   tables are actually updated; if not set, only a syntax check is
+   done.  If DEFAULTS is true the global options are written to the
+   configuration files.  If LISTFP is set, no changes are done but the
+   configuration file is printed to LISTFP in a colon separated format.
+
+   Returns 0 on success or if the config file is not present; -1 is
+   returned on error. */
+int
+gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
+                         FILE *listfp)
+{
+  int result = 0;
+  char *line = NULL;
+  size_t line_len = 0;
+  ssize_t length;
+  FILE *config;
+  int lineno = 0;
+  int in_rule = 0;
+  int got_match = 0;
+  int runtime[GC_BACKEND_NR];
+  int used_components[GC_COMPONENT_NR];
+  int backend_id, component_id;
+  char *fname;
+
+  if (fname_arg)
+    fname = xstrdup (fname_arg);
+  else
+    fname = make_filename (gnupg_sysconfdir (), "gpgconf.conf", NULL);
+
+  for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)
+    runtime[backend_id] = 0;
+  for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
+    used_components[component_id] = 0;
+
+  config = fopen (fname, "r");
+  if (!config)
+    {
+      /* Do not print an error if the file is not available, except
+         when running in syntax check mode.  */
+      if (errno != ENOENT || !update)
+        {
+          gc_error (0, errno, "can not open global config file `%s'", fname);
+          result = -1;
+        }
+      xfree (fname);
+      return result;
+    }
+
+  while ((length = read_line (config, &line, &line_len, NULL)) > 0)
+    {
+      char *key, *component, *option, *flags, *value;
+      char *empty;
+      gc_option_t *option_info = NULL;
+      char *p;
+      int is_continuation;
+      
+      lineno++;
+      key = line;
+      while (*key == ' ' || *key == '\t')
+        key++;
+      if (!*key || *key == '#' || *key == '\r' || *key == '\n')
+        continue;
+
+      is_continuation = (key != line);
+
+      /* Parse the key field.  */
+      if (!is_continuation && got_match)
+        break;  /* Finish after the first match.  */
+      else if (!is_continuation)
+        {
+          in_rule = 0;
+          for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
+            ;
+          if (!*p)
+            {
+              gc_error (0, 0, "missing rule at `%s', line %d", fname, lineno);
+              result = -1;
+              continue;
+            }
+          *p++ = 0;
+          component = p;
+        }
+      else if (!in_rule)
+        {
+          gc_error (0, 0, "continuation but no rule at `%s', line %d",
+                    fname, lineno);
+          result = -1;
+          continue;
+        }
+      else
+        {
+          component = key;
+          key = NULL;
+        }
+
+      in_rule = 1;
+
+      /* Parse the component.  */
+      while (*component == ' ' || *component == '\t')
+        component++;
+      for (p=component; *p && !strchr (" \t\r\n", *p); p++)
+        ;
+      if (p == component)
+        {
+          gc_error (0, 0, "missing component at `%s', line %d",
+                    fname, lineno);
+          result = -1;
+          continue;
+        }
+      empty = p;
+      *p++ = 0;
+      option = p;
+      component_id = gc_component_find (component);
+      if (component_id < 0)
+        {
+          gc_error (0, 0, "unknown component at `%s', line %d",
+                    fname, lineno);
+          result = -1;
+        }
+
+      /* Parse the option name.  */
+      while (*option == ' ' || *option == '\t')
+        option++;
+      for (p=option; *p && !strchr (" \t\r\n", *p); p++)
+        ;
+      if (p == option)
+        {
+          gc_error (0, 0, "missing option at `%s', line %d",
+                    fname, lineno);
+          result = -1;
+          continue;
+        }
+      *p++ = 0;
+      flags = p;
+      if ( component_id != -1)
+        {
+          option_info = find_option (component_id, option, GC_BACKEND_ANY);
+          if (!option_info)
+            {
+              gc_error (0, 0, "unknown option at `%s', line %d",
+                        fname, lineno);
+              result = -1;
+            }
+        }
+
+
+      /* Parse the optional flags.  */
+      while (*flags == ' ' || *flags == '\t')
+        flags++;
+      if (*flags == '[')
+        {
+          flags++;
+          p = strchr (flags, ']');
+          if (!p)
+            {
+              gc_error (0, 0, "syntax error in rule at `%s', line %d",
+                        fname, lineno);
+              result = -1;
+              continue;
+            }
+          *p++ = 0;
+          value = p;
+        }
+      else  /* No flags given.  */
+        {
+          value = flags;
+          flags = NULL;
+        }
+
+      /* Parse the optional value.  */
+      while (*value == ' ' || *value == '\t')
+       value++;
+      for (p=value; *p && !strchr ("\r\n", *p); p++)
+        ;
+      if (p == value)
+        value = empty; /* No value given; let it point to an empty string.  */
+      else
+        {
+          /* Strip trailing white space.  */
+          *p = 0;
+          for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
+            *p = 0;
+        }
+
+      /* Check flag combinations.  */
+      if (!flags)
+        ;
+      else if (!strcmp (flags, "default"))
+        {
+          if (*value)
+            {
+              gc_error (0, 0, "flag \"default\" may not be combined "
+                        "with a value at `%s', line %d",
+                        fname, lineno);
+              result = -1;
+            }
+        }
+      else if (!strcmp (flags, "change"))
+        ;
+      else if (!strcmp (flags, "no-change"))
+        ;
+      else
+        {
+          gc_error (0, 0, "unknown flag at `%s', line %d",
+                    fname, lineno);
+          result = -1;
+        }
+
+      /* In list mode we print out all records.  */
+      if (listfp && !result)
+        {
+          /* If this is a new ruleset, print a key record.  */
+          if (!is_continuation)
+            {
+              char *group = strchr (key, ':');
+              if (group)
+                {
+                  *group++ = 0;
+                  if ((p = strchr (group, ':')))
+                    *p = 0; /* We better strip any extra stuff. */
+                }                    
+              
+              fprintf (listfp, "k:%s:", my_percent_escape (key));
+              fprintf (listfp, "%s\n", group? my_percent_escape (group):"");
+            }
+
+          /* All other lines are rule records.  */
+          fprintf (listfp, "r:::%s:%s:%s:",
+                   gc_component[component_id].name,                     
+                   option_info->name? option_info->name : "",
+                   flags? flags : "");
+          if (value != empty)
+            fprintf (listfp, "\"%s", my_percent_escape (value));
+          
+          putc ('\n', listfp);
+        }
+
+      /* Check whether the key matches but do this only if we are not
+         running in syntax check mode. */
+      if ( update 
+           && !result && !listfp
+           && (got_match || (key && key_matches_user_or_group (key))) )
+        {
+          int newflags = 0;
+
+          got_match = 1;
+
+          /* Apply the flags from gpgconf.conf.  */
+          if (!flags)
+            ;
+          else if (!strcmp (flags, "default"))
+            newflags |= GC_OPT_FLAG_DEFAULT;
+          else if (!strcmp (flags, "no-change"))
+            option_info->flags |= GC_OPT_FLAG_NO_CHANGE;
+          else if (!strcmp (flags, "change"))
+            option_info->flags &= ~GC_OPT_FLAG_NO_CHANGE;
+
+          if (defaults)
+            {
+              assert (component_id >= 0 && component_id < GC_COMPONENT_NR);
+              used_components[component_id] = 1;
+
+              /* Here we explicitly allow to update the value again.  */
+              if (newflags)
+                {
+                  option_info->new_flags = 0;
+                }
+              if (*value)
+                {
+                  xfree (option_info->new_value);
+                  option_info->new_value = NULL;
+                }
+              change_one_value (option_info, runtime, newflags, value);
+            }
+        }
+    }
+
+  if (length < 0 || ferror (config))
+    {
+      gc_error (0, errno, "error reading from `%s'", fname);
+      result = -1;
+    }
+  if (fclose (config) && ferror (config))
+    gc_error (0, errno, "error closing `%s'", fname);
+
+  xfree (line);
+
+  /* If it all worked, process the options. */
+  if (!result && update && defaults && !listfp)
+    {
+      /* We need to switch off the runtime update, so that we can do
+         it later all at once. */
+      int save_opt_runtime = opt.runtime;
+      opt.runtime = 0;
+
+      for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
+        {
+          gc_component_change_options (component_id, NULL, NULL);
+        }
+      opt.runtime = save_opt_runtime;
+
+      if (opt.runtime)
+        {
+          for (backend_id = 0; backend_id < GC_BACKEND_NR; backend_id++)  
+            if (runtime[backend_id] && gc_backend[backend_id].runtime_change)
+              (*gc_backend[backend_id].runtime_change) ();
+        }
+    }
+
+  xfree (fname);
+  return result;
 }