diff options
Diffstat (limited to 'src/engine-gpgconf.c')
-rw-r--r-- | src/engine-gpgconf.c | 919 |
1 files changed, 919 insertions, 0 deletions
diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c new file mode 100644 index 00000000..3d107d45 --- /dev/null +++ b/src/engine-gpgconf.c @@ -0,0 +1,919 @@ +/* engine-gpgconf.c - gpg-conf engine. + Copyright (C) 2000 Werner Koch (dd9jn) + Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 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, see <http://www.gnu.org/licenses/>. + */ + +#if HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <assert.h> +#include <unistd.h> +#include <locale.h> +#include <fcntl.h> /* FIXME */ +#include <errno.h> + +#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 linebuf[LINELENGTH] = ""; + int linelen = 0; + char *argv[4] = { NULL /* file_name */, NULL, NULL, NULL }; + int rp[2]; + struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, + {-1, -1} }; + int status; + int nread; + char *mark = NULL; + + argv[1] = arg1; + argv[2] = arg2; + + + /* 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 (); + + cfd[0].fd = rp[1]; + + status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, NULL); + 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], + linebuf + linelen, LINELENGTH - linelen - 1); + if (nread > 0) + { + char *line; + const char *lastmark = NULL; + size_t nused; + + linelen += nread; + linebuf[linelen] = '\0'; + + for (line=linebuf; (mark = strchr (line, '\n')); line = mark+1 ) + { + lastmark = mark; + if (mark > line && mark[-1] == '\r') + mark[-1] = '\0'; + else + mark[0] = '\0'; + + /* Got a full line. Due to the CR removal code (which + occurs only on Windows) we might be one-off and thus + would see empty lines. Don't pass them to the + callback. */ + err = *line? (*cb) (hook, line) : 0; + if (err) + goto leave; + } + + nused = lastmark? (lastmark + 1 - linebuf) : 0; + memmove (linebuf, linebuf + nused, linelen - nused); + linelen -= nused; + } + } + 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); + + leave: + _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->program_name = strdup (field[2]); + if (!comp->program_name) + 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; + + while (line) + { + gpgme_conf_arg_t arg; + + mark = strchr (line, ','); + if (mark) + *mark = '\0'; + + 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: + /* The complex types below are only here to silent the + compiler warning. */ + case GPGME_CONF_FILENAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + /* 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 (); + } + + if (opt->flags & GPGME_CONF_NO_ARG_DESC) + { + opt->no_arg_description = strdup (field[8]); + if (!opt->no_arg_description) + return gpg_error_from_syserror (); + } + else + { + 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 + { + /* We need to switch on type here because the alt-type is not + yet known. */ + 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_FILENAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + 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) +{ + /* Lacking the alt_type we need to switch on type here. */ + switch (type) + { + case GPGME_CONF_NONE: + case GPGME_CONF_UINT32: + case GPGME_CONF_INT32: + case GPGME_CONF_STRING: + default: + break; + + case GPGME_CONF_FILENAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + 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 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] = "/nowhere/path-needs-to-be-fixed/gpgconf"; + + if (_gpgme_io_pipe (rp, 0) < 0) + return gpg_error_from_syserror (); + + cfd[0].fd = rp[0]; + + status = _gpgme_io_spawn (gpgconf->file_name, argv, cfd, NULL); + 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: + /* The complex types below are only here to silent the + compiler warning. */ + case GPGME_CONF_FILENAME: + case GPGME_CONF_LDAP_SERVER: + case GPGME_CONF_KEY_FPR: + case GPGME_CONF_PUB_KEY: + case GPGME_CONF_SEC_KEY: + case GPGME_CONF_ALIAS_LIST: + /* 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 */ + }; |