From 19025d791890e4c42931f0023a41fea45e866945 Mon Sep 17 00:00:00 2001 From: Marcus Brinkmann Date: Fri, 4 Jan 2008 14:31:15 +0000 Subject: [PATCH] 2008-01-04 Marcus Brinkmann * configure.ac: Support gpgconf. gpgme/ 2008-01-04 Marcus Brinkmann * Makefile.am (gpgconf_components): New variable. (main_sources): Add gpgconf.c. * gpgme.h (gpgme_protocol_t): New protocol GPGME_PROTOCOL_GPGCONF. (gpgme_conf_level_t, gpgme_conf_type_t, gpgme_conf_arg_t) (gpgme_conf_opt_t, gpgme_conf_comp_t, gpgme_conf_arg_new) (gpgme_conf_arg_release, gpgme_conf_opt_change) (gpgme_conf_release, gpgme_op_conf_load, gpgme_op_conf_save): New types. * gpgconf.c, engine-gpgconf.c: New files. * engine.h: (_gpgme_engine_op_conf_load, (_gpgme_engine_op_conf_save): New prototypes. * op-support.c (_gpgme_op_reset): Ignore not implemented locale function. * posix-util.c (_gpgme_get_gpgconf_path): New function. * w32-util.c (_gpgme_get_gpgconf_path): New function. * engine-gpgsm.c: (_gpgme_engine_ops_gpgsm): Add stubs for conf_load and conf_save. * rungpg.c: (_gpgme_engine_ops_gpg): Add stubs for conf_load and conf_save. * gpgme.def: Add new gpgconf related interfaces. * libgpgme.vers: Likewise. * util.h (_gpgme_get_gpgconf_path): New prototype. * gpgme.h (gpgme_protocol_t): Add GPGME_PROTOCOL_GPGCONF. * engine-backend.h (_gpgme_engine_ops_gpgconf): New prototype. (struct engine_ops): Add members for conf_load and conf_save. * engine.c (engine_ops): Add _gpgme_engine_ops_gpgconf. (_gpgme_engine_op_conf_load, (_gpgme_engine_op_conf_save): New functions. (gpgme_get_engine_info): Allow protocol GPGME_PROTOCOL_GPGCONF. tests/ 2008-01-04 Marcus Brinkmann * Makefile.am (TESTS_ENVIRONMENT): Use absolute path for GNUPGHOME. * gpg/Makefile.am (TESTS_ENVIRONMENT): Use absolute path for GNUPGHOME. * gpgsm/Makefile.am (TESTS_ENVIRONMENT): Use absolute path for GNUPGHOME. * gpg/Makefile.am (TESTS): Add t-gpgconf. t-gpgconf.c: New file. --- ChangeLog | 4 + configure.ac | 134 +++++- gpgme/ChangeLog | 32 ++ gpgme/Makefile.am | 9 +- gpgme/engine-backend.h | 6 + gpgme/engine-gpgconf.c | 877 ++++++++++++++++++++++++++++++++++++++++ gpgme/engine-gpgsm.c | 2 + gpgme/engine.c | 36 +- gpgme/engine.h | 5 + gpgme/gpgconf.c | 134 ++++++ gpgme/gpgme.def | 7 + gpgme/gpgme.h | 160 +++++++- gpgme/libgpgme.vers | 7 + gpgme/op-support.c | 2 + gpgme/posix-util.c | 10 + gpgme/rungpg.c | 4 +- gpgme/util.h | 1 + gpgme/w32-util.c | 20 + tests/ChangeLog | 11 + tests/Makefile.am | 2 +- tests/gpg/Makefile.am | 6 +- tests/gpg/t-gpgconf.c | 305 ++++++++++++++ tests/gpgsm/Makefile.am | 2 +- 23 files changed, 1760 insertions(+), 16 deletions(-) create mode 100644 gpgme/engine-gpgconf.c create mode 100644 gpgme/gpgconf.c create mode 100644 tests/gpg/t-gpgconf.c diff --git a/ChangeLog b/ChangeLog index 5cb11bcd..4c77add1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2008-01-04 Marcus Brinkmann + + * configure.ac: Support gpgconf. + 2007-09-27 Marcus Brinkmann * assuan-pipe-connect.c (pipe_connect_gpgme): Do not close process diff --git a/configure.ac b/configure.ac index 5145d128..fcae5ab5 100644 --- a/configure.ac +++ b/configure.ac @@ -114,6 +114,7 @@ AM_CONDITIONAL(HAVE_LD_VERSION_SCRIPT, test "$have_ld_version_script" = "yes") GPG_DEFAULT=no GPGSM_DEFAULT=no +GPGCONF_DEFAULT=no component_system=None have_dosish_system=no have_w32_system=no @@ -126,6 +127,7 @@ case "${host}" in have_w32_system=yes GPG_DEFAULT='c:\\gnupg\\gpg.exe' GPGSM_DEFAULT='c:\\gnupg\\gpgsm.exe' + GPGCONF_DEFAULT='c:\\gnupg\\gpgconf.exe' #component_system='COM+' AM_PATH_GLIB_2_0 @@ -158,6 +160,7 @@ case "${host}" in # XXX: Probably use exec-prefix here? # GPG_DEFAULT='/usr/bin/gpg' # GPGSM_DEFAULT='/usr/bin/gpgsm' +# GPGCONF_DEFAULT='/usr/bin/gpgconf' ;; esac @@ -261,8 +264,10 @@ AC_DEFINE(GPG_ERR_SOURCE_DEFAULT, GPG_ERR_SOURCE_GPGME, # Checks for system services NEED_GPG_VERSION_DEFAULT=1.3.0 NEED_GPGSM_VERSION_DEFAULT=1.9.6 +NEED_GPGCONF_VERSION_DEFAULT=2.0.4 NEED_GPG_VERSION="$NEED_GPG_VERSION_DEFAULT" NEED_GPGSM_VERSION="$NEED_GPGSM_VERSION_DEFAULT" +NEED_GPGCONF_VERSION="$NEED_GPGCONF_VERSION_DEFAULT" AC_ARG_WITH(gpg-version, AC_HELP_STRING([--with-gpg-version=VER], [require GnuPG version VER]), NEED_GPG_VERSION=$withval) @@ -281,11 +286,22 @@ fi if test "$NEED_GPGSM_VERSION" = "no"; then NEED_GPGSM_VERSION=0.0.0 fi +AC_ARG_WITH(gpgconf-version, + AC_HELP_STRING([--with-gpgconf-version=VER], [require GPGCONF version VER]), + NEED_GPGCONF_VERSION=$withval) +if test "$NEED_GPGCONF_VERSION" = "yes"; then + NEED_GPGCONF_VERSION="$NEED_GPGCONF_VERSION_DEFAULT" +fi +if test "$NEED_GPGCONF_VERSION" = "no"; then + NEED_GPGCONF_VERSION=0.0.0 +fi AC_DEFINE_UNQUOTED(NEED_GPG_VERSION, "$NEED_GPG_VERSION", [Min. needed GnuPG version.]) AC_DEFINE_UNQUOTED(NEED_GPGSM_VERSION, "$NEED_GPGSM_VERSION", [Min. needed GPGSM version.]) +AC_DEFINE_UNQUOTED(NEED_GPGCONF_VERSION, "$NEED_GPGCONF_VERSION", + [Min. needed GPGCONF version.]) NO_OVERRIDE=no @@ -477,6 +493,109 @@ AC_ARG_ENABLE(gpgsm-test, AM_CONDITIONAL(RUN_GPGSM_TESTS, test "$run_gpgsm_test" = "yes") +NO_OVERRIDE=no +AC_ARG_WITH(gpgconf, + AC_HELP_STRING([--with-gpgconf=PATH], + [use gpgconf binary at PATH]), + GPGCONF=$withval, NO_OVERRIDE=yes) +if test "$NO_OVERRIDE" = "yes" || test "$GPGCONF" = "yes"; then + GPGCONF= + NO_OVERRIDE=yes + if test "$cross_compiling" != "yes"; then + AC_PATH_PROG(GPGCONF, gpgconf) + fi + if test -z "$GPGCONF"; then + GPGCONF="$GPGCONF_DEFAULT" + fi +fi +if test "$GPGCONF" = no; then + if test "$NO_OVERRIDE" = "yes"; then + if test "$cross_compiling" != "yes"; then + AC_MSG_WARN([ +*** +*** Could not find gpgconf, install gpgconf or use --with-gpgconf=PATH to enable it +***]) + else + AC_MSG_ERROR([ +*** +*** Can not determine path to gpgconf when cross-compiling, use --with-gpgconf=PATH +***]) + fi + fi +else + AC_DEFINE_UNQUOTED(GPGCONF_PATH, "$GPGCONF", [Path to the GPGCONF binary.]) + AC_DEFINE(ENABLE_GPGCONF,1,[Whether GPGCONF support is enabled]) +fi +AM_CONDITIONAL(HAVE_GPGCONF, test "$GPGCONF" != "no") + +dnl Check for GPGCONF version requirement. +GPGCONF_VERSION=unknown +ok=maybe +if test -z "$GPGCONF" -o "x$GPGCONF" = "xno"; then + ok=no +else + if test "$cross_compiling" = "yes"; then + AC_MSG_WARN([GPGCONF version can not be checked when cross compiling]) + ok=no + else + if test ! -x "$GPGCONF"; then + AC_MSG_WARN([GPGCONF not executable, version check disabled]) + ok=no + fi + fi +fi +if test "$ok" = "maybe"; then + AC_MSG_CHECKING(for GPGCONF >= $NEED_GPGCONF_VERSION) + req_major=`echo $NEED_GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'` + req_minor=`echo $NEED_GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'` + req_micro=`echo $NEED_GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'` + GPGCONF_VERSION=`$GPGCONF --version | sed -n '1 s/[[^0-9]]*\(.*\)/\1/p'` + major=`echo $GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'` + minor=`echo $GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'` + micro=`echo $GPGCONF_VERSION | \ + sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'` + + if test "$major" -gt "$req_major"; then + ok=yes + else + if test "$major" -eq "$req_major"; then + if test "$minor" -gt "$req_minor"; then + ok=yes + else + if test "$minor" -eq "$req_minor"; then + if test "$micro" -ge "$req_micro"; then + ok=yes + fi + fi + fi + fi + fi + if test "$ok" = "yes"; then + AC_MSG_RESULT(yes) + else + AC_MSG_RESULT(no) + AC_MSG_WARN([GPGCONF must be at least version $NEED_GPGCONF_VERSION]) + fi +fi +run_gpgconf_test="$ok" +AC_ARG_ENABLE(gpgconf-test, + AC_HELP_STRING([--disable-gpgconf-test], [disable GPGCONF run test]), + run_gpgconf_test=$enableval) +AM_CONDITIONAL(RUN_GPGCONF_TESTS, test "$run_gpgconf_test" = "yes") + +# Only build if supported. +AM_CONDITIONAL(BUILD_GPGCONF, test "$GPGCONF" != "no") +if test "$GPGCONF" != "no"; then + AC_DEFINE(HAVE_GPGCONF, 1, + [Defined if we are building with gpgconf support.]) +fi + + # FIXME: Only build if supported. AM_CONDITIONAL(BUILD_ASSUAN, test "$GPGSM" != "no") if test "$GPGSM" != "no"; then @@ -634,12 +753,15 @@ AC_OUTPUT echo " GPGME v${VERSION} has been configured as follows: - GnuPG path: $GPG - GnuPG version: $GPG_VERSION, min. $NEED_GPG_VERSION + GnuPG path: $GPG + GnuPG version: $GPG_VERSION, min. $NEED_GPG_VERSION - GpgSM path: $GPGSM - GpgSM version: $GPGSM_VERSION, min. $NEED_GPGSM_VERSION + GpgSM path: $GPGSM + GpgSM version: $GPGSM_VERSION, min. $NEED_GPGSM_VERSION - GPGME Pthread: $have_pthread - GPGME Pth: $have_pth + GpgConf path: $GPGCONF + GpgConf version: $GPGCONF_VERSION, min. $NEED_GPGCONF_VERSION + + GPGME Pthread: $have_pthread + GPGME Pth: $have_pth " diff --git a/gpgme/ChangeLog b/gpgme/ChangeLog index f942d83c..ea6a9a01 100644 --- a/gpgme/ChangeLog +++ b/gpgme/ChangeLog @@ -1,3 +1,35 @@ +2008-01-04 Marcus Brinkmann + + * Makefile.am (gpgconf_components): New variable. + (main_sources): Add gpgconf.c. + * gpgme.h (gpgme_protocol_t): New protocol GPGME_PROTOCOL_GPGCONF. + (gpgme_conf_level_t, gpgme_conf_type_t, gpgme_conf_arg_t) + (gpgme_conf_opt_t, gpgme_conf_comp_t, gpgme_conf_arg_new) + (gpgme_conf_arg_release, gpgme_conf_opt_change) + (gpgme_conf_release, gpgme_op_conf_load, gpgme_op_conf_save): New + types. + * gpgconf.c, engine-gpgconf.c: New files. + * engine.h: (_gpgme_engine_op_conf_load, + (_gpgme_engine_op_conf_save): New prototypes. + * op-support.c (_gpgme_op_reset): Ignore not implemented locale + function. + * posix-util.c (_gpgme_get_gpgconf_path): New function. + * w32-util.c (_gpgme_get_gpgconf_path): New function. + * engine-gpgsm.c: + (_gpgme_engine_ops_gpgsm): Add stubs for conf_load and conf_save. + * rungpg.c: + (_gpgme_engine_ops_gpg): Add stubs for conf_load and conf_save. + * gpgme.def: Add new gpgconf related interfaces. + * libgpgme.vers: Likewise. + * util.h (_gpgme_get_gpgconf_path): New prototype. + * gpgme.h (gpgme_protocol_t): Add GPGME_PROTOCOL_GPGCONF. + * engine-backend.h (_gpgme_engine_ops_gpgconf): New prototype. + (struct engine_ops): Add members for conf_load and conf_save. + * engine.c (engine_ops): Add _gpgme_engine_ops_gpgconf. + (_gpgme_engine_op_conf_load, + (_gpgme_engine_op_conf_save): New functions. + (gpgme_get_engine_info): Allow protocol GPGME_PROTOCOL_GPGCONF. + 2007-11-28 Marcus Brinkmann * w32-util.c (_gpgme_get_gpg_path, _gpgme_get_gpgsm_path): Search diff --git a/gpgme/Makefile.am b/gpgme/Makefile.am index f74dffef..bd0191ae 100644 --- a/gpgme/Makefile.am +++ b/gpgme/Makefile.am @@ -83,6 +83,12 @@ else gpgsm_components = endif +if HAVE_GPGCONF +gpgconf_components = engine-gpgconf.c +else +gpgconf_components = +endif + # These are the source files common to all library versions. We used # to build a non-installed library for that, but that does not work # correctly on all platforms (in particular, one can not specify the @@ -100,7 +106,8 @@ main_sources = \ key.c keylist.c trust-item.c trustlist.c \ import.c export.c genkey.c delete.c edit.c getauditlog.c \ engine.h engine-backend.h engine.c rungpg.c status-table.h \ - $(gpgsm_components) sema.h priv-io.h $(system_components) \ + $(gpgsm_components) $(gpgconf_components) gpgconf.c \ + sema.h priv-io.h $(system_components) \ debug.c debug.h gpgme.c version.c error.c libgpgme_la_SOURCES = $(main_sources) \ diff --git a/gpgme/engine-backend.h b/gpgme/engine-backend.h index f1b4dc00..2e2ef5ec 100644 --- a/gpgme/engine-backend.h +++ b/gpgme/engine-backend.h @@ -97,6 +97,9 @@ struct engine_ops gpgme_error_t (*getauditlog) (void *engine, gpgme_data_t output, unsigned int flags); + gpgme_error_t (*conf_load) (void *engine, gpgme_conf_comp_t *conf_p); + gpgme_error_t (*conf_save) (void *engine, gpgme_conf_comp_t conf); + void (*set_io_cbs) (void *engine, gpgme_io_cbs_t io_cbs); void (*io_event) (void *engine, gpgme_event_io_t type, void *type_data); @@ -108,5 +111,8 @@ extern struct engine_ops _gpgme_engine_ops_gpg; /* OpenPGP. */ #ifdef ENABLE_GPGSM extern struct engine_ops _gpgme_engine_ops_gpgsm; /* CMS. */ #endif +#ifdef ENABLE_GPGCONF +extern struct engine_ops _gpgme_engine_ops_gpgconf; /* gpg-conf. */ +#endif #endif /* ENGINE_BACKEND_H */ diff --git a/gpgme/engine-gpgconf.c b/gpgme/engine-gpgconf.c new file mode 100644 index 00000000..ee545f88 --- /dev/null +++ b/gpgme/engine-gpgconf.c @@ -0,0 +1,877 @@ +// Check protocol. +// IMPLEMENT NO_ARG_DESC!!!! + +/* engine-gpgconf.c - gpg-conf engine. + Copyright (C) 2000 Werner Koch (dd9jn) + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#if HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include /* FIXME */ +#include + +#include "gpgme.h" +#include "util.h" +#include "ops.h" +#include "wait.h" +#include "priv-io.h" +#include "sema.h" + +#include "assuan.h" +#include "debug.h" + +#include "engine-backend.h" + + +struct engine_gpgconf +{ + char *file_name; + char *home_dir; +}; + +typedef struct engine_gpgconf *engine_gpgconf_t; + + +static char * +gpgconf_get_version (const char *file_name) +{ + return _gpgme_get_program_version (file_name ? file_name + : _gpgme_get_gpgconf_path ()); +} + + +static const char * +gpgconf_get_req_version (void) +{ + return NEED_GPGCONF_VERSION; +} + + +static void +gpgconf_release (void *engine) +{ + engine_gpgconf_t gpgconf = engine; + + if (!gpgconf) + return; + + if (gpgconf->file_name) + free (gpgconf->file_name); + if (gpgconf->home_dir) + free (gpgconf->home_dir); + + free (gpgconf); +} + + +static gpgme_error_t +gpgconf_new (void **engine, const char *file_name, const char *home_dir) +{ + gpgme_error_t err = 0; + engine_gpgconf_t gpgconf; + + gpgconf = calloc (1, sizeof *gpgconf); + if (!gpgconf) + return gpg_error_from_errno (errno); + + gpgconf->file_name = strdup (file_name ? file_name + : _gpgme_get_gpgconf_path ()); + if (!gpgconf->file_name) + err = gpg_error_from_syserror (); + + if (!err && home_dir) + { + gpgconf->home_dir = strdup (home_dir); + if (!gpgconf->home_dir) + err = gpg_error_from_syserror (); + } + + if (err) + gpgconf_release (gpgconf); + else + *engine = gpgconf; + + return err; +} + + +static void +release_arg (gpgme_conf_arg_t arg, gpgme_conf_type_t alt_type) +{ + while (arg) + { + gpgme_conf_arg_t next = arg->next; + + if (alt_type == GPGME_CONF_STRING) + free (arg->value.string); + free (arg); + arg = next; + } +} + + +static void +release_opt (gpgme_conf_opt_t opt) +{ + if (opt->name) + free (opt->name); + if (opt->description) + free (opt->description); + if (opt->argname) + free (opt->argname); + + release_arg (opt->default_value, opt->alt_type); + if (opt->default_description) + free (opt->default_description); + + release_arg (opt->no_arg_value, opt->alt_type); + release_arg (opt->value, opt->alt_type); + release_arg (opt->new_value, opt->alt_type); + + free (opt); +} + + +static void +release_comp (gpgme_conf_comp_t comp) +{ + gpgme_conf_opt_t opt; + + if (comp->name) + free (comp->name); + if (comp->description) + free (comp->description); + if (comp->program_name) + free (comp->program_name); + + opt = comp->options; + while (opt) + { + gpgme_conf_opt_t next = opt->next; + release_opt (opt); + opt = next; + } + + free (comp); +} + + +static void +gpgconf_config_release (gpgme_conf_comp_t conf) +{ + while (conf) + { + gpgme_conf_comp_t next = conf->next; + release_comp (conf); + conf = next; + } +} + + +static gpgme_error_t +gpgconf_read (void *engine, char *arg1, char *arg2, + gpgme_error_t (*cb) (void *hook, char *line), + void *hook) +{ + struct engine_gpgconf *gpgconf = engine; + gpgme_error_t err = 0; +#define LINELENGTH 1024 + char line[LINELENGTH] = ""; + int linelen = 0; + char *argv[] = { NULL /* file_name */, arg1, arg2, 0 }; + int rp[2]; + struct spawn_fd_item_s pfd[] = { {0, -1}, {-1, -1} }; + struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */}, {-1, -1} }; + int status; + int nread; + char *mark = NULL; + + /* FIXME: Deal with engine->home_dir. */ + + /* _gpgme_engine_new guarantees that this is not NULL. */ + argv[0] = gpgconf->file_name; + + if (_gpgme_io_pipe (rp, 1) < 0) + return gpg_error_from_syserror (); + + pfd[0].fd = rp[1]; + cfd[0].fd = rp[1]; + + status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, pfd); + if (status < 0) + { + _gpgme_io_close (rp[0]); + _gpgme_io_close (rp[1]); + return gpg_error_from_syserror (); + } + + do + { + nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1); + if (nread > 0) + { + line[linelen + nread] = '\0'; + linelen += nread; + + while ((mark = strchr (line, '\n'))) + { + char *eol = mark; + + if (eol > &line[0] && *eol == '\r') + eol--; + *eol = '\0'; + + /* Got a full line. */ + err = (*cb) (hook, line); + if (err) + break; + + linelen -= mark - line; + memmove (line, eol + 1, linelen); + } + } + } + while (nread > 0 && linelen < LINELENGTH - 1); + + if (!err && nread < 0) + err = gpg_error_from_syserror (); + if (!err && nread > 0) + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + + _gpgme_io_close (rp[0]); + + return err; +} + + +static gpgme_error_t +gpgconf_config_load_cb (void *hook, char *line) +{ + gpgme_conf_comp_t *comp_p = hook; + gpgme_conf_comp_t comp = *comp_p; +#define NR_FIELDS 16 + char *field[NR_FIELDS]; + int fields = 0; + + while (line && fields < NR_FIELDS) + { + field[fields++] = line; + line = strchr (line, ':'); + if (line) + *(line++) = '\0'; + } + + /* We require at least the first 3 fields. */ + if (fields < 2) + return gpg_error (GPG_ERR_INV_ENGINE); + + /* Find the pointer to the new component in the list. */ + while (comp && comp->next) + comp = comp->next; + if (comp) + comp_p = &comp->next; + + comp = calloc (1, sizeof (*comp)); + if (!comp) + return gpg_error_from_syserror (); + /* Prepare return value. */ + comp->_last_opt_p = &comp->options; + *comp_p = comp; + + comp->name = strdup (field[0]); + if (!comp->name) + return gpg_error_from_syserror (); + + comp->description = strdup (field[1]); + if (!comp->description) + return gpg_error_from_syserror (); + + if (fields >= 3) + { + comp->description = strdup (field[2]); + if (!comp->description) + return gpg_error_from_syserror (); + } + + return 0; +} + + +static gpgme_error_t +gpgconf_parse_option (gpgme_conf_opt_t opt, + gpgme_conf_arg_t *arg_p, char *line) +{ + gpgme_error_t err; + char *mark; + + if (!line[0]) + return 0; + + mark = strchr (line, ','); + if (mark) + *mark = '\0'; + + while (line) + { + gpgme_conf_arg_t arg = calloc (1, sizeof (*arg)); + if (!arg) + return gpg_error_from_syserror (); + *arg_p = arg; + arg_p = &arg->next; + + if (*line == '\0') + arg->no_arg = 1; + else + { + switch (opt->alt_type) + { + /* arg->value.count is an alias for arg->value.uint32. */ + case GPGME_CONF_NONE: + case GPGME_CONF_UINT32: + arg->value.uint32 = strtoul (line, NULL, 0); + break; + + case GPGME_CONF_INT32: + arg->value.uint32 = strtol (line, NULL, 0); + break; + + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + /* Skip quote character. */ + line++; + + err = _gpgme_decode_percent_string (line, &arg->value.string, + 0, 0); + if (err) + return err; + break; + } + } + + /* Find beginning of next value. */ + if (mark++ && *mark) + line = mark; + else + line = NULL; + } + + return 0; +} + + +static gpgme_error_t +gpgconf_config_load_cb2 (void *hook, char *line) +{ + gpgme_error_t err; + gpgme_conf_comp_t comp = hook; + gpgme_conf_opt_t *opt_p = comp->_last_opt_p; + gpgme_conf_opt_t opt; +#define NR_FIELDS 16 + char *field[NR_FIELDS]; + int fields = 0; + + while (line && fields < NR_FIELDS) + { + field[fields++] = line; + line = strchr (line, ':'); + if (line) + *(line++) = '\0'; + } + + /* We require at least the first 10 fields. */ + if (fields < 10) + return gpg_error (GPG_ERR_INV_ENGINE); + + opt = calloc (1, sizeof (*opt)); + if (!opt) + return gpg_error_from_syserror (); + + comp->_last_opt_p = &opt->next; + *opt_p = opt; + + if (field[0][0]) + { + opt->name = strdup (field[0]); + if (!opt->name) + return gpg_error_from_syserror (); + } + + opt->flags = strtoul (field[1], NULL, 0); + + opt->level = strtoul (field[2], NULL, 0); + + if (field[3][0]) + { + opt->description = strdup (field[3]); + if (!opt->description) + return gpg_error_from_syserror (); + } + + opt->type = strtoul (field[4], NULL, 0); + + opt->alt_type = strtoul (field[5], NULL, 0); + + if (field[6][0]) + { + opt->argname = strdup (field[6]); + if (!opt->argname) + return gpg_error_from_syserror (); + } + + if (opt->flags & GPGME_CONF_DEFAULT) + { + err = gpgconf_parse_option (opt, &opt->default_value, field[7]); + if (err) + return err; + } + else if ((opt->flags & GPGME_CONF_DEFAULT_DESC) && field[7][0]) + { + opt->default_description = strdup (field[7]); + if (!opt->default_description) + return gpg_error_from_syserror (); + } + + err = gpgconf_parse_option (opt, &opt->no_arg_value, field[8]); + if (err) + return err; + + err = gpgconf_parse_option (opt, &opt->value, field[9]); + if (err) + return err; + + return 0; +} + + +static gpgme_error_t +gpgconf_conf_load (void *engine, gpgme_conf_comp_t *comp_p) +{ + gpgme_error_t err; + gpgme_conf_comp_t comp = NULL; + gpgme_conf_comp_t cur_comp; + + *comp_p = NULL; + + err = gpgconf_read (engine, "--list-components", NULL, + gpgconf_config_load_cb, &comp); + if (err) + { + gpgconf_release (comp); + return err; + } + + cur_comp = comp; + while (!err && cur_comp) + { + err = gpgconf_read (engine, "--list-options", cur_comp->name, + gpgconf_config_load_cb2, cur_comp); + cur_comp = cur_comp->next; + } + + if (err) + { + gpgconf_release (comp); + return err; + } + + *comp_p = comp; + return 0; +} + + + +gpgme_error_t +_gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p, + gpgme_conf_type_t type, void *value) +{ + gpgme_conf_arg_t arg; + + arg = calloc (1, sizeof (*arg)); + if (!arg) + return gpg_error_from_syserror (); + + if (!value) + arg->no_arg = 1; + else + { + switch (type) + { + case GPGME_CONF_NONE: + case GPGME_CONF_UINT32: + arg->value.uint32 = *((unsigned int *) value); + break; + + case GPGME_CONF_INT32: + arg->value.int32 = *((int *) value); + break; + + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + arg->value.string = strdup (value); + if (!arg->value.string) + { + free (arg); + return gpg_error_from_syserror (); + } + break; + + default: + free (arg); + return gpg_error (GPG_ERR_INV_VALUE); + } + } + + *arg_p = arg; + return 0; +} + + +void +_gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type) +{ + switch (type) + { + case GPGME_CONF_NONE: + case GPGME_CONF_UINT32: + case GPGME_CONF_INT32: + case GPGME_CONF_STRING: + default: + break; + + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + type = GPGME_CONF_STRING; + break; + } + + release_arg (arg, type); +} + + +gpgme_error_t +_gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg) +{ + if (opt->new_value) + release_arg (opt->new_value, opt->alt_type); + + if (reset) + { + opt->new_value = NULL; + opt->change_value = 0; + } + else + { + opt->new_value = arg; + opt->change_value = 1; + } + return 0; +} + + +/* FIXME: Major problem: We don't get errors from gpgconf. */ + +static gpgme_error_t +gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf) +{ + struct engine_gpgconf *gpgconf = engine; + gpgme_error_t err = 0; +#define BUFLEN 1024 + char buf[BUFLEN]; + int buflen = 0; + char *argv[] = { NULL /* file_name */, arg1, arg2, 0 }; + int rp[2]; + struct spawn_fd_item_s pfd[] = { {1, -1}, {-1, -1} }; + struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, -1} }; + int status; + int nwrite; + + /* FIXME: Deal with engine->home_dir. */ + + /* _gpgme_engine_new guarantees that this is not NULL. */ + argv[0] = gpgconf->file_name; + argv[0] = "/home/marcus/g10/install/bin/gpgconf"; + + if (_gpgme_io_pipe (rp, 0) < 0) + return gpg_error_from_syserror (); + + pfd[0].fd = rp[0]; + cfd[0].fd = rp[0]; + + status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, pfd); + if (status < 0) + { + _gpgme_io_close (rp[0]); + _gpgme_io_close (rp[1]); + return gpg_error_from_syserror (); + } + + for (;;) + { + if (buflen == 0) + { + do + { + buflen = gpgme_data_read (conf, buf, BUFLEN); + } + while (buflen < 0 && errno == EAGAIN); + + if (buflen < 0) + { + err = gpg_error_from_syserror (); + _gpgme_io_close (rp[1]); + return err; + } + else if (buflen == 0) + { + /* All is written. */ + _gpgme_io_close (rp[1]); + return 0; + } + } + + do + { + nwrite = _gpgme_io_write (rp[1], buf, buflen); + } + while (nwrite < 0 && errno == EAGAIN); + + if (nwrite > 0) + { + buflen -= nwrite; + if (buflen > 0) + memmove (&buf[0], &buf[nwrite], buflen); + } + else if (nwrite < 0) + { + _gpgme_io_close (rp[1]); + return gpg_error_from_syserror (); + } + } + + return 0; +} + + +static gpgme_error_t +arg_to_data (gpgme_data_t conf, gpgme_conf_opt_t option, gpgme_conf_arg_t arg) +{ + gpgme_error_t err = 0; + int amt = 0; + char buf[16]; + + while (amt >= 0 && arg) + { + switch (option->alt_type) + { + case GPGME_CONF_NONE: + case GPGME_CONF_UINT32: + default: + snprintf (buf, sizeof (buf), "%u", arg->value.uint32); + buf[sizeof (buf) - 1] = '\0'; + amt = gpgme_data_write (conf, buf, strlen (buf)); + break; + + case GPGME_CONF_INT32: + snprintf (buf, sizeof (buf), "%i", arg->value.uint32); + buf[sizeof (buf) - 1] = '\0'; + amt = gpgme_data_write (conf, buf, strlen (buf)); + break; + + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + /* One quote character, and three times to allow + for percent escaping. */ + { + char *ptr = arg->value.string; + amt = gpgme_data_write (conf, "\"", 1); + if (amt < 0) + break; + + while (!err && *ptr) + { + switch (*ptr) + { + case '%': + amt = gpgme_data_write (conf, "%25", 3); + break; + + case ':': + amt = gpgme_data_write (conf, "%3a", 3); + break; + + case ',': + amt = gpgme_data_write (conf, "%2c", 3); + break; + + default: + amt = gpgme_data_write (conf, ptr, 1); + } + ptr++; + } + } + break; + } + + if (amt < 0) + break; + + arg = arg->next; + /* Comma separator. */ + if (arg) + amt = gpgme_data_write (conf, ",", 1); + } + + if (amt < 0) + return gpg_error_from_syserror (); + + return 0; +} + + +static gpgme_error_t +gpgconf_conf_save (void *engine, gpgme_conf_comp_t comp) +{ + gpgme_error_t err; + int amt = 0; + /* We use a data object to store the new configuration. */ + gpgme_data_t conf; + gpgme_conf_opt_t option; + int something_changed = 0; + + err = gpgme_data_new (&conf); + if (err) + return err; + + option = comp->options; + while (!err && amt >= 0 && option) + { + if (option->change_value) + { + unsigned int flags = 0; + char buf[16]; + + something_changed = 1; + + amt = gpgme_data_write (conf, option->name, strlen (option->name)); + if (amt >= 0) + amt = gpgme_data_write (conf, ":", 1); + if (amt < 0) + break; + + if (!option->new_value) + flags |= GPGME_CONF_DEFAULT; + snprintf (buf, sizeof (buf), "%u", flags); + buf[sizeof (buf) - 1] = '\0'; + + amt = gpgme_data_write (conf, buf, strlen (buf)); + if (amt >= 0) + amt = gpgme_data_write (conf, ":", 1); + if (amt < 0) + break; + + if (option->new_value) + { + err = arg_to_data (conf, option, option->new_value); + if (err) + break; + } + amt = gpgme_data_write (conf, "\n", 1); + } + option = option->next; + } + if (!err && amt < 0) + err = gpg_error_from_syserror (); + if (err || !something_changed) + goto bail; + + err = gpgme_data_seek (conf, 0, SEEK_SET); + if (err) + goto bail; + + err = gpgconf_write (engine, "--change-options", comp->name, conf); + bail: + gpgme_data_release (conf); + return err; +} + + +static void +gpgconf_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ + /* Nothing to do. */ +} + + +/* Currently, we do not use the engine interface for the various + operations. */ +void +_gpgme_conf_release (gpgme_conf_comp_t conf) +{ + gpgconf_config_release (conf); +} + + +struct engine_ops _gpgme_engine_ops_gpgconf = + { + /* Static functions. */ + _gpgme_get_gpgconf_path, + gpgconf_get_version, + gpgconf_get_req_version, + gpgconf_new, + + /* Member functions. */ + gpgconf_release, + NULL, /* reset */ + NULL, /* set_status_handler */ + NULL, /* set_command_handler */ + NULL, /* set_colon_line_handler */ + NULL, /* set_locale */ + NULL, /* decrypt */ + NULL, /* delete */ + NULL, /* edit */ + NULL, /* encrypt */ + NULL, /* encrypt_sign */ + NULL, /* export */ + NULL, /* export_ext */ + NULL, /* genkey */ + NULL, /* import */ + NULL, /* keylist */ + NULL, /* keylist_ext */ + NULL, /* sign */ + NULL, /* trustlist */ + NULL, /* verify */ + NULL, /* getauditlog */ + gpgconf_conf_load, + gpgconf_conf_save, + gpgconf_set_io_cbs, + NULL, /* io_event */ + NULL /* cancel */ + }; diff --git a/gpgme/engine-gpgsm.c b/gpgme/engine-gpgsm.c index 3815613e..1e9ddd23 100644 --- a/gpgme/engine-gpgsm.c +++ b/gpgme/engine-gpgsm.c @@ -1885,6 +1885,8 @@ struct engine_ops _gpgme_engine_ops_gpgsm = NULL, /* trustlist */ gpgsm_verify, gpgsm_getauditlog, + NULL, /* conf_load */ + NULL, /* conf_save */ gpgsm_set_io_cbs, gpgsm_io_event, gpgsm_cancel diff --git a/gpgme/engine.c b/gpgme/engine.c index ef7147d4..cf3fe9fe 100644 --- a/gpgme/engine.c +++ b/gpgme/engine.c @@ -47,7 +47,12 @@ static struct engine_ops *engine_ops[] = { &_gpgme_engine_ops_gpg, /* OpenPGP. */ #ifdef ENABLE_GPGSM - &_gpgme_engine_ops_gpgsm /* CMS. */ + &_gpgme_engine_ops_gpgsm, /* CMS. */ +#else + NULL, +#endif +#ifdef ENABLE_GPGCONF + &_gpgme_engine_ops_gpgconf /* gpg-conf. */ #else NULL #endif @@ -169,7 +174,8 @@ gpgme_get_engine_info (gpgme_engine_info_t *info) { gpgme_engine_info_t *lastp = &engine_info; gpgme_protocol_t proto_list[] = { GPGME_PROTOCOL_OpenPGP, - GPGME_PROTOCOL_CMS }; + GPGME_PROTOCOL_CMS, + GPGME_PROTOCOL_GPGCONF }; unsigned int proto; for (proto = 0; proto < DIM (proto_list); proto++) @@ -724,6 +730,32 @@ _gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output, } +gpgme_error_t +_gpgme_engine_op_conf_load (engine_t engine, gpgme_conf_comp_t *conf_p) +{ + if (!engine) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!engine->ops->conf_load) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return (*engine->ops->conf_load) (engine->engine, conf_p); +} + + +gpgme_error_t +_gpgme_engine_op_conf_save (engine_t engine, gpgme_conf_comp_t conf) +{ + if (!engine) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!engine->ops->conf_save) + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + return (*engine->ops->conf_save) (engine->engine, conf); +} + + void _gpgme_engine_set_io_cbs (engine_t engine, gpgme_io_cbs_t io_cbs) { diff --git a/gpgme/engine.h b/gpgme/engine.h index 6c636e04..e67399ec 100644 --- a/gpgme/engine.h +++ b/gpgme/engine.h @@ -127,6 +127,11 @@ gpgme_error_t _gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output, unsigned int flags); +gpgme_error_t _gpgme_engine_op_conf_load (engine_t engine, + gpgme_conf_comp_t *conf_p); +gpgme_error_t _gpgme_engine_op_conf_save (engine_t engine, + gpgme_conf_comp_t conf); + void _gpgme_engine_set_io_cbs (engine_t engine, gpgme_io_cbs_t io_cbs); void _gpgme_engine_io_event (engine_t engine, diff --git a/gpgme/gpgconf.c b/gpgme/gpgconf.c new file mode 100644 index 00000000..9fa2ce9e --- /dev/null +++ b/gpgme/gpgconf.c @@ -0,0 +1,134 @@ +/* gpgconf.c - GnuPG Made Easy. + Copyright (C) 2007 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#if HAVE_CONFIG_H +#include +#endif + +#include "gpgme.h" + +#include "ops.h" +#include "engine.h" + +#ifdef ENABLE_GPGCONF +/* engine-gpgconf.c. */ +gpgme_error_t _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p, + gpgme_conf_type_t type, void *value); +void _gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type); +gpgme_error_t _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, + gpgme_conf_arg_t arg); +void _gpgme_conf_release (gpgme_conf_comp_t conf); +gpgme_error_t _gpgme_conf_load (void *engine, gpgme_conf_comp_t *conf_p); +gpgme_error_t gpgme_op_conf_save (gpgme_ctx_t ctx, gpgme_conf_comp_t comp); + +#endif + + +/* Allocate a new gpgme_conf_arg_t. */ +gpgme_error_t +gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p, + gpgme_conf_type_t type, void *value) +{ +#ifdef ENABLE_GPGCONF + return _gpgme_conf_arg_new (arg_p, type, value); +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +/* This also releases all chained argument structures! */ +void +gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type) +{ +#ifdef ENABLE_GPGCONF + return _gpgme_conf_arg_release (arg, type); +#endif +} + + +/* Register a change for the value of OPT to ARG. */ +gpgme_error_t +gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg) +{ +#ifdef ENABLE_GPGCONF + return _gpgme_conf_opt_change (opt, reset, arg); +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + + +/* Public function to release a gpgme_conf_comp list. */ +void +gpgme_conf_release (gpgme_conf_comp_t conf) +{ +#ifdef ENABLE_GPGCONF + _gpgme_conf_release (conf); +#endif +} + + +/* Public function to release load a configuration list. No + asynchronous interface for now. */ +gpgme_error_t +gpgme_op_conf_load (gpgme_ctx_t ctx, gpgme_conf_comp_t *conf_p) +{ +#ifdef ENABLE_GPGCONF + gpgme_error_t err; + gpgme_protocol_t proto = ctx->protocol; + + ctx->protocol = GPGME_PROTOCOL_GPGCONF; + err = _gpgme_op_reset (ctx, 1); + if (err) + return err; + + err = _gpgme_engine_op_conf_load (ctx->engine, conf_p); + ctx->protocol = proto; + return err; +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +/* This function does not follow chained components! */ +gpgme_error_t +gpgme_op_conf_save (gpgme_ctx_t ctx, gpgme_conf_comp_t comp) +{ +#ifdef ENABLE_GPGCONF + gpgme_error_t err; + gpgme_protocol_t proto = ctx->protocol; + + ctx->protocol = GPGME_PROTOCOL_GPGCONF; + err = _gpgme_op_reset (ctx, 1); + if (err) + return err; + + err = _gpgme_engine_op_conf_save (ctx->engine, comp); + ctx->protocol = proto; + return err; +#else + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + diff --git a/gpgme/gpgme.def b/gpgme/gpgme.def index 57dbe40d..3c5e335b 100644 --- a/gpgme/gpgme.def +++ b/gpgme/gpgme.def @@ -159,5 +159,12 @@ EXPORTS gpgme_op_getauditlog_start @123 gpgme_op_getauditlog @124 + gpgme_conf_release @125 + gpgme_conf_arg_new @126 + gpgme_conf_arg_release @127 + gpgme_conf_opt_change @128 + gpgme_op_conf_load @129 + gpgme_op_conf_save @130 + ; END diff --git a/gpgme/gpgme.h b/gpgme/gpgme.h index b867419c..bf42035e 100644 --- a/gpgme/gpgme.h +++ b/gpgme/gpgme.h @@ -72,7 +72,7 @@ extern "C" { AM_PATH_GPGME macro) check that this header matches the installed library. Warning: Do not edit the next line. configure will do that for you! */ -#define GPGME_VERSION "1.1.6-svn1258" +#define GPGME_VERSION "1.1.6-svn1282" @@ -300,6 +300,7 @@ typedef enum { GPGME_PROTOCOL_OpenPGP = 0, /* The default mode. */ GPGME_PROTOCOL_CMS = 1, + GPGME_PROTOCOL_GPGCONF = 2, /* Special code for gpgconf. */ GPGME_PROTOCOL_UNKNOWN = 255 } gpgme_protocol_t; @@ -1652,6 +1653,163 @@ gpgme_error_t gpgme_op_getauditlog_start (gpgme_ctx_t ctx, gpgme_data_t output, gpgme_error_t gpgme_op_getauditlog (gpgme_ctx_t ctx, gpgme_data_t output, unsigned int flags); + +/* Interface to gpg-conf. */ + +/* The expert level at which a configuration option or group of + options should be displayed. See the gpg-conf documentation for + more details. */ +typedef enum + { + GPGME_CONF_BASIC = 0, + GPGME_CONF_ADVANCED = 1, + GPGME_CONF_EXPERT = 2, + GPGME_CONF_INVISIBLE = 3, + GPGME_CONF_INTERNAL = 4 + } +gpgme_conf_level_t; + + +/* The data type of a configuration option argument. See the gpg-conf + documentation for more details. */ +typedef enum + { + /* Basic types. */ + GPGME_CONF_NONE = 0, + GPGME_CONF_STRING = 1, + GPGME_CONF_INT32 = 2, + GPGME_CONF_UINT32 = 3, + + /* Complex types. */ + GPGME_CONF_PATHNAME = 32, + GPGME_CONF_LDAP_SERVER = 33 + } +gpgme_conf_type_t; + + +/* This represents a single argument for a configuration option. + Which of the members of value is used depends on the ALT_TYPE. */ +typedef struct gpgme_conf_arg +{ + struct gpgme_conf_arg *next; + /* True if the option appears without an (optional) argument. */ + unsigned int no_arg; + union + { + unsigned int count; + unsigned int uint32; + int int32; + char *string; + } value; +} *gpgme_conf_arg_t; + + +/* The flags of a configuration option. See the gpg-conf + documentation for details. */ +#define GPGME_CONF_GROUP (1 << 0) +#define GPGME_CONF_OPTIONAL (1 << 1) +#define GPGME_CONF_LIST (1 << 2) +#define GPGME_CONF_RUNTIME (1 << 3) +#define GPGME_CONF_DEFAULT (1 << 4) +#define GPGME_CONF_DEFAULT_DESC (1 << 5) +#define GPGME_CONF_NO_ARG_DESC (1 << 6) +#define GPGME_CONF_NO_CHANGE (1 << 7) + + +/* The representation of a single configuration option. See the + gpg-conf documentation for details. */ +typedef struct gpgme_conf_opt +{ + struct gpgme_conf_opt *next; + + /* The option name. */ + char *name; + + /* The flags for this option. */ + unsigned int flags; + + /* The level of this option. */ + gpgme_conf_level_t level; + + /* The localized description of this option. */ + char *description; + + /* The type and alternate type of this option. */ + gpgme_conf_type_t type; + gpgme_conf_type_t alt_type; + + /* The localized (short) name of the argument, if any. */ + char *argname; + + /* The default value. */ + gpgme_conf_arg_t default_value; + char *default_description; + + /* The default value if the option is not set. */ + gpgme_conf_arg_t no_arg_value; + char *no_arg_description; + + /* The current value if the option is set. */ + gpgme_conf_arg_t value; + + /* The new value, if any. NULL means reset to default. */ + int change_value; + gpgme_conf_arg_t new_value; + + /* Free for application use. */ + void *user_data; +} *gpgme_conf_opt_t; + + +/* The representation of a component that can be configured. See the + gpg-conf documentation for details. */ +typedef struct gpgme_conf_comp +{ + struct gpgme_conf_comp *next; + + /* Internal to GPGME, do not use! */ + gpgme_conf_opt_t *_last_opt_p; + + /* The component name. */ + char *name; + + /* A human-readable description for the component. */ + char *description; + + /* The program name (an absolute path to the program). */ + char *program_name; + + /* A linked list of options for this component. */ + struct gpgme_conf_opt *options; +} *gpgme_conf_comp_t; + + +/* Allocate a new gpgme_conf_arg_t. If VALUE is NULL, a "no arg + default" is prepared. If type is a string type, VALUE should point + to the string. Else, it should point to an unsigned or signed + integer respectively. */ +gpgme_error_t gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p, + gpgme_conf_type_t type, void *value); + +/* This also releases all chained argument structures! */ +void gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type); + +/* Register a change for the value of OPT to ARG. If RESET is 1 (do + not use any values but 0 or 1), ARG is ignored and the option is + not changed (reverting a previous change). Otherwise, if ARG is + NULL, the option is cleared or reset to its default. */ +gpgme_error_t gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, + gpgme_conf_arg_t arg); + +/* Release a set of configurations. */ +void gpgme_conf_release (gpgme_conf_comp_t conf); + +/* Retrieve the current configurations. */ +gpgme_error_t gpgme_op_conf_load (gpgme_ctx_t ctx, gpgme_conf_comp_t *conf_p); + +/* Save the configuration of component comp. This function does not + follow chained components! */ +gpgme_error_t gpgme_op_conf_save (gpgme_ctx_t ctx, gpgme_conf_comp_t comp); /* Various functions. */ diff --git a/gpgme/libgpgme.vers b/gpgme/libgpgme.vers index 9f18dafc..183a3943 100644 --- a/gpgme/libgpgme.vers +++ b/gpgme/libgpgme.vers @@ -39,6 +39,13 @@ GPGME_1.1 { gpgme_op_getauditlog_start; gpgme_op_getauditlog; + + gpgme_conf_release; + gpgme_conf_arg_new; + gpgme_conf_arg_release; + gpgme_conf_opt_change; + gpgme_op_conf_load; + gpgme_op_conf_save; }; diff --git a/gpgme/op-support.c b/gpgme/op-support.c index 20c07385..1212c542 100644 --- a/gpgme/op-support.c +++ b/gpgme/op-support.c @@ -115,6 +115,8 @@ _gpgme_op_reset (gpgme_ctx_t ctx, int type) err = _gpgme_engine_set_locale (ctx->engine, LC_MESSAGES, ctx->lc_messages); #endif + if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) + err = 0; if (err) { _gpgme_engine_release (ctx->engine); diff --git a/gpgme/posix-util.c b/gpgme/posix-util.c index d5b4172d..168dad6e 100644 --- a/gpgme/posix-util.c +++ b/gpgme/posix-util.c @@ -49,6 +49,16 @@ _gpgme_get_gpgsm_path (void) #endif } +const char * +_gpgme_get_gpgconf_path (void) +{ +#ifdef GPGCONF_PATH + return GPGCONF_PATH; +#else + return NULL; +#endif +} + /* See w32-util.c */ int _gpgme_get_conf_int (const char *key, int *value) diff --git a/gpgme/rungpg.c b/gpgme/rungpg.c index c54da3a2..0ffdc7c7 100644 --- a/gpgme/rungpg.c +++ b/gpgme/rungpg.c @@ -2120,7 +2120,9 @@ struct engine_ops _gpgme_engine_ops_gpg = gpg_sign, gpg_trustlist, gpg_verify, - NULL, + NULL, /* getauditlog */ + NULL, /* conf_load */ + NULL, /* conf_save */ gpg_set_io_cbs, gpg_io_event, gpg_cancel diff --git a/gpgme/util.h b/gpgme/util.h index 28d5e192..0cd6ab5c 100644 --- a/gpgme/util.h +++ b/gpgme/util.h @@ -31,6 +31,7 @@ /*-- {posix,w32}-util.c --*/ const char *_gpgme_get_gpg_path (void); const char *_gpgme_get_gpgsm_path (void); +const char *_gpgme_get_gpgconf_path (void); int _gpgme_get_conf_int (const char *key, int *value); diff --git a/gpgme/w32-util.c b/gpgme/w32-util.c index 60c8396d..b0f6df2d 100644 --- a/gpgme/w32-util.c +++ b/gpgme/w32-util.c @@ -274,6 +274,7 @@ find_program_in_inst_dir (const char *name) return result; } + static char * find_program_at_standard_place (const char *name) { @@ -313,6 +314,7 @@ _gpgme_get_gpg_path (void) return gpg_program; } + const char * _gpgme_get_gpgsm_path (void) { @@ -330,6 +332,24 @@ _gpgme_get_gpgsm_path (void) } +const char * +_gpgme_get_gpgconf_path (void) +{ + static char *gpgconf_program; + + LOCK (get_path_lock); + if (!gpgconf_program) + gpgconf_program = find_program_in_registry ("gpgconfProgram"); + if (!gpgconf_program) + gpgconf_program = find_program_in_inst_dir ("gpgconf.exe"); + if (!gpgconf_program) + gpgconf_program + = find_program_at_standard_place ("GNU\\GnuPG\\gpgconf.exe"); + UNLOCK (get_path_lock); + return gpgconf_program; +} + + /* Return an integer value from gpgme specific configuration entries. VALUE receives that value; function returns true if a value has been configured and false if not. */ diff --git a/tests/ChangeLog b/tests/ChangeLog index 9e16ce2e..dd7c3968 100644 --- a/tests/ChangeLog +++ b/tests/ChangeLog @@ -1,3 +1,14 @@ +2008-01-04 Marcus Brinkmann + + * Makefile.am (TESTS_ENVIRONMENT): Use absolute path for + GNUPGHOME. + * gpg/Makefile.am (TESTS_ENVIRONMENT): Use absolute path for + GNUPGHOME. + * gpgsm/Makefile.am (TESTS_ENVIRONMENT): Use absolute path for + GNUPGHOME. + * gpg/Makefile.am (TESTS): Add t-gpgconf. + t-gpgconf.c: New file. + 2007-11-23 Marcus Brinkmann * gpgsm/t-verify.c (show_auditlog): Check for GPG_ERR_ASS_UNKNOWN_CMD. diff --git a/tests/Makefile.am b/tests/Makefile.am index aee20ca1..ceabc999 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,7 +20,7 @@ ## Process this file with automake to produce Makefile.in -TESTS_ENVIRONMENT = GNUPGHOME=. +TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) TESTS = t-version t-data t-engine-info diff --git a/tests/gpg/Makefile.am b/tests/gpg/Makefile.am index e5d9de9b..cac589f2 100644 --- a/tests/gpg/Makefile.am +++ b/tests/gpg/Makefile.am @@ -21,7 +21,7 @@ GPG = @GPG@ -TESTS_ENVIRONMENT = GNUPGHOME=. GPG_AGENT_INFO= +TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO= # The keylist tests must come after the import and the edit test. noinst_HEADERS = t-support.h @@ -35,9 +35,9 @@ endif TESTS = t-encrypt t-encrypt-sym t-encrypt-sign t-sign t-signers \ t-decrypt t-verify t-decrypt-verify t-sig-notation t-export \ t-import t-trustlist t-edit t-keylist t-keylist-sig t-wait \ - t-encrypt-large t-file-name $(tests_unix) + t-encrypt-large t-file-name t-gpgconf $(tests_unix) -CLEANFILES = secring.gpg pubring.gpg trustdb.gpg +CLEANFILES = secring.gpg pubring.gpg trustdb.gpg dirmngr.conf DISTCLEANFILES = pubring.gpg~ random_seed gpg.conf EXTRA_DIST = mkdemodirs pubdemo.asc secdemo.asc cipher-1.asc cipher-2.asc \ diff --git a/tests/gpg/t-gpgconf.c b/tests/gpg/t-gpgconf.c new file mode 100644 index 00000000..458bbe75 --- /dev/null +++ b/tests/gpg/t-gpgconf.c @@ -0,0 +1,305 @@ +/* t-gpgconf.c - Regression test. + Copyright (C) 2001, 2004, 2007 g10 Code GmbH + + This file is part of GPGME. + + GPGME is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 2.1 of + the License, or (at your option) any later version. + + GPGME is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_W32_SYSTEM +#include +#endif + +#include + + +#define fail_if_err(err) \ + do \ + { \ + if (err) \ + { \ + fprintf (stderr, "%s:%d: %s: %s\n", \ + __FILE__, __LINE__, gpgme_strsource (err), \ + gpgme_strerror (err)); \ + exit (1); \ + } \ + } \ + while (0) + + +void +init_gpgme (gpgme_protocol_t proto) +{ + gpgme_error_t err; + + gpgme_check_version (NULL); + setlocale (LC_ALL, ""); + gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL)); +#ifndef HAVE_W32_SYSTEM + gpgme_set_locale (NULL, LC_MESSAGES, setlocale (LC_MESSAGES, NULL)); +#endif + + err = gpgme_engine_check_version (proto); + fail_if_err (err); +} + + +static char * +spaces (char *str, int extra) +{ + static char buf[80]; + int len = str ? strlen (str) : 0; + int n; + +#define TABSTOP 30 + n = TABSTOP - len - extra; + + memset (buf, ' ', sizeof (buf)); + if (n < 1 || n > (sizeof (buf) - 1)) + { + buf[0] = '\n'; + n = TABSTOP + 1; + } + + buf[n] = '\0'; + return buf; +} + + +void +dump_arg (int type, gpgme_conf_arg_t arg) +{ + if (!arg) + { + printf ("(none)"); + return; + } + + while (arg) + { + switch (type) + { + case GPGME_CONF_STRING: + case GPGME_CONF_PATHNAME: + case GPGME_CONF_LDAP_SERVER: + printf ("%s", arg->value.string); + break; + + case GPGME_CONF_UINT32: + printf ("%u", arg->value.uint32); + break; + + case GPGME_CONF_INT32: + printf ("%i", arg->value.int32); + break; + + case GPGME_CONF_NONE: + printf ("%i (times)", arg->value.count); + break; + + default: + printf ("(unknown type)"); + } + + arg = arg->next; + if (arg) + printf (" "); + } +} + + +void +dump_opt (gpgme_conf_opt_t opt) +{ + char level; + char runtime = (opt->flags & GPGME_CONF_RUNTIME) ? 'r' : ' '; + + switch (opt->level) + { + case GPGME_CONF_BASIC: + level = 'b'; + break; + case GPGME_CONF_ADVANCED: + level = 'a'; + break; + case GPGME_CONF_EXPERT: + level = 'e'; + break; + case GPGME_CONF_INVISIBLE: + level = 'i'; + break; + case GPGME_CONF_INTERNAL: + level = '#'; + break; + default: + level = '?'; + } + + if (opt->flags & GPGME_CONF_GROUP) + { + printf ("\n"); + printf ("%c%c [%s]%s%s\n", level, runtime, opt->name, spaces (opt->name, 5), + opt->description + ? opt->description : ""); + } + else + { + if (opt->argname) + { + char *more = (opt->flags & GPGME_CONF_LIST) ? "..." : ""; + + if (opt->flags & GPGME_CONF_OPTIONAL) + { + printf ("%c%c --%s [%s%s] %s", level, runtime, opt->name, opt->argname, more, + spaces (opt->name, 9 + strlen (opt->argname) + strlen (more))); + } + else + { + printf ("%c%c --%s %s%s %s", level, runtime, opt->name, opt->argname, more, + spaces (opt->name, 7 + strlen (opt->argname) + strlen (more))); + } + } + else + printf ("%c%c --%s%s", level, runtime, opt->name, spaces (opt->name, 5)); + + if (opt->description) + printf ("%s", opt->description); + printf ("\n"); + + if (opt->flags & GPGME_CONF_DEFAULT) + { + printf ("%s%s = ", spaces (NULL, 0), opt->argname ? opt->argname : "(default)"); + dump_arg (opt->type, opt->default_value); + printf ("\n"); + } + else if (opt->flags & GPGME_CONF_DEFAULT_DESC) + printf ("%s%s = %s\n", spaces (NULL, 0), opt->argname ? opt->argname : "(default)", + opt->default_description); + + if (opt->no_arg_value) + { + printf ("%sNo Arg Def = ", spaces (NULL, 0)); + dump_arg (opt->type, opt->no_arg_value); + printf ("\n"); + } + if (opt->value) + { + printf ("%sCurrent = ", spaces (NULL, 0)); + dump_arg (opt->type, opt->value); + printf ("\n"); + } + } + +#if 0 + arg = comp->options; + while (opt) + { + dump_opt (opt); + opt = opt->next; + } +#endif +} + + +void +dump_comp (gpgme_conf_comp_t comp) +{ + gpgme_conf_opt_t opt; + + printf ("COMPONENT\n"); + printf ("=========\n"); + printf (" Name: %s\n", comp->name); + if (comp->description) + printf (" Desc: %s\n", comp->description); + if (comp->program_name) + printf (" Path: %s\n", comp->program_name); + printf ("\n"); + + opt = comp->options; + while (opt) + { + dump_opt (opt); + opt = opt->next; + } +} + + +int +main (int argc, char **argv) +{ + gpgme_ctx_t ctx; + gpgme_error_t err; + gpgme_conf_comp_t conf; + gpgme_conf_comp_t comp; + int first; + init_gpgme (GPGME_PROTOCOL_GPGCONF); + + err = gpgme_new (&ctx); + fail_if_err (err); + + err = gpgme_op_conf_load (ctx, &conf); + fail_if_err (err); + + comp = conf; + first = 1; + while (comp) + { + if (!first) + printf ("\n"); + else + first = 0; + dump_comp (comp); + comp = comp->next; + } + +#if 1 + /* Now change something. */ + { + unsigned int count = 1; + gpgme_conf_arg_t arg; + gpgme_conf_opt_t opt; + + err = gpgme_conf_arg_new (&arg, GPGME_CONF_NONE, &count); + fail_if_err (err); + + comp = conf; + while (comp && strcmp (comp->name, "dirmngr")) + comp = comp->next; + opt = comp->options; + while (opt && strcmp (opt->name, "verbose")) + opt = opt->next; + + err = gpgme_conf_opt_change (opt, 0, arg); + fail_if_err (err); + + err = gpgme_op_conf_save (ctx, comp); + fail_if_err (err); + } +#endif + + gpgme_conf_release (conf); + + return 0; +} diff --git a/tests/gpgsm/Makefile.am b/tests/gpgsm/Makefile.am index 86c04b0d..4d34b91f 100644 --- a/tests/gpgsm/Makefile.am +++ b/tests/gpgsm/Makefile.am @@ -21,7 +21,7 @@ GPGSM = @GPGSM@ -TESTS_ENVIRONMENT = GNUPGHOME=. GPG_AGENT_INFO= +TESTS_ENVIRONMENT = GNUPGHOME=$(abs_builddir) GPG_AGENT_INFO= noinst_HEADERS = t-support.h TESTS = t-import t-keylist t-encrypt t-verify t-decrypt t-sign t-export