diff options
Diffstat (limited to 'src/engine-gpgconf.c')
-rw-r--r-- | src/engine-gpgconf.c | 273 |
1 files changed, 259 insertions, 14 deletions
diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c index 90f32c7..2ea8673 100644 --- a/src/engine-gpgconf.c +++ b/src/engine-gpgconf.c @@ -16,7 +16,7 @@ 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/>. + License along with this program; if not, see <https://www.gnu.org/licenses/>. */ #if HAVE_CONFIG_H @@ -47,16 +47,26 @@ #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) { @@ -84,6 +94,8 @@ gpgconf_release (void *engine) free (gpgconf->file_name); if (gpgconf->home_dir) free (gpgconf->home_dir); + if (gpgconf->version) + free (gpgconf->version); free (gpgconf); } @@ -96,8 +108,6 @@ gpgconf_new (void **engine, const char *file_name, const char *home_dir, gpgme_error_t err = 0; engine_gpgconf_t gpgconf; - (void)version; /* Not yet used. */ - gpgconf = calloc (1, sizeof *gpgconf); if (!gpgconf) return gpg_error_from_syserror (); @@ -114,6 +124,13 @@ gpgconf_new (void **engine, const char *file_name, const char *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 @@ -209,7 +226,8 @@ gpgconf_read (void *engine, const char *arg1, char *arg2, char *linebuf; size_t linebufsize; int linelen; - char *argv[4] = { NULL /* file_name */, NULL, NULL, NULL }; + char *argv[6]; + int argc = 0; int rp[2]; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; @@ -217,14 +235,19 @@ gpgconf_read (void *engine, const char *arg1, char *arg2, int nread; char *mark = NULL; - argv[1] = (char*)arg1; - argv[2] = arg2; - + /* _gpgme_engine_new guarantees that this is not NULL. */ + argv[argc++] = gpgconf->file_name; - /* FIXME: Deal with engine->home_dir. */ + if (gpgconf->home_dir && have_gpgconf_version (gpgconf, "2.1.13")) + { + argv[argc++] = (char*)"--homedir"; + argv[argc++] = gpgconf->home_dir; + } - /* _gpgme_engine_new guarantees that this is not NULL. */ - argv[0] = gpgconf->file_name; + 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 (); @@ -685,16 +708,26 @@ gpgconf_write (void *engine, const char *arg1, char *arg2, gpgme_data_t conf) #define BUFLEN 1024 char buf[BUFLEN]; int buflen = 0; - char *argv[] = { NULL /* file_name */, (char*)arg1, arg2, 0 }; + char *argv[6]; + int argc = 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[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, 0) < 0) return gpg_error_from_syserror (); @@ -909,6 +942,217 @@ gpgconf_conf_save (void *engine, gpgme_conf_comp_t comp) } +/* 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) { @@ -966,6 +1210,7 @@ struct engine_ops _gpgme_engine_ops_gpgconf = NULL, /* opassuan_transact */ gpgconf_conf_load, gpgconf_conf_save, + gpgconf_query_swdb, gpgconf_set_io_cbs, NULL, /* io_event */ NULL, /* cancel */ |