core: New API gpgme_op_set_uid_flag.
[gpgme.git] / src / engine-gpg.c
index 606b4d7..6e4b833 100644 (file)
@@ -1,22 +1,22 @@
 /* engine-gpg.c - Gpg Engine.
    Copyright (C) 2000 Werner Koch (dd9jn)
    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007,
-                 2009 g10 Code GmbH
+                 2009, 2010, 2012, 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 <string.h>
 #include <assert.h>
 #include <errno.h>
-#include <unistd.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_LOCALE_H
 #include <locale.h>
+#endif
 
 #include "gpgme.h"
 #include "util.h"
@@ -38,8 +42,8 @@
 #include "priv-io.h"
 #include "sema.h"
 #include "debug.h"
+#include "data.h"
 
-#include "status-table.h"
 #include "engine-backend.h"
 
 
@@ -70,11 +74,16 @@ struct fd_data_map_s
 };
 
 
+/* NB.: R_LINE is allocated an gpgrt function and thus gpgrt_free
+ * shall be used to release it.  This takes care of custom memory
+ * allocators and avoids problems on Windows with different runtimes
+ * used for libgpg-error/gpgrt and gpgme.  */
 typedef gpgme_error_t (*colon_preprocessor_t) (char *line, char **rline);
 
 struct engine_gpg
 {
   char *file_name;
+  char *version;
 
   char *lc_messages;
   char *lc_ctype;
@@ -84,7 +93,7 @@ struct engine_gpg
 
   struct
   {
-    int fd[2];  
+    int fd[2];
     int arg_loc;
     size_t bufsize;
     char *buffer;
@@ -92,13 +101,15 @@ struct engine_gpg
     int eof;
     engine_status_handler_t fnc;
     void *fnc_value;
+    gpgme_status_cb_t mon_cb;
+    void *mon_cb_value;
     void *tag;
   } status;
 
   /* This is a kludge - see the comment at colon_line_handler.  */
   struct
   {
-    int fd[2];  
+    int fd[2];
     int arg_loc;
     size_t bufsize;
     char *buffer;
@@ -110,7 +121,7 @@ struct engine_gpg
     colon_preprocessor_t preprocess_fnc;
   } colon;
 
-  char **argv;  
+  char **argv;
   struct fd_data_map_s *fd_data_map;
 
   /* stuff needed for interactive (command) mode */
@@ -122,7 +133,7 @@ struct engine_gpg
     int idx;           /* Index in fd_data_map */
     gpgme_status_code_t code;  /* last code */
     char *keyword;       /* what has been requested (malloced) */
-    engine_command_handler_t fnc; 
+    engine_command_handler_t fnc;
     void *fnc_value;
     /* The kludges never end.  This is used to couple command handlers
        with output data in edit key mode.  */
@@ -131,6 +142,10 @@ struct engine_gpg
   } cmd;
 
   struct gpgme_io_cbs io_cbs;
+  gpgme_pinentry_mode_t pinentry_mode;
+
+  /* NULL or the data object fed to --override_session_key-fd.  */
+  gpgme_data_t override_session_key;
 };
 
 typedef struct engine_gpg *engine_gpg_t;
@@ -171,6 +186,8 @@ close_notify_handler (int fd, void *opaque)
     }
   else if (gpg->colon.fd[1] == fd)
     gpg->colon.fd[1] = -1;
+  else if (gpg->cmd.fd == fd)
+    gpg->cmd.fd = -1;
   else if (gpg->fd_data_map)
     {
       int i;
@@ -196,22 +213,27 @@ close_notify_handler (int fd, void *opaque)
 /* If FRONT is true, push at the front of the list.  Use this for
    options added late in the process.  */
 static gpgme_error_t
-_add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp)
+_add_arg (engine_gpg_t gpg, const char *prefix, const char *arg, size_t arglen,
+          int front, int *arg_locp)
 {
   struct arg_and_data_s *a;
+  size_t prefixlen = prefix? strlen (prefix) : 0;
 
   assert (gpg);
   assert (arg);
 
-  a = malloc (sizeof *a + strlen (arg));
+  a = malloc (sizeof *a + prefixlen + arglen);
   if (!a)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   a->data = NULL;
   a->dup_to = -1;
   a->arg_locp = arg_locp;
 
-  strcpy (a->arg, arg);
+  if (prefixlen)
+    memcpy (a->arg, prefix, prefixlen);
+  memcpy (a->arg + prefixlen, arg, arglen);
+  a->arg[prefixlen + arglen] = 0;
   if (front)
     {
       a->next = gpg->arglist;
@@ -233,24 +255,36 @@ _add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp)
   return 0;
 }
 
+
 static gpgme_error_t
 add_arg_ext (engine_gpg_t gpg, const char *arg, int front)
 {
-  return _add_arg (gpg, arg, front, NULL);
+  return _add_arg (gpg, NULL, arg, strlen (arg), front, NULL);
 }
 
-
 static gpgme_error_t
 add_arg_with_locp (engine_gpg_t gpg, const char *arg, int *locp)
 {
-  return _add_arg (gpg, arg, 0, locp);
+  return _add_arg (gpg, NULL, arg, strlen (arg), 0, locp);
 }
 
-
 static gpgme_error_t
 add_arg (engine_gpg_t gpg, const char *arg)
 {
-  return add_arg_ext (gpg, arg, 0);
+  return _add_arg (gpg, NULL, arg, strlen (arg), 0, NULL);
+}
+
+static gpgme_error_t
+add_arg_pfx (engine_gpg_t gpg, const char *prefix, const char *arg)
+{
+  return _add_arg (gpg, prefix, arg, strlen (arg), 0, NULL);
+}
+
+static gpgme_error_t
+add_arg_len (engine_gpg_t gpg, const char *prefix,
+             const char *arg, size_t arglen)
+{
+  return _add_arg (gpg, prefix, arg, arglen, 0, NULL);
 }
 
 
@@ -264,7 +298,7 @@ add_data (engine_gpg_t gpg, gpgme_data_t data, int dup_to, int inbound)
 
   a = malloc (sizeof *a - 1);
   if (!a)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
   a->next = NULL;
   a->data = data;
   a->inbound = inbound;
@@ -285,19 +319,28 @@ add_data (engine_gpg_t gpg, gpgme_data_t data, int dup_to, int inbound)
   return 0;
 }
 
+
+/* Return true if the engine's version is at least VERSION.  */
+static int
+have_gpg_version (engine_gpg_t gpg, const char *version)
+{
+  return _gpgme_compare_versions (gpg->version, version);
+}
+
+
 \f
 static char *
 gpg_get_version (const char *file_name)
 {
   return _gpgme_get_program_version (file_name ? file_name
-                                    : _gpgme_get_gpg_path ());
+                                    : _gpgme_get_default_gpg_name ());
 }
 
 
 static const char *
 gpg_get_req_version (void)
 {
-  return NEED_GPG_VERSION;
+  return "1.4.0";
 }
 
 
@@ -380,6 +423,8 @@ gpg_release (void *engine)
 
   if (gpg->file_name)
     free (gpg->file_name);
+  if (gpg->version)
+    free (gpg->version);
 
   if (gpg->lc_messages)
     free (gpg->lc_messages);
@@ -390,8 +435,7 @@ gpg_release (void *engine)
     {
       struct arg_and_data_s *next = gpg->arglist->next;
 
-      if (gpg->arglist)
-       free (gpg->arglist);
+      free (gpg->arglist);
       gpg->arglist = next;
     }
 
@@ -404,29 +448,43 @@ gpg_release (void *engine)
   if (gpg->cmd.keyword)
     free (gpg->cmd.keyword);
 
+  gpgme_data_release (gpg->override_session_key);
+
   free (gpg);
 }
 
 
 static gpgme_error_t
-gpg_new (void **engine, const char *file_name, const char *home_dir)
+gpg_new (void **engine, const char *file_name, const char *home_dir,
+         const char *version)
 {
   engine_gpg_t gpg;
   gpgme_error_t rc = 0;
   char *dft_display = NULL;
   char dft_ttyname[64];
   char *dft_ttytype = NULL;
+  char *env_tty = NULL;
 
   gpg = calloc (1, sizeof *gpg);
   if (!gpg)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   if (file_name)
     {
       gpg->file_name = strdup (file_name);
       if (!gpg->file_name)
        {
-         rc = gpg_error_from_errno (errno);
+         rc = gpg_error_from_syserror ();
+         goto leave;
+       }
+    }
+
+  if (version)
+    {
+      gpg->version = strdup (version);
+      if (!gpg->version)
+       {
+         rc = gpg_error_from_syserror ();
          goto leave;
        }
     }
@@ -447,14 +505,14 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
   gpg->status.buffer = malloc (gpg->status.bufsize);
   if (!gpg->status.buffer)
     {
-      rc = gpg_error_from_errno (errno);
+      rc = gpg_error_from_syserror ();
       goto leave;
     }
   /* In any case we need a status pipe - create it right here and
      don't handle it with our generic gpgme_data_t mechanism.  */
   if (_gpgme_io_pipe (gpg->status.fd, 1) == -1)
     {
-      rc = gpg_error_from_errno (errno);
+      rc = gpg_error_from_syserror ();
       goto leave;
     }
   if (_gpgme_io_set_close_notify (gpg->status.fd[0],
@@ -495,6 +553,8 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
     rc = add_arg (gpg, "utf8");
   if (!rc)
     rc = add_arg (gpg, "--enable-progress-filter");
+  if (!rc && have_gpg_version (gpg, "2.1.11"))
+    rc = add_arg (gpg, "--exit-on-status-write-error");
   if (rc)
     goto leave;
 
@@ -508,16 +568,28 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
        rc = add_arg (gpg, dft_display);
 
       free (dft_display);
+      if (rc)
+       goto leave;
     }
 
-  if (isatty (1))
+  rc = _gpgme_getenv ("GPG_TTY", &env_tty);
+  if (isatty (1) || env_tty || rc)
     {
-      int err;
+      int err = 0;
 
-      err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
-      if (err)
-       rc = gpg_error_from_errno (err);
+      if (rc)
+        goto leave;
+      else if (env_tty)
+        {
+          snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty);
+          free (env_tty);
+        }
       else
+        err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
+
+      /* Even though isatty() returns 1, ttyname_r() may fail in many
+        ways, e.g., when /dev/pts is not accessible under chroot.  */
+      if (!err)
        {
           if (*dft_ttyname)
             {
@@ -532,7 +604,7 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
              rc = _gpgme_getenv ("TERM", &dft_ttytype);
              if (rc)
                goto leave;
-              
+
               if (dft_ttytype)
                 {
                   rc = add_arg (gpg, "--ttytype");
@@ -542,9 +614,9 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
 
              free (dft_ttytype);
            }
+         if (rc)
+           goto leave;
        }
-      if (rc)
-       goto leave;
     }
 
  leave:
@@ -561,7 +633,10 @@ gpg_set_locale (void *engine, int category, const char *value)
 {
   engine_gpg_t gpg = engine;
 
-  if (category == LC_CTYPE)
+  if (0)
+    ;
+#ifdef LC_CTYPE
+  else if (category == LC_CTYPE)
     {
       if (gpg->lc_ctype)
         {
@@ -575,6 +650,7 @@ gpg_set_locale (void *engine, int category, const char *value)
            return gpg_error_from_syserror ();
        }
     }
+#endif
 #ifdef LC_MESSAGES
   else if (category == LC_MESSAGES)
     {
@@ -597,6 +673,17 @@ gpg_set_locale (void *engine, int category, const char *value)
   return 0;
 }
 
+/* This sets a status callback for monitoring status lines before they
+ * are passed to a caller set handler.  */
+static void
+gpg_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value)
+{
+  engine_gpg_t gpg = engine;
+
+  gpg->status.mon_cb = cb;
+  gpg->status.mon_cb_value = cb_value;
+}
+
 
 /* Note, that the status_handler is allowed to modifiy the args
    value.  */
@@ -621,14 +708,14 @@ gpg_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
   gpg->colon.readpos = 0;
   gpg->colon.buffer = malloc (gpg->colon.bufsize);
   if (!gpg->colon.buffer)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
-  if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1) 
+  if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1)
     {
-      int saved_errno = errno;
+      int saved_err = gpg_error_from_syserror ();
       free (gpg->colon.buffer);
       gpg->colon.buffer = NULL;
-      return gpg_error_from_errno (saved_errno);
+      return saved_err;
     }
   if (_gpgme_io_set_close_notify (gpg->colon.fd[0], close_notify_handler, gpg)
       || _gpgme_io_set_close_notify (gpg->colon.fd[1],
@@ -644,10 +731,10 @@ gpg_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
 static gpgme_error_t
 command_handler (void *opaque, int fd)
 {
+  struct io_cb_data *data = (struct io_cb_data *) opaque;
+  engine_gpg_t gpg = (engine_gpg_t) data->handler_value;
   gpgme_error_t err;
-  engine_gpg_t gpg = (engine_gpg_t) opaque;
   int processed = 0;
-
   assert (gpg->cmd.used);
   assert (gpg->cmd.code);
   assert (gpg->cmd.fnc);
@@ -676,7 +763,7 @@ command_handler (void *opaque, int fd)
 
 
 /* The Fnc will be called to get a value for one of the commands with
-   a key KEY.  If the Code pssed to FNC is 0, the function may release
+   a key KEY.  If the Code passed to FNC is 0, the function may release
    resources associated with the returned value from another call.  To
    match such a second call to a first call, the returned value from
    the first call is passed as keyword.  */
@@ -708,26 +795,31 @@ gpg_set_command_handler (void *engine, engine_command_handler_t fnc,
 
 
 static gpgme_error_t
-build_argv (engine_gpg_t gpg)
+build_argv (engine_gpg_t gpg, const char *pgmname)
 {
   gpgme_error_t err;
   struct arg_and_data_s *a;
   struct fd_data_map_s *fd_data_map;
-  size_t datac=0, argc=0;  
+  size_t datac=0, argc=0;
   char **argv;
   int need_special = 0;
   int use_agent = 0;
   char *p;
 
-  /* We don't want to use the agent with a malformed environment
-     variable.  This is only a very basic test but sufficient to make
-     our life in the regression tests easier. */
-  err = _gpgme_getenv ("GPG_AGENT_INFO", &p);
-  if (err)
-    return err;
-  use_agent = (p && strchr (p, ':'));
-  if (p)
-    free (p);
+  if (_gpgme_in_gpg_one_mode ())
+    {
+      /* In GnuPG-1 mode we don't want to use the agent with a
+         malformed environment variable.  This is only a very basic
+         test but sufficient to make our life in the regression tests
+         easier.  With GnuPG-2 the agent is anyway required and on
+         modern installations GPG_AGENT_INFO is optional.  */
+      err = _gpgme_getenv ("GPG_AGENT_INFO", &p);
+      if (err)
+        return err;
+      use_agent = (p && strchr (p, ':'));
+      if (p)
+        free (p);
+    }
 
   if (gpg->argv)
     {
@@ -760,29 +852,31 @@ build_argv (engine_gpg_t gpg)
     argc++;
   if (use_agent)
     argc++;
+  if (gpg->pinentry_mode)
+    argc++;
   if (!gpg->cmd.used)
     argc++;    /* --batch */
-  argc += 1;   /* --no-sk-comment */
+  argc += 1;   /* --no-sk-comments */
 
   argv = calloc (argc + 1, sizeof *argv);
   if (!argv)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
   fd_data_map = calloc (datac + 1, sizeof *fd_data_map);
   if (!fd_data_map)
     {
-      int saved_errno = errno;
+      int saved_err = gpg_error_from_syserror ();
       free_argv (argv);
-      return gpg_error_from_errno (saved_errno);
+      return saved_err;
     }
 
   argc = datac = 0;
-  argv[argc] = strdup ("gpg"); /* argv[0] */
+  argv[argc] = strdup (_gpgme_get_basename (pgmname)); /* argv[0] */
   if (!argv[argc])
     {
-      int saved_errno = errno;
+      int saved_err = gpg_error_from_syserror ();
       free (fd_data_map);
       free_argv (argv);
-      return gpg_error_from_errno (saved_errno);
+      return saved_err;
     }
   argc++;
   if (need_special)
@@ -790,10 +884,10 @@ build_argv (engine_gpg_t gpg)
       argv[argc] = strdup ("--enable-special-filenames");
       if (!argv[argc])
        {
-         int saved_errno = errno;
+          int saved_err = gpg_error_from_syserror ();
          free (fd_data_map);
          free_argv (argv);
-         return gpg_error_from_errno (saved_errno);
+         return saved_err;
         }
       argc++;
     }
@@ -802,32 +896,58 @@ build_argv (engine_gpg_t gpg)
       argv[argc] = strdup ("--use-agent");
       if (!argv[argc])
        {
-         int saved_errno = errno;
+          int saved_err = gpg_error_from_syserror ();
          free (fd_data_map);
          free_argv (argv);
-         return gpg_error_from_errno (saved_errno);
+         return saved_err;
         }
       argc++;
     }
+
+  if (gpg->pinentry_mode && have_gpg_version (gpg, "2.1.0"))
+    {
+      const char *s = NULL;
+      switch (gpg->pinentry_mode)
+        {
+        case GPGME_PINENTRY_MODE_DEFAULT: break;
+        case GPGME_PINENTRY_MODE_ASK:     s = "--pinentry-mode=ask"; break;
+        case GPGME_PINENTRY_MODE_CANCEL:  s = "--pinentry-mode=cancel"; break;
+        case GPGME_PINENTRY_MODE_ERROR:   s = "--pinentry-mode=error"; break;
+        case GPGME_PINENTRY_MODE_LOOPBACK:s = "--pinentry-mode=loopback"; break;
+        }
+      if (s)
+        {
+          argv[argc] = strdup (s);
+          if (!argv[argc])
+            {
+              int saved_err = gpg_error_from_syserror ();
+              free (fd_data_map);
+              free_argv (argv);
+              return saved_err;
+            }
+          argc++;
+        }
+    }
+
   if (!gpg->cmd.used)
     {
       argv[argc] = strdup ("--batch");
       if (!argv[argc])
        {
-         int saved_errno = errno;
+          int saved_err = gpg_error_from_syserror ();
          free (fd_data_map);
          free_argv (argv);
-         return gpg_error_from_errno (saved_errno);
+         return saved_err;
         }
       argc++;
     }
-  argv[argc] = strdup ("--no-sk-comment");
+  argv[argc] = strdup ("--no-sk-comments");
   if (!argv[argc])
     {
-      int saved_errno = errno;
+      int saved_err = gpg_error_from_syserror ();
       free (fd_data_map);
       free_argv (argv);
-      return gpg_error_from_errno (saved_errno);
+      return saved_err;
     }
   argc++;
   for (a = gpg->arglist; a; a = a->next)
@@ -841,9 +961,9 @@ build_argv (engine_gpg_t gpg)
          fd_data_map[datac].inbound = a->inbound;
 
          /* Create a pipe.  */
-         {   
+         {
            int fds[2];
-           
+
            if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0)
                == -1)
              {
@@ -858,6 +978,10 @@ build_argv (engine_gpg_t gpg)
                                               close_notify_handler,
                                               gpg))
              {
+                /* We leak fd_data_map and the fds.  This is not easy
+                   to avoid and given that we reach this here only
+                   after a malloc failure for a small object, it is
+                   probably better not to do anything.  */
                return gpg_error (GPG_ERR_GENERAL);
              }
            /* If the data_type is FD, we have to do a dup2 here.  */
@@ -899,10 +1023,10 @@ build_argv (engine_gpg_t gpg)
              argv[argc] = malloc (buflen);
              if (!argv[argc])
                {
-                 int saved_errno = errno;
+                  int saved_err = gpg_error_from_syserror ();
                  free (fd_data_map);
                  free_argv (argv);
-                 return gpg_error_from_errno (saved_errno);
+                 return saved_err;
                 }
 
              ptr = argv[argc];
@@ -924,10 +1048,10 @@ build_argv (engine_gpg_t gpg)
          argv[argc] = strdup (a->arg);
          if (!argv[argc])
            {
-             int saved_errno = errno;
+              int saved_err = gpg_error_from_syserror ();
              free (fd_data_map);
              free_argv (argv);
-             return gpg_error_from_errno (saved_errno);
+             return saved_err;
             }
             argc++;
         }
@@ -955,16 +1079,6 @@ add_io_cb (engine_gpg_t gpg, int fd, int dir, gpgme_io_cb_t handler, void *data,
 }
 
 
-static int
-status_cmp (const void *ap, const void *bp)
-{
-  const struct status_table_s *a = ap;
-  const struct status_table_s *b = bp;
-
-  return strcmp (a->name, b->name);
-}
-
-
 /* Handle the status output of GnuPG.  This function does read entire
    lines and passes them as C strings to the callback function (we can
    use C Strings because the status output is always UTF-8 encoded).
@@ -977,36 +1091,42 @@ read_status (engine_gpg_t gpg)
 {
   char *p;
   int nread;
-  size_t bufsize = gpg->status.bufsize; 
+  size_t bufsize = gpg->status.bufsize;
   char *buffer = gpg->status.buffer;
-  size_t readpos = gpg->status.readpos; 
+  size_t readpos = gpg->status.readpos;
+  gpgme_error_t err;
 
   assert (buffer);
   if (bufsize - readpos < 256)
-    { 
+    {
       /* Need more room for the read.  */
       bufsize += 1024;
       buffer = realloc (buffer, bufsize);
       if (!buffer)
-       return gpg_error_from_errno (errno);
+       return gpg_error_from_syserror ();
     }
 
   nread = _gpgme_io_read (gpg->status.fd[0],
                          buffer + readpos, bufsize-readpos);
   if (nread == -1)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   if (!nread)
     {
+      err = 0;
       gpg->status.eof = 1;
+      if (gpg->status.mon_cb)
+        err = gpg->status.mon_cb (gpg->status.mon_cb_value, "", "");
       if (gpg->status.fnc)
-       {
-         gpgme_error_t err;
-         err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, "");
-         if (err)
-           return err;
-       }
-      return 0;
+        {
+          char emptystring[1] = {0};
+          err = gpg->status.fnc (gpg->status.fnc_value,
+                                 GPGME_STATUS_EOF, emptystring);
+          if (gpg_err_code (err) == GPG_ERR_FALSE)
+            err = 0; /* Drop special error code.  */
+        }
+
+      return err;
     }
 
   while (nread > 0)
@@ -1022,32 +1142,38 @@ read_status (engine_gpg_t gpg)
              if (!strncmp (buffer, "[GNUPG:] ", 9)
                  && buffer[9] >= 'A' && buffer[9] <= 'Z')
                {
-                 struct status_table_s t, *r;
                  char *rest;
+                 gpgme_status_code_t r;
 
                  rest = strchr (buffer + 9, ' ');
                  if (!rest)
                    rest = p; /* Set to an empty string.  */
                  else
                    *rest++ = 0;
-                    
-                 t.name = buffer+9;
-                 /* (the status table has one extra element) */
-                 r = bsearch (&t, status_table, DIM(status_table) - 1,
-                              sizeof t, status_cmp);
-                 if (r)
+
+                 r = _gpgme_parse_status (buffer + 9);
+                  if (gpg->status.mon_cb && r != GPGME_STATUS_PROGRESS)
+                    {
+                      /* Note that we call the monitor even if we do
+                       * not know the status code (r < 0).  */
+                      err = gpg->status.mon_cb (gpg->status.mon_cb_value,
+                                                buffer + 9, rest);
+                      if (err)
+                        return err;
+                    }
+                 if (r >= 0)
                    {
                      if (gpg->cmd.used
-                         && (r->code == GPGME_STATUS_GET_BOOL
-                             || r->code == GPGME_STATUS_GET_LINE
-                             || r->code == GPGME_STATUS_GET_HIDDEN))
+                         && (r == GPGME_STATUS_GET_BOOL
+                             || r == GPGME_STATUS_GET_LINE
+                             || r == GPGME_STATUS_GET_HIDDEN))
                        {
-                         gpg->cmd.code = r->code;
+                         gpg->cmd.code = r;
                          if (gpg->cmd.keyword)
                            free (gpg->cmd.keyword);
                          gpg->cmd.keyword = strdup (rest);
                          if (!gpg->cmd.keyword)
-                           return gpg_error_from_errno (errno);
+                           return gpg_error_from_syserror ();
                          /* This should be the last thing we have
                             received and the next thing will be that
                             the command handler does its action.  */
@@ -1063,14 +1189,15 @@ read_status (engine_gpg_t gpg)
                         }
                      else if (gpg->status.fnc)
                        {
-                         gpgme_error_t err;
-                         err = gpg->status.fnc (gpg->status.fnc_value, 
-                                                r->code, rest);
+                         err = gpg->status.fnc (gpg->status.fnc_value,
+                                                r, rest);
+                          if (gpg_err_code (err) == GPG_ERR_FALSE)
+                            err = 0; /* Drop special error code.  */
                          if (err)
                            return err;
                         }
-                    
-                     if (r->code == GPGME_STATUS_END_STREAM)
+
+                     if (r == GPGME_STATUS_END_STREAM)
                        {
                          if (gpg->cmd.used)
                            {
@@ -1124,7 +1251,7 @@ read_status (engine_gpg_t gpg)
          else
            readpos++;
         }
-    } 
+    }
 
   /* Update the gpg object.  */
   gpg->status.bufsize = bufsize;
@@ -1137,7 +1264,8 @@ read_status (engine_gpg_t gpg)
 static gpgme_error_t
 status_handler (void *opaque, int fd)
 {
-  engine_gpg_t gpg = opaque;
+  struct io_cb_data *data = (struct io_cb_data *) opaque;
+  engine_gpg_t gpg = (engine_gpg_t) data->handler_value;
   int err;
 
   assert (fd == gpg->status.fd[0]);
@@ -1155,23 +1283,23 @@ read_colon_line (engine_gpg_t gpg)
 {
   char *p;
   int nread;
-  size_t bufsize = gpg->colon.bufsize; 
+  size_t bufsize = gpg->colon.bufsize;
   char *buffer = gpg->colon.buffer;
-  size_t readpos = gpg->colon.readpos; 
+  size_t readpos = gpg->colon.readpos;
 
   assert (buffer);
   if (bufsize - readpos < 256)
-    { 
+    {
       /* Need more room for the read.  */
       bufsize += 1024;
       buffer = realloc (buffer, bufsize);
-      if (!buffer) 
-       return gpg_error_from_errno (errno);
+      if (!buffer)
+       return gpg_error_from_syserror ();
     }
 
   nread = _gpgme_io_read (gpg->colon.fd[0], buffer+readpos, bufsize-readpos);
   if (nread == -1)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   if (!nread)
     {
@@ -1207,11 +1335,27 @@ read_colon_line (engine_gpg_t gpg)
                    }
 
                  assert (gpg->colon.fnc);
-                 gpg->colon.fnc (gpg->colon.fnc_value, line ? line : buffer);
-                 if (line)
-                   free (line);
-               }
-            
+                  if (line)
+                    {
+                      char *linep = line;
+                      char *endp;
+
+                      do
+                        {
+                          endp = strchr (linep, '\n');
+                          if (endp)
+                            *endp++ = 0;
+                          gpg->colon.fnc (gpg->colon.fnc_value, linep);
+                          linep = endp;
+                        }
+                      while (linep && *linep);
+
+                      gpgrt_free (line);
+                    }
+                  else
+                    gpg->colon.fnc (gpg->colon.fnc_value, buffer);
+                }
+
              /* To reuse the buffer for the next line we have to
                 shift the remaining data to the buffer start and
                 restart the loop Hmmm: We can optimize this function
@@ -1227,7 +1371,7 @@ read_colon_line (engine_gpg_t gpg)
          else
            readpos++;
         }
-    } 
+    }
 
   /* Update the gpg object.  */
   gpg->colon.bufsize = bufsize;
@@ -1244,7 +1388,8 @@ read_colon_line (engine_gpg_t gpg)
 static gpgme_error_t
 colon_line_handler (void *opaque, int fd)
 {
-  engine_gpg_t gpg = opaque;
+  struct io_cb_data *data = (struct io_cb_data *) opaque;
+  engine_gpg_t gpg = (engine_gpg_t) data->handler_value;
   gpgme_error_t rc = 0;
 
   assert (fd == gpg->colon.fd[0]);
@@ -1261,17 +1406,17 @@ static gpgme_error_t
 start (engine_gpg_t gpg)
 {
   gpgme_error_t rc;
-  int saved_errno;
   int i, n;
   int status;
   struct spawn_fd_item_s *fd_list;
   pid_t pid;
+  const char *pgmname;
 
   if (!gpg)
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  if (!gpg->file_name && !_gpgme_get_gpg_path ())
-    return gpg_error (GPG_ERR_INV_ENGINE);
+  if (!gpg->file_name && !_gpgme_get_default_gpg_name ())
+    return trace_gpg_error (GPG_ERR_INV_ENGINE);
 
   if (gpg->lc_ctype)
     {
@@ -1291,17 +1436,18 @@ start (engine_gpg_t gpg)
        return rc;
     }
 
-  rc = build_argv (gpg);
+  pgmname = gpg->file_name ? gpg->file_name : _gpgme_get_default_gpg_name ();
+  rc = build_argv (gpg, pgmname);
   if (rc)
     return rc;
 
   /* status_fd, colon_fd and end of list.  */
   n = 3;
-  for (i = 0; gpg->fd_data_map[i].data; i++) 
+  for (i = 0; gpg->fd_data_map[i].data; i++)
     n++;
   fd_list = calloc (n, sizeof *fd_list);
   if (! fd_list)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   /* Build the fd list for the child.  */
   n = 0;
@@ -1311,7 +1457,7 @@ start (engine_gpg_t gpg)
   n++;
   if (gpg->colon.fnc)
     {
-      fd_list[n].fd = gpg->colon.fd[1]; 
+      fd_list[n].fd = gpg->colon.fd[1];
       fd_list[n].dup_to = 1;
       n++;
     }
@@ -1325,13 +1471,15 @@ start (engine_gpg_t gpg)
   fd_list[n].fd = -1;
   fd_list[n].dup_to = -1;
 
-  status = _gpgme_io_spawn (gpg->file_name ? gpg->file_name :
-                           _gpgme_get_gpg_path (), gpg->argv, fd_list, &pid);
-  saved_errno = errno;
-
-  free (fd_list);
-  if (status == -1)
-    return gpg_error_from_errno (saved_errno);
+  status = _gpgme_io_spawn (pgmname, gpg->argv,
+                            (IOSPAWN_FLAG_DETACHED |IOSPAWN_FLAG_ALLOW_SET_FG),
+                            fd_list, NULL, NULL, &pid);
+  {
+    int saved_err = gpg_error_from_syserror ();
+    free (fd_list);
+    if (status == -1)
+      return saved_err;
+  }
 
   /*_gpgme_register_term_handler ( closure, closure_value, pid );*/
 
@@ -1367,30 +1515,89 @@ start (engine_gpg_t gpg)
                          ? _gpgme_data_inbound_handler
                          : _gpgme_data_outbound_handler,
                          gpg->fd_data_map[i].data, &gpg->fd_data_map[i].tag);
-         
+
          if (rc)
            /* FIXME: kill the child */
            return rc;
        }
     }
 
-  _gpgme_allow_set_foregound_window (pid);
-
   gpg_io_event (gpg, GPGME_EVENT_START, NULL);
-  
+
   /* fixme: check what data we can release here */
   return 0;
 }
 
 
+/* Add the --input-size-hint option if requested.  */
+static gpgme_error_t
+add_input_size_hint (engine_gpg_t gpg, gpgme_data_t data)
+{
+  gpgme_error_t err;
+  gpgme_off_t value = _gpgme_data_get_size_hint (data);
+  char numbuf[50];  /* Large enough for even 2^128 in base-10.  */
+  char *p;
+
+  if (!value || !have_gpg_version (gpg, "2.1.15"))
+    return 0;
+
+  err = add_arg (gpg, "--input-size-hint");
+  if (!err)
+    {
+      p = numbuf + sizeof numbuf;
+      *--p = 0;
+      do
+        {
+          *--p = '0' + (value % 10);
+          value /= 10;
+        }
+      while (value);
+      err = add_arg (gpg, p);
+    }
+  return err;
+}
+
+
 static gpgme_error_t
-gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain)
+gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain,
+             int export_session_key, const char *override_session_key)
 {
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
   err = add_arg (gpg, "--decrypt");
 
+  if (!err && export_session_key)
+    err = add_arg (gpg, "--show-session-key");
+
+  if (!err && override_session_key && *override_session_key)
+    {
+      if (have_gpg_version (gpg, "2.1.16"))
+        {
+          gpgme_data_release (gpg->override_session_key);
+          TRACE2 (DEBUG_ENGINE, "override", gpg, "seskey='%s' len=%zu\n",
+                  override_session_key,
+                  strlen (override_session_key));
+
+          err = gpgme_data_new_from_mem (&gpg->override_session_key,
+                                         override_session_key,
+                                         strlen (override_session_key), 1);
+          if (!err)
+            {
+              err = add_arg (gpg, "--override-session-key-fd");
+              if (!err)
+                err = add_data (gpg, gpg->override_session_key, -2, 0);
+            }
+        }
+      else
+        {
+          /* Using that option may leak the session key via ps(1).  */
+          err = add_arg (gpg, "--override-session-key");
+          if (!err)
+            err = add_arg (gpg, override_session_key);
+        }
+    }
+
   /* Tell the gpg object about the data.  */
   if (!err)
     err = add_arg (gpg, "--output");
@@ -1399,12 +1606,14 @@ gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain)
   if (!err)
     err = add_data (gpg, plain, 1, 1);
   if (!err)
+    err = add_input_size_hint (gpg, ciph);
+  if (!err)
     err = add_arg (gpg, "--");
   if (!err)
     err = add_data (gpg, ciph, -1, 0);
 
   if (!err)
-    start (gpg);
+    err = start (gpg);
   return err;
 }
 
@@ -1427,7 +1636,27 @@ gpg_delete (void *engine, gpgme_key_t key, int allow_secret)
     }
 
   if (!err)
-    start (gpg);
+    err = start (gpg);
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_passwd (void *engine, gpgme_key_t key, unsigned int flags)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+
+  (void)flags;
+
+  if (!key || !key->subkeys || !key->subkeys->fpr)
+    return gpg_error (GPG_ERR_INV_CERT_OBJ);
+
+  err = add_arg (gpg, "--passwd");
+  if (!err)
+    err = add_arg (gpg, key->subkeys->fpr);
+  if (!err)
+    err = start (gpg);
   return err;
 }
 
@@ -1450,8 +1679,26 @@ append_args_from_signers (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */)
            err = add_arg (gpg, s);
        }
       gpgme_key_unref (key);
-      if (err) break;
+      if (err)
+        break;
+    }
+  return err;
+}
+
+
+static gpgme_error_t
+append_args_from_sender (engine_gpg_t gpg, gpgme_ctx_t ctx)
+{
+  gpgme_error_t err;
+
+  if (ctx->sender && have_gpg_version (gpg, "2.1.15"))
+    {
+      err = add_arg (gpg, "--sender");
+      if (!err)
+        err = add_arg (gpg, ctx->sender);
     }
+  else
+    err = 0;
   return err;
 }
 
@@ -1479,7 +1726,7 @@ append_args_from_sig_notations (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */)
 
          arg = malloc (1 + notation->name_len + 1 + notation->value_len + 1);
          if (!arg)
-           err = gpg_error_from_errno (errno);
+           err = gpg_error_from_syserror ();
 
          if (!err)
            {
@@ -1515,7 +1762,7 @@ append_args_from_sig_notations (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */)
            {
              value = malloc (1 + notation->value_len + 1);
              if (!value)
-               err = gpg_error_from_errno (errno);
+               err = gpg_error_from_syserror ();
              else
                {
                  value[0] = '!';
@@ -1589,7 +1836,7 @@ append_args_from_recipients (engine_gpg_t gpg, gpgme_key_t recp[])
       if (err)
        break;
       i++;
-    }    
+    }
   return err;
 }
 
@@ -1599,15 +1846,28 @@ gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags,
             gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
 {
   engine_gpg_t gpg = engine;
-  gpgme_error_t err;
-  int symmetric = !recp;
+  gpgme_error_t err = 0;
 
-  err = add_arg (gpg, symmetric ? "--symmetric" : "--encrypt");
+  if (recp)
+    err = add_arg (gpg, "--encrypt");
+
+  if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || !recp))
+    err = add_arg (gpg, "--symmetric");
 
   if (!err && use_armor)
     err = add_arg (gpg, "--armor");
 
-  if (!symmetric)
+  if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS))
+    err = add_arg (gpg, "--compress-algo=none");
+
+  if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS))
+    err = add_arg (gpg, "--throw-keyids");
+
+  if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME
+      && have_gpg_version (gpg, "2.1.14"))
+    err = add_arg (gpg, "--mimemode");
+
+  if (recp)
     {
       /* If we know that all recipients are valid (full or ultimate trust)
         we can suppress further checks.  */
@@ -1636,6 +1896,8 @@ gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags,
        err = add_arg (gpg, gpgme_data_get_file_name (plain));
     }
   if (!err)
+    err = add_input_size_hint (gpg, plain);
+  if (!err)
     err = add_arg (gpg, "--");
   if (!err)
     err = add_data (gpg, plain, -1, 0);
@@ -1654,24 +1916,49 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[],
                  gpgme_ctx_t ctx /* FIXME */)
 {
   engine_gpg_t gpg = engine;
-  gpgme_error_t err;
+  gpgme_error_t err = 0;
+
+  if (recp)
+    err = add_arg (gpg, "--encrypt");
+
+  if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || !recp))
+    err = add_arg (gpg, "--symmetric");
 
-  err = add_arg (gpg, "--encrypt");
   if (!err)
     err = add_arg (gpg, "--sign");
   if (!err && use_armor)
     err = add_arg (gpg, "--armor");
 
-  /* If we know that all recipients are valid (full or ultimate trust)
-     we can suppress further checks.  */
-  if (!err && (flags & GPGME_ENCRYPT_ALWAYS_TRUST))
-    err = add_arg (gpg, "--always-trust");
+  if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS))
+    err = add_arg (gpg, "--compress-algo=none");
 
-  if (!err)
-    err = append_args_from_recipients (gpg, recp);
+  if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS))
+    err = add_arg (gpg, "--throw-keyids");
+
+  if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME
+      && have_gpg_version (gpg, "2.1.14"))
+    err = add_arg (gpg, "--mimemode");
+
+  if (recp)
+    {
+      /* If we know that all recipients are valid (full or ultimate trust)
+        we can suppress further checks.  */
+      if (!err && (flags & GPGME_ENCRYPT_ALWAYS_TRUST))
+       err = add_arg (gpg, "--always-trust");
+
+      if (!err && (flags & GPGME_ENCRYPT_NO_ENCRYPT_TO))
+       err = add_arg (gpg, "--no-encrypt-to");
+
+      if (!err)
+       err = append_args_from_recipients (gpg, recp);
+    }
 
   if (!err)
     err = append_args_from_signers (gpg, ctx);
+
+  if (!err)
+    err = append_args_from_sender (gpg, ctx);
+
   if (!err)
     err = append_args_from_sig_notations (gpg, ctx);
 
@@ -1690,6 +1977,8 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[],
        err = add_arg (gpg, gpgme_data_get_file_name (plain));
     }
   if (!err)
+    err = add_input_size_hint (gpg, plain);
+  if (!err)
     err = add_arg (gpg, "--");
   if (!err)
     err = add_data (gpg, plain, -1, 0);
@@ -1702,23 +1991,52 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[],
 
 
 static gpgme_error_t
-gpg_export (void *engine, const char *pattern, unsigned int reserved,
-           gpgme_data_t keydata, int use_armor)
+export_common (engine_gpg_t gpg, gpgme_export_mode_t mode,
+               gpgme_data_t keydata, int use_armor)
 {
-  engine_gpg_t gpg = engine;
-  gpgme_error_t err;
+  gpgme_error_t err = 0;
 
-  if (reserved)
-    return gpg_error (GPG_ERR_INV_VALUE);
+  if ((mode & ~(GPGME_EXPORT_MODE_EXTERN
+                |GPGME_EXPORT_MODE_MINIMAL
+                |GPGME_EXPORT_MODE_SECRET)))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
-  err = add_arg (gpg, "--export");
-  if (!err && use_armor)
-    err = add_arg (gpg, "--armor");
-  if (!err)
-    err = add_data (gpg, keydata, 1, 1);
+  if ((mode & GPGME_EXPORT_MODE_MINIMAL))
+    err = add_arg (gpg, "--export-options=export-minimal");
+
+  if (err)
+    ;
+  else if ((mode & GPGME_EXPORT_MODE_EXTERN))
+    {
+      err = add_arg (gpg, "--send-keys");
+    }
+  else
+    {
+      if ((mode & GPGME_EXPORT_MODE_SECRET))
+        err = add_arg (gpg, "--export-secret-keys");
+      else
+        err = add_arg (gpg, "--export");
+      if (!err && use_armor)
+        err = add_arg (gpg, "--armor");
+      if (!err)
+        err = add_data (gpg, keydata, 1, 1);
+    }
   if (!err)
     err = add_arg (gpg, "--");
 
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_export (void *engine, const char *pattern, gpgme_export_mode_t mode,
+           gpgme_data_t keydata, int use_armor)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+
+  err = export_common (gpg, mode, keydata, use_armor);
+
   if (!err && pattern && *pattern)
     err = add_arg (gpg, pattern);
 
@@ -1730,22 +2048,13 @@ gpg_export (void *engine, const char *pattern, unsigned int reserved,
 
 
 static gpgme_error_t
-gpg_export_ext (void *engine, const char *pattern[], unsigned int reserved,
+gpg_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode,
                gpgme_data_t keydata, int use_armor)
 {
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
-  if (reserved)
-    return gpg_error (GPG_ERR_INV_VALUE);
-
-  err = add_arg (gpg, "--export");
-  if (!err && use_armor)
-    err = add_arg (gpg, "--armor");
-  if (!err)
-    err = add_data (gpg, keydata, 1, 1);
-  if (!err)
-    err = add_arg (gpg, "--");
+  err = export_common (gpg, mode, keydata, use_armor);
 
   if (pattern)
     {
@@ -1760,49 +2069,405 @@ gpg_export_ext (void *engine, const char *pattern[], unsigned int reserved,
 }
 
 
+\f
+/* Helper to add algo, usage, and expire to the list of args.  */
 static gpgme_error_t
-gpg_genkey (void *engine, gpgme_data_t help_data, int use_armor,
-           gpgme_data_t pubkey, gpgme_data_t seckey)
+gpg_add_algo_usage_expire (engine_gpg_t gpg,
+                           const char *algo,
+                           unsigned long expires,
+                           unsigned int flags)
 {
-  engine_gpg_t gpg = engine;
-  gpgme_error_t err;
+  gpg_error_t err;
 
-  if (!gpg)
-    return gpg_error (GPG_ERR_INV_VALUE);
+  /* This condition is only required to allow the use of gpg < 2.1.16 */
+  if (algo
+      || (flags & (GPGME_CREATE_SIGN | GPGME_CREATE_ENCR
+                   | GPGME_CREATE_CERT | GPGME_CREATE_AUTH
+                   | GPGME_CREATE_NOEXPIRE))
+      || expires)
+    {
+      err = add_arg (gpg, algo? algo : "default");
+      if (!err)
+        {
+          char tmpbuf[5*4+1];
+          snprintf (tmpbuf, sizeof tmpbuf, "%s%s%s%s",
+                    (flags & GPGME_CREATE_SIGN)? " sign":"",
+                    (flags & GPGME_CREATE_ENCR)? " encr":"",
+                    (flags & GPGME_CREATE_CERT)? " cert":"",
+                    (flags & GPGME_CREATE_AUTH)? " auth":"");
+          err = add_arg (gpg, *tmpbuf? tmpbuf : "default");
+        }
+      if (!err)
+        {
+          if ((flags & GPGME_CREATE_NOEXPIRE))
+            err = add_arg (gpg, "never");
+          else if (expires == 0)
+            err = add_arg (gpg, "-");
+          else
+            {
+              char tmpbuf[8+20];
+              snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expires);
+              err = add_arg (gpg, tmpbuf);
+            }
+        }
+    }
+  else
+    err = 0;
 
-  /* We need a special mechanism to get the fd of a pipe here, so that
-     we can use this for the %pubring and %secring parameters.  We
-     don't have this yet, so we implement only the adding to the
-     standard keyrings.  */
-  if (pubkey || seckey)
-    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  return err;
+}
 
-  err = add_arg (gpg, "--gen-key");
-  if (!err && use_armor)
-    err = add_arg (gpg, "--armor");
+
+static gpgme_error_t
+gpg_createkey_from_param (engine_gpg_t gpg,
+                          gpgme_data_t help_data, unsigned int extraflags)
+{
+  gpgme_error_t err;
+
+  err = add_arg (gpg, "--gen-key");
+  if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR))
+    err = add_arg (gpg, "--armor");
   if (!err)
     err = add_arg (gpg, "--");
   if (!err)
     err = add_data (gpg, help_data, -1, 0);
+  if (!err)
+    err = start (gpg);
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_createkey (engine_gpg_t gpg,
+               const char *userid, const char *algo,
+               unsigned long expires,
+               unsigned int flags,
+               unsigned int extraflags)
+{
+  gpgme_error_t err;
+
+  err = add_arg (gpg, "--quick-gen-key");
+  if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR))
+    err = add_arg (gpg, "--armor");
+  if (!err && (flags & GPGME_CREATE_NOPASSWD))
+    {
+      err = add_arg (gpg, "--passphrase");
+      if (!err)
+        err = add_arg (gpg, "");
+      if (!err)
+        err = add_arg (gpg, "--batch");
+    }
+  if (!err && (flags & GPGME_CREATE_FORCE))
+    err = add_arg (gpg, "--yes");
+  if (!err)
+    err = add_arg (gpg, "--");
+  if (!err)
+    err = add_arg (gpg, userid);
+
+  if (!err)
+    err = gpg_add_algo_usage_expire (gpg, algo, expires, flags);
 
   if (!err)
     err = start (gpg);
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_addkey (engine_gpg_t gpg,
+            const char *algo,
+            unsigned long expires,
+            gpgme_key_t key,
+            unsigned int flags,
+            unsigned int extraflags)
+{
+  gpgme_error_t err;
 
+  if (!key || !key->fpr)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  err = add_arg (gpg, "--quick-addkey");
+  if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR))
+    err = add_arg (gpg, "--armor");
+  if (!err && (flags & GPGME_CREATE_NOPASSWD))
+    {
+      err = add_arg (gpg, "--passphrase");
+      if (!err)
+        err = add_arg (gpg, "");
+      if (!err)
+        err = add_arg (gpg, "--batch");
+    }
+  if (!err)
+    err = add_arg (gpg, "--");
+  if (!err)
+    err = add_arg (gpg, key->fpr);
+
+  if (!err)
+    err = gpg_add_algo_usage_expire (gpg, algo, expires, flags);
+
+  if (!err)
+    err = start (gpg);
   return err;
 }
 
 
 static gpgme_error_t
-gpg_import (void *engine, gpgme_data_t keydata)
+gpg_adduid (engine_gpg_t gpg,
+            gpgme_key_t key,
+            const char *userid,
+            unsigned int extraflags)
 {
-  engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
-  err = add_arg (gpg, "--import");
+  if (!key || !key->fpr || !userid)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  if ((extraflags & GENKEY_EXTRAFLAG_SETPRIMARY))
+    {
+      if (!have_gpg_version (gpg, "2.1.20"))
+        err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      else
+        err = add_arg (gpg, "--quick-set-primary-uid");
+    }
+  else if ((extraflags & GENKEY_EXTRAFLAG_REVOKE))
+    err = add_arg (gpg, "--quick-revuid");
+  else
+    err = add_arg (gpg, "--quick-adduid");
+
   if (!err)
     err = add_arg (gpg, "--");
   if (!err)
-    err = add_data (gpg, keydata, -1, 0);
+    err = add_arg (gpg, key->fpr);
+  if (!err)
+    err = add_arg (gpg, userid);
+
+  if (!err)
+    err = start (gpg);
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_genkey (void *engine,
+            const char *userid, const char *algo,
+            unsigned long reserved, unsigned long expires,
+            gpgme_key_t key, unsigned int flags,
+            gpgme_data_t help_data, unsigned int extraflags,
+           gpgme_data_t pubkey, gpgme_data_t seckey)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+
+  (void)reserved;
+
+  if (!gpg)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  /* If HELP_DATA is given the use of the old interface
+   * (gpgme_op_genkey) has been requested.  The other modes are:
+   *
+   *  USERID && !KEY          - Create a new keyblock.
+   * !USERID &&  KEY          - Add a new subkey to KEY (gpg >= 2.1.14)
+   *  USERID &&  KEY && !ALGO - Add a new user id to KEY (gpg >= 2.1.14).
+   *                            or set a flag on a user id.
+   */
+  if (help_data)
+    {
+      /* We need a special mechanism to get the fd of a pipe here, so
+         that we can use this for the %pubring and %secring
+         parameters.  We don't have this yet, so we implement only the
+         adding to the standard keyrings.  */
+      if (pubkey || seckey)
+        err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      else
+        err = gpg_createkey_from_param (gpg, help_data, extraflags);
+    }
+  else if (!have_gpg_version (gpg, "2.1.13"))
+    err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+  else if (userid && !key)
+    err = gpg_createkey (gpg, userid, algo, expires, flags, extraflags);
+  else if (!userid && key)
+    err = gpg_addkey (gpg, algo, expires, key, flags, extraflags);
+  else if (userid && key && !algo)
+    err = gpg_adduid (gpg, key, userid, extraflags);
+  else
+    err = gpg_error (GPG_ERR_INV_VALUE);
+
+  return err;
+}
+
+/* Return the next DELIM delimited string from DATA as a C-string.
+   The caller needs to provide the address of a pointer variable which
+   he has to set to NULL before the first call.  After the last call
+   to this function, this function needs to be called once more with
+   DATA set to NULL so that the function can release its internal
+   state.  After that the pointer variable is free for use again.
+   Note that we use a delimiter and thus a trailing delimiter is not
+   required.  DELIM may not be changed after the first call. */
+static const char *
+string_from_data (gpgme_data_t data, int delim,
+                  void **helpptr, gpgme_error_t *r_err)
+{
+#define MYBUFLEN 2000 /* Fixme: We don't support URLs longer than that.  */
+  struct {
+    int  eof_seen;
+    int  nbytes;      /* Length of the last returned string including
+                         the delimiter. */
+    int  buflen;      /* Valid length of BUF.  */
+    char buf[MYBUFLEN+1];  /* Buffer with one byte extra space.  */
+  } *self;
+  char *p;
+  int nread;
+
+  *r_err = 0;
+  if (!data)
+    {
+      if (*helpptr)
+        {
+          free (*helpptr);
+          *helpptr = NULL;
+        }
+      return NULL;
+    }
+
+  if (*helpptr)
+    self = *helpptr;
+  else
+    {
+      self = malloc (sizeof *self);
+      if (!self)
+        {
+          *r_err = gpg_error_from_syserror ();
+          return NULL;
+        }
+      *helpptr = self;
+      self->eof_seen = 0;
+      self->nbytes = 0;
+      self->buflen = 0;
+    }
+
+  if (self->eof_seen)
+    return NULL;
+
+  assert (self->nbytes <= self->buflen);
+  memmove (self->buf, self->buf + self->nbytes, self->buflen - self->nbytes);
+  self->buflen -= self->nbytes;
+  self->nbytes = 0;
+
+  do
+    {
+      /* Fixme: This is fairly infective scanning because we may scan
+         the buffer several times.  */
+      p = memchr (self->buf, delim, self->buflen);
+      if (p)
+        {
+          *p = 0;
+          self->nbytes = p - self->buf + 1;
+          return self->buf;
+        }
+
+      if ( !(MYBUFLEN - self->buflen) )
+        {
+          /* Not enough space - URL too long.  */
+          *r_err = gpg_error (GPG_ERR_TOO_LARGE);
+          return NULL;
+        }
+
+      nread = gpgme_data_read (data, self->buf + self->buflen,
+                               MYBUFLEN - self->buflen);
+      if (nread < 0)
+        {
+          *r_err = gpg_error_from_syserror ();
+          return NULL;
+        }
+      self->buflen += nread;
+    }
+  while (nread);
+
+  /* EOF reached.  If we have anything in the buffer, append a Nul and
+     return it. */
+  self->eof_seen = 1;
+  if (self->buflen)
+    {
+      self->buf[self->buflen] = 0;  /* (we allocated one extra byte)  */
+      return self->buf;
+    }
+  return NULL;
+#undef MYBUFLEN
+}
+
+
+
+static gpgme_error_t
+gpg_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+  int idx;
+  gpgme_data_encoding_t dataenc;
+
+  if (keydata && keyarray)
+    return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed.  */
+
+  dataenc = gpgme_data_get_encoding (keydata);
+
+  if (keyarray)
+    {
+      err = add_arg (gpg, "--recv-keys");
+      if (!err)
+        err = add_arg (gpg, "--");
+      for (idx=0; !err && keyarray[idx]; idx++)
+        {
+          if (keyarray[idx]->protocol != GPGME_PROTOCOL_OpenPGP)
+            ;
+          else if (!keyarray[idx]->subkeys)
+            ;
+          else if (keyarray[idx]->subkeys->fpr && *keyarray[idx]->subkeys->fpr)
+            err = add_arg (gpg, keyarray[idx]->subkeys->fpr);
+          else if (*keyarray[idx]->subkeys->keyid)
+            err = add_arg (gpg, keyarray[idx]->subkeys->keyid);
+        }
+    }
+  else if (dataenc == GPGME_DATA_ENCODING_URL
+           || dataenc == GPGME_DATA_ENCODING_URL0)
+    {
+      void *helpptr;
+      const char *string;
+      gpgme_error_t xerr;
+      int delim = (dataenc == GPGME_DATA_ENCODING_URL)? '\n': 0;
+
+      /* FIXME: --fetch-keys is probably not correct because it can't
+         grok all kinds of URLs.  On Unix it should just work but on
+         Windows we will build the command line and that may fail for
+         some embedded control characters.  It is anyway limited to
+         the maximum size of the command line.  We need another
+         command which can take its input from a file.  Maybe we
+         should use an option to gpg to modify such commands (ala
+         --multifile).  */
+      err = add_arg (gpg, "--fetch-keys");
+      if (!err)
+        err = add_arg (gpg, "--");
+      helpptr = NULL;
+      while (!err
+             && (string = string_from_data (keydata, delim, &helpptr, &xerr)))
+        err = add_arg (gpg, string);
+      if (!err)
+        err = xerr;
+      string_from_data (NULL, delim, &helpptr, &xerr);
+    }
+  else if (dataenc == GPGME_DATA_ENCODING_URLESC)
+    {
+      /* Already escaped URLs are not yet supported.  */
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+    }
+  else
+    {
+      err = add_arg (gpg, "--import");
+      if (!err)
+        err = add_arg (gpg, "--");
+      if (!err)
+        err = add_data (gpg, keydata, -1, 0);
+    }
 
   if (!err)
     err = start (gpg);
@@ -1825,6 +2490,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
 #define NR_FIELDS 16
   char *field[NR_FIELDS];
   int fields = 0;
+  size_t n;
 
   *r_line = NULL;
 
@@ -1842,7 +2508,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
     rectype = RT_PUB;
   else if (!strcmp (field[0], "uid"))
     rectype = RT_UID;
-  else 
+  else
     rectype = RT_NONE;
 
   switch (rectype)
@@ -1860,16 +2526,34 @@ gpg_keylist_preprocess (char *line, char **r_line)
         pub:<keyid>:<algo>:<keylen>:<creationdate>:<expirationdate>:<flags>
 
         as defined in 5.2. Machine Readable Indexes of the OpenPGP
-        HTTP Keyserver Protocol (draft). 
+        HTTP Keyserver Protocol (draft).  Modern versions of the SKS
+        keyserver return the fingerprint instead of the keyid.  We
+        detect this here and use the v4 fingerprint format to convert
+        it to a key id.
 
         We want:
         pub:o<flags>:<keylen>:<algo>:<keyid>:<creatdate>:<expdate>::::::::
       */
 
-      if (asprintf (r_line, "pub:o%s:%s:%s:%s:%s:%s::::::::",
-                   field[6], field[3], field[2], field[1],
-                   field[4], field[5]) < 0)
-       return gpg_error_from_errno (errno);
+      n = strlen (field[1]);
+      if (n > 16)
+        {
+          if (gpgrt_asprintf (r_line,
+                        "pub:o%s:%s:%s:%s:%s:%s::::::::\n"
+                        "fpr:::::::::%s:",
+                        field[6], field[3], field[2], field[1] + n - 16,
+                        field[4], field[5], field[1]) < 0)
+            return gpg_error_from_syserror ();
+        }
+      else
+        {
+          if (gpgrt_asprintf (r_line,
+                        "pub:o%s:%s:%s:%s:%s:%s::::::::",
+                        field[6], field[3], field[2], field[1],
+                        field[4], field[5]) < 0)
+            return gpg_error_from_syserror ();
+        }
+
       return 0;
 
     case RT_UID:
@@ -1878,7 +2562,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
          uid:<escaped uid string>:<creationdate>:<expirationdate>:<flags>
 
         as defined in 5.2. Machine Readable Indexes of the OpenPGP
-        HTTP Keyserver Protocol (draft). 
+        HTTP Keyserver Protocol (draft).
 
         We want:
         uid:o<flags>::::<creatdate>:<expdate>:::<c-coded uid>:
@@ -1894,7 +2578,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
        char *dst;
 
        if (! uid)
-         return gpg_error_from_errno (errno);
+         return gpg_error_from_syserror ();
        src = field[1];
        dst = uid;
        while (*src)
@@ -1914,15 +2598,16 @@ gpg_keylist_preprocess (char *line, char **r_line)
               {
                 *dst++ = '\\';
                 *dst++ = '\\';
+                src++;
               }
            else
              *(dst++) = *(src++);
          }
        *dst = '\0';
 
-       if (asprintf (r_line, "uid:o%s::::%s:%s:::%s:",
+       if (gpgrt_asprintf (r_line, "uid:o%s::::%s:%s:::%s:",
                      field[4], field[2], field[3], uid) < 0)
-         return gpg_error_from_errno (errno);
+         return gpg_error_from_syserror ();
       }
       return 0;
 
@@ -1942,12 +2627,26 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
   gpg_error_t err;
 
   err = add_arg (gpg, "--with-colons");
-  if (!err)
-    err = add_arg (gpg, "--fixed-list-mode");
-  if (!err)
-    err = add_arg (gpg, "--with-fingerprint");
-  if (!err)
-    err = add_arg (gpg, "--with-fingerprint");
+
+  /* Since gpg 2.1.15 fingerprints are always printed, thus there is
+   * no more need to explicitly request them.  */
+  if (!have_gpg_version (gpg, "2.1.15"))
+    {
+      if (!err)
+        err = add_arg (gpg, "--fixed-list-mode");
+      if (!err)
+        err = add_arg (gpg, "--with-fingerprint");
+      if (!err)
+        err = add_arg (gpg, "--with-fingerprint");
+    }
+
+  if (!err && (mode & GPGME_KEYLIST_MODE_WITH_TOFU)
+      && have_gpg_version (gpg, "2.1.16"))
+    err = add_arg (gpg, "--with-tofu-info");
+
+  if (!err && (mode & GPGME_KEYLIST_MODE_WITH_SECRET))
+    err = add_arg (gpg, "--with-secret");
+
   if (!err
       && (mode & GPGME_KEYLIST_MODE_SIGS)
       && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS))
@@ -1956,6 +2655,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
       if (!err)
        err = add_arg (gpg, "show-sig-subpackets=\"20,26\"");
     }
+
   if (!err)
     {
       if ( (mode & GPGME_KEYLIST_MODE_EXTERN) )
@@ -1968,8 +2668,8 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
                  gpg >= 2.0.10.  FIXME: We should check that we have
                  such a version to that we can return a proper error
                  code.  The problem is that we don't know the context
-                 here and thus can't accesses the cached version
-                 number for the engine info structure.  */
+                 here and thus can't access the cached version number
+                 for the engine info structure.  */
               err = add_arg (gpg, "--locate-keys");
               if ((mode & GPGME_KEYLIST_MODE_SIGS))
                 err = add_arg (gpg, "--with-sig-check");
@@ -1987,20 +2687,23 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
                             ? "--check-sigs" : "--list-keys"));
         }
     }
+
   if (!err)
     err = add_arg (gpg, "--");
-  
+
   return err;
 }
-                           
+
 
 static gpgme_error_t
 gpg_keylist (void *engine, const char *pattern, int secret_only,
-            gpgme_keylist_mode_t mode)
+            gpgme_keylist_mode_t mode, int engine_flags)
 {
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
+  (void)engine_flags;
+
   err = gpg_keylist_build_options (gpg, secret_only, mode);
 
   if (!err && pattern && *pattern)
@@ -2015,11 +2718,13 @@ gpg_keylist (void *engine, const char *pattern, int secret_only,
 
 static gpgme_error_t
 gpg_keylist_ext (void *engine, const char *pattern[], int secret_only,
-                int reserved, gpgme_keylist_mode_t mode)
+                int reserved, gpgme_keylist_mode_t mode, int engine_flags)
 {
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
+  (void)engine_flags;
+
   if (reserved)
     return gpg_error (GPG_ERR_INV_VALUE);
 
@@ -2039,6 +2744,143 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only,
 
 
 static gpgme_error_t
+gpg_keylist_data (void *engine, gpgme_data_t data)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+
+  if (!have_gpg_version (gpg, "2.1.14"))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  err = add_arg (gpg, "--with-colons");
+  if (!err)
+    err = add_arg (gpg, "--with-fingerprint");
+  if (!err)
+    err = add_arg (gpg, "--import-options");
+  if (!err)
+    err = add_arg (gpg, "import-show");
+  if (!err)
+    err = add_arg (gpg, "--dry-run");
+  if (!err)
+    err = add_arg (gpg, "--import");
+  if (!err)
+    err = add_arg (gpg, "--");
+  if (!err)
+    err = add_data (gpg, data, -1, 0);
+
+  if (!err)
+    err = start (gpg);
+
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_keysign (void *engine, gpgme_key_t key, const char *userid,
+             unsigned long expire, unsigned int flags,
+             gpgme_ctx_t ctx)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+  const char *s;
+
+  if (!key || !key->fpr)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  if (!have_gpg_version (gpg, "2.1.12"))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  if ((flags & GPGME_KEYSIGN_LOCAL))
+    err = add_arg (gpg, "--quick-lsign-key");
+  else
+    err = add_arg (gpg, "--quick-sign-key");
+
+  if (!err)
+    err = append_args_from_signers (gpg, ctx);
+
+  /* If an expiration time has been given use that.  If none has been
+   * given the default from gpg.conf is used.  To make sure not to set
+   * an expiration time at all the flag GPGME_KEYSIGN_NOEXPIRE can be
+   * used.  */
+  if (!err && (expire || (flags & GPGME_KEYSIGN_NOEXPIRE)))
+    {
+      char tmpbuf[8+20];
+
+      if ((flags & GPGME_KEYSIGN_NOEXPIRE))
+        expire = 0;
+      snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expire);
+      err = add_arg (gpg, "--default-cert-expire");
+      if (!err)
+        err = add_arg (gpg, tmpbuf);
+    }
+
+  if (!err)
+    err = add_arg (gpg, "--");
+
+  if (!err)
+    err = add_arg (gpg, key->fpr);
+  if (!err && userid)
+    {
+      if ((flags & GPGME_KEYSIGN_LFSEP))
+        {
+          for (; !err && (s = strchr (userid, '\n')); userid = s + 1)
+            if ((s - userid))
+              err = add_arg_len (gpg, "=", userid, s - userid);
+          if (!err && *userid)
+            err = add_arg_pfx (gpg, "=", userid);
+        }
+      else
+        err = add_arg_pfx (gpg, "=", userid);
+    }
+
+  if (!err)
+    err = start (gpg);
+
+  return err;
+}
+
+
+static gpgme_error_t
+gpg_tofu_policy (void *engine, gpgme_key_t key, gpgme_tofu_policy_t policy)
+{
+  engine_gpg_t gpg = engine;
+  gpgme_error_t err;
+  const char *policystr = NULL;
+
+  if (!key || !key->fpr)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  switch (policy)
+    {
+    case GPGME_TOFU_POLICY_NONE:                           break;
+    case GPGME_TOFU_POLICY_AUTO:    policystr = "auto";    break;
+    case GPGME_TOFU_POLICY_GOOD:    policystr = "good";    break;
+    case GPGME_TOFU_POLICY_BAD:     policystr = "bad";     break;
+    case GPGME_TOFU_POLICY_ASK:     policystr = "ask";     break;
+    case GPGME_TOFU_POLICY_UNKNOWN: policystr = "unknown"; break;
+    }
+  if (!policystr)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (!have_gpg_version (gpg, "2.1.10"))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  err = add_arg (gpg, "--tofu-policy");
+  if (!err)
+    err = add_arg (gpg, "--");
+  if (!err)
+    err = add_arg (gpg, policystr);
+  if (!err)
+    err = add_arg (gpg, key->fpr);
+
+  if (!err)
+    err = start (gpg);
+
+  return err;
+}
+
+
+static gpgme_error_t
 gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
          gpgme_sig_mode_t mode, int use_armor, int use_textmode,
          int include_certs, gpgme_ctx_t ctx /* FIXME */)
@@ -2046,6 +2888,8 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
+  (void)include_certs;
+
   if (mode == GPGME_SIG_MODE_CLEAR)
     err = add_arg (gpg, "--clearsign");
   else
@@ -2055,13 +2899,21 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
        err = add_arg (gpg, "--detach");
       if (!err && use_armor)
        err = add_arg (gpg, "--armor");
-      if (!err && use_textmode)
-       err = add_arg (gpg, "--textmode");
+      if (!err)
+        {
+          if (gpgme_data_get_encoding (in) == GPGME_DATA_ENCODING_MIME
+              && have_gpg_version (gpg, "2.1.14"))
+            err = add_arg (gpg, "--mimemode");
+          else if (use_textmode)
+            err = add_arg (gpg, "--textmode");
+        }
     }
 
   if (!err)
     err = append_args_from_signers (gpg, ctx);
   if (!err)
+    err = append_args_from_sender (gpg, ctx);
+  if (!err)
     err = append_args_from_sig_notations (gpg, ctx);
 
   if (gpgme_data_get_file_name (in))
@@ -2074,6 +2926,8 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
 
   /* Tell the gpg object about the data.  */
   if (!err)
+    err = add_input_size_hint (gpg, in);
+  if (!err)
     err = add_arg (gpg, "--");
   if (!err)
     err = add_data (gpg, in, -1, 0);
@@ -2081,7 +2935,7 @@ gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out,
     err = add_data (gpg, out, 1, 1);
 
   if (!err)
-    start (gpg);
+    err = start (gpg);
 
   return err;
 }
@@ -2095,7 +2949,7 @@ gpg_trustlist (void *engine, const char *pattern)
   err = add_arg (gpg, "--with-colons");
   if (!err)
     err = add_arg (gpg, "--list-trust-path");
-  
+
   /* Tell the gpg object about the data.  */
   if (!err)
     err = add_arg (gpg, "--");
@@ -2111,19 +2965,23 @@ gpg_trustlist (void *engine, const char *pattern)
 
 static gpgme_error_t
 gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
-           gpgme_data_t plaintext)
+           gpgme_data_t plaintext, gpgme_ctx_t ctx)
 {
   engine_gpg_t gpg = engine;
-  gpgme_error_t err = 0;
+  gpgme_error_t err;
 
-  if (plaintext)
+  err = append_args_from_sender (gpg, ctx);
+  if (err)
+    ;
+  else if (plaintext)
     {
       /* Normal or cleartext signature.  */
-
       err = add_arg (gpg, "--output");
       if (!err)
        err = add_arg (gpg, "-");
       if (!err)
+        err = add_input_size_hint (gpg, sig);
+      if (!err)
        err = add_arg (gpg, "--");
       if (!err)
        err = add_data (gpg, sig, -1, 0);
@@ -2134,6 +2992,8 @@ gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
     {
       err = add_arg (gpg, "--verify");
       if (!err)
+        err = add_input_size_hint (gpg, signed_text);
+      if (!err)
        err = add_arg (gpg, "--");
       if (!err)
        err = add_data (gpg, sig, -1, 0);
@@ -2156,12 +3016,23 @@ gpg_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
   gpg->io_cbs = *io_cbs;
 }
 
+
+static gpgme_error_t
+gpg_set_pinentry_mode (void *engine, gpgme_pinentry_mode_t mode)
+{
+  engine_gpg_t gpg = engine;
+
+  gpg->pinentry_mode = mode;
+  return 0;
+}
+
+
 \f
 struct engine_ops _gpgme_engine_ops_gpg =
   {
     /* Static functions.  */
-    _gpgme_get_gpg_path,
-    NULL,               
+    _gpgme_get_default_gpg_name,
+    NULL,
     gpg_get_version,
     gpg_get_req_version,
     gpg_new,
@@ -2169,11 +3040,14 @@ struct engine_ops _gpgme_engine_ops_gpg =
     /* Member functions.  */
     gpg_release,
     NULL,                              /* reset */
+    gpg_set_status_cb,
     gpg_set_status_handler,
     gpg_set_command_handler,
     gpg_set_colon_line_handler,
     gpg_set_locale,
+    NULL,                              /* set_protocol */
     gpg_decrypt,
+    gpg_decrypt,                       /* decrypt_verify */
     gpg_delete,
     gpg_edit,
     gpg_encrypt,
@@ -2184,6 +3058,9 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_import,
     gpg_keylist,
     gpg_keylist_ext,
+    gpg_keylist_data,
+    gpg_keysign,
+    gpg_tofu_policy,    /* tofu_policy */
     gpg_sign,
     gpg_trustlist,
     gpg_verify,
@@ -2191,7 +3068,12 @@ struct engine_ops _gpgme_engine_ops_gpg =
     NULL,               /* opassuan_transact */
     NULL,              /* conf_load */
     NULL,              /* conf_save */
+    NULL,               /* query_swdb */
     gpg_set_io_cbs,
     gpg_io_event,
-    gpg_cancel
+    gpg_cancel,
+    NULL,              /* cancel_op */
+    gpg_passwd,
+    gpg_set_pinentry_mode,
+    NULL                /* opspawn */
   };