diff options
Diffstat (limited to '')
| -rw-r--r-- | src/engine-spawn.c | 467 | 
1 files changed, 467 insertions, 0 deletions
| diff --git a/src/engine-spawn.c b/src/engine-spawn.c new file mode 100644 index 00000000..1e71c1cc --- /dev/null +++ b/src/engine-spawn.c @@ -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); + + + +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; + +  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; +} + + + +/* +    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 */ +  }; | 
