summaryrefslogtreecommitdiff
path: root/src/engine-spawn.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine-spawn.c')
-rw-r--r--src/engine-spawn.c475
1 files changed, 475 insertions, 0 deletions
diff --git a/src/engine-spawn.c b/src/engine-spawn.c
new file mode 100644
index 0000000..8ffc628
--- /dev/null
+++ b/src/engine-spawn.c
@@ -0,0 +1,475 @@
+/* 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);
+
+
+
+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;
+}
+
+
+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;
+ const char *save_argv0 = NULL;
+
+ 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+1, 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;
+
+ if (argv[0] && !*argv[0])
+ {
+ save_argv0 = argv[0];
+ argv[0] = _gpgme_get_basename (file);
+ }
+ status = _gpgme_io_spawn (file, (char * const *)argv, spflags,
+ fd_list, NULL, NULL, &pid);
+ if (save_argv0)
+ argv[0] = save_argv0;
+ 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;
+}
+
+
+
+/*
+ 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;
+}
+
+
+
+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 */
+ };