/* engine-gpgconf.c - gpg-conf engine.
Copyright (C) 2000 Werner Koch (dd9jn)
- Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 g10 Code GmbH
+ Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008,
+ 2013 g10 Code GmbH
This file is part of GPGME.
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
#include "engine-backend.h"
+
\f
struct engine_gpgconf
{
char *file_name;
char *home_dir;
+ char *version;
};
typedef struct engine_gpgconf *engine_gpgconf_t;
\f
+/* 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_gpgconf_path ());
+ : _gpgme_get_default_gpgconf_name ());
}
static const char *
gpgconf_get_req_version (void)
{
- return NEED_GPGCONF_VERSION;
+ return "2.0.4";
}
\f
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)
+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_errno (errno);
+ return gpg_error_from_syserror ();
gpgconf->file_name = strdup (file_name ? file_name
- : _gpgme_get_gpgconf_path ());
+ : _gpgme_get_default_gpgconf_name ());
if (!gpgconf->file_name)
err = gpg_error_from_syserror ();
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
}
}
-
+/* 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, char *arg1, char *arg2,
+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;
-#define LINELENGTH 1024
- char linebuf[LINELENGTH] = "";
- int linelen = 0;
- char *argv[4] = { NULL /* file_name */, NULL, NULL, NULL };
+ 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 nread;
char *mark = NULL;
- argv[1] = 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 ();
cfd[0].fd = rp[1];
- status = _gpgme_io_spawn (gpgconf->file_name, argv, 0, cfd, NULL, NULL, NULL);
+ status = _gpgme_io_spawn (gpgconf->file_name, argv,
+ IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL);
if (status < 0)
{
_gpgme_io_close (rp[0]);
return gpg_error_from_syserror ();
}
- do
+ linebufsize = 1024; /* Usually enough for conf lines. */
+ linebuf = malloc (linebufsize);
+ if (!linebuf)
{
- 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';
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ 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;
- }
+ 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;
+ }
- nused = lastmark? (lastmark + 1 - linebuf) : 0;
- memmove (linebuf, linebuf + nused, linelen - nused);
- linelen -= nused;
- }
+ newlinebuf = realloc (linebuf, linebufsize);
+ if (!newlinebuf)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ linebuf = newlinebuf;
+ }
}
- 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:
+ free (linebuf);
_gpgme_io_close (rp[0]);
-
return err;
}
gpgme_conf_arg_t *arg_p, char *line)
{
gpgme_error_t err;
- char *mark;
+ char *mark = NULL;
if (!line[0])
return 0;
{
gpgme_conf_arg_t arg;
- mark = strchr (line, ',');
+ if (opt->type != GPGME_CONF_STRING)
+ mark = strchr (line, ',');
if (mark)
*mark = '\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)
+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[] = { NULL /* file_name */, arg1, arg2, 0 };
- int rp[2];
- struct spawn_fd_item_s cfd[] = { {-1, 0 /* STDIN_FILENO */}, {-1, -1} };
+ 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;
- /* 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*)"--runtime";
+ 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 ();
+ {
+ 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, 0, cfd, NULL, NULL, NULL);
+ 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 ();
+ err = gpg_error_from_syserror ();
+ goto leave;
}
+ rp[0] = -1;
+ errp[1] = -1;
+
for (;;)
{
if (buflen == 0)
if (buflen < 0)
{
err = gpg_error_from_syserror ();
- _gpgme_io_close (rp[1]);
- return err;
+ goto leave;
}
else if (buflen == 0)
{
/* All is written. */
_gpgme_io_close (rp[1]);
- return 0;
+ 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. */
+ }
}
}
}
else if (nwrite < 0)
{
- _gpgme_io_close (rp[1]);
- return gpg_error_from_syserror ();
+ err = gpg_error_from_syserror ();
+ goto leave;
}
}
- return 0;
+ 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;
}
}
+struct gpgconf_config_dir_s
+{
+ const char *what;
+ char *result;
+};
+
+/* Called for each line in the gpgconf --list-dirs output. Searches
+ for the desired line and returns the result, indicating success by
+ a special error value GPG_ERR_USER_1 (which terminates the
+ operation immediately). */
+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 = (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;
+}
+
+
+/* Like gpgme_get_dirinfo, but uses the home directory of ENGINE and
+ does not cache the result. */
+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 us 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. */
}
struct engine_ops _gpgme_engine_ops_gpgconf =
{
/* Static functions. */
- _gpgme_get_gpgconf_path,
+ _gpgme_get_default_gpgconf_name,
NULL,
gpgconf_get_version,
gpgconf_get_req_version,
/* 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, /* set_engine_flags */
NULL, /* decrypt */
- NULL, /* decrypt_verify */
NULL, /* delete */
NULL, /* edit */
NULL, /* encrypt */
NULL, /* import */
NULL, /* keylist */
NULL, /* keylist_ext */
+ NULL, /* keylist_data */
+ NULL, /* keysign */
+ NULL, /* tofu_policy */
NULL, /* sign */
NULL, /* trustlist */
NULL, /* verify */
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 */
+ NULL, /* cancel_op */
+ NULL, /* passwd */
+ NULL, /* set_pinentry_mode */
+ NULL /* opspawn */
};