From e60771ab087ad70e8db7808fb9d423b153d17fad Mon Sep 17 00:00:00 2001
From: Andre Heinecke <aheinecke@gnupg.org>
Date: Mon, 27 Jul 2020 14:15:45 +0200
Subject: [PATCH] core: Add gpgme_op_set_expire

* doc/gpgme.texi: Document it.
* src/engine-assuan.c, src/engine-g13.c, src/engine-gpgconf.c,
src/engine-gpgsm.c (_gpgme_engine_ops_): Initialize setexpire to NULL.
* src/engine-gpg.c (gpg_setexpire): New. The implementation.
(_gpgme_engine_ops_gpg): Add setexpire.
* src/engine.c (_gpgme_engine_op_setexpire): New. Wrap engine call.
* src/engine.h: (_gpgme_engine_op_setexpire): Add prototype.
* src/genkey.c (set_expire): New.
(gpgme_op_set_expire_start, gpgme_op_set_expire): Call set_expire.
* src/gpgme.def, src/gpgme.vers
(gpgme_op_set_expire, gpgme_op_set_expire_start): Export it.
* src/gpgme.h.in: Add prototype.
* tests/run-genkey.c: Add set-expire.

--
This extends GPGME to support the --quick-set-expire command
added by GnuPG 2.1.22. This allows changing subkeys expiry
date without going through the editinteractor interface.

GnuPG-Bug-Id: T4999
---
 doc/gpgme.texi       | 52 ++++++++++++++++++++++++++++++++++++++++++++
 src/engine-assuan.c  |  1 +
 src/engine-backend.h |  3 +++
 src/engine-g13.c     |  1 +
 src/engine-gpg.c     | 36 ++++++++++++++++++++++++++++++
 src/engine-gpgconf.c |  1 +
 src/engine-gpgsm.c   |  1 +
 src/engine.c         | 15 +++++++++++++
 src/engine.h         |  4 ++++
 src/genkey.c         | 49 +++++++++++++++++++++++++++++++++++++++++
 src/gpgme.def        |  3 +++
 src/gpgme.h.in       |  9 ++++++++
 src/libgpgme.vers    |  3 +++
 tests/run-genkey.c   | 41 ++++++++++++++++++++++++++++++++--
 14 files changed, 217 insertions(+), 2 deletions(-)

diff --git a/doc/gpgme.texi b/doc/gpgme.texi
index 6c1d0622..af2d6155 100644
--- a/doc/gpgme.texi
+++ b/doc/gpgme.texi
@@ -4016,6 +4016,58 @@ The function @code{gpgme_key_unref} releases a reference for the key
 and all resources associated to it will be released.
 @end deftypefun
 
+@c
+@c  gpgme_op_set_expire
+@c
+@deftypefun gpgme_error_t gpgme_op_set_expire@
+      (@w{gpgme_ctx_t @var{ctx}}, @
+       @w{const char *@var{fpr}}, @
+       @w{unsigned long @var{expire}}, @
+       @w{const char *@var{subfprs}});
+
+@since{1.14.1}
+
+The function @code{gpgme_op_set_expire} sets the expiry date of
+a key idefntified by the fingerprint fpr.
+This function requires at least version 2.1.22 of GnuPG.
+
+@var{fpr} specifies the key to operate on.
+
+@var{expire} specifies the expiration time in seconds from now.
+To be similar to other usages where expire dates are provided
+in unsigned long this is similar to the key creation date
+and so it is in seconds from NOW.
+
+The common case is to use 0 to not set an expiration date.
+Note that this parameter takes an unsigned long value and not
+a @code{time_t} to avoid problems on systems which use a signed
+32 bit @code{time_t}.  Note further that the OpenPGP protocol
+uses 32 bit values for timestamps and thus can
+only encode dates up to the year 2106.
+
+@var{subfprs} This is an optional parameter to identify subkeys
+for which the expiration should be set.  This can be an asterix
+to change the expiry date of all subkeys or a space seperated
+list of fingerprints.  If this parameter is ommited only the
+expiry of the primary key, provided by @var{fpr} is changed.
+
+@end deftypefun
+
+@deftypefun gpgme_error_t gpgme_op_revuid_start @
+      (@w{gpgme_ctx_t @var{ctx}}, @
+       @w{gpgme_key_t @var{key}}, @
+       @w{const char *@var{userid}}, @
+       @w{unsigned int @var{flags}});
+
+@since{1.14.1}
+
+The function @code{gpgme_op_set_expire_start} initiates a
+@code{gpgme_op_set_expire} operation; see there for details.  It must
+be completed by calling @code{gpgme_wait} on the context.
+@xref{Waiting For Completion}.
+
+@end deftypefun
+
 
 @node Generating Keys
 @subsection Generating Keys
diff --git a/src/engine-assuan.c b/src/engine-assuan.c
index a40328f7..b51c17e3 100644
--- a/src/engine-assuan.c
+++ b/src/engine-assuan.c
@@ -828,6 +828,7 @@ struct engine_ops _gpgme_engine_ops_assuan =
     NULL,               /* sign */
     NULL,               /* verify */
     NULL,               /* getauditlog */
+    NULL,               /* setexpire */
     llass_transact,     /* opassuan_transact */
     NULL,		/* conf_load */
     NULL,		/* conf_save */
diff --git a/src/engine-backend.h b/src/engine-backend.h
index c8bfad96..89679b65 100644
--- a/src/engine-backend.h
+++ b/src/engine-backend.h
@@ -120,6 +120,9 @@ struct engine_ops
                            gpgme_ctx_t ctx);
   gpgme_error_t  (*getauditlog) (void *engine, gpgme_data_t output,
                                  unsigned int flags);
+  /* The setexpire command. */
+  gpgme_error_t (*setexpire) (void *engine, const char *fpr,
+                              unsigned long expire, const char *subfprs);
   gpgme_error_t  (*opassuan_transact) (void *engine,
                                        const char *command,
                                        gpgme_assuan_data_cb_t data_cb,
diff --git a/src/engine-g13.c b/src/engine-g13.c
index 45f6c94b..82f26305 100644
--- a/src/engine-g13.c
+++ b/src/engine-g13.c
@@ -808,6 +808,7 @@ struct engine_ops _gpgme_engine_ops_g13 =
     NULL,               /* sign */
     NULL,               /* verify */
     NULL,               /* getauditlog */
+    NULL,               /* setexpire */
     g13_transact,
     NULL,		/* conf_load */
     NULL,		/* conf_save */
diff --git a/src/engine-gpg.c b/src/engine-gpg.c
index af2533d8..4e76d123 100644
--- a/src/engine-gpg.c
+++ b/src/engine-gpg.c
@@ -3426,6 +3426,41 @@ gpg_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
 #undef MYBUFLEN
 }
 
+static gpgme_error_t
+gpg_setexpire (void *engine,
+               const char *fpr,
+               unsigned long expire,
+               const char *subfprs)
+{
+  gpgme_error_t err;
+
+  engine_gpg_t gpg = (engine_gpg_t) (engine);
+
+  if (!fpr)
+    return gpg_error (GPG_ERR_INV_ARG);
+
+  err = add_arg (gpg, "--quick-set-expire");
+  if (!err)
+    {
+      err = add_arg (gpg, fpr);
+    }
+  if (!err)
+    {
+      char tmpbuf[8+20];
+      snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expire);
+      err = add_arg (gpg, tmpbuf);
+    }
+
+  if (!err && subfprs)
+    {
+      err = add_arg (gpg, subfprs);
+    }
+
+  if (!err)
+    err = start (gpg);
+  return err;
+}
+
 
 
 struct engine_ops _gpgme_engine_ops_gpg =
@@ -3464,6 +3499,7 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_sign,
     gpg_verify,
     gpg_getauditlog,
+    gpg_setexpire,
     NULL,               /* opassuan_transact */
     NULL,		/* conf_load */
     NULL,		/* conf_save */
diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c
index d4465e97..fba8f23b 100644
--- a/src/engine-gpgconf.c
+++ b/src/engine-gpgconf.c
@@ -1306,6 +1306,7 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
     NULL,		/* sign */
     NULL,		/* verify */
     NULL,		/* getauditlog */
+    NULL,               /* setexpire */
     NULL,               /* opassuan_transact */
     gpgconf_conf_load,
     gpgconf_conf_save,
diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c
index 671b3857..c92320e1 100644
--- a/src/engine-gpgsm.c
+++ b/src/engine-gpgsm.c
@@ -2326,6 +2326,7 @@ struct engine_ops _gpgme_engine_ops_gpgsm =
     gpgsm_sign,
     gpgsm_verify,
     gpgsm_getauditlog,
+    NULL,               /* setexpire */
     NULL,               /* opassuan_transact */
     NULL,		/* conf_load */
     NULL,		/* conf_save */
diff --git a/src/engine.c b/src/engine.c
index ded2f4d1..9103d538 100644
--- a/src/engine.c
+++ b/src/engine.c
@@ -1128,3 +1128,18 @@ _gpgme_engine_op_spawn (engine_t engine,
   return (*engine->ops->opspawn) (engine->engine, file, argv,
                                   datain, dataout, dataerr, flags);
 }
+
+gpgme_error_t
+_gpgme_engine_op_setexpire (engine_t engine,
+                            const char *fpr,
+                            unsigned long expire,
+                            const char *subfprs)
+{
+  if (!engine)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (!engine->ops->setexpire)
+    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+  return (*engine->ops->setexpire) (engine->engine, fpr, expire, subfprs);
+}
diff --git a/src/engine.h b/src/engine.h
index c512a252..179aeaf0 100644
--- a/src/engine.h
+++ b/src/engine.h
@@ -210,6 +210,10 @@ gpgme_error_t _gpgme_engine_op_spawn (engine_t engine,
                                       gpgme_data_t dataout,
                                       gpgme_data_t dataerr,
                                       unsigned int flags);
+gpgme_error_t _gpgme_engine_op_setexpire (engine_t engine,
+                                          const char *fpr,
+                                          unsigned long expire,
+                                          const char *subfprs);
 
 /* The available engine option flags.  */
 #define GPGME_ENGINE_FLAG_OFFLINE        1
diff --git a/src/genkey.c b/src/genkey.c
index 77576b18..eff28b8a 100644
--- a/src/genkey.c
+++ b/src/genkey.c
@@ -663,3 +663,52 @@ gpgme_op_set_uid_flag (gpgme_ctx_t ctx,
 {
   return set_uid_flag (ctx, 1, key, userid, name, value);
 }
+
+/* Set the expiry of a key and or subkeys.  See --quick-set-expire
+  in the gnupg documentation. */
+static gpg_error_t
+set_expire (gpgme_ctx_t ctx, int synchronous,
+            const char *fpr,
+            unsigned long expire,
+            const char *subfprs)
+{
+  gpgme_error_t err = 0;
+
+  TRACE_BEG  (DEBUG_CTX, "gpgme_op_set_expire", ctx,
+              "%d fpr='%s' subkeys: '%s' expiry: %lu", synchronous,
+              fpr, subfprs, expire);
+
+  if (!ctx || !fpr)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+  err = _gpgme_op_reset (ctx, synchronous);
+  if (err)
+    return err;
+
+  err = _gpgme_engine_op_setexpire (ctx->engine, fpr, expire, subfprs);
+
+  if (synchronous && !err)
+    err = _gpgme_wait_one (ctx);
+  return TRACE_ERR (err);
+}
+
+/* See set_expire. */
+gpgme_error_t
+gpgme_op_set_expire_start (gpgme_ctx_t ctx,
+                           const char *fpr,
+                           unsigned long expire,
+                           const char *subfprs)
+{
+  return set_expire (ctx, 0, fpr, expire, subfprs);
+}
+
+
+/* See set_expire.  This is the synchronous variant.  */
+gpgme_error_t
+gpgme_op_set_expire (gpgme_ctx_t ctx,
+                     const char *fpr,
+                     unsigned long expire,
+                     const char *subfprs)
+{
+  return set_expire (ctx, 1, fpr, expire, subfprs);
+}
diff --git a/src/gpgme.def b/src/gpgme.def
index c6902208..73f73af3 100644
--- a/src/gpgme.def
+++ b/src/gpgme.def
@@ -274,5 +274,8 @@ EXPORTS
 
     gpgme_data_new_from_estream           @204
 
+    gpgme_op_set_expire                   @205
+    gpgme_op_set_expire_start             @206
+
 ; END
 
diff --git a/src/gpgme.h.in b/src/gpgme.h.in
index b4f817b4..3eda1896 100644
--- a/src/gpgme.h.in
+++ b/src/gpgme.h.in
@@ -1868,6 +1868,15 @@ gpgme_error_t gpgme_op_set_uid_flag       (gpgme_ctx_t ctx,
                                            gpgme_key_t key, const char *userid,
                                            const char *name, const char *value);
 
+/* Change the expiry of a key.  */
+gpgme_error_t gpgme_op_set_expire_start (gpgme_ctx_t ctx,
+                                         const char *fpr,
+                                         unsigned long expire,
+                                         const char *subfprs);
+gpgme_error_t gpgme_op_set_expire (gpgme_ctx_t ctx,
+                                   const char *fpr,
+                                   unsigned long expire,
+                                   const char *subfprs);
 
 /* Retrieve a pointer to the result of a genkey, createkey, or
  * createsubkey operation.  */
diff --git a/src/libgpgme.vers b/src/libgpgme.vers
index 79cbeef2..267e0e07 100644
--- a/src/libgpgme.vers
+++ b/src/libgpgme.vers
@@ -273,6 +273,9 @@ GPGME_1.0 {
     gpgme_err_code_from_syserror;
     gpgme_err_set_errno;
 
+    gpgme_op_set_expire;
+    gpgme_op_set_expire_start;
+
   local:
     *;
 
diff --git a/tests/run-genkey.c b/tests/run-genkey.c
index fd9b42a7..1b5e5449 100644
--- a/tests/run-genkey.c
+++ b/tests/run-genkey.c
@@ -205,12 +205,14 @@ show_usage (int ex)
          "   for addkey: FPR    [ALGO [USAGE [EXPIRESECONDS]]]\n"
          "   for adduid: FPR    USERID\n"
          "   for revuid: FPR    USERID\n"
+         "   for set-expire:   FPR EXPIRE [SUBFPRS]\n"
          "   for set-primary: FPR    USERID\n"
          "Options:\n"
          "  --addkey         add a subkey to the key with FPR\n"
          "  --adduid         add a user id to the key with FPR\n"
          "  --revuid         revoke a user id from the key with FPR\n"
          "  --set-primary    set the primary key flag on USERID\n"
+         "  --set-expire     set the expiry date of the key FPR\n"
          "  --verbose        run in verbose mode\n"
          "  --status         print status lines from the backend\n"
          "  --progress       print progress info\n"
@@ -238,9 +240,12 @@ main (int argc, char **argv)
   int adduid = 0;
   int revuid = 0;
   int setpri = 0;
+  int setexpire = 0;
   const char *userid;
   const char *algo = NULL;
   const char *newuserid = NULL;
+  const char *fpr = NULL;
+  const char *subfprs = NULL;
   unsigned int flags = 0;
   unsigned long expire = 0;
   gpgme_genkey_result_t result;
@@ -258,6 +263,14 @@ main (int argc, char **argv)
         }
       else if (!strcmp (*argv, "--help"))
         show_usage (0);
+      else if (!strcmp (*argv, "--set-expire"))
+        {
+          setexpire = 1;
+          adduid = 0;
+          revuid = 0;
+          setpri = 0;
+          argc--; argv++;
+        }
       else if (!strcmp (*argv, "--addkey"))
         {
           addkey = 1;
@@ -341,6 +354,19 @@ main (int argc, char **argv)
       userid = argv[0];
       newuserid = argv[1];
     }
+  else if (setexpire)
+    {
+      if (argc < 2)
+        {
+          show_usage (1);
+        }
+      fpr = argv[0];
+      expire = parse_expire_string (argv[1]);
+      if (argc == 3)
+        {
+          subfprs = argv[2];
+        }
+    }
   else
     {
       if (!argc || argc > 4)
@@ -373,7 +399,18 @@ main (int argc, char **argv)
       gpgme_set_passphrase_cb (ctx, passphrase_cb, NULL);
     }
 
-  if (addkey || adduid || revuid || setpri)
+
+  if (setexpire)
+    {
+      err = gpgme_op_set_expire (ctx, fpr, expire, subfprs);
+      if (err)
+        {
+          fprintf (stderr, PGM ": gpgme_op_set_expire failed: %s\n",
+                   gpg_strerror (err));
+          exit (1);
+        }
+    }
+  else if (addkey || adduid || revuid || setpri)
     {
       gpgme_key_t akey;
 
@@ -438,7 +475,7 @@ main (int argc, char **argv)
         }
     }
 
-  if (!setpri)
+  if (!setpri && !setexpire)
     {
       result = gpgme_op_genkey_result (ctx);
       if (!result)
-- 
2.20.1

