From b13587ef167d58f960270fbcef674d8c919ba8dc Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 29 Aug 2007 09:51:37 +0000 Subject: [PATCH] New command --check-programs for gpgconf. --- NEWS | 4 ++ agent/genkey.c | 2 +- common/ChangeLog | 8 +++ common/exechelp.c | 33 +++++++++--- common/exechelp.h | 6 ++- common/homedir.c | 6 +++ common/util.h | 4 ++ doc/ChangeLog | 4 ++ doc/examples/gpgconf.conf | 11 +++- doc/tools.texi | 68 ++++++++++++++++++++++++ sm/export.c | 2 +- sm/import.c | 2 +- tools/ChangeLog | 11 +++- tools/gpgconf-comp.c | 109 ++++++++++++++++++++++++++++++++++---- tools/gpgconf.c | 10 +++- tools/gpgconf.h | 3 ++ tools/no-libgcrypt.c | 9 ++++ 17 files changed, 267 insertions(+), 25 deletions(-) diff --git a/NEWS b/NEWS index 4ea308e72..99380d82a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,10 @@ Noteworthy changes in version 2.0.7 enforce-passphrase-constraints and max-passphrase-days to gpg-agent. + * Add command --check-components to gpgconf. Gpgconf now uses the + installed versions of the programs and does not anymore search via + PATH for them. + Noteworthy changes in version 2.0.6 (2007-08-16) ------------------------------------------------ diff --git a/agent/genkey.c b/agent/genkey.c index 11b093d86..48ba39dee 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -126,7 +126,7 @@ check_passphrase_pattern (ctrl_t ctrl, const char *pw) if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid)) result = 1; /* Execute error - assume password should no be used. */ - else if (gnupg_wait_process (pgmname, pid)) + else if (gnupg_wait_process (pgmname, pid, NULL)) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ diff --git a/common/ChangeLog b/common/ChangeLog index 7f6174340..e577363c3 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,11 @@ +2007-08-29 Werner Koch + + * exechelp.c (gnupg_wait_process): Add arg EXITCODE. Changed all + callers. + + * util.h (GNUPG_MODULE_NAME_GPGSM, GNUPG_MODULE_NAME_GPG): New. + * homedir.c (gnupg_module_name): Add them + 2007-08-28 Werner Koch * gettime.c (check_isotime, add_isotime): New. Orginally written diff --git a/common/exechelp.c b/common/exechelp.c index 2a65970bd..4ec481f99 100644 --- a/common/exechelp.c +++ b/common/exechelp.c @@ -570,11 +570,13 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[], /* Wait for the process identified by PID to terminate. PGMNAME should - be the same as suplieed to the spawn fucntion and is only used for - diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL for - any failures of the spawned program or other error codes.*/ + be the same as supplied to the spawn function and is only used for + diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL + for any failures of the spawned program or other error codes. If + EXITCODE is not NULL the exit code of the process is stored at this + address or -1 if it could not be retrieved. */ gpg_error_t -gnupg_wait_process (const char *pgmname, pid_t pid) +gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode) { gpg_err_code_t ec; @@ -583,6 +585,9 @@ gnupg_wait_process (const char *pgmname, pid_t pid) int code; DWORD exc; + if (exitcode) + *exitcode = -1; + if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); @@ -609,10 +614,16 @@ gnupg_wait_process (const char *pgmname, pid_t pid) { log_error (_("error running `%s': exit status %d\n"), pgmname, (int)exc ); + if (exitcode) + *exitcode = (int)exc; ec = GPG_ERR_GENERAL; } else - ec = 0; + { + if (exitcode) + *exitcode = 0; + ec = 0; + } CloseHandle (proc); break; @@ -626,6 +637,9 @@ gnupg_wait_process (const char *pgmname, pid_t pid) #else /* !HAVE_W32_SYSTEM */ int i, status; + if (exitcode) + *exitcode = -1; + if (pid == (pid_t)(-1)) return gpg_error (GPG_ERR_INV_VALUE); @@ -650,6 +664,8 @@ gnupg_wait_process (const char *pgmname, pid_t pid) { log_error (_("error running `%s': exit status %d\n"), pgmname, WEXITSTATUS (status)); + if (exitcode) + *exitcode = WEXITSTATUS (status); ec = GPG_ERR_GENERAL; } else if (!WIFEXITED (status)) @@ -658,11 +674,14 @@ gnupg_wait_process (const char *pgmname, pid_t pid) ec = GPG_ERR_GENERAL; } else - ec = 0; + { + if (exitcode) + *exitcode = 0; + ec = 0; + } #endif /* !HAVE_W32_SYSTEM */ return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec); - } diff --git a/common/exechelp.h b/common/exechelp.h index ff7e59d39..7abc03708 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -52,8 +52,10 @@ gpg_error_t gnupg_spawn_process_fd (const char *pgmname, /* Wait for the process identified by PID to terminate. PGMNAME should be the same as supplied to the spawn fucntion and is only used for diagnostics. Returns 0 if the process succeded, GPG_ERR_GENERAL - for any failures of the spawned program or other error codes.*/ -gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid); + for any failures of the spawned program or other error codes. If + EXITCODE is not NULL the exit code of the process is stored at this + address or -1 if it could not be retrieved. */ +gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode); /* Spawn a new process and immediatley detach from it. The name of diff --git a/common/homedir.c b/common/homedir.c index 85506b838..3105aec06 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -372,6 +372,12 @@ gnupg_module_name (int which) case GNUPG_MODULE_NAME_CHECK_PATTERN: X(libexecdir, "gpg-check-pattern"); + case GNUPG_MODULE_NAME_GPGSM: + X(bindir, "gpgsm"); + + case GNUPG_MODULE_NAME_GPG: + X(bindir, "gpg2"); + default: BUG (); } diff --git a/common/util.h b/common/util.h index 75f6ed05c..f35850904 100644 --- a/common/util.h +++ b/common/util.h @@ -182,12 +182,16 @@ const char *gnupg_libdir (void); const char *gnupg_datadir (void); const char *dirmngr_socket_name (void); +/* All module names. We also include gpg and gpgsm for the sake for + gpgconf. */ #define GNUPG_MODULE_NAME_AGENT 1 #define GNUPG_MODULE_NAME_PINENTRY 2 #define GNUPG_MODULE_NAME_SCDAEMON 3 #define GNUPG_MODULE_NAME_DIRMNGR 4 #define GNUPG_MODULE_NAME_PROTECT_TOOL 5 #define GNUPG_MODULE_NAME_CHECK_PATTERN 6 +#define GNUPG_MODULE_NAME_GPGSM 7 +#define GNUPG_MODULE_NAME_GPG 8 const char *gnupg_module_name (int which); diff --git a/doc/ChangeLog b/doc/ChangeLog index 10c9f1bb9..d083158eb 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,7 @@ +2007-08-29 Werner Koch + + * tools.texi (Checking programs): New. + 2007-08-27 Werner Koch * examples/pwpattern.list: New. diff --git a/doc/examples/gpgconf.conf b/doc/examples/gpgconf.conf index f66b6b128..0f4a021eb 100644 --- a/doc/examples/gpgconf.conf +++ b/doc/examples/gpgconf.conf @@ -34,7 +34,14 @@ # :staff gpg-agent allow-mark-trusted [change] # gpg-agent min-passphrase-len 6 # -# * gpg-agent min-passphrase-len [no-change] 12 +# * gpg-agent min-passphrase-len [no-change] 8 +# gpg-agent min-passphrase-nonalpha [no-change] 1 +# gpg-agent max-passphrase-days [no-change] 700 +# gpg-agent enable-passphrase-history [no-change] +# gpg-agent enforce-passphrase-policy [default] +# gpg-agent enforce-passphrase-policy [no-change] +# gpg-agent max-cache-ttl [no-change] 10800 +# gpg-agent max-cache-ttl-ssh [no-change] 10800 # gpg-agent allow-mark-trusted [default] # gpg-agent allow-mark-trusted [no-change] # gpgsm enable-ocsp @@ -46,7 +53,7 @@ # 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 +# 8, "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. #------------------------------------------------------------------- diff --git a/doc/tools.texi b/doc/tools.texi index cce773d9b..47263311c 100644 --- a/doc/tools.texi +++ b/doc/tools.texi @@ -200,6 +200,7 @@ throughout this section. * Invoking gpgconf:: List of all commands and options. * Format conventions:: Formatting conventions relevant for all commands. * Listing components:: List all gpgconf components. +* Checking programs:: Check all programs know to gpgconf. * 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. @@ -218,6 +219,9 @@ One of the following commands must be given: List all components. This is the default command used if none is specified. +@item --check-programs +List all available backend programs and test whether they are runnable. + @item --list-options @var{component} List all options of the component @var{component}. @@ -335,6 +339,14 @@ by a space, followed by a human readable description of that value (if the verbose option is used). You should ignore everything in the field that follows the number. +@item @w{boolean value} +Some fields contain a @emph{boolean value}. This is a number with +either the value 0 or 1. The number may be followed by a space, +followed by a human readable description of that value (if the verbose +option is used). You should ignore everything in the field that follows +the number; checking just the first character is sufficient in this +case. + @item option Some fields contain an @emph{option} argument. The format of an option argument depends on the type of the option and on some flags: @@ -436,6 +448,62 @@ dirmngr:Directory Manager @end example + +@node Checking programs +@subsection Checking programs + +The command @code{--check-programs} is similar to +@code{--list-components} but works on backend programs and not on +components. It runs each program to test wether it is installed and +runnable. This also includes a syntax check of all config file options +of the program. + +The command argument @code{--check-programs} lists all available +programs, one per line. The format of each line is: + +@code{@var{name}:@var{description}:@var{program name}:@var{available}:@var{config okay}:} + +@table @var +@item name +This field contains a name tag of the program which is identical to the +name of the component. The name tag is to be used @emph{verbatim}. It +is thus not in any escaped format. + +@item description +The @emph{string} in this field contains a human-readable description +of the component. It can be displayed to the user of the GUI for +informational purposes. It is @emph{percent-escaped} and +@emph{localized}. + +@item program name +The @emph{string} in this field contains the absolute name of the +program's file. It can be used to unambiguously invoke that program. +It is @emph{percent-escaped}. + +@item available +The @emph{boolean value} in this field indicates whether the program is +installed and runnable. + +@item config okay +The @emph{boolean value} in this field indicates whether the program's +config file is syntactically okay. + +@end table + +@noindent +In the following example the @command{dirmngr} is not runnable and the +configuration file of @command{scdaemon} is not okay. + +@example +$ gpgconf --check-programs +gpg:GPG for OpenPGP:/usr/local/bin/gpg2:1:1: +gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:1:1: +scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:1:0: +gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:1:1: +dirmngr:Directory Manager:/usr/local/bin/dirmngr:0:0: +@end example + + @node Listing options @subsection Listing options diff --git a/sm/export.c b/sm/export.c index e6c29ef17..6854c2a16 100644 --- a/sm/export.c +++ b/sm/export.c @@ -713,7 +713,7 @@ export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen, fclose (fp); if (pid != -1) { - if (!gnupg_wait_process (pgmname, pid)) + if (!gnupg_wait_process (pgmname, pid, NULL)) child_err = 0; } if (!err) diff --git a/sm/import.c b/sm/import.c index 4cbea8481..069408f85 100644 --- a/sm/import.c +++ b/sm/import.c @@ -635,7 +635,7 @@ parse_p12 (ctrl_t ctrl, ksba_reader_t reader, fclose (fp); if (pid != -1) { - if (!gnupg_wait_process (pgmname, pid)) + if (!gnupg_wait_process (pgmname, pid, NULL)) child_err = 0; } if (!err) diff --git a/tools/ChangeLog b/tools/ChangeLog index dd4f12d87..7e7784dd5 100644 --- a/tools/ChangeLog +++ b/tools/ChangeLog @@ -1,3 +1,12 @@ +2007-08-29 Werner Koch + + * gpgconf.c: New comamnd --check-programs. + * gpgconf-comp.c (gc_component_check_programs): New. + (gc_backend): Add member MODULE_NAME and add these module names. + (retrieve_options_from_program): Use module name so that we use an + absolute file name and don't rely on $PATH. + * no-libgcrypt.c (gcry_control): New. + 2007-08-28 Werner Koch * gpgconf-comp.c : Add options --max-passphrase-days @@ -713,7 +722,7 @@ * watchgnupg.c: New. - Copyright 2003, 2004, 2005 Free Software Foundation, Inc. + Copyright 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index e2c03d4f8..748db7edd 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -43,6 +43,7 @@ #define JNLIB_NEED_LOG_LOGV #include "util.h" #include "i18n.h" +#include "exechelp.h" #include "gc-opt-flags.h" #include "gpgconf.h" @@ -153,6 +154,12 @@ static struct GPGConf. In this case, PROGRAM is NULL. */ char *program; + /* The module name (GNUPG_MODULE_NAME_foo) as defined by + ../common/util.h. This value is used to get the actual installed + path of the program. 0 is used if no backedn program is + available. */ + char module_name; + /* The runtime change callback. */ void (*runtime_change) (void); @@ -168,14 +175,18 @@ static struct } gc_backend[GC_BACKEND_NR] = { { NULL }, /* GC_BACKEND_ANY dummy entry. */ - { "GnuPG", GPGNAME, NULL, "gpgconf-gpg.conf" }, - { "GPGSM", "gpgsm", NULL, "gpgconf-gpgsm.conf" }, - { "GPG Agent", "gpg-agent", gpg_agent_runtime_change, - "gpgconf-gpg-agent.conf" }, - { "SCDaemon", "scdaemon", NULL, "gpgconf-scdaemon.conf" }, - { "DirMngr", "dirmngr", NULL, "gpgconf-dirmngr.conf" }, - { "DirMngr LDAP Server List", NULL, NULL, "ldapserverlist-file", - "LDAP Server" }, + { "GnuPG", GPGNAME, GNUPG_MODULE_NAME_GPG, + NULL, "gpgconf-gpg.conf" }, + { "GPGSM", "gpgsm", GNUPG_MODULE_NAME_GPGSM, + NULL, "gpgconf-gpgsm.conf" }, + { "GPG Agent", "gpg-agent", GNUPG_MODULE_NAME_AGENT, + gpg_agent_runtime_change, "gpgconf-gpg-agent.conf" }, + { "SCDaemon", "scdaemon", GNUPG_MODULE_NAME_SCDAEMON, + NULL, "gpgconf-scdaemon.conf" }, + { "DirMngr", "dirmngr", GNUPG_MODULE_NAME_DIRMNGR, + NULL, "gpgconf-dirmngr.conf" }, + { "DirMngr LDAP Server List", NULL, 0, + NULL, "ldapserverlist-file", "LDAP Server" }, }; @@ -1129,6 +1140,81 @@ gc_component_list_components (FILE *out) } } + + +/* Check all components that are available. */ +void +gc_component_check_programs (FILE *out) +{ + gc_component_t component; + unsigned int result; + int backend_seen[GC_BACKEND_NR]; + gc_backend_t backend; + gc_option_t *option; + const char *desc; + const char *pgmname; + const char *argv[2]; + pid_t pid; + int exitcode; + + for (component = 0; component < GC_COMPONENT_NR; component++) + { + if (!gc_component[component].options) + continue; + + for (backend = 0; backend < GC_BACKEND_NR; backend++) + backend_seen[backend] = 0; + + option = gc_component[component].options; + for (; option && option->name; option++) + { + if ((option->flags & GC_OPT_FLAG_GROUP)) + continue; + backend = option->backend; + if (backend_seen[backend]) + continue; + backend_seen[backend] = 1; + assert (backend != GC_BACKEND_ANY); + if (!gc_backend[backend].program) + continue; + if (!gc_backend[backend].module_name) + continue; + + pgmname = gnupg_module_name (gc_backend[backend].module_name); + argv[0] = "--gpgconf-test"; + argv[1] = NULL; + + /* Note that under Windows the spawn fucntion returns an + error if the progrom could not be executed whereas under + Unix the wait function returns an error. */ + result = 0; + if (gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid)) + result |= 1; /* Program could not be run. */ + else if (gnupg_wait_process (pgmname, pid, &exitcode)) + { + if (exitcode == -1) + result |= 1; /* Program could not be run or it + terminated abnormally. */ + result |= 2; /* Program returned an error. */ + } + + /* If the program could not be run, we can't tell whether + the config file is good. */ + if ((result&1)) + result |= 2; + + desc = gc_component[component].desc; + desc = my_dgettext (gc_component[component].desc_domain, desc); + fprintf (out, "%s:%s:", + gc_component[component].name, my_percent_escape (desc)); + fputs (my_percent_escape (pgmname), out); + fprintf (out, ":%d:%d:\n", !(result & 1), !(result & 2)); + break; /* Loop over options of this component */ + } + } +} + + /* Find the component with the name NAME. Returns -1 if not found. */ @@ -1362,7 +1448,10 @@ retrieve_options_from_program (gc_component_t component, gc_backend_t backend) FILE *config; char *config_pathname; - cmd_line = xasprintf ("%s --gpgconf-list", gc_backend[backend].program); + cmd_line = xasprintf ("%s --gpgconf-list", + gc_backend[backend].module_name ? + gnupg_module_name (gc_backend[backend].module_name) : + gc_backend[backend].program ); config = popen (cmd_line, "r"); if (!config) @@ -1663,6 +1752,8 @@ gc_component_retrieve_options (int component) 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 diff --git a/tools/gpgconf.c b/tools/gpgconf.c index e71a24e7a..3d81f0169 100644 --- a/tools/gpgconf.c +++ b/tools/gpgconf.c @@ -1,5 +1,5 @@ /* gpgconf.c - Configuration utility for GnuPG - * Copyright (C) 2003 Free Software Foundation, Inc. + * Copyright (C) 2003, 2007 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -40,6 +40,7 @@ enum cmd_and_opt_values oHomedir, aListComponents, + aCheckPrograms, aListOptions, aChangeOptions, aApplyDefaults, @@ -54,6 +55,7 @@ static ARGPARSE_OPTS opts[] = { 300, NULL, 0, N_("@Commands:\n ") }, { aListComponents, "list-components", 256, N_("list all components") }, + { aCheckPrograms, "check-programs", 256, N_("check all programs") }, { aListOptions, "list-options", 256, N_("|COMPONENT|list options") }, { aChangeOptions, "change-options", 256, N_("|COMPONENT|change options") }, { aApplyDefaults, "apply-defaults", 256, @@ -137,6 +139,7 @@ main (int argc, char **argv) case oNoVerbose: opt.verbose = 0; break; case aListComponents: + case aCheckPrograms: case aListOptions: case aChangeOptions: case aApplyDefaults: @@ -161,6 +164,11 @@ main (int argc, char **argv) gc_component_list_components (stdout); break; + case aCheckPrograms: + /* Check all programs. */ + gc_component_check_programs (stdout); + break; + case aListOptions: case aChangeOptions: if (!fname) diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 59900b185..f0d3c599d 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -40,6 +40,9 @@ struct /* List all components that are available. */ void gc_component_list_components (FILE *out); +/* List all programs along with their status. */ +void gc_component_check_programs (FILE *out); + /* Find the component with the name NAME. Returns -1 if not found. */ int gc_component_find (const char *name); diff --git a/tools/no-libgcrypt.c b/tools/no-libgcrypt.c index 966ff162d..c9122e755 100644 --- a/tools/no-libgcrypt.c +++ b/tools/no-libgcrypt.c @@ -102,3 +102,12 @@ gcry_free (void *a) if (a) free (a); } + + +/* We need this dummy because exechelp.c uses gcry_control to + terminate the secure memeory. */ +gcry_error_t +gcry_control (enum gcry_ctl_cmds CMD, ...) +{ + return 0; +}