core: Add GPGME_KEYLIST_MODE_WITH_TOFU.
[gpgme.git] / src / engine-gpg.c
index 4c7a8b2..7036ee0 100644 (file)
@@ -1,7 +1,7 @@
 /* engine-gpg.c - Gpg Engine.
    Copyright (C) 2000 Werner Koch (dd9jn)
    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007,
-                 2009, 2010, 2012 g10 Code GmbH
+                 2009, 2010, 2012, 2013 g10 Code GmbH
 
    This file is part of GPGME.
 
@@ -42,6 +42,7 @@
 #include "priv-io.h"
 #include "sema.h"
 #include "debug.h"
+#include "data.h"
 
 #include "engine-backend.h"
 
@@ -78,6 +79,7 @@ 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;
@@ -95,6 +97,8 @@ 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;
 
@@ -134,6 +138,7 @@ struct engine_gpg
   } cmd;
 
   struct gpgme_io_cbs io_cbs;
+  gpgme_pinentry_mode_t pinentry_mode;
 };
 
 typedef struct engine_gpg *engine_gpg_t;
@@ -210,7 +215,7 @@ _add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp)
 
   a = malloc (sizeof *a + strlen (arg));
   if (!a)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
 
   a->data = NULL;
   a->dup_to = -1;
@@ -269,7 +274,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;
@@ -290,19 +295,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";
 }
 
 
@@ -385,6 +399,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);
@@ -395,8 +411,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;
     }
 
@@ -414,7 +429,8 @@ gpg_release (void *engine)
 
 
 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;
@@ -424,14 +440,24 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
 
   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;
        }
     }
@@ -452,14 +478,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],
@@ -513,6 +539,8 @@ 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))
@@ -520,9 +548,10 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
       int err;
 
       err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
-      if (err)
-       rc = gpg_error_from_errno (err);
-      else
+
+      /* 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)
             {
@@ -547,9 +576,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:
@@ -606,6 +635,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.  */
@@ -630,14 +670,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)
     {
-      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],
@@ -717,7 +757,7 @@ 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;
@@ -728,15 +768,20 @@ build_argv (engine_gpg_t gpg)
   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)
     {
@@ -769,29 +814,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)
@@ -799,10 +846,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++;
     }
@@ -811,32 +858,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)
+    {
+      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)
@@ -867,6 +940,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.  */
@@ -908,10 +985,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];
@@ -933,10 +1010,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++;
         }
@@ -979,6 +1056,7 @@ read_status (engine_gpg_t gpg)
   size_t bufsize = gpg->status.bufsize;
   char *buffer = gpg->status.buffer;
   size_t readpos = gpg->status.readpos;
+  gpgme_error_t err;
 
   assert (buffer);
   if (bufsize - readpos < 256)
@@ -987,25 +1065,25 @@ read_status (engine_gpg_t gpg)
       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,
+                                  GPGME_STATUS_EOF, "");
       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;
+        err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, "");
+
+      return err;
     }
 
   while (nread > 0)
@@ -1031,6 +1109,15 @@ read_status (engine_gpg_t gpg)
                    *rest++ = 0;
 
                  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
@@ -1043,7 +1130,7 @@ read_status (engine_gpg_t gpg)
                            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.  */
@@ -1059,7 +1146,6 @@ read_status (engine_gpg_t gpg)
                         }
                      else if (gpg->status.fnc)
                        {
-                         gpgme_error_t err;
                          err = gpg->status.fnc (gpg->status.fnc_value,
                                                 r, rest);
                          if (err)
@@ -1163,12 +1249,12 @@ read_colon_line (engine_gpg_t gpg)
       bufsize += 1024;
       buffer = realloc (buffer, bufsize);
       if (!buffer)
-       return gpg_error_from_errno (errno);
+       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)
     {
@@ -1204,10 +1290,26 @@ 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);
+
+                      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
@@ -1259,16 +1361,16 @@ 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 ())
+  if (!gpg->file_name && !_gpgme_get_default_gpg_name ())
     return trace_gpg_error (GPG_ERR_INV_ENGINE);
 
   if (gpg->lc_ctype)
@@ -1289,7 +1391,8 @@ 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;
 
@@ -1299,7 +1402,7 @@ start (engine_gpg_t gpg)
     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;
@@ -1323,15 +1426,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,
-                            IOSPAWN_FLAG_ALLOW_SET_FG,
+  status = _gpgme_io_spawn (pgmname, gpg->argv,
+                            (IOSPAWN_FLAG_DETACHED |IOSPAWN_FLAG_ALLOW_SET_FG),
                             fd_list, NULL, NULL, &pid);
-  saved_errno = errno;
-
-  free (fd_list);
-  if (status == -1)
-    return gpg_error_from_errno (saved_errno);
+  {
+    int saved_err = gpg_error_from_syserror ();
+    free (fd_list);
+    if (status == -1)
+      return saved_err;
+  }
 
   /*_gpgme_register_term_handler ( closure, closure_value, pid );*/
 
@@ -1381,6 +1484,35 @@ start (engine_gpg_t gpg)
 }
 
 
+/* 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)
 {
@@ -1397,12 +1529,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;
 }
 
@@ -1425,7 +1559,7 @@ gpg_delete (void *engine, gpgme_key_t key, int allow_secret)
     }
 
   if (!err)
-    start (gpg);
+    err = start (gpg);
   return err;
 }
 
@@ -1443,7 +1577,7 @@ gpg_passwd (void *engine, gpgme_key_t key, unsigned int flags)
   if (!err)
     err = add_arg (gpg, key->subkeys->fpr);
   if (!err)
-    start (gpg);
+    err = start (gpg);
   return err;
 }
 
@@ -1495,7 +1629,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)
            {
@@ -1531,7 +1665,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] = '!';
@@ -1615,15 +1749,25 @@ 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;
+
+  if (recp)
+    err = add_arg (gpg, "--encrypt");
 
-  err = add_arg (gpg, symmetric ? "--symmetric" : "--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 (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.  */
@@ -1652,6 +1796,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);
@@ -1670,24 +1816,43 @@ 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 (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_sig_notations (gpg, ctx);
 
@@ -1706,6 +1871,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);
@@ -1724,7 +1891,8 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode,
   gpgme_error_t err = 0;
 
   if ((mode & ~(GPGME_EXPORT_MODE_EXTERN
-                |GPGME_EXPORT_MODE_MINIMAL)))
+                |GPGME_EXPORT_MODE_MINIMAL
+                |GPGME_EXPORT_MODE_SECRET)))
     return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
   if ((mode & GPGME_EXPORT_MODE_MINIMAL))
@@ -1738,7 +1906,10 @@ export_common (engine_gpg_t gpg, gpgme_export_mode_t mode,
     }
   else
     {
-      err = add_arg (gpg, "--export");
+      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)
@@ -2017,6 +2188,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;
 
@@ -2052,16 +2224,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 (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 (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:
@@ -2086,7 +2276,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)
@@ -2106,6 +2296,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
               {
                 *dst++ = '\\';
                 *dst++ = '\\';
+                src++;
               }
            else
              *(dst++) = *(src++);
@@ -2114,7 +2305,7 @@ gpg_keylist_preprocess (char *line, char **r_line)
 
        if (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;
 
@@ -2134,12 +2325,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 explictly 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))
@@ -2148,6 +2353,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) )
@@ -2179,6 +2385,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
                             ? "--check-sigs" : "--list-keys"));
         }
     }
+
   if (!err)
     err = add_arg (gpg, "--");
 
@@ -2188,7 +2395,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
 
 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;
@@ -2207,7 +2414,7 @@ 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;
@@ -2247,8 +2454,14 @@ 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)
@@ -2266,6 +2479,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);
@@ -2273,7 +2488,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;
 }
@@ -2311,11 +2526,12 @@ gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
   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);
@@ -2326,6 +2542,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);
@@ -2348,11 +2566,22 @@ 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,
+    _gpgme_get_default_gpg_name,
     NULL,
     gpg_get_version,
     gpg_get_req_version,
@@ -2361,6 +2590,7 @@ 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,
@@ -2389,5 +2619,7 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_io_event,
     gpg_cancel,
     NULL,              /* cancel_op */
-    gpg_passwd
+    gpg_passwd,
+    gpg_set_pinentry_mode,
+    NULL                /* opspawn */
   };