core: Check for GPG_TTY as well as DISPLAY.
[gpgme.git] / src / engine-gpg.c
index 289578b..8bb348f 100644 (file)
@@ -206,14 +206,16 @@ 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_syserror ();
 
@@ -221,7 +223,10 @@ _add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp)
   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;
@@ -243,24 +248,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);
 }
 
 
@@ -437,6 +454,7 @@ gpg_new (void **engine, const char *file_name, const char *home_dir,
   char *dft_display = NULL;
   char dft_ttyname[64];
   char *dft_ttytype = NULL;
+  char *env_tty = NULL;
 
   gpg = calloc (1, sizeof *gpg);
   if (!gpg)
@@ -543,11 +561,20 @@ gpg_new (void **engine, const char *file_name, const char *home_dir,
        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 (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.  */
@@ -1078,10 +1105,15 @@ read_status (engine_gpg_t gpg)
       err = 0;
       gpg->status.eof = 1;
       if (gpg->status.mon_cb)
-        err = gpg->status.mon_cb (gpg->status.mon_cb_value,
-                                  GPGME_STATUS_EOF, "");
+        err = gpg->status.mon_cb (gpg->status.mon_cb_value, "", "");
       if (gpg->status.fnc)
-        err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, "");
+        {
+          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;
     }
@@ -1148,6 +1180,8 @@ read_status (engine_gpg_t gpg)
                        {
                          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;
                         }
@@ -1570,6 +1604,8 @@ 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);
 
@@ -1600,7 +1636,8 @@ 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;
 }
@@ -1963,14 +2000,55 @@ gpg_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode,
 }
 
 
+\f
+/* Helper to add algo, usage, and expire to the list of args.  */
+static gpgme_error_t
+gpg_add_algo_usage_expire (engine_gpg_t gpg,
+                           const char *algo,
+                           unsigned long expires,
+                           unsigned int flags)
+{
+  gpg_error_t err;
+
+  /* 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))
+      || 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 && expires)
+        {
+          char tmpbuf[8+20];
+          snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expires);
+          err = add_arg (gpg, tmpbuf);
+        }
+    }
+  else
+    err = 0;
+
+  return err;
+}
+
+
 static gpgme_error_t
 gpg_createkey_from_param (engine_gpg_t gpg,
-                          gpgme_data_t help_data, int use_armor)
+                          gpgme_data_t help_data, unsigned int extraflags)
 {
   gpgme_error_t err;
 
   err = add_arg (gpg, "--gen-key");
-  if (!err && use_armor)
+  if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR))
     err = add_arg (gpg, "--armor");
   if (!err)
     err = add_arg (gpg, "--");
@@ -1982,30 +2060,17 @@ gpg_createkey_from_param (engine_gpg_t gpg,
 }
 
 
-/* This is used for gpg versions which do not support the quick-genkey
- * command to emulate the gpgme_op_createkey API.  */
-static gpgme_error_t
-gpg_createkey_legacy (engine_gpg_t gpg,
-               const char *userid, const char *algo,
-               unsigned long expires,
-               unsigned int flags,
-               int use_armor)
-{
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
-}
-
-
 static gpgme_error_t
 gpg_createkey (engine_gpg_t gpg,
                const char *userid, const char *algo,
                unsigned long expires,
                unsigned int flags,
-               int use_armor)
+               unsigned int extraflags)
 {
   gpgme_error_t err;
 
   err = add_arg (gpg, "--quick-gen-key");
-  if (!err && use_armor)
+  if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR))
     err = add_arg (gpg, "--armor");
   if (!err && (flags & GPGME_CREATE_NOPASSWD))
     {
@@ -2020,32 +2085,8 @@ gpg_createkey (engine_gpg_t gpg,
   if (!err)
     err = add_arg (gpg, userid);
 
-  /* 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))
-      || expires)
-    {
-
-      if (!err)
-        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 && expires)
-        {
-          char tmpbuf[8+20];
-          snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expires);
-          err = add_arg (gpg, tmpbuf);
-        }
-    }
+  if (!err)
+    err = gpg_add_algo_usage_expire (gpg, algo, expires, flags);
 
   if (!err)
     err = start (gpg);
@@ -2059,19 +2100,62 @@ gpg_addkey (engine_gpg_t gpg,
             unsigned long expires,
             gpgme_key_t key,
             unsigned int flags,
-            int use_armor)
+            unsigned int extraflags)
 {
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  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, "--");
+  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_adduid (engine_gpg_t gpg,
+            gpgme_key_t key,
             const char *userid,
-            unsigned int flags,
-            int use_armor)
+            unsigned int extraflags)
 {
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  gpgme_error_t err;
+
+  if (!key || !key->fpr || !userid)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  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_arg (gpg, key->fpr);
+  if (!err)
+    err = add_arg (gpg, userid);
+
+  if (!err)
+    err = start (gpg);
+  return err;
 }
 
 
@@ -2080,7 +2164,7 @@ 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, int use_armor,
+            gpgme_data_t help_data, unsigned int extraflags,
            gpgme_data_t pubkey, gpgme_data_t seckey)
 {
   engine_gpg_t gpg = engine;
@@ -2108,22 +2192,16 @@ gpg_genkey (void *engine,
       if (pubkey || seckey)
         err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
       else
-        err = gpg_createkey_from_param (gpg, help_data, use_armor);
-    }
-  else if (userid && !key)
-    {
-      if (!have_gpg_version (gpg, "2.1.13"))
-        err = gpg_createkey_legacy (gpg, userid, algo, expires, flags,
-                                    use_armor);
-      else
-        err = gpg_createkey (gpg, userid, algo, expires, flags, use_armor);
+        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, use_armor);
+    err = gpg_addkey (gpg, algo, expires, key, flags, extraflags);
   else if (userid && key && !algo)
-    err = gpg_adduid (gpg, userid, flags, use_armor);
+    err = gpg_adduid (gpg, key, userid, extraflags);
   else
     err = gpg_error (GPG_ERR_INV_VALUE);
 
@@ -2536,6 +2614,8 @@ gpg_keylist (void *engine, const char *pattern, int secret_only,
   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)
@@ -2555,6 +2635,8 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only,
   engine_gpg_t gpg = engine;
   gpgme_error_t err;
 
+  (void)engine_flags;
+
   if (reserved)
     return gpg_error (GPG_ERR_INV_VALUE);
 
@@ -2574,6 +2656,111 @@ gpg_keylist_ext (void *engine, const char *pattern[], int secret_only,
 
 
 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 */)
@@ -2581,6 +2768,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
@@ -2744,6 +2933,8 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_import,
     gpg_keylist,
     gpg_keylist_ext,
+    gpg_keysign,
+    gpg_tofu_policy,    /* tofu_policy */
     gpg_sign,
     gpg_trustlist,
     gpg_verify,