diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c index 84d8df7b..ba5a73d5 100644 --- a/src/engine-gpgconf.c +++ b/src/engine-gpgconf.c @@ -1,1314 +1,1314 @@ /* engine-gpgconf.c - gpg-conf engine. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008, 2013 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 . */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #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; char *version; }; typedef struct engine_gpgconf *engine_gpgconf_t; /* Return true if the engine's version is at least VERSION. */ static int have_gpgconf_version (engine_gpgconf_t gpgconf, const char *version) { return _gpgme_compare_versions (gpgconf->version, version); } static char * gpgconf_get_version (const char *file_name) { return _gpgme_get_program_version (file_name ? file_name : _gpgme_get_default_gpgconf_name ()); } static const char * gpgconf_get_req_version (void) { return "2.0.4"; } 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); if (gpgconf->version) free (gpgconf->version); free (gpgconf); } static gpgme_error_t gpgconf_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_gpgconf_t gpgconf; gpgconf = calloc (1, sizeof *gpgconf); if (!gpgconf) return gpg_error_from_syserror (); gpgconf->file_name = strdup (file_name ? file_name : _gpgme_get_default_gpgconf_name ()); 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 && version) { gpgconf->version = strdup (version); if (!gpgconf->version) 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; } } /* Read from gpgconf and pass line after line to the hook function. We put a limit of 64 k on the maximum size for a line. This should allow for quite a long "group" line, which is usually the longest line (mine is currently ~3k). */ static gpgme_error_t gpgconf_read (void *engine, const char *arg1, char *arg2, gpgme_error_t (*cb) (void *hook, char *line), void *hook) { struct engine_gpgconf *gpgconf = engine; gpgme_error_t err = 0; char *linebuf; size_t linebufsize; int linelen; char *argv[6]; int argc = 0; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; int nread; char *mark = NULL; /* _gpgme_engine_new guarantees that this is not NULL. */ argv[argc++] = gpgconf->file_name; if (gpgconf->home_dir && have_gpgconf_version (gpgconf, "2.1.13")) { argv[argc++] = (char*)"--homedir"; argv[argc++] = gpgconf->home_dir; } argv[argc++] = (char*)arg1; argv[argc++] = arg2; argv[argc] = NULL; assert (argc < DIM (argv)); 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, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } linebufsize = 1024; /* Usually enough for conf lines. */ linebuf = malloc (linebufsize); if (!linebuf) { err = gpg_error_from_syserror (); goto leave; } linelen = 0; while ((nread = _gpgme_io_read (rp[0], linebuf + linelen, linebufsize - linelen - 1))) { char *line; const char *lastmark = NULL; size_t nused; if (nread < 0) { err = gpg_error_from_syserror (); goto leave; } 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; if (!(linelen < linebufsize - 1)) { char *newlinebuf; if (linelen < 8 * 1024 - 1) linebufsize = 8 * 1024; else if (linelen < 64 * 1024 - 1) linebufsize = 64 * 1024; else { /* We reached our limit - give up. */ err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } newlinebuf = realloc (linebuf, linebufsize); if (!newlinebuf) { err = gpg_error_from_syserror (); goto leave; } linebuf = newlinebuf; } } leave: free (linebuf); _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 trace_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 = NULL; if (!line[0]) return 0; while (line) { gpgme_conf_arg_t arg; if (opt->type != GPGME_CONF_STRING) 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 trace_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, const 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 (reset) { if (opt->new_value) release_arg (opt->new_value, opt->alt_type); opt->new_value = NULL; opt->change_value = 0; } else { /* Support self-assignment, for example for adding an item to an existing list. */ if (opt->new_value && arg != opt->new_value) release_arg (opt->new_value, opt->alt_type); 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, const 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[7]; int argc = 0; int rp[2] = { -1, -1 }; int errp[2] = { -1, -1 }; struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, 2 /* STDERR_FILENO */, -1}, {-1, -1} }; int status; int nwrite; /* _gpgme_engine_new guarantees that this is not NULL. */ argv[argc++] = gpgconf->file_name; if (gpgconf->home_dir && have_gpgconf_version (gpgconf, "2.1.13")) { argv[argc++] = (char*)"--homedir"; argv[argc++] = gpgconf->home_dir; } argv[argc++] = (char*)"--runtime"; argv[argc++] = (char*)arg1; argv[argc++] = arg2; argv[argc] = NULL; assert (argc < DIM (argv)); if (_gpgme_io_pipe (rp, 0) < 0) { err = gpg_error_from_syserror (); goto leave; } if (_gpgme_io_pipe (errp, 1) < 0) { err = gpg_error_from_syserror (); goto leave; } cfd[0].fd = rp[0]; cfd[1].fd = errp[1]; status = _gpgme_io_spawn (gpgconf->file_name, argv, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { err = gpg_error_from_syserror (); goto leave; } rp[0] = -1; errp[1] = -1; 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 (); goto leave; } else if (buflen == 0) { /* All is written. */ _gpgme_io_close (rp[1]); rp[1] = -1; for (;;) { do { buflen = _gpgme_io_read (errp[0], buf, BUFLEN); } while (buflen < 0 && errno == EAGAIN); if (buflen == 0) { err = 0; goto leave; } /* XXX: Do something useful with BUF. */ } } } 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) { err = gpg_error_from_syserror (); goto leave; } } assert (! "reached"); leave: if (rp[0] != -1) _gpgme_io_close (rp[0]); if (rp[1] != -1) _gpgme_io_close (rp[1]); if (errp[0] != -1) _gpgme_io_close (errp[0]); if (errp[1] != -1) _gpgme_io_close (errp[1]); return err; } 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: if (arg->value.string) { /* 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; } struct gpgconf_config_dir_s { const char *what; char *result; }; static gpgme_error_t gpgconf_config_dir_cb (void *hook, char *line) { /* This is an input- and output-parameter. */ - struct gpgconf_config_dir_s *data = (char **) hook; + struct gpgconf_config_dir_s *data = (struct gpgconf_config_dir_s *) hook; int len = strlen(data->what); if (!strncmp(line, data->what, len) && line[len] == ':') { char *result = strdup(&line[len + 1]); if (!result) return gpg_error_from_syserror (); data->result = result; return gpg_error(GPG_ERR_USER_1); } return 0; } static gpgme_error_t gpgconf_conf_dir (void *engine, const char *what, char **result) { gpgme_error_t err; struct gpgconf_config_dir_s data; data.what = what; data.result = NULL; err = gpgconf_read (engine, "--list-dirs", NULL, gpgconf_config_dir_cb, &data); if (gpg_err_code (err) == GPG_ERR_USER_1) { /* This signals to use that a result was found. */ *result = data.result; return 0; } if (!err) err = gpg_error(GPG_ERR_NOT_FOUND); return 0; } /* Parse a line received from gpgconf --query-swdb. This function may * modify LINE. The result is stored at RESUL. */ static gpg_error_t parse_swdb_line (char *line, gpgme_query_swdb_result_t result) { char *field[9]; int fields = 0; gpg_err_code_t ec; while (line && fields < DIM (field)) { field[fields++] = line; line = strchr (line, ':'); if (line) *line++ = 0; } /* We require that all fields exists - gpgme emits all these fields * even on error. They might be empty, though. */ if (fields < 9) return gpg_error (GPG_ERR_INV_ENGINE); free (result->name); result->name = strdup (field[0]); if (!result->name) return gpg_error_from_syserror (); free (result->iversion); result->iversion = strdup (field[1]); if (!result->iversion) return gpg_error_from_syserror (); result->urgent = (strtol (field[3], NULL, 10) > 0); ec = gpg_err_code (strtoul (field[4], NULL, 10)); result->created = _gpgme_parse_timestamp (field[5], NULL); result->retrieved= _gpgme_parse_timestamp (field[6], NULL); free (result->version); result->version = strdup (field[7]); if (!result->version) return gpg_error_from_syserror (); result->reldate = _gpgme_parse_timestamp (field[8], NULL); /* Set other flags. */ result->warning = !!ec; result->update = 0; result->noinfo = 0; result->unknown = 0; result->tooold = 0; result->error = 0; switch (*field[2]) { case '-': result->warning = 1; break; case '?': result->unknown = result->warning = 1; break; case 'u': result->update = 1; break; case 'c': break; case 'n': break; default: result->warning = 1; if (!ec) ec = GPG_ERR_INV_ENGINE; break; } if (ec == GPG_ERR_TOO_OLD) result->tooold = 1; else if (ec == GPG_ERR_ENOENT) result->noinfo = 1; else if (ec) result->error = 1; return 0; } static gpgme_error_t gpgconf_query_swdb (void *engine, const char *name, const char *iversion, gpgme_query_swdb_result_t result) { struct engine_gpgconf *gpgconf = engine; gpgme_error_t err = 0; char *linebuf; size_t linebufsize; int linelen; char *argv[7]; int argc = 0; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; int nread; char *mark = NULL; if (!have_gpgconf_version (gpgconf, "2.1.16")) return gpg_error (GPG_ERR_ENGINE_TOO_OLD); /* _gpgme_engine_new guarantees that this is not NULL. */ argv[argc++] = gpgconf->file_name; if (gpgconf->home_dir) { argv[argc++] = (char*)"--homedir"; argv[argc++] = gpgconf->home_dir; } argv[argc++] = (char*)"--query-swdb"; argv[argc++] = (char*)name; argv[argc++] = (char*)iversion; argv[argc] = NULL; assert (argc < DIM (argv)); 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, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return gpg_error_from_syserror (); } linebufsize = 2048; /* Same as used by gpgconf. */ linebuf = malloc (linebufsize); if (!linebuf) { err = gpg_error_from_syserror (); goto leave; } linelen = 0; while ((nread = _gpgme_io_read (rp[0], linebuf + linelen, linebufsize - linelen - 1))) { char *line; const char *lastmark = NULL; size_t nused; if (nread < 0) { err = gpg_error_from_syserror (); goto leave; } 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. */ if (*line) { err = parse_swdb_line (line, result); goto leave; /* Ready. */ } else /* empty line. */ err = 0; } nused = lastmark? (lastmark + 1 - linebuf) : 0; memmove (linebuf, linebuf + nused, linelen - nused); linelen -= nused; if (!(linelen < linebufsize - 1)) { char *newlinebuf; if (linelen < 8 * 1024 - 1) linebufsize = 8 * 1024; else if (linelen < 64 * 1024 - 1) linebufsize = 64 * 1024; else { /* We reached our limit - give up. */ err = gpg_error (GPG_ERR_LINE_TOO_LONG); goto leave; } newlinebuf = realloc (linebuf, linebufsize); if (!newlinebuf) { err = gpg_error_from_syserror (); goto leave; } linebuf = newlinebuf; } } leave: free (linebuf); _gpgme_io_close (rp[0]); return err; } static void gpgconf_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { (void)engine; (void)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_default_gpgconf_name, NULL, gpgconf_get_version, gpgconf_get_req_version, gpgconf_new, /* Member functions. */ gpgconf_release, NULL, /* reset */ NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ NULL, /* set_locale */ NULL, /* set_protocol */ 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, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ NULL, /* opassuan_transact */ gpgconf_conf_load, gpgconf_conf_save, gpgconf_conf_dir, gpgconf_query_swdb, gpgconf_set_io_cbs, NULL, /* io_event */ NULL, /* cancel */ NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/key.c b/src/key.c index e2e30dba..bb4d5fd6 100644 --- a/src/key.c +++ b/src/key.c @@ -1,780 +1,780 @@ /* key.c - Key objects. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004 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 "util.h" #include "ops.h" #include "sema.h" #include "debug.h" #include "mbox-util.h" /* Protects all reference counters in keys. All other accesses to a key are read only. */ DEFINE_STATIC_LOCK (key_ref_lock); /* Create a new key. */ gpgme_error_t _gpgme_key_new (gpgme_key_t *r_key) { gpgme_key_t key; key = calloc (1, sizeof *key); if (!key) return gpg_error_from_syserror (); key->_refs = 1; *r_key = key; return 0; } gpgme_error_t _gpgme_key_add_subkey (gpgme_key_t key, gpgme_subkey_t *r_subkey) { gpgme_subkey_t subkey; subkey = calloc (1, sizeof *subkey); if (!subkey) return gpg_error_from_syserror (); subkey->keyid = subkey->_keyid; subkey->_keyid[16] = '\0'; if (!key->subkeys) key->subkeys = subkey; if (key->_last_subkey) key->_last_subkey->next = subkey; key->_last_subkey = subkey; *r_subkey = subkey; return 0; } static char * set_user_id_part (char *tail, const char *buf, size_t len) { while (len && (buf[len - 1] == ' ' || buf[len - 1] == '\t')) len--; for (; len; len--) *tail++ = *buf++; *tail++ = 0; return tail; } static void parse_user_id (char *src, char **name, char **email, char **comment, char *tail) { const char *start = NULL; int in_name = 0; int in_email = 0; int in_comment = 0; while (*src) { if (in_email) { if (*src == '<') /* Not legal but anyway. */ in_email++; else if (*src == '>') { if (!--in_email && !*email) { *email = tail; tail = set_user_id_part (tail, start, src - start); } } } else if (in_comment) { if (*src == '(') in_comment++; else if (*src == ')') { if (!--in_comment && !*comment) { *comment = tail; tail = set_user_id_part (tail, start, src - start); } } } else if (*src == '<') { if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } in_name = 0; } in_email = 1; start = src + 1; } else if (*src == '(') { if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } in_name = 0; } in_comment = 1; start = src + 1; } else if (!in_name && *src != ' ' && *src != '\t') { in_name = 1; start = src; } src++; } if (in_name) { if (!*name) { *name = tail; tail = set_user_id_part (tail, start, src - start); } } /* Let unused parts point to an EOS. */ tail--; if (!*name) *name = tail; if (!*email) *email = tail; if (!*comment) *comment = tail; } static void parse_x509_user_id (char *src, char **name, char **email, char **comment, char *tail) { if (*src == '<' && src[strlen (src) - 1] == '>') *email = src; /* Let unused parts point to an EOS. */ tail--; if (!*name) *name = tail; if (!*email) *email = tail; if (!*comment) *comment = tail; } /* Take a name from the --with-colon listing, remove certain escape sequences sequences and put it into the list of UIDs. */ gpgme_error_t _gpgme_key_append_name (gpgme_key_t key, const char *src, int convert) { gpgme_user_id_t uid; char *dst; int src_len = strlen (src); assert (key); /* We can malloc a buffer of the same length, because the converted string will never be larger. Actually we allocate it twice the size, so that we are able to store the parsed stuff there too. */ uid = malloc (sizeof (*uid) + 2 * src_len + 3); if (!uid) return gpg_error_from_syserror (); memset (uid, 0, sizeof *uid); uid->uid = ((char *) uid) + sizeof (*uid); dst = uid->uid; if (convert) _gpgme_decode_c_string (src, &dst, src_len + 1); else memcpy (dst, src, src_len + 1); dst += strlen (dst) + 1; if (key->protocol == GPGME_PROTOCOL_CMS) parse_x509_user_id (uid->uid, &uid->name, &uid->email, &uid->comment, dst); else parse_user_id (uid->uid, &uid->name, &uid->email, &uid->comment, dst); uid->address = _gpgme_mailbox_from_userid (uid->uid); if ((!uid->email || !*uid->email) && uid->address && uid->name && !strcmp (uid->name, uid->address)) { /* Name and address are the same. This is a mailbox only key. Use address as email and remove name. */ *uid->name = '\0'; uid->email = uid->address; } if (!key->uids) key->uids = uid; if (key->_last_uid) key->_last_uid->next = uid; key->_last_uid = uid; return 0; } gpgme_key_sig_t _gpgme_key_add_sig (gpgme_key_t key, char *src) { int src_len = src ? strlen (src) : 0; gpgme_user_id_t uid; gpgme_key_sig_t sig; assert (key); /* XXX */ uid = key->_last_uid; assert (uid); /* XXX */ /* We can malloc a buffer of the same length, because the converted string will never be larger. Actually we allocate it twice the size, so that we are able to store the parsed stuff there too. */ sig = malloc (sizeof (*sig) + 2 * src_len + 3); if (!sig) return NULL; memset (sig, 0, sizeof *sig); sig->keyid = sig->_keyid; sig->_keyid[16] = '\0'; sig->uid = ((char *) sig) + sizeof (*sig); if (src) { char *dst = sig->uid; _gpgme_decode_c_string (src, &dst, src_len + 1); dst += strlen (dst) + 1; if (key->protocol == GPGME_PROTOCOL_CMS) parse_x509_user_id (sig->uid, &sig->name, &sig->email, &sig->comment, dst); else parse_user_id (sig->uid, &sig->name, &sig->email, &sig->comment, dst); } else - sig->uid = '\0'; + sig->uid[0] = '\0'; if (!uid->signatures) uid->signatures = sig; if (uid->_last_keysig) uid->_last_keysig->next = sig; uid->_last_keysig = sig; return sig; } /* Acquire a reference to KEY. */ void gpgme_key_ref (gpgme_key_t key) { LOCK (key_ref_lock); key->_refs++; UNLOCK (key_ref_lock); } /* gpgme_key_unref releases the key object. Note, that this function may not do an actual release if there are other shallow copies of the objects. You have to call this function for every newly created key object as well as for every gpgme_key_ref() done on the key object. */ void gpgme_key_unref (gpgme_key_t key) { gpgme_user_id_t uid; gpgme_subkey_t subkey; if (!key) return; LOCK (key_ref_lock); assert (key->_refs > 0); if (--key->_refs) { UNLOCK (key_ref_lock); return; } UNLOCK (key_ref_lock); subkey = key->subkeys; while (subkey) { gpgme_subkey_t next = subkey->next; free (subkey->fpr); free (subkey->curve); free (subkey->keygrip); free (subkey->card_number); free (subkey); subkey = next; } uid = key->uids; while (uid) { gpgme_user_id_t next_uid = uid->next; gpgme_key_sig_t keysig = uid->signatures; gpgme_tofu_info_t tofu = uid->tofu; while (keysig) { gpgme_key_sig_t next_keysig = keysig->next; gpgme_sig_notation_t notation = keysig->notations; while (notation) { gpgme_sig_notation_t next_notation = notation->next; _gpgme_sig_notation_free (notation); notation = next_notation; } free (keysig); keysig = next_keysig; } while (tofu) { /* NB: The ->next is currently not used but we are prepared * for it. */ gpgme_tofu_info_t tofu_next = tofu->next; free (tofu->description); free (tofu); tofu = tofu_next; } free (uid->address); free (uid); uid = next_uid; } free (key->issuer_serial); free (key->issuer_name); free (key->chain_id); free (key->fpr); free (key); } /* Support functions. */ /* Create a dummy key to specify an email address. */ gpgme_error_t gpgme_key_from_uid (gpgme_key_t *r_key, const char *name) { gpgme_error_t err; gpgme_key_t key; *r_key = NULL; err = _gpgme_key_new (&key); if (err) return err; /* Note: protocol doesn't matter if only email is provided. */ err = _gpgme_key_append_name (key, name, 0); if (err) gpgme_key_unref (key); else *r_key = key; return err; } /* Compatibility interfaces. */ void gpgme_key_release (gpgme_key_t key) { gpgme_key_unref (key); } static const char * otrust_to_string (int otrust) { switch (otrust) { case GPGME_VALIDITY_NEVER: return "n"; case GPGME_VALIDITY_MARGINAL: return "m"; case GPGME_VALIDITY_FULL: return "f"; case GPGME_VALIDITY_ULTIMATE: return "u"; default: return "?"; } } static const char * validity_to_string (int validity) { switch (validity) { case GPGME_VALIDITY_UNDEFINED: return "q"; case GPGME_VALIDITY_NEVER: return "n"; case GPGME_VALIDITY_MARGINAL: return "m"; case GPGME_VALIDITY_FULL: return "f"; case GPGME_VALIDITY_ULTIMATE: return "u"; case GPGME_VALIDITY_UNKNOWN: default: return "?"; } } static const char * capabilities_to_string (gpgme_subkey_t subkey) { static const char *const strings[8] = { "", "c", "s", "sc", "e", "ec", "es", "esc" }; return strings[(!!subkey->can_encrypt << 2) | (!!subkey->can_sign << 1) | (!!subkey->can_certify)]; } /* Return the value of the attribute WHAT of ITEM, which has to be representable by a string. */ const char * gpgme_key_get_string_attr (gpgme_key_t key, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_subkey_t subkey; gpgme_user_id_t uid; int i; if (!key || reserved || idx < 0) return NULL; /* Select IDXth subkey. */ subkey = key->subkeys; for (i = 0; i < idx; i++) { subkey = subkey->next; if (!subkey) break; } /* Select the IDXth user ID. */ uid = key->uids; for (i = 0; i < idx; i++) { uid = uid->next; if (!uid) break; } switch (what) { case GPGME_ATTR_KEYID: return subkey ? subkey->keyid : NULL; case GPGME_ATTR_FPR: return subkey ? subkey->fpr : NULL; case GPGME_ATTR_ALGO: return subkey ? gpgme_pubkey_algo_name (subkey->pubkey_algo) : NULL; case GPGME_ATTR_TYPE: return key->protocol == GPGME_PROTOCOL_CMS ? "X.509" : "PGP"; case GPGME_ATTR_OTRUST: return otrust_to_string (key->owner_trust); case GPGME_ATTR_USERID: return uid ? uid->uid : NULL; case GPGME_ATTR_NAME: return uid ? uid->name : NULL; case GPGME_ATTR_EMAIL: return uid ? uid->email : NULL; case GPGME_ATTR_COMMENT: return uid ? uid->comment : NULL; case GPGME_ATTR_VALIDITY: return uid ? validity_to_string (uid->validity) : NULL; case GPGME_ATTR_KEY_CAPS: return subkey ? capabilities_to_string (subkey) : NULL; case GPGME_ATTR_SERIAL: return key->issuer_serial; case GPGME_ATTR_ISSUER: return idx ? NULL : key->issuer_name; case GPGME_ATTR_CHAINID: return key->chain_id; default: return NULL; } } unsigned long gpgme_key_get_ulong_attr (gpgme_key_t key, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_subkey_t subkey; gpgme_user_id_t uid; int i; if (!key || reserved || idx < 0) return 0; /* Select IDXth subkey. */ subkey = key->subkeys; for (i = 0; i < idx; i++) { subkey = subkey->next; if (!subkey) break; } /* Select the IDXth user ID. */ uid = key->uids; for (i = 0; i < idx; i++) { uid = uid->next; if (!uid) break; } switch (what) { case GPGME_ATTR_ALGO: return subkey ? (unsigned long) subkey->pubkey_algo : 0; case GPGME_ATTR_LEN: return subkey ? (unsigned long) subkey->length : 0; case GPGME_ATTR_TYPE: return key->protocol == GPGME_PROTOCOL_CMS ? 1 : 0; case GPGME_ATTR_CREATED: return (subkey && subkey->timestamp >= 0) ? (unsigned long) subkey->timestamp : 0; case GPGME_ATTR_EXPIRE: return (subkey && subkey->expires >= 0) ? (unsigned long) subkey->expires : 0; case GPGME_ATTR_VALIDITY: return uid ? uid->validity : 0; case GPGME_ATTR_OTRUST: return key->owner_trust; case GPGME_ATTR_IS_SECRET: return !!key->secret; case GPGME_ATTR_KEY_REVOKED: return subkey ? subkey->revoked : 0; case GPGME_ATTR_KEY_INVALID: return subkey ? subkey->invalid : 0; case GPGME_ATTR_KEY_EXPIRED: return subkey ? subkey->expired : 0; case GPGME_ATTR_KEY_DISABLED: return subkey ? subkey->disabled : 0; case GPGME_ATTR_UID_REVOKED: return uid ? uid->revoked : 0; case GPGME_ATTR_UID_INVALID: return uid ? uid->invalid : 0; case GPGME_ATTR_CAN_ENCRYPT: return key->can_encrypt; case GPGME_ATTR_CAN_SIGN: return key->can_sign; case GPGME_ATTR_CAN_CERTIFY: return key->can_certify; default: return 0; } } static gpgme_key_sig_t get_keysig (gpgme_key_t key, int uid_idx, int idx) { gpgme_user_id_t uid; gpgme_key_sig_t sig; if (!key || uid_idx < 0 || idx < 0) return NULL; uid = key->uids; while (uid && uid_idx > 0) { uid = uid->next; uid_idx--; } if (!uid) return NULL; sig = uid->signatures; while (sig && idx > 0) { sig = sig->next; idx--; } return sig; } const char * gpgme_key_sig_get_string_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx); if (!certsig || reserved) return NULL; switch (what) { case GPGME_ATTR_KEYID: return certsig->keyid; case GPGME_ATTR_ALGO: return gpgme_pubkey_algo_name (certsig->pubkey_algo); case GPGME_ATTR_USERID: return certsig->uid; case GPGME_ATTR_NAME: return certsig->name; case GPGME_ATTR_EMAIL: return certsig->email; case GPGME_ATTR_COMMENT: return certsig->comment; default: return NULL; } } unsigned long gpgme_key_sig_get_ulong_attr (gpgme_key_t key, int uid_idx, _gpgme_attr_t what, const void *reserved, int idx) { gpgme_key_sig_t certsig = get_keysig (key, uid_idx, idx); if (!certsig || reserved) return 0; switch (what) { case GPGME_ATTR_ALGO: return (unsigned long) certsig->pubkey_algo; case GPGME_ATTR_CREATED: return certsig->timestamp < 0 ? 0L : (unsigned long) certsig->timestamp; case GPGME_ATTR_EXPIRE: return certsig->expires < 0 ? 0L : (unsigned long) certsig->expires; case GPGME_ATTR_KEY_REVOKED: return certsig->revoked; case GPGME_ATTR_KEY_INVALID: return certsig->invalid; case GPGME_ATTR_KEY_EXPIRED: return certsig->expired; case GPGME_ATTR_SIG_CLASS: return certsig->sig_class; case GPGME_ATTR_SIG_STATUS: return certsig->status; default: return 0; } }