2014-04-10 11:01:00 +00:00
|
|
|
|
/* 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;
|
2014-04-10 12:17:19 +00:00
|
|
|
|
const char *save_argv0 = NULL;
|
2014-04-10 11:01:00 +00:00
|
|
|
|
|
|
|
|
|
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++;
|
2014-05-08 18:35:57 +00:00
|
|
|
|
fd_list = calloc (n+1, sizeof *fd_list);
|
2014-04-10 11:01:00 +00:00
|
|
|
|
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;
|
|
|
|
|
|
2014-04-10 12:17:19 +00:00
|
|
|
|
if (argv[0] && !*argv[0])
|
|
|
|
|
{
|
|
|
|
|
save_argv0 = argv[0];
|
|
|
|
|
argv[0] = _gpgme_get_basename (file);
|
|
|
|
|
}
|
2014-04-10 11:01:00 +00:00
|
|
|
|
status = _gpgme_io_spawn (file, (char * const *)argv, spflags,
|
|
|
|
|
fd_list, NULL, NULL, &pid);
|
2014-04-10 12:17:19 +00:00
|
|
|
|
if (save_argv0)
|
|
|
|
|
argv[0] = save_argv0;
|
2014-04-10 11:01:00 +00:00
|
|
|
|
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 */
|
|
|
|
|
};
|