core: Add 'is_mime' flags to the verify and decrypt results.
[gpgme.git] / src / engine-gpgconf.c
index d08ed03..24867c7 100644 (file)
@@ -1,21 +1,22 @@
 /* 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.
 
    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/>.
+   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
@@ -83,23 +94,26 @@ gpgconf_release (void *engine)
     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 ();
 
@@ -110,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
@@ -147,7 +168,7 @@ release_opt (gpgme_conf_opt_t opt)
   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);
@@ -191,18 +212,22 @@ gpgconf_config_release (gpgme_conf_comp_t conf)
     }
 }
 
-
+/* 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} };
@@ -210,21 +235,27 @@ gpgconf_read (void *engine, char *arg1, char *arg2,
   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;
 
+  if (gpgconf->home_dir && have_gpgconf_version (gpgconf, "2.1.13"))
+    {
+      argv[argc++] = (char*)"--homedir";
+      argv[argc++] = gpgconf->home_dir;
+    }
 
-  /* FIXME: Deal with engine->home_dir.  */
+  argv[argc++] = (char*)arg1;
+  argv[argc++] = arg2;
+  argv[argc] = NULL;
+  assert (argc < DIM (argv));
 
-  /* _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, 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]);
@@ -232,51 +263,80 @@ gpgconf_read (void *engine, char *arg1, char *arg2,
       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';
-
-         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;
-           }
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+  linelen = 0;
 
-          nused = lastmark? (lastmark + 1 - linebuf) : 0;
-          memmove (linebuf, linebuf + nused, linelen - nused);
-          linelen -= nused;
-       }
+  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;
+        }
     }
-  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;
 }
 
@@ -300,7 +360,7 @@ gpgconf_config_load_cb (void *hook, char *line)
 
   /* We require at least the first 3 fields.  */
   if (fields < 2)
-    return gpg_error (GPG_ERR_INV_ENGINE);
+    return trace_gpg_error (GPG_ERR_INV_ENGINE);
 
   /* Find the pointer to the new component in the list.  */
   while (comp && comp->next)
@@ -339,7 +399,7 @@ gpgconf_parse_option (gpgme_conf_opt_t opt,
                      gpgme_conf_arg_t *arg_p, char *line)
 {
   gpgme_error_t err;
-  char *mark;
+  char *mark = NULL;
 
   if (!line[0])
     return 0;
@@ -348,7 +408,8 @@ gpgconf_parse_option (gpgme_conf_opt_t opt,
     {
       gpgme_conf_arg_t arg;
 
-      mark = strchr (line, ',');
+      if (opt->type != GPGME_CONF_STRING)
+        mark = strchr (line, ',');
       if (mark)
        *mark = '\0';
 
@@ -369,15 +430,15 @@ gpgconf_parse_option (gpgme_conf_opt_t opt,
            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_FILENAME:
             case GPGME_CONF_LDAP_SERVER:
             case GPGME_CONF_KEY_FPR:
             case GPGME_CONF_PUB_KEY:
@@ -385,7 +446,7 @@ gpgconf_parse_option (gpgme_conf_opt_t opt,
             case GPGME_CONF_ALIAS_LIST:
              /* Skip quote character.  */
              line++;
-             
+
              err = _gpgme_decode_percent_string (line, &arg->value.string,
                                                  0, 0);
              if (err)
@@ -426,7 +487,7 @@ gpgconf_config_load_cb2 (void *hook, char *line)
 
   /* We require at least the first 10 fields.  */
   if (fields < 10)
-    return gpg_error (GPG_ERR_INV_ENGINE);
+    return trace_gpg_error (GPG_ERR_INV_ENGINE);
 
   opt = calloc (1, sizeof (*opt));
   if (!opt)
@@ -557,11 +618,11 @@ _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p,
        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:
@@ -576,7 +637,7 @@ _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p,
              return gpg_error_from_syserror ();
            }
          break;
-         
+
        default:
          free (arg);
          return gpg_error (GPG_ERR_INV_VALUE);
@@ -600,7 +661,7 @@ _gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type)
     case GPGME_CONF_STRING:
     default:
       break;
-       
+
     case GPGME_CONF_FILENAME:
     case GPGME_CONF_LDAP_SERVER:
     case GPGME_CONF_KEY_FPR:
@@ -622,7 +683,7 @@ _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);
-     opt->new_value = NULL;
+      opt->new_value = NULL;
       opt->change_value = 0;
     }
   else
@@ -630,10 +691,8 @@ _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg)
       /* 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;
-       }
+       release_arg (opt->new_value, opt->alt_type);
+      opt->new_value = arg;
       opt->change_value = 1;
     }
   return 0;
@@ -643,37 +702,64 @@ _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg)
 /* 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)
@@ -687,14 +773,29 @@ gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf)
          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.  */
+                }
            }
        }
 
@@ -712,12 +813,24 @@ gpgconf_write (void *engine, char *arg1, char *arg2, gpgme_data_t conf)
        }
       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;
 }
 
 
@@ -739,53 +852,54 @@ arg_to_data (gpgme_data_t conf, gpgme_conf_opt_t option, gpgme_conf_arg_t arg)
          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_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++;
-             }
-         }
+          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;
        }
 
@@ -800,7 +914,7 @@ arg_to_data (gpgme_data_t conf, gpgme_conf_opt_t option, gpgme_conf_arg_t arg)
 
   if (amt < 0)
     return gpg_error_from_syserror ();
-  
+
   return 0;
 }
 
@@ -872,9 +986,276 @@ gpgconf_conf_save (void *engine, gpgme_conf_comp_t comp)
 }
 
 
+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.  */
 }
 
@@ -891,7 +1272,7 @@ _gpgme_conf_release (gpgme_conf_comp_t conf)
 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,
@@ -900,13 +1281,14 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
     /* 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 */
@@ -917,6 +1299,9 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
     NULL,              /* import */
     NULL,              /* keylist */
     NULL,              /* keylist_ext */
+    NULL,               /* keylist_data */
+    NULL,               /* keysign */
+    NULL,               /* tofu_policy */
     NULL,              /* sign */
     NULL,              /* trustlist */
     NULL,              /* verify */
@@ -924,7 +1309,13 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
     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 */
   };