/* 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 . */ #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 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 */ };