Add GPGME_PROTOCOL_SPAWN and gpgme_op_spawn.
authorWerner Koch <wk@gnupg.org>
Thu, 10 Apr 2014 11:01:00 +0000 (13:01 +0200)
committerWerner Koch <wk@gnupg.org>
Thu, 10 Apr 2014 11:01:00 +0000 (13:01 +0200)
* src/gpgme.h.in (GPGME_PROTOCOL_SPAWN): New.
(GPGME_SPAWN_DETACHED, GPGME_SPAWN_ALLOW_SET_FG): New.
* src/gpgme.c (gpgme_set_protocol): Add new protocol.
(gpgme_get_protocol_name): Ditto.
* src/spawn.c: New.
* src/libgpgme.vers, src/gpgme.def: Add new public functions.
* src/engine-spawn.c: New.
* src/Makefile.am: Add new files.
* src/engine-backend.h (struct engine_ops): Add OPSPAWN.
* src/engine.c (engine_ops): Add _gpgme_engine_ops_spawn.
(gpgme_get_engine_info): Add Spawn to the list of protocols.
(_gpgme_engine_op_spawn): New.

* src/gpgme-tool.c (gt_protocol_from_name): Add new protocol.
(gt_spawn, cmd_spawn): New.

19 files changed:
NEWS
doc/gpgme.texi
src/Makefile.am
src/engine-assuan.c
src/engine-backend.h
src/engine-g13.c
src/engine-gpg.c
src/engine-gpgconf.c
src/engine-gpgsm.c
src/engine-spawn.c [new file with mode: 0644]
src/engine-uiserver.c
src/engine.c
src/engine.h
src/gpgme-tool.c
src/gpgme.c
src/gpgme.def
src/gpgme.h.in
src/libgpgme.vers
src/spawn.c [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index f625ab9..acba24d 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -9,9 +9,17 @@ Noteworthy changes in version 1.5.0 (unreleased)
    whatever gpgconf tells as name for the OpenPGP engine.  If gpgconf
    is not found, GPGME looks for an engine named "gpg".
 
+ * Add feature to use the gpgme I/O subsystem to run arbitrary
+   commands.
+
  * Interface changes relative to the 1.4.3 release:
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  gpgme_get_dirinfo              NEW.
+ gpgme_op_spawn_start           NEW.
+ gpgme_op_spawn                 NEW.
+ GPGME_PROTOCOL_SPAWN           NEW.
+ GPGME_SPAWN_DETACHED           NEW.
+ GPGME_SPAWN_ALLOW_SET_FG       NEW.
 
 
 Noteworthy changes in version 1.4.3 (2013-08-12)
index e12fd73..027e1ef 100644 (file)
@@ -113,7 +113,6 @@ Indices
 * Concept Index::                 Index of concepts and programs.
 * Function and Data Index::       Index of functions, variables and data types.
 
-
 @detailmenu
  --- The Detailed Node Listing ---
 
@@ -170,6 +169,7 @@ Manipulating Data Buffers
 
 * Data Buffer I/O Operations::    I/O operations on data buffers.
 * Data Buffer Meta-Data::         Meta-data manipulation of data buffers.
+* Data Buffer Convenience::       Convenience function for data buffers.
 
 Contexts
 
@@ -180,6 +180,7 @@ Contexts
 * Key Management::                Managing keys with @acronym{GPGME}.
 * Trust Item Management::         Managing trust items with @acronym{GPGME}.
 * Crypto Operations::             Using a context for cryptography.
+* Miscellaneous::                 Miscellaneous operations.
 * Run Control::                   Controlling how operations are run.
 
 Context Attributes
@@ -204,6 +205,7 @@ Key Management
 * Exporting Keys::                Retrieving key data from the key ring.
 * Importing Keys::                Adding keys to the key ring.
 * Deleting Keys::                 Removing keys from the key ring.
+* Changing Passphrases::          Change the passphrase of a key.
 * Advanced Key Editing::          Advanced key edit operation.
 
 Trust Item Management
@@ -230,6 +232,10 @@ Encrypt
 
 * Encrypting a Plaintext::        How to encrypt a plaintext.
 
+Miscellaneous
+
+* Running other Programs::        Running other Programs
+
 Run Control
 
 * Waiting For Completion::        Waiting until an operation is completed.
@@ -850,6 +856,9 @@ Under development.  Please ask on @email{gnupg-devel@@gnupg.org} for help.
 @item GPGME_PROTOCOL_UISERVER
 Under development.  Please ask on @email{gnupg-devel@@gnupg.org} for help.
 
+@item GPGME_PROTOCOL_SPAWN
+Special protocol for use with @code{gpgme_op_spawn}.
+
 @item GPGME_PROTOCOL_UNKNOWN
 Reserved for future extension.  You may use this to indicate that the
 used protocol is not known to the application.  Currently,
@@ -1946,7 +1955,7 @@ be used to manipulate both.
 @menu
 * Data Buffer I/O Operations::    I/O operations on data buffers.
 * Data Buffer Meta-Data::         Meta-data manipulation of data buffers.
-* Data Buffer Convenience::       Convenience fucntion for data buffers.
+* Data Buffer Convenience::       Convenience function for data buffers.
 @end menu
 
 
@@ -2187,6 +2196,7 @@ cryptographic operations.
 * Key Management::                Managing keys with @acronym{GPGME}.
 * Trust Item Management::         Managing trust items with @acronym{GPGME}.
 * Crypto Operations::             Using a context for cryptography.
+* Miscellaneous::                 Miscellaneous operations
 * Run Control::                   Controlling how operations are run.
 @end menu
 
@@ -2261,7 +2271,7 @@ started.  In fact, these references are accessed through the
 * Crypto Engine::                 Configuring the crypto engine.
 * ASCII Armor::                   Requesting @acronym{ASCII} armored output.
 * Text Mode::                     Choosing canonical text mode.
-* Included Certificates::       Including a number of certificates.
+* Included Certificates::         Including a number of certificates.
 * Key Listing Mode::              Selecting key listing mode.
 * Passphrase Callback::           Getting the passphrase from the user.
 * Progress Meter Callback::       Being informed about the progress.
@@ -5222,6 +5232,66 @@ pointer.
 @end deftypefun
 
 
+@node Miscellaneous
+@section Miscellaneous operations
+
+Here are some support functions which are sometimes useful.
+
+@menu
+* Running other Programs::      Running other Programs
+@end menu
+
+
+@node Running other Programs
+@subsection Running other Programs
+
+GPGME features an internal subsystem to run the actual backend
+engines.  Along with data abstraction object this subsystem can be
+used to run arbitrary simple programs which even need not be related
+to cryptographic features.  It may for example be used to run tools
+which are part of the GnuPG system but are not directly accessible
+with the GPGME API.
+
+
+@deftypefun gpgme_error_t gpgme_op_spawn
+            (@w{gpgme_ctx_t @var{ctx}}, @w{const char *@var{file}}, @
+             @w{const char *@var{argv}[]}, @w{gpgme_data_t @var{datain}}, @
+             @w{gpgme_data_t @var{dataout}}, @w{gpgme_data_t @var{dataerr}}, @
+             @w{unsigned int @var{flags}})
+
+The function @code{gpgme_op_spawn} runs the program @var{file} with
+the arguments taken from the NULL terminated array @var{argv}.  If no
+arguments are required @var{argv} may be given as @code{NULL} (in that
+case GPGME uses the basename of @var{file} for @code{argv[0]}).  The
+file descriptors @code{stdin}, @code{stdout}, and @code{stderr} are
+connected to the data objects @var{datain}, @var{dataout}, and
+@var{dataerr}.  If NULL is passed for one of these data objects the
+corresponding file descriptor is connected to @file{/dev/null}.
+
+The value in @var{flags} is a bitwise-or combination of one or
+multiple of the following bit values:
+
+@table @code
+@item GPGME_SPAWN_DETACHED
+Under Windows this flag inhibits the allocation of a new console for
+the program.  This is useful for a GUI application which needs to call
+a command line helper tool.
+@item GPGME_SPAWN_ALLOW_SET_FG
+Under Windows this flag allows the called program to put itself into
+the foreground.
+@end table
+@end deftypefun
+
+@deftypefun gpgme_error_t gpgme_op_spawn_start
+            (@w{gpgme_ctx_t @var{ctx}}, @w{const char *@var{file}}, @
+             @w{const char *@var{argv}[]}, @w{gpgme_data_t @var{datain}}, @
+             @w{gpgme_data_t @var{dataout}}, @w{gpgme_data_t @var{dataerr}}, @
+             @w{unsigned int @var{flags}})
+
+This is the asynchronous variant of @code{gpgme_op_spawn}.
+@end deftypefun
+
+
 @node Run Control
 @section Run Control
 @cindex run control
index abc014c..82f5327 100644 (file)
@@ -89,11 +89,12 @@ main_sources =                                                              \
        sign.c passphrase.c progress.c                                  \
        key.c keylist.c trust-item.c trustlist.c                        \
        import.c export.c genkey.c delete.c edit.c getauditlog.c        \
-       opassuan.c passwd.c assuan-support.c                            \
+       opassuan.c passwd.c spawn.c assuan-support.c                    \
        engine.h engine-backend.h engine.c engine-gpg.c status-table.c  \
        engine-gpgsm.c engine-assuan.c engine-gpgconf.c                 \
        $(uiserver_components)                                          \
        engine-g13.c vfs-mount.c vfs-create.c                           \
+       engine-spawn.c                                                  \
        gpgconf.c                                                       \
        sema.h priv-io.h $(system_components) sys-util.h dirinfo.c      \
        debug.c debug.h gpgme.c version.c error.c
index 5ef3047..663b2ea 100644 (file)
@@ -783,5 +783,6 @@ struct engine_ops _gpgme_engine_ops_assuan =
     llass_cancel,
     llass_cancel_op,
     NULL,               /* passwd */
-    NULL                /* set_pinentry_mode */
+    NULL,               /* set_pinentry_mode */
+    NULL                /* opspawn */
   };
index 7e6c0c1..dbb9e93 100644 (file)
@@ -124,6 +124,14 @@ struct engine_ops
 
   /* Set the pinentry mode.  */
   gpgme_error_t (*set_pinentry_mode) (void *engine, gpgme_pinentry_mode_t mode);
+
+  /* The spawn command.  */
+  gpgme_error_t (*opspawn) (void * engine,
+                            const char *file, const char *argv[],
+                            gpgme_data_t datain,
+                            gpgme_data_t dataout,
+                            gpgme_data_t dataerr);
+
 };
 
 
@@ -135,6 +143,7 @@ extern struct engine_ops _gpgme_engine_ops_g13;         /* Crypto VFS. */
 #ifdef ENABLE_UISERVER
 extern struct engine_ops _gpgme_engine_ops_uiserver;
 #endif
+extern struct engine_ops _gpgme_engine_ops_spawn;       /* Spawn engine. */
 
 
 /* Prototypes for extra functions in engine-gpgconf.c  */
index 75154ca..a9717ee 100644 (file)
@@ -799,5 +799,6 @@ struct engine_ops _gpgme_engine_ops_g13 =
     g13_cancel,
     g13_cancel_op,
     NULL,               /* passwd */
-    NULL                /* set_pinentry_mode */
+    NULL,               /* set_pinentry_mode */
+    NULL                /* opspawn */
   };
index 9037dd7..a8eab3a 100644 (file)
@@ -2445,5 +2445,6 @@ struct engine_ops _gpgme_engine_ops_gpg =
     gpg_cancel,
     NULL,              /* cancel_op */
     gpg_passwd,
-    gpg_set_pinentry_mode
+    gpg_set_pinentry_mode,
+    NULL                /* opspawn */
   };
index 811ad9d..a2407ac 100644 (file)
@@ -963,5 +963,6 @@ struct engine_ops _gpgme_engine_ops_gpgconf =
     NULL,              /* cancel */
     NULL,               /* cancel_op */
     NULL,               /* passwd */
-    NULL                /* set_pinentry_mode */
+    NULL,               /* set_pinentry_mode */
+    NULL                /* opspawn */
   };
index 6bcc095..710bf14 100644 (file)
@@ -1988,5 +1988,6 @@ struct engine_ops _gpgme_engine_ops_gpgsm =
     gpgsm_cancel,
     NULL,              /* cancel_op */
     gpgsm_passwd,
-    NULL                /* set_pinentry_mode */
+    NULL,               /* set_pinentry_mode */
+    NULL                /* opspawn */
   };
diff --git a/src/engine-spawn.c b/src/engine-spawn.c
new file mode 100644 (file)
index 0000000..1e71c1c
--- /dev/null
@@ -0,0 +1,467 @@
+/* engine-spawn.c - Run an arbitrary program
+   Copyright (C) 2014 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/>.
+*/
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+#ifdef HAVE_LOCALE_H
+#include <locale.h>
+#endif
+
+#include "gpgme.h"
+#include "util.h"
+#include "ops.h"
+#include "wait.h"
+#include "context.h"  /*temp hack until we have GpmeData methods to do I/O */
+#include "priv-io.h"
+#include "sema.h"
+#include "debug.h"
+
+#include "engine-backend.h"
+
+
+/* This type is used to build a list of data sources/sinks.  */
+struct datalist_s
+{
+  struct datalist_s *next;
+  gpgme_data_t data;  /* The data object. */
+  int inbound;        /* True if this is used for reading from the peer.  */
+  int dup_to;         /* The fd used by the peer.  */
+};
+
+
+struct fd_data_map_s
+{
+  gpgme_data_t data;
+  int inbound;  /* True if this is used for reading from the peer. */
+  int dup_to;   /* Dup the fd to that one.  */
+  int fd;       /* The fd to use.  */
+  int peer_fd;  /* The other side of the pipe. */
+  void *tag;    /* Tag used by the I/O callback.  */
+};
+
+
+struct engine_spawn
+{
+  struct datalist_s *arglist;
+  struct datalist_s **argtail;
+
+  struct fd_data_map_s *fd_data_map;
+
+  struct gpgme_io_cbs io_cbs;
+};
+typedef struct engine_spawn *engine_spawn_t;
+
+
+static void engspawn_io_event (void *engine,
+                               gpgme_event_io_t type, void *type_data);
+static gpgme_error_t engspawn_cancel (void *engine);
+
+
+\f
+static void
+close_notify_handler (int fd, void *opaque)
+{
+  engine_spawn_t esp = opaque;
+  int i;
+
+  assert (fd != -1);
+
+  if (esp->fd_data_map)
+    {
+      for (i = 0; esp->fd_data_map[i].data; i++)
+       {
+         if (esp->fd_data_map[i].fd == fd)
+           {
+             if (esp->fd_data_map[i].tag)
+               (*esp->io_cbs.remove) (esp->fd_data_map[i].tag);
+             esp->fd_data_map[i].fd = -1;
+             break;
+            }
+         if (esp->fd_data_map[i].peer_fd == fd)
+           {
+             esp->fd_data_map[i].peer_fd = -1;
+             break;
+            }
+        }
+    }
+}
+
+
+static gpgme_error_t
+add_data (engine_spawn_t esp, gpgme_data_t data, int dup_to, int inbound)
+{
+  struct datalist_s *a;
+
+  assert (esp);
+  assert (data);
+
+  a = malloc (sizeof *a - 1);
+  if (!a)
+    return gpg_error_from_syserror ();
+  a->next = NULL;
+  a->data = data;
+  a->inbound = inbound;
+  a->dup_to = dup_to;
+  *esp->argtail = a;
+  esp->argtail = &a->next;
+  return 0;
+}
+
+\f
+static void
+free_fd_data_map (struct fd_data_map_s *fd_data_map)
+{
+  int i;
+
+  if (!fd_data_map)
+    return;
+
+  for (i = 0; fd_data_map[i].data; i++)
+    {
+      if (fd_data_map[i].fd != -1)
+       _gpgme_io_close (fd_data_map[i].fd);
+      if (fd_data_map[i].peer_fd != -1)
+       _gpgme_io_close (fd_data_map[i].peer_fd);
+      /* Don't release data because this is only a reference.  */
+    }
+  free (fd_data_map);
+}
+
+
+static gpgme_error_t
+build_fd_data_map (engine_spawn_t esp)
+{
+  struct datalist_s *a;
+  size_t datac;
+  int fds[2];
+
+  for (datac = 0, a = esp->arglist; a; a = a->next)
+    if (a->data)
+      datac++;
+
+  free_fd_data_map (esp->fd_data_map);
+  esp->fd_data_map = calloc (datac + 1, sizeof *esp->fd_data_map);
+  if (!esp->fd_data_map)
+    return gpg_error_from_syserror ();
+
+  for (datac = 0, a = esp->arglist; a; a = a->next)
+    {
+      assert (a->data);
+
+      if (_gpgme_io_pipe (fds, a->inbound ? 1 : 0) == -1)
+        {
+          free (esp->fd_data_map);
+          esp->fd_data_map = NULL;
+          return gpg_error_from_syserror ();
+        }
+      if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, esp)
+          || _gpgme_io_set_close_notify (fds[1], close_notify_handler, esp))
+        {
+          /* FIXME: Need error cleanup.  */
+          return gpg_error (GPG_ERR_GENERAL);
+        }
+
+      esp->fd_data_map[datac].inbound = a->inbound;
+      if (a->inbound)
+        {
+          esp->fd_data_map[datac].fd       = fds[0];
+          esp->fd_data_map[datac].peer_fd  = fds[1];
+        }
+      else
+        {
+          esp->fd_data_map[datac].fd       = fds[1];
+          esp->fd_data_map[datac].peer_fd  = fds[0];
+        }
+      esp->fd_data_map[datac].data    = a->data;
+      esp->fd_data_map[datac].dup_to  = a->dup_to;
+      datac++;
+    }
+
+  return 0;
+}
+
+
+static gpgme_error_t
+add_io_cb (engine_spawn_t esp, int fd, int dir, gpgme_io_cb_t handler,
+           void *data, void **tag)
+{
+  gpgme_error_t err;
+
+  err = (*esp->io_cbs.add) (esp->io_cbs.add_priv, fd, dir, handler, data, tag);
+  if (err)
+    return err;
+  if (!dir) /* Fixme: Kludge around poll() problem.  */
+    err = _gpgme_io_set_nonblocking (fd);
+  return err;
+}
+
+
+static gpgme_error_t
+engspawn_start (engine_spawn_t esp, const char *file, const char *argv[],
+                unsigned int flags)
+{
+  gpgme_error_t err;
+  int i, n;
+  int status;
+  struct spawn_fd_item_s *fd_list;
+  pid_t pid;
+  unsigned int spflags;
+
+  if (!esp || !file || !argv || !argv[0])
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  spflags = 0;
+  if ((flags & GPGME_SPAWN_DETACHED))
+    spflags |= IOSPAWN_FLAG_DETACHED;
+  if ((flags & GPGME_SPAWN_ALLOW_SET_FG))
+    spflags |= IOSPAWN_FLAG_ALLOW_SET_FG;
+
+
+  err = build_fd_data_map (esp);
+  if (err)
+    return err;
+
+  n = 0;
+  for (i = 0; esp->fd_data_map[i].data; i++)
+    n++;
+  fd_list = calloc (n, sizeof *fd_list);
+  if (!fd_list)
+    return gpg_error_from_syserror ();
+
+  /* Build the fd list for the child.  */
+  n = 0;
+  for (i = 0; esp->fd_data_map[i].data; i++)
+    {
+      fd_list[n].fd = esp->fd_data_map[i].peer_fd;
+      fd_list[n].dup_to = esp->fd_data_map[i].dup_to;
+      n++;
+    }
+  fd_list[n].fd = -1;
+  fd_list[n].dup_to = -1;
+
+  status = _gpgme_io_spawn (file, (char * const *)argv, spflags,
+                            fd_list, NULL, NULL, &pid);
+  free (fd_list);
+  if (status == -1)
+    return gpg_error_from_syserror ();
+
+  for (i = 0; esp->fd_data_map[i].data; i++)
+    {
+      err = add_io_cb (esp, esp->fd_data_map[i].fd,
+                       esp->fd_data_map[i].inbound,
+                       esp->fd_data_map[i].inbound
+                       ? _gpgme_data_inbound_handler
+                       : _gpgme_data_outbound_handler,
+                       esp->fd_data_map[i].data, &esp->fd_data_map[i].tag);
+      if (err)
+        return err;  /* FIXME: kill the child */
+    }
+
+  engspawn_io_event (esp, GPGME_EVENT_START, NULL);
+
+  return 0;
+}
+
+
+\f
+/*
+    Public functions
+ */
+
+static const char *
+engspawn_get_file_name (void)
+{
+  return "/nonexistent";
+}
+
+
+static char *
+engspawn_get_version (const char *file_name)
+{
+  (void)file_name;
+  return strdup ("1.0");
+}
+
+
+static const char *
+engspawn_get_req_version (void)
+{
+  return "1.0";
+}
+
+
+static gpgme_error_t
+engspawn_new (void **engine, const char *file_name, const char *home_dir)
+{
+  engine_spawn_t esp;
+
+  (void)file_name;
+  (void)home_dir;
+
+  esp = calloc (1, sizeof *esp);
+  if (!esp)
+    return gpg_error_from_syserror ();
+
+  esp->argtail = &esp->arglist;
+  *engine = esp;
+  return 0;
+}
+
+
+static void
+engspawn_release (void *engine)
+{
+  engine_spawn_t esp = engine;
+
+  if (!esp)
+    return;
+
+  engspawn_cancel (engine);
+
+  while (esp->arglist)
+    {
+      struct datalist_s *next = esp->arglist->next;
+
+      if (esp->arglist)
+       free (esp->arglist);
+      esp->arglist = next;
+    }
+
+  free (esp);
+}
+
+
+static void
+engspawn_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
+{
+  engine_spawn_t esp = engine;
+
+  esp->io_cbs = *io_cbs;
+}
+
+
+static void
+engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data)
+{
+  engine_spawn_t esp = engine;
+
+  TRACE3 (DEBUG_ENGINE, "gpgme:engspawn_io_event", esp,
+          "event %p, type %d, type_data %p",
+          esp->io_cbs.event, type, type_data);
+  if (esp->io_cbs.event)
+    (*esp->io_cbs.event) (esp->io_cbs.event_priv, type, type_data);
+}
+
+
+static gpgme_error_t
+engspawn_cancel (void *engine)
+{
+  engine_spawn_t esp = engine;
+
+  if (!esp)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (esp->fd_data_map)
+    {
+      free_fd_data_map (esp->fd_data_map);
+      esp->fd_data_map = NULL;
+    }
+
+  return 0;
+}
+
+
+static gpgme_error_t
+engspawn_op_spawn (void *engine,
+                   const char *file, const char *argv[],
+                   gpgme_data_t datain,
+                   gpgme_data_t dataout, gpgme_data_t dataerr,
+                   unsigned int flags)
+{
+  engine_spawn_t esp = engine;
+  gpgme_error_t err = 0;
+
+  if (datain)
+    err = add_data (esp, datain, 0, 0);
+  if (!err && dataout)
+    err = add_data (esp, dataout, 1, 1);
+  if (!err && dataerr)
+    err = add_data (esp, dataerr, 2, 1);
+
+  if (!err)
+    err = engspawn_start (esp, file, argv, flags);
+
+  return err;
+}
+
+
+\f
+struct engine_ops _gpgme_engine_ops_spawn =
+  {
+    /* Static functions.  */
+    engspawn_get_file_name,
+    NULL,               /* get_home_dir */
+    engspawn_get_version,
+    engspawn_get_req_version,
+    engspawn_new,
+
+    /* Member functions.  */
+    engspawn_release,
+    NULL,              /* reset */
+    NULL,              /* set_status_handler */
+    NULL,              /* set_command_handler */
+    NULL,              /* set_colon_line_handler */
+    NULL,              /* set_locale */
+    NULL,              /* set_protocol */
+    NULL,              /* decrypt */
+    NULL,              /* decrypt_verify */
+    NULL,              /* delete */
+    NULL,              /* edit */
+    NULL,              /* encrypt */
+    NULL,              /* encrypt_sign */
+    NULL,              /* export */
+    NULL,              /* export_ext */
+    NULL,              /* genkey */
+    NULL,              /* import */
+    NULL,              /* keylist */
+    NULL,              /* keylist_ext */
+    NULL,              /* sign */
+    NULL,              /* trustlist */
+    NULL,              /* verify */
+    NULL,              /* getauditlog */
+    NULL,               /* opassuan_transact */
+    NULL,              /* conf_load */
+    NULL,              /* conf_save */
+    engspawn_set_io_cbs,
+    engspawn_io_event, /* io_event */
+    engspawn_cancel,   /* cancel */
+    NULL,               /* cancel_op */
+    NULL,               /* passwd */
+    NULL,               /* set_pinentry_mode */
+    engspawn_op_spawn   /* opspawn */
+  };
index bd140f9..2738c36 100644 (file)
@@ -1340,5 +1340,6 @@ struct engine_ops _gpgme_engine_ops_uiserver =
     uiserver_cancel,
     NULL,              /* cancel_op */
     NULL,               /* passwd */
-    NULL                /* set_pinentry_mode */
+    NULL,                /* set_pinentry_mode */
+    NULL                /* opspawn */
   };
index 4f2000c..f503430 100644 (file)
@@ -51,10 +51,11 @@ static struct engine_ops *engine_ops[] =
     &_gpgme_engine_ops_assuan,         /* Low-Level Assuan.  */
     &_gpgme_engine_ops_g13,            /* Crypto VFS.  */
 #ifdef ENABLE_UISERVER
-    &_gpgme_engine_ops_uiserver                /* UI-Server.  */
+    &_gpgme_engine_ops_uiserver,       /* UI-Server.  */
 #else
-    NULL
+    NULL,
 #endif
+    &_gpgme_engine_ops_spawn
   };
 
 
@@ -193,7 +194,8 @@ gpgme_get_engine_info (gpgme_engine_info_t *info)
                                        GPGME_PROTOCOL_GPGCONF,
                                        GPGME_PROTOCOL_ASSUAN,
                                        GPGME_PROTOCOL_G13,
-                                       GPGME_PROTOCOL_UISERVER };
+                                       GPGME_PROTOCOL_UISERVER,
+                                        GPGME_PROTOCOL_SPAWN    };
       unsigned int proto;
 
       err = 0;
@@ -936,3 +938,20 @@ _gpgme_engine_set_pinentry_mode (engine_t engine, gpgme_pinentry_mode_t mode)
 
   return (*engine->ops->set_pinentry_mode) (engine->engine, mode);
 }
+
+
+gpgme_error_t
+_gpgme_engine_op_spawn (engine_t engine,
+                        const char *file, const char *argv[],
+                        gpgme_data_t datain,
+                        gpgme_data_t dataout, gpgme_data_t dataerr)
+{
+  if (!engine)
+    return gpg_error (GPG_ERR_INV_VALUE);
+
+  if (!engine->ops->opspawn)
+    return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+  return (*engine->ops->opspawn) (engine->engine, file, argv,
+                                  datain, dataout, dataerr);
+}
index a0287ad..ade7de1 100644 (file)
@@ -163,5 +163,11 @@ gpgme_error_t _gpgme_engine_op_passwd (engine_t engine, gpgme_key_t key,
 gpgme_error_t _gpgme_engine_set_pinentry_mode (engine_t engine,
                                                gpgme_pinentry_mode_t mode);
 
+gpgme_error_t _gpgme_engine_op_spawn (engine_t engine,
+                                      const char *file, const char *argv[],
+                                      gpgme_data_t datain,
+                                      gpgme_data_t dataout,
+                                      gpgme_data_t dataerr);
+
 
 #endif /* ENGINE_H */
index 2bf7654..a980347 100644 (file)
@@ -1742,6 +1742,8 @@ gt_protocol_from_name (const char *name)
     return GPGME_PROTOCOL_G13;
   if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_UISERVER)))
     return GPGME_PROTOCOL_UISERVER;
+  if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_SPAWN)))
+    return GPGME_PROTOCOL_SPAWN;
   if (! strcasecmp (name, gpgme_get_protocol_name (GPGME_PROTOCOL_DEFAULT)))
     return GPGME_PROTOCOL_DEFAULT;
   return GPGME_PROTOCOL_UNKNOWN;
@@ -2106,6 +2108,18 @@ gt_identify (gpgme_tool_t gt, gpgme_data_t data)
 }
 
 
+gpg_error_t
+gt_spawn (gpgme_tool_t gt, const char *pgm,
+          gpgme_data_t inp, gpgme_data_t outp)
+{
+  gpg_error_t err;
+
+  err = gpgme_op_spawn (gt->ctx, pgm, NULL, inp, outp, outp, 0);
+
+  return err;
+}
+
+
 #define GT_RESULT_ENCRYPT 0x1
 #define GT_RESULT_DECRYPT 0x2
 #define GT_RESULT_SIGN 0x4
@@ -3487,6 +3501,55 @@ cmd_identify (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_spawn[] =
+  "SPAWN PGM [args]\n"
+  "\n"
+  "Run program PGM with stdin connected to the INPUT source;\n"
+  "stdout and stderr to the OUTPUT source.";
+static gpg_error_t
+cmd_spawn (assuan_context_t ctx, char *line)
+{
+  struct server *server = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_fd_t inp_fd;
+  char *inp_fn;
+  assuan_fd_t out_fd;
+  char *out_fn;
+  gpgme_data_t inp_data = NULL;
+  gpgme_data_t out_data = NULL;
+
+  inp_fd = server->input_fd;
+  inp_fn = server->input_filename;
+  out_fd = server->output_fd;
+  out_fn = server->output_filename;
+  if (inp_fd != ASSUAN_INVALID_FD || inp_fn)
+    {
+      err = server_data_obj (inp_fd, inp_fn, 0, server->input_enc, &inp_data,
+                            &server->input_stream);
+      if (err)
+       return err;
+    }
+  if (out_fd != ASSUAN_INVALID_FD || out_fn)
+    {
+      err = server_data_obj (out_fd, out_fn, 1, server->output_enc, &out_data,
+                            &server->output_stream);
+      if (err)
+       {
+         gpgme_data_release (inp_data);
+         return err;
+       }
+    }
+
+  err = gt_spawn (server->gt, line, inp_data, out_data);
+
+  gpgme_data_release (inp_data);
+  gpgme_data_release (out_data);
+
+  server_reset_fds (server);
+
+  return err;
+}
+
 
 /* Tell the assuan library about our commands.  */
 static gpg_error_t
@@ -3547,6 +3610,7 @@ register_commands (assuan_context_t ctx)
     { "HASH_ALGO_NAME", cmd_hash_algo_name },
     { "PASSWD", cmd_passwd, hlp_passwd },
     { "IDENTIFY", cmd_identify, hlp_identify },
+    { "SPAWN", cmd_spawn, hlp_spawn },
     { NULL }
   };
   int idx;
index 438fef2..24b04fc 100644 (file)
@@ -1,6 +1,7 @@
 /* gpgme.c - GnuPG Made Easy.
    Copyright (C) 2000 Werner Koch (dd9jn)
-   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012 g10 Code GmbH
+   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012,
+                 2014 g10 Code GmbH
 
    This file is part of GPGME.
 
@@ -15,9 +16,8 @@
    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, write to the Free Software
-   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-   02111-1307, USA.  */
+   License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
 
 #if HAVE_CONFIG_H
 #include <config.h>
@@ -321,7 +321,8 @@ gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
       && protocol != GPGME_PROTOCOL_GPGCONF
       && protocol != GPGME_PROTOCOL_ASSUAN
       && protocol != GPGME_PROTOCOL_G13
-      && protocol != GPGME_PROTOCOL_UISERVER)
+      && protocol != GPGME_PROTOCOL_UISERVER
+      && protocol != GPGME_PROTOCOL_SPAWN)
     return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
 
   if (!ctx)
@@ -405,6 +406,9 @@ gpgme_get_protocol_name (gpgme_protocol_t protocol)
     case GPGME_PROTOCOL_UISERVER:
       return "UIServer";
 
+    case GPGME_PROTOCOL_SPAWN:
+      return "Spawn";
+
     case GPGME_PROTOCOL_DEFAULT:
       return "default";
 
index ee0c42e..dc18948 100644 (file)
@@ -215,5 +215,7 @@ EXPORTS
 
     gpgme_get_dirinfo                     @162
 
+    gpgme_op_spawn_start                  @163
+    gpgme_op_spawn                        @164
 ; END
 
index 050051a..655602d 100644 (file)
@@ -1,7 +1,7 @@
 /* gpgme.h - Public interface to GnuPG Made Easy.                   -*- c -*-
    Copyright (C) 2000 Werner Koch (dd9jn)
    Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009
-                 2010, 2011, 2012, 2013 g10 Code GmbH
+                 2010, 2011, 2012, 2013, 2014 g10 Code GmbH
 
    This file is part of GPGME.
 
@@ -354,6 +354,7 @@ typedef enum
     GPGME_PROTOCOL_ASSUAN  = 3,  /* Low-level access to an Assuan server.  */
     GPGME_PROTOCOL_G13     = 4,
     GPGME_PROTOCOL_UISERVER= 5,
+    GPGME_PROTOCOL_SPAWN   = 6,  /* Direct access to any program.  */
     GPGME_PROTOCOL_DEFAULT = 254,
     GPGME_PROTOCOL_UNKNOWN = 255
   }
@@ -1694,6 +1695,26 @@ gpgme_error_t gpgme_op_card_edit (gpgme_ctx_t ctx, gpgme_key_t key,
                                  gpgme_edit_cb_t fnc, void *fnc_value,
                                  gpgme_data_t out);
 
+
+/* Flags for the spawn operations.  */
+#define GPGME_SPAWN_DETACHED      1
+#define GPGME_SPAWN_ALLOW_SET_FG  2
+
+
+/* Run the command FILE with the arguments in ARGV.  Connect stdin to
+   DATAIN, stdout to DATAOUT, and STDERR to DATAERR.  If one the data
+   streams is NULL, connect to /dev/null instead.  */
+gpgme_error_t gpgme_op_spawn_start (gpgme_ctx_t ctx,
+                                    const char *file, const char *argv[],
+                                    gpgme_data_t datain,
+                                    gpgme_data_t dataout, gpgme_data_t dataerr,
+                                    unsigned int flags);
+gpgme_error_t gpgme_op_spawn (gpgme_ctx_t ctx,
+                              const char *file, const char *argv[],
+                              gpgme_data_t datain,
+                              gpgme_data_t dataout, gpgme_data_t dataerr,
+                              unsigned int flags);
+
 \f
 /* Key management functions.  */
 struct _gpgme_op_keylist_result
index 4db1d40..39663c1 100644 (file)
@@ -89,6 +89,9 @@ GPGME_1.1 {
     gpgme_get_pinentry_mode;
 
     gpgme_get_dirinfo;
+
+    gpgme_op_spawn_start;
+    gpgme_op_spawn;
 };
 
 
diff --git a/src/spawn.c b/src/spawn.c
new file mode 100644 (file)
index 0000000..e3454f3
--- /dev/null
@@ -0,0 +1,105 @@
+/* spawn.c - Run an arbitrary command with callbacks.
+   Copyright (C) 2014 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, write to the Free Software
+   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+   02111-1307, USA.  */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+
+#include "gpgme.h"
+#include "debug.h"
+#include "context.h"
+#include "util.h"
+#include "ops.h"
+
+
+static gpgme_error_t
+spawn_start (gpgme_ctx_t ctx, int synchronous,
+             const char *file, const char *argv[],
+             gpgme_data_t datain,
+             gpgme_data_t dataout, gpgme_data_t dataerr)
+{
+  gpgme_error_t err;
+  const char *tmp_argv[2];
+
+  if (ctx->protocol != GPGME_PROTOCOL_SPAWN)
+    return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
+
+  err = _gpgme_op_reset (ctx, synchronous);
+  if (err)
+    return err;
+
+  if (!argv)
+    {
+      tmp_argv[0] = _gpgme_get_basename (file);
+      tmp_argv[1] = NULL;
+      argv = tmp_argv;
+    }
+
+  return _gpgme_engine_op_spawn (ctx->engine, file, argv,
+                                 datain, dataout, dataerr);
+}
+
+
+/* Run the command FILE with the arguments in ARGV.  Connect stdin to
+   DATAIN, stdout to DATAOUT, and STDERR to DATAERR.  If one the data
+   streams is NULL, connect to /dev/null instead.  */
+gpgme_error_t
+gpgme_op_spawn_start (gpgme_ctx_t ctx, const char *file, const char *argv[],
+                      gpgme_data_t datain,
+                      gpgme_data_t dataout, gpgme_data_t dataerr,
+                      unsigned int flags)
+{
+  gpgme_error_t err;
+
+  TRACE_BEG2 (DEBUG_CTX, "gpgme_op_spawn_start", ctx, "file=(%s) flaggs=%x",
+              file, flags);
+
+  if (!ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+  err = spawn_start (ctx, 0, file, argv, datain, dataout, dataerr);
+  return err;
+}
+
+
+/* Run the command FILE with the arguments in ARGV.  Connect stdin to
+   DATAIN, stdout to DATAOUT, and STDERR to DATAERR.  If one the data
+   streams is NULL, connect to /dev/null instead.  Synchronous
+   variant. */
+gpgme_error_t
+gpgme_op_spawn (gpgme_ctx_t ctx, const char *file, const char *argv[],
+               gpgme_data_t datain,
+                gpgme_data_t dataout, gpgme_data_t dataerr,
+                unsigned int flags)
+{
+  gpgme_error_t err;
+
+  TRACE_BEG2 (DEBUG_CTX, "gpgme_op_spawn", ctx, "file=(%s) flags=%x",
+              file, flags);
+  if (!ctx)
+    return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
+
+  err = spawn_start (ctx, 1, file, argv, datain, dataout, dataerr);
+
+  if (!err)
+    err = _gpgme_wait_one (ctx);
+  return TRACE_ERR (err);
+}