Support for a global gpgconf configuration file.
authorWerner Koch <wk@gnupg.org>
Tue, 6 Mar 2007 20:44:41 +0000 (20:44 +0000)
committerWerner Koch <wk@gnupg.org>
Tue, 6 Mar 2007 20:44:41 +0000 (20:44 +0000)
13 files changed:
NEWS
agent/ChangeLog
agent/gpg-agent.c
doc/ChangeLog
doc/Makefile.am
doc/examples/gpgconf.conf [new file with mode: 0644]
doc/gpg-agent.texi
doc/tools.texi
doc/vuln-announce-2007-multiple-message.txt [new file with mode: 0644]
tools/ChangeLog
tools/gpgconf-comp.c
tools/gpgconf.c
tools/gpgconf.h

diff --git a/NEWS b/NEWS
index 5de5043..59d4633 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,9 @@ Noteworthy changes in version 2.0.3
 
  * New --verify-option show-primary-uid-only. 
 
+ * gpgconf may now read a global configuration file to select which
+   options are changeable by a frontend.
+
 
 Noteworthy changes in version 2.0.2 (2007-01-31)
 ------------------------------------------------
index e78d2dc..cc1ae2d 100644 (file)
@@ -1,3 +1,7 @@
+2007-03-06  Werner Koch  <wk@g10code.com>
+
+       * gpg-agent.c (main) <gpgconf>: Add entries for all ttl options.
+
 2007-02-20  Werner Koch  <wk@g10code.com>
 
        * call-pinentry.c (start_pinentry): Fix for OS X to allow loading
index c88e22c..5da3087 100644 (file)
@@ -168,6 +168,7 @@ static ARGPARSE_OPTS opts[] = {
 #define DEFAULT_CACHE_TTL     (10*60)  /* 10 minutes */
 #define DEFAULT_CACHE_TTL_SSH (30*60)  /* 30 minutes */
 #define MAX_CACHE_TTL         (120*60) /* 2 hours */
+#define MAX_CACHE_TTL_SSH     (120*60) /* 2 hours */
 #define MIN_PASSPHRASE_LEN    (8)      
 
 
@@ -408,7 +409,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       opt.def_cache_ttl = DEFAULT_CACHE_TTL;
       opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
       opt.max_cache_ttl = MAX_CACHE_TTL;
-      opt.max_cache_ttl_ssh = MAX_CACHE_TTL;
+      opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
       opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
       opt.ignore_cache_for_signing = 0;
       opt.allow_mark_trusted = 0;
@@ -775,6 +776,14 @@ main (int argc, char **argv )
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
       printf ("default-cache-ttl:%lu:%d:\n",
               GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL );
+      printf ("default-cache-ttl-ssh:%lu:%d:\n",
+              GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL_SSH );
+      printf ("max-cache-ttl:%lu:%d:\n",
+              GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL );
+      printf ("max-cache-ttl-ssh:%lu:%d:\n",
+              GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH );
+      printf ("min-passphrase-len:%lu:%d:\n",
+              GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN );
       printf ("no-grab:%lu:\n", 
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       printf ("ignore-cache-for-signing:%lu:\n",
index aff5149..7999069 100644 (file)
@@ -1,3 +1,7 @@
+2007-03-06  Werner Koch  <wk@g10code.com>
+
+       * examples/gpgconf.conf: New.
+
 2007-03-04  David Shaw  <dshaw@jabberwocky.com>
 
        * gpg.texi (GPG Esoteric Options): Document
index c93df6f..9614b22 100644 (file)
@@ -19,7 +19,8 @@
 
 ## Process this file with automake to produce Makefile.in
 
-examples = examples/README examples/scd-event examples/trustlist.txt
+examples = examples/README examples/scd-event examples/trustlist.txt \
+          examples/gpgconf.conf
 
 EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \
             gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \
diff --git a/doc/examples/gpgconf.conf b/doc/examples/gpgconf.conf
new file mode 100644 (file)
index 0000000..194ed79
--- /dev/null
@@ -0,0 +1,59 @@
+# gpgconf.conf - configuration for gpgconf
+#----------------------------------------------------------------------
+# This file is read by gpgconf(1) to setup defaults for all or
+# specified users and groups.  It may be used to change the hardwired
+# defaults in gpgconf and to enforce certain values for the various
+# GnuPG related configuration files.
+#
+# Empty lines and comment lines, indicated by a hash mark as first non
+# white space character, are ignored.  The line is separated by white
+# space into fields. The first field is used to match the user or
+# group and must start at the first column, the file is processes
+# sequential until a matching rle is found.  A rule may contain
+# several lines, continuation lines are indicated by a indenting them.
+#
+# Syntax of a line:
+# <key>|WS  <component> <option> ["["<flag>"]"] [<value>]
+#
+# Examples for the <key> field:
+#   foo         - Matches the user "foo".
+#   foo:        - Matches the user "foo".
+#   foo:staff   - Matches the user "foo" or the group "staff".
+#   :staff      - Matches the group "staff".
+#   *           - Matches any user.
+# All other variants are not defined and reserved for future use.
+#
+# <component> and <option> are as specified by gpgconf. 
+# <flag> may be one of:
+#   default     - Delete the option so that the default is used.
+#   no-change   - Mark the field as non changeable by gpgconf.
+#   change      - Mark the field as changeable by gpgconf.
+#
+# Example file:
+#==========
+# :staff  gpg-agent allow-mark-trusted [change]
+#         gpg-agent min-passphrase-len 6
+#
+# *       gpg-agent min-passphrase-len [no-change] 12
+#         gpg-agent allow-mark-trusted [default]
+#         gpg-agent allow-mark-trusted [no-change]
+#         gpgsm     enable-ocsp           
+#===========
+# All users in the group "staff" are allowed to change the value for
+# --allow-mark-trusted; gpgconf's default is not to allow a change
+# through its interface.  When "gpgconf --apply-defaults" is used,
+# "allow-mark-trusted" will get enabled and "min-passphrase-len" set
+# to 6.  All other users are not allowed to change
+# "min-passphrase-len" and "allow-mark-trusted".  When "gpgconf
+# --apply-defaults" is used for them, "min-passphrase-len" is set to
+# 12, "allow-mark-trusted" deleted from the config file and
+# "enable-ocsp" is put into the config file of gpgsm.  The latter may
+# be changed by any user.
+#-------------------------------------------------------------------
+
+
+# Allow all users to change the allow-mark-trusted option.
+# (This was the default prior to gnupg 2.0.3)
+* gpg-agent allow-mark-trusted [change]
+
+
index 4119d66..a886d15 100644 (file)
@@ -336,7 +336,7 @@ been accessed recently.  The default are 2 hours (7200 seconds).
 
 @item --min-passphrase-len @var{n}
 @opindex min-passphrase-len
-Set the minimal length of a passphrase.  When entereing a new passphrase
+Set the minimal length of a passphrase.  When entering a new passphrase
 shorter than this value a warning will be displayed.  Defaults to 8.
 
 @item --pinentry-program @var{filename}
index 914a121..1e386f5 100644 (file)
@@ -199,6 +199,7 @@ throughout this section.
 * Listing components::    List all gpgconf components.
 * Listing options::       List all options of a component.
 * Changing options::      Changing options of a component.
+* Files used by gpgconf:: What files are used by gpgconf.
 @end menu
 
 @manpause
@@ -219,8 +220,18 @@ List all options of the component @var{component}.
 
 @item --change-options @var{component}
 Change the options of the component @var{component}.
+
+@item --apply-defaults
+Update all configuration files with values taken from the global
+configuration file (usually @file{/etc/gnupg/gpgconf.conf}).
+
+@item --check-config [@var{filename}]
+Run a syntax check ion the global configuration file.  If @var{filename}
+is given, check that file instead.
+
 @end table
 
+
 @mansect options
 
 The following options may be used:
@@ -486,6 +497,11 @@ If this flag is set, a (runtime) default is available.  This and the
 @item no arg desc (64)
 If this flag is set, and the @code{optional arg} flag is set, then the
 option has a special meaning if no argument is given.
+
+@item no change (128)
+If this flag is set, gpgconf ignores requests to change the value.  GUI
+frontends should grey out this option.  Note, that manual changes of the
+configuration files are still possible.
 @end table
 
 @item level
@@ -658,6 +674,20 @@ $ echo 'force:16:' | gpgconf --change-options dirmngr
 The @code{--runtime} option can influence when the changes take
 effect.
 
+@mansect files
+@node Files used by gpgconf
+@subsection Files used by gpgconf
+
+@table @file
+
+@item /etc/gnupg/gpg-agent.conf
+@cindex gpgconf.conf
+  If this file exists, it is processed as a global configuration file.
+  A commented example can be found in the @file{examples} directory of
+  the distribution.
+@end table
+
+
 @mansect see also
 @command{gpg}(1), 
 @command{gpgsm}(1), 
diff --git a/doc/vuln-announce-2007-multiple-message.txt b/doc/vuln-announce-2007-multiple-message.txt
new file mode 100644 (file)
index 0000000..dcdb482
--- /dev/null
@@ -0,0 +1,145 @@
+             Multiple Messages Problem in GnuPG and GPGME
+            ==============================================
+                              2007-03-05
+
+
+Summary
+=======
+
+Gerardo Richarte from Core Security Technologies identified a problem
+when using GnuPG in streaming mode.
+
+The problem is actually a variant of a well known problem in the way
+signed material is presented in a MUA.  It is possible to insert
+additional text before or after a signed (or signed and encrypted)
+OpenPGP message and make the user believe that this additional text is
+also covered by the signature.  The Core Security advisory describes
+several variants of the attack; they all boil down to the fact that it
+might not be possible to identify which part of a message is actually
+signed if gpg is not used correctly.
+
+[ Please do not send private mail in response to this message.  The
+  mailing list gnupg-devel is the best place to discuss this problem
+  (please subscribe first so you don't need moderator approval [1]). ]
+
+
+Impact
+======
+
+All applications using GnuPG without properly using the status
+interface to verify signed or signed and encrypted messages.
+
+All GPGME versions up to and including 1.1.3.
+
+Starting with version 1.4.7 and 2.0.3, GnuPG implements an additional
+and sufficient protection against this common usage problem.
+
+Detached signatures are in no way affected by this problem.
+
+
+Description
+===========
+
+When using gpg (or gpg2) in a pipeline or with redirected input and
+output additional data may be inserted into a message.  This allows to
+forge a signed message by prefixing it with arbitrary material.  A way
+to create such a message is:
+
+  echo "This is my sneaky plaintext message" > foobar.txt
+  gpg -z0 --output prefix.gpg --store foobar.txt
+  cat prefix.gpg original-signed-message.gpg > forged.gpg
+
+Using gpg naively this results in:
+
+  $ gpg <forged.gpg
+  This is my sneaky plaintext message
+  Either I'm dead or my watch has stopped.
+                  -- Groucho Marx's last words
+  gpg: Signature made Mon Feb 26 09:57:04 2007 CET using DSA key ID 68697734
+  gpg: Good signature from "Alfa Test (demo key) <alfa@example.net>"
+  [...]
+
+and thus gives the impression that the sneaky message is part of the
+signed Groucho quote.  The correct way to use gpg with redirection is
+by taking care of the status interface:
+
+  $ gpg --status-fd 1 <forged.gpg
+  [GNUPG:] PLAINTEXT 62 1172479053 foobar.txt
+  [GNUPG:] PLAINTEXT_LENGTH 36
+  This is my sneaky plaintext message
+  [GNUPG:] PLAINTEXT 62 1172480224 original-signed-message
+  [GNUPG:] PLAINTEXT_LENGTH 86
+  Either I'm dead or my watch has stopped.
+                  -- Groucho Marx's last words
+  gpg: Signature made Mon Feb 26 09:57:04 2007 CET using DSA key ID 68697734
+  [GNUPG:] SIG_ID UncMPBJYgbG/uszJVNKoCAz+hvY 2007-02-26 1172480224
+  [GNUPG:] GOODSIG 2D727CC768697734 Alfa Test (demo key) <alfa@example.net>
+  gpg: Good signature from "Alfa Test (demo key) <alfa@example.net>"
+  [...]
+
+Here the PLAINTEXT status lines clearly identify the start of a new
+message.
+
+Note, that using gpg on the command line is in almost all cases not
+done with redirection but by letting gpg save the the signed message.
+In this case gpg will save the message to different files or in case
+the file names are identical, prompt the over to overwrite the first
+one again.
+
+Because the problem of identifying the actual signed content when
+mixing the signed data and the signature is very common, the long
+standing suggestion for all digital signatures is to use a detached
+signature.  A detached signature allows to clearly identify what is
+signed and what is the signature.  This is also the reason why
+PGP/MIME signed messages are in general to be preferred over the old
+style clear signed messages.
+
+
+Solution
+========
+
+Given that there are many applications in use which are subject to the
+described problem, we have decided to change GnuPG so that such forged
+OpenPGP messages are detected and the signature verification will
+fail.  GnuPG 1.4.7 has been released today and is available from the
+usual places [2].  If you don't want to update, a minimal patch
+against GnuPG 1.4.6 is available at
+
+ ftp://ftp.gnupg.org/gcrypt/gnupg/patches/gnupg-1.4.6-multiple-message.patch
+
+Many applications are using the library GPGME which implements an easy
+way to process OpenPGP messages using gpg.  We have updated GPGME to
+make it immune against this problem even if an old version of gpg is
+being used.  GPGME 1.1.4 is available from the usual places [2].  A
+patch (against version 1.1.3 or 1.1.2) is available at
+
+ ftp://ftp.gnupg.org/gcrypt/gpgme/patches/gpgme-1.1.3-multiple-message.patch
+
+Please note that - after applying one of these patches - some
+vulnerable applications (mainly MUAs) may fail to handle certain
+messages which are composed of several OpenPGP messages.  To continue
+the support of such messages fixing the application is required as
+there is no way for GnuPG to do it.
+
+
+Support 
+=======
+
+g10 Code GmbH [3], a Duesseldorf based company owned and headed by
+GnuPG's principal author, is currently funding GnuPG development.
+Support contracts or other financial backing will greatly help us to
+improve the quality of GnuPG.
+
+
+Thanks
+======
+
+Gerardo Richarte found this problem.  David Shaw greatly helped to
+analyse and describe the core of the problem.
+
+
+
+
+[1] See http://lists.gnupg.org/mailman/listinfo/gnupg-devel
+[2] See http://www.gnupg.org/download/ 
+[3] See http://www.gnupg.org/service.html
index 0cfadae..56247c6 100644 (file)
@@ -1,3 +1,18 @@
+2007-03-06  Werner Koch  <wk@g10code.com>
+
+       * gpgconf-comp.c: Include pwd.h and grp.h.
+       (GC_OPT_FLAG_NO_CHANGE): New.
+       (gc_component_change_options): Implement it.
+       (gc_options_gpg_agent): Add options for all ttl values and
+       min-passphrase-length.  Apply new flag to some of them.
+       (gc_process_gpgconf_conf, key_matches_user_or_group): New.
+       (gc_component_change_options): Factor some code out to ..
+       (change_one_value): .. new. 
+       (gc_component_retrieve_options): Allow -1 for COMPONENT to iterate
+       over al components.
+       * gpgconf.c (main): New commands --check-config and
+       --apply-defaults.  Call gc_process_gpgconf_conf.
+
 2007-01-31  Werner Koch  <wk@g10code.com>
 
        * Makefile.am (symcryptrun_LDADD): Add LIBICONV.
index e1d96d2..1eb6353 100644 (file)
@@ -33,6 +33,8 @@
 #include <time.h>
 #include <stdarg.h>
 #include <signal.h>
+#include <pwd.h>
+#include <grp.h>
 
 /* For log_logv(), asctimestamp(), gnupg_get_time ().  */
 #define JNLIB_NEED_LOG_LOGV
@@ -321,6 +323,11 @@ static struct
 /* 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 chnage this option using the standard gpgconf method.
+   Frontends using gpgconf should grey out such otions, 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
@@ -334,7 +341,8 @@ static struct
     { "runtime" },
     { "default" },
     { "default desc" },
-    { "no arg desc" }
+    { "no arg desc" },
+    { "no change" }
   };
 
 
@@ -471,20 +479,36 @@ static gc_option_t gc_options_gpg_agent[] =
    { "Security",
      GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
      "gnupg", N_("Options controlling the security") },
-   { "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
-     "gnupg", "|N|expire cached PINs after N seconds",
+   { "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_BASIC, "gnupg", 
+     "|N|expire cached PINs after N seconds",
      GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
-   { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC,
-     "gnupg", "do not use the PIN cache when signing",
+   { "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 },
+   { "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\"",
+   { "allow-mark-trusted", GC_OPT_FLAG_RUNTIME | GC_OPT_FLAG_NO_CHANGE,
+     GC_LEVEL_ADVANCED, "gnupg", "allow clients to mark keys as \"trusted\"",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+   { "min-passphrase-len", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_EXPERT, "gnupg", 
+     N_("|N|set minimal required length for new passphrases to N"),
+     GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
    { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
      "gnupg", "do not grab keyboard and mouse",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
 
-
    GC_OPTION_NULL
  };
 
@@ -1539,41 +1563,56 @@ retrieve_options_from_file (gc_component_t component, gc_backend_t backend)
 
 
 /* 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->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
@@ -1585,7 +1624,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);
@@ -2270,7 +2310,49 @@ 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)
 {
@@ -2293,90 +2375,71 @@ gc_component_change_options (int component, FILE *in)
       orig_pathname[backend] = NULL;
     }
 
-  while ((length = read_line (in, &line, &line_len, NULL)) > 0)
+  if (in)
     {
-      char *linep;
-      unsigned long flags = 0;
-      char *new_value = "";
-      unsigned long new_value_nr = 0;
-
-      /* Strip newline and carriage return, if present.  */
-      while (length > 0
-            && (line[length - 1] == '\n' || line[length - 1] == '\r'))
-       line[--length] = '\0';
-
-      linep = strchr (line, ':');
-      if (linep)
-       *(linep++) = '\0';
-
-      /* Extract additional flags.  Default to none.  */
-      if (linep)
-       {
-         char *end;
-         char *tail;
-
-         end = strchr (linep, ':');
-         if (end)
-           *(end++) = '\0';
-
-         errno = 0;
-         flags = strtoul (linep, &tail, 0);
-         if (errno)
-           gc_error (1, errno, "malformed flags in option %s", line);
-         if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
-           gc_error (1, 0, "garbage after flags in option %s", line);
-
-         linep = end;
-       }
-
-      /* Extract default value, if present.  Default to empty if
-        not.  */
-      if (linep)
-       {
-         char *end;
-
-         end = strchr (linep, ':');
-         if (end)
-           *(end++) = '\0';
-
-         new_value = linep;
-
-         linep = end;
-       }
-
-      option = find_option (component, line, GC_BACKEND_ANY);
-      if (!option)
-       gc_error (1, 0, "unknown option %s", line);
-
-      option_check_validity (option, flags, new_value, &new_value_nr);
-
-      if (option->flags & GC_OPT_FLAG_RUNTIME)
-       runtime[option->backend] = 1;
-
-      option->new_flags = flags;
-      if (!(flags & GC_OPT_FLAG_DEFAULT))
-       {
-         if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
-             && (option->flags & GC_OPT_FLAG_LIST))
-           {
-             char *str;
-
-             /* We convert the number to a list of 1's for
-                convenient list handling.  */
-             assert (new_value_nr > 0);
-             option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
-             str = option->new_value;
-             *(str++) = '1';
-             while (--new_value_nr > 0)
-               {
-                 *(str++) = ',';
-                 *(str++) = '1';
-               }
-             *(str++) = '\0';
-           }
-         else
-           option->new_value = xstrdup (new_value);
-       }
+      /* 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,
@@ -2500,3 +2563,365 @@ gc_component_change_options (int component, FILE *in)
 
   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;
+  int n;
+
+  if (*user == '*' && user[1] == 0)
+    return 1; /* A single asterisk matches all users.  */
+
+  group = strchr (user, ':');
+  if (group)
+    *group++ = 0;
+
+  /* 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;
+
+      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.  */
+    }
+
+  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.
+
+   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, int update, int defaults)
+{
+  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;
+
+  if (!fname)
+    fname = GNUPG_SYSCONFDIR "/gpgconf.conf";
+
+  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 runnign in syntax check mode.  */
+      if (errno != ENOENT || !update)
+        {
+          gc_error (0, errno, "can not open global config file `%s'", fname);
+          result = -1;
+        }
+      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;
+        }
+      
+          
+      /* Check whether the key matches but do this only if we are not
+         running in syntax check mode. */
+      if ( update 
+           && !result
+           && (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)
+    {
+      /* 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);
+        }
+      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) ();
+        }
+    }
+
+  return result;
+}
index 87ba45a..6d73e88 100644 (file)
@@ -44,6 +44,8 @@ enum cmd_and_opt_values
     aListComponents,
     aListOptions,
     aChangeOptions,
+    aApplyDefaults,
+    aCheckConfig
 
   };
 
@@ -56,6 +58,10 @@ static ARGPARSE_OPTS opts[] =
     { aListComponents, "list-components", 256, N_("list all components") },
     { aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
     { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") },
+    { aApplyDefaults, "apply-defaults", 256,
+      N_("apply global default values") },
+    { aCheckConfig,   "check-config", 256,
+      N_("check global configuration file") },
 
     { 301, NULL, 0, N_("@\nOptions:\n ") },
     
@@ -64,7 +70,6 @@ static ARGPARSE_OPTS opts[] =
     { oQuiet, "quiet",      0, N_("quiet") },
     { oDryRun, "dry-run",   0, N_("do not make any changes") },
     { oRuntime, "runtime",  0, N_("activate changes at runtime, if possible") },
-
     /* hidden options */
     { oNoVerbose, "no-verbose",  0, "@"},
     {0}
@@ -149,6 +154,8 @@ main (int argc, char **argv)
         case aListComponents:
         case aListOptions:
         case aChangeOptions:
+        case aApplyDefaults:
+        case aCheckConfig:
          cmd = pargs.r_opt;
          break;
 
@@ -189,11 +196,34 @@ main (int argc, char **argv)
              exit (1);
            }
          gc_component_retrieve_options (idx);
+          if (gc_process_gpgconf_conf (NULL, 1, 0))
+            exit (1);
          if (cmd == aListOptions)
            gc_component_list_options (idx, stdout);
          else
-           gc_component_change_options (idx, stdin);
+            gc_component_change_options (idx, stdin);
        }
+      break;
+
+    case aCheckConfig:
+      if (gc_process_gpgconf_conf (fname, 0, 0))
+        exit (1);
+      break;
+
+    case aApplyDefaults:
+      if (fname)
+       {
+         fputs (_("usage: gpgconf [options] "), stderr);
+         putc ('\n',stderr);
+         fputs (_("No argument allowed"), stderr);
+         putc ('\n',stderr);
+         exit (2);
+       }
+      gc_component_retrieve_options (-1);
+      if (gc_process_gpgconf_conf (NULL, 1, 1))
+        exit (1);
+      break;
+
     }
   
   return 0; 
index c083c26..b3ff258 100644 (file)
@@ -56,4 +56,8 @@ 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);
 
+/* Process global configuration file.  */
+int gc_process_gpgconf_conf (const char *fname, int update, int defaults);
+
+
 #endif /*GPGCONF_H*/