diff options
-rw-r--r-- | NEWS | 3 | ||||
-rw-r--r-- | agent/ChangeLog | 4 | ||||
-rw-r--r-- | agent/gpg-agent.c | 11 | ||||
-rw-r--r-- | doc/ChangeLog | 4 | ||||
-rw-r--r-- | doc/Makefile.am | 3 | ||||
-rw-r--r-- | doc/examples/gpgconf.conf | 59 | ||||
-rw-r--r-- | doc/gpg-agent.texi | 2 | ||||
-rw-r--r-- | doc/tools.texi | 30 | ||||
-rw-r--r-- | doc/vuln-announce-2007-multiple-message.txt | 145 | ||||
-rw-r--r-- | tools/ChangeLog | 15 | ||||
-rw-r--r-- | tools/gpgconf-comp.c | 655 | ||||
-rw-r--r-- | tools/gpgconf.c | 34 | ||||
-rw-r--r-- | tools/gpgconf.h | 4 |
13 files changed, 849 insertions, 120 deletions
@@ -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) ------------------------------------------------ diff --git a/agent/ChangeLog b/agent/ChangeLog index e78d2dc5c..cc1ae2d53 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,7 @@ +2007-03-06 Werner Koch <[email protected]> + + * gpg-agent.c (main) <gpgconf>: Add entries for all ttl options. + 2007-02-20 Werner Koch <[email protected]> * call-pinentry.c (start_pinentry): Fix for OS X to allow loading diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index c88e22cc1..5da308797 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -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", diff --git a/doc/ChangeLog b/doc/ChangeLog index aff5149c6..799906988 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,7 @@ +2007-03-06 Werner Koch <[email protected]> + + * examples/gpgconf.conf: New. + 2007-03-04 David Shaw <[email protected]> * gpg.texi (GPG Esoteric Options): Document diff --git a/doc/Makefile.am b/doc/Makefile.am index c93df6fad..9614b222b 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -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 index 000000000..194ed7938 --- /dev/null +++ b/doc/examples/gpgconf.conf @@ -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] + + diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index 4119d6601..a886d1534 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -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} diff --git a/doc/tools.texi b/doc/tools.texi index 914a1213f..1e386f521 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -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 index 000000000..dcdb48293 --- /dev/null +++ b/doc/vuln-announce-2007-multiple-message.txt @@ -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) <[email protected]>" + [...] + +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) <[email protected]> + gpg: Good signature from "Alfa Test (demo key) <[email protected]>" + [...] + +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 diff --git a/tools/ChangeLog b/tools/ChangeLog index 0cfadae02..56247c6d8 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,18 @@ +2007-03-06 Werner Koch <[email protected]> + + * 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 <[email protected]> * Makefile.am (symcryptrun_LDADD): Add LIBICONV. diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index e1d96d2c8..1eb6353fb 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -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); +} /* 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; +} diff --git a/tools/gpgconf.c b/tools/gpgconf.c index 87ba45ae1..6d73e88e0 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -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; diff --git a/tools/gpgconf.h b/tools/gpgconf.h index c083c26aa..b3ff258b0 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -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*/ |