core: Add GPGME_KEYLIST_MODE_WITH_TOFU.
[gpgme.git] / src / engine-gpg.c
index e14fd8d..7036ee0 100644 (file)
@@ -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;
 
@@ -291,6 +295,15 @@ 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)
@@ -386,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);
@@ -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;
@@ -436,6 +452,16 @@ gpg_new (void **engine, const char *file_name, const char *home_dir)
        }
     }
 
+  if (version)
+    {
+      gpg->version = strdup (version);
+      if (!gpg->version)
+       {
+         rc = gpg_error_from_syserror ();
+         goto leave;
+       }
+    }
+
   gpg->argtail = &gpg->arglist;
   gpg->status.fd[0] = -1;
   gpg->status.fd[1] = -1;
@@ -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.  */
@@ -1016,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)
@@ -1034,15 +1075,15 @@ read_status (engine_gpg_t gpg)
 
   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)
@@ -1068,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
@@ -1096,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)
@@ -1435,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)
 {
@@ -1451,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;
 }
 
@@ -1479,7 +1559,7 @@ gpg_delete (void *engine, gpgme_key_t key, int allow_secret)
     }
 
   if (!err)
-    start (gpg);
+    err = start (gpg);
   return err;
 }
 
@@ -1497,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;
 }
 
@@ -1669,10 +1749,13 @@ 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");
@@ -1680,7 +1763,11 @@ gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags,
   if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS))
     err = add_arg (gpg, "--compress-algo=none");
 
-  if (!symmetric)
+  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.  */
@@ -1709,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);
@@ -1727,10 +1816,13 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[],
                  gpgme_ctx_t ctx /* FIXME */)
 {
   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)
     err = add_arg (gpg, "--sign");
@@ -1740,7 +1832,11 @@ gpg_encrypt_sign (void *engine, gpgme_key_t recp[],
   if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS))
     err = add_arg (gpg, "--compress-algo=none");
 
-  if (!symmetric)
+  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.  */
@@ -1775,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);
@@ -1793,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))
@@ -1807,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)
@@ -2223,14 +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))
@@ -2239,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) )
@@ -2270,6 +2385,7 @@ gpg_keylist_build_options (engine_gpg_t gpg, int secret_only,
                             ? "--check-sigs" : "--list-keys"));
         }
     }
+
   if (!err)
     err = add_arg (gpg, "--");
 
@@ -2279,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;
@@ -2298,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;
@@ -2338,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)
@@ -2357,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);
@@ -2364,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;
 }
@@ -2402,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);
@@ -2417,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);
@@ -2463,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,