1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-12-22 10:19:57 +01:00

Support for a global gpgconf configuration file.

This commit is contained in:
Werner Koch 2007-03-06 20:44:41 +00:00
parent 9491ab44c5
commit ed84b0f787
13 changed files with 848 additions and 119 deletions

3
NEWS
View File

@ -10,6 +10,9 @@ Noteworthy changes in version 2.0.3
* New --verify-option show-primary-uid-only. * 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) Noteworthy changes in version 2.0.2 (2007-01-31)
------------------------------------------------ ------------------------------------------------

View 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> 2007-02-20 Werner Koch <wk@g10code.com>
* call-pinentry.c (start_pinentry): Fix for OS X to allow loading * call-pinentry.c (start_pinentry): Fix for OS X to allow loading

View File

@ -168,6 +168,7 @@ static ARGPARSE_OPTS opts[] = {
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */ #define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */ #define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */ #define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8) #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 = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL; 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.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.ignore_cache_for_signing = 0; opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 0; opt.allow_mark_trusted = 0;
@ -775,6 +776,14 @@ main (int argc, char **argv )
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME ); GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME );
printf ("default-cache-ttl:%lu:%d:\n", printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, DEFAULT_CACHE_TTL ); 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", printf ("no-grab:%lu:\n",
GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
printf ("ignore-cache-for-signing:%lu:\n", printf ("ignore-cache-for-signing:%lu:\n",

View 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> 2007-03-04 David Shaw <dshaw@jabberwocky.com>
* gpg.texi (GPG Esoteric Options): Document * gpg.texi (GPG Esoteric Options): Document

View File

@ -19,7 +19,8 @@
## Process this file with automake to produce Makefile.in ## 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 \ EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \
gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \ gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \

59
doc/examples/gpgconf.conf Normal file
View File

@ -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]

View File

@ -336,7 +336,7 @@ been accessed recently. The default are 2 hours (7200 seconds).
@item --min-passphrase-len @var{n} @item --min-passphrase-len @var{n}
@opindex min-passphrase-len @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. shorter than this value a warning will be displayed. Defaults to 8.
@item --pinentry-program @var{filename} @item --pinentry-program @var{filename}

View File

@ -199,6 +199,7 @@ throughout this section.
* Listing components:: List all gpgconf components. * Listing components:: List all gpgconf components.
* Listing options:: List all options of a component. * Listing options:: List all options of a component.
* Changing options:: Changing options of a component. * Changing options:: Changing options of a component.
* Files used by gpgconf:: What files are used by gpgconf.
@end menu @end menu
@manpause @manpause
@ -219,8 +220,18 @@ List all options of the component @var{component}.
@item --change-options @var{component} @item --change-options @var{component}
Change the options of the component @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 @end table
@mansect options @mansect options
The following options may be used: 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) @item no arg desc (64)
If this flag is set, and the @code{optional arg} flag is set, then the 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. 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 @end table
@item level @item level
@ -658,6 +674,20 @@ $ echo 'force:16:' | gpgconf --change-options dirmngr
The @code{--runtime} option can influence when the changes take The @code{--runtime} option can influence when the changes take
effect. 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 @mansect see also
@command{gpg}(1), @command{gpg}(1),
@command{gpgsm}(1), @command{gpgsm}(1),

View File

@ -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

View 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> 2007-01-31 Werner Koch <wk@g10code.com>
* Makefile.am (symcryptrun_LDADD): Add LIBICONV. * Makefile.am (symcryptrun_LDADD): Add LIBICONV.

View File

@ -33,6 +33,8 @@
#include <time.h> #include <time.h>
#include <stdarg.h> #include <stdarg.h>
#include <signal.h> #include <signal.h>
#include <pwd.h>
#include <grp.h>
/* For log_logv(), asctimestamp(), gnupg_get_time (). */ /* For log_logv(), asctimestamp(), gnupg_get_time (). */
#define JNLIB_NEED_LOG_LOGV #define JNLIB_NEED_LOG_LOGV
@ -321,6 +323,11 @@ static struct
/* The NO_ARG_DESC flag for an option indicates that the argument has /* 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. */ a default, which is described by the value of the ARGDEF field. */
#define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6) #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. */ /* A human-readable description for each flag. */
static struct static struct
@ -334,7 +341,8 @@ static struct
{ "runtime" }, { "runtime" },
{ "default" }, { "default" },
{ "default desc" }, { "default desc" },
{ "no arg desc" } { "no arg desc" },
{ "no change" }
}; };
@ -471,20 +479,36 @@ static gc_option_t gc_options_gpg_agent[] =
{ "Security", { "Security",
GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC, GC_OPT_FLAG_GROUP, GC_LEVEL_BASIC,
"gnupg", N_("Options controlling the security") }, "gnupg", N_("Options controlling the security") },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, { "default-cache-ttl", GC_OPT_FLAG_RUNTIME,
"gnupg", "|N|expire cached PINs after N seconds", GC_LEVEL_BASIC, "gnupg",
"|N|expire cached PINs after N seconds",
GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT }, GC_ARG_TYPE_UINT32, GC_BACKEND_GPG_AGENT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC, { "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME,
"gnupg", "do not use the PIN cache when signing", 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 }, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
{ "allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, { "allow-mark-trusted", GC_OPT_FLAG_RUNTIME | GC_OPT_FLAG_NO_CHANGE,
"gnupg", "allow clients to mark keys as \"trusted\"", GC_LEVEL_ADVANCED, "gnupg", "allow clients to mark keys as \"trusted\"",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, 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, { "no-grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
"gnupg", "do not grab keyboard and mouse", "gnupg", "do not grab keyboard and mouse",
GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT }, GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
GC_OPTION_NULL 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 /* 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 void
gc_component_retrieve_options (int component) gc_component_retrieve_options (int component)
{ {
int process_all = 0;
int backend_seen[GC_BACKEND_NR]; int backend_seen[GC_BACKEND_NR];
gc_backend_t backend; gc_backend_t backend;
gc_option_t *option = gc_component[component].options; gc_option_t *option;
for (backend = 0; backend < GC_BACKEND_NR; backend++) for (backend = 0; backend < GC_BACKEND_NR; backend++)
backend_seen[backend] = 0; backend_seen[backend] = 0;
while (option->name) if (component == -1)
{ {
if (!(option->flags & GC_OPT_FLAG_GROUP)) process_all = 1;
{ component = 0;
backend = option->backend; assert (component < GC_COMPONENT_NR);
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++;
} }
}
do
{
option = gc_component[component].options;
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 /* 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 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; char *arg;
if (!option->active) 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) if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name); 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 void
gc_component_change_options (int component, FILE *in) gc_component_change_options (int component, FILE *in)
{ {
@ -2293,90 +2375,71 @@ gc_component_change_options (int component, FILE *in)
orig_pathname[backend] = NULL; orig_pathname[backend] = NULL;
} }
while ((length = read_line (in, &line, &line_len, NULL)) > 0) if (in)
{ {
char *linep; /* Read options from the file IN. */
unsigned long flags = 0; while ((length = read_line (in, &line, &line_len, NULL)) > 0)
char *new_value = ""; {
unsigned long new_value_nr = 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;
/* Strip newline and carriage return, if present. */ end = strchr (linep, ':');
while (length > 0 if (end)
&& (line[length - 1] == '\n' || line[length - 1] == '\r')) *(end++) = '\0';
line[--length] = '\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;
}
linep = strchr (line, ':'); /* Don't allow setting of the no change flag. */
if (linep) flags &= ~GC_OPT_FLAG_NO_CHANGE;
*(linep++) = '\0';
/* Extract default value, if present. Default to empty if not. */
/* Extract additional flags. Default to none. */ if (linep)
if (linep) {
{ char *end;
char *end; end = strchr (linep, ':');
char *tail; if (end)
*(end++) = '\0';
end = strchr (linep, ':'); new_value = linep;
if (end) linep = end;
*(end++) = '\0'; }
errno = 0; option = find_option (component, line, GC_BACKEND_ANY);
flags = strtoul (linep, &tail, 0); if (!option)
if (errno) gc_error (1, 0, "unknown option %s", line);
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' ')) if ((option->flags & GC_OPT_FLAG_NO_CHANGE))
gc_error (1, 0, "garbage after flags in option %s", line); {
gc_error (0, 0, "ignoring new value for option %s",
linep = end; option->name);
} continue;
}
/* Extract default value, if present. Default to empty if
not. */ change_one_value (option, runtime, flags, new_value);
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);
}
} }
/* Now that we have collected and locally verified the changes, /* Now that we have collected and locally verified the changes,
@ -2500,3 +2563,365 @@ gc_component_change_options (int component, FILE *in)
xfree (line); 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;
}

View File

@ -44,6 +44,8 @@ enum cmd_and_opt_values
aListComponents, aListComponents,
aListOptions, aListOptions,
aChangeOptions, aChangeOptions,
aApplyDefaults,
aCheckConfig
}; };
@ -56,6 +58,10 @@ static ARGPARSE_OPTS opts[] =
{ aListComponents, "list-components", 256, N_("list all components") }, { aListComponents, "list-components", 256, N_("list all components") },
{ aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") },
{ aChangeOptions, "change-options", 256, N_("|COMPONENT|change 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 ") }, { 301, NULL, 0, N_("@\nOptions:\n ") },
@ -64,7 +70,6 @@ static ARGPARSE_OPTS opts[] =
{ oQuiet, "quiet", 0, N_("quiet") }, { oQuiet, "quiet", 0, N_("quiet") },
{ oDryRun, "dry-run", 0, N_("do not make any changes") }, { oDryRun, "dry-run", 0, N_("do not make any changes") },
{ oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") }, { oRuntime, "runtime", 0, N_("activate changes at runtime, if possible") },
/* hidden options */ /* hidden options */
{ oNoVerbose, "no-verbose", 0, "@"}, { oNoVerbose, "no-verbose", 0, "@"},
{0} {0}
@ -149,6 +154,8 @@ main (int argc, char **argv)
case aListComponents: case aListComponents:
case aListOptions: case aListOptions:
case aChangeOptions: case aChangeOptions:
case aApplyDefaults:
case aCheckConfig:
cmd = pargs.r_opt; cmd = pargs.r_opt;
break; break;
@ -189,11 +196,34 @@ main (int argc, char **argv)
exit (1); exit (1);
} }
gc_component_retrieve_options (idx); gc_component_retrieve_options (idx);
if (gc_process_gpgconf_conf (NULL, 1, 0))
exit (1);
if (cmd == aListOptions) if (cmd == aListOptions)
gc_component_list_options (idx, stdout); gc_component_list_options (idx, stdout);
else 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; return 0;

View File

@ -56,4 +56,8 @@ void gc_component_list_options (int component, FILE *out);
/* Read the modifications from IN and apply them. */ /* Read the modifications from IN and apply them. */
void gc_component_change_options (int component, FILE *in); 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*/ #endif /*GPGCONF_H*/