From 4f2d652e60700e03809307a10015ff9003ac3579 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 10 Apr 2014 13:01:00 +0200 Subject: [PATCH] Add GPGME_PROTOCOL_SPAWN and gpgme_op_spawn. * 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. --- NEWS | 8 + doc/gpgme.texi | 76 ++++++- src/Makefile.am | 3 +- src/engine-assuan.c | 3 +- src/engine-backend.h | 9 + src/engine-g13.c | 3 +- src/engine-gpg.c | 3 +- src/engine-gpgconf.c | 3 +- src/engine-gpgsm.c | 3 +- src/engine-spawn.c | 467 ++++++++++++++++++++++++++++++++++++++++++ src/engine-uiserver.c | 3 +- src/engine.c | 25 ++- src/engine.h | 6 + src/gpgme-tool.c | 64 ++++++ src/gpgme.c | 14 +- src/gpgme.def | 2 + src/gpgme.h.in | 23 ++- src/libgpgme.vers | 3 + src/spawn.c | 105 ++++++++++ 19 files changed, 804 insertions(+), 19 deletions(-) create mode 100644 src/engine-spawn.c create mode 100644 src/spawn.c diff --git a/NEWS b/NEWS index f625ab95..acba24df 100644 --- 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) diff --git a/doc/gpgme.texi b/doc/gpgme.texi index e12fd738..027e1eff 100644 --- a/doc/gpgme.texi +++ b/doc/gpgme.texi @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index abc014c7..82f5327a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/engine-assuan.c b/src/engine-assuan.c index 5ef30479..663b2eab 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -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 */ }; diff --git a/src/engine-backend.h b/src/engine-backend.h index 7e6c0c12..dbb9e932 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -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 */ diff --git a/src/engine-g13.c b/src/engine-g13.c index 75154ca0..a9717eec 100644 --- a/src/engine-g13.c +++ b/src/engine-g13.c @@ -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 */ }; diff --git a/src/engine-gpg.c b/src/engine-gpg.c index 9037dd7c..a8eab3a4 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -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 */ }; diff --git a/src/engine-gpgconf.c b/src/engine-gpgconf.c index 811ad9d7..a2407ac7 100644 --- a/src/engine-gpgconf.c +++ b/src/engine-gpgconf.c @@ -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 */ }; diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index 6bcc0952..710bf14a 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -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 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 . +*/ + +#if HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +# include +#endif +#ifdef HAVE_LOCALE_H +#include +#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 */ + }; diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index bd140f90..2738c366 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -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 */ }; diff --git a/src/engine.c b/src/engine.c index 4f2000c2..f5034306 100644 --- a/src/engine.c +++ b/src/engine.c @@ -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); +} diff --git a/src/engine.h b/src/engine.h index a0287add..ade7de15 100644 --- a/src/engine.h +++ b/src/engine.h @@ -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 */ diff --git a/src/gpgme-tool.c b/src/gpgme-tool.c index 2bf7654a..a980347c 100644 --- a/src/gpgme-tool.c +++ b/src/gpgme-tool.c @@ -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; diff --git a/src/gpgme.c b/src/gpgme.c index 438fef21..24b04fc8 100644 --- a/src/gpgme.c +++ b/src/gpgme.c @@ -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 . + */ #if HAVE_CONFIG_H #include @@ -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"; diff --git a/src/gpgme.def b/src/gpgme.def index ee0c42e2..dc189484 100644 --- a/src/gpgme.def +++ b/src/gpgme.def @@ -215,5 +215,7 @@ EXPORTS gpgme_get_dirinfo @162 + gpgme_op_spawn_start @163 + gpgme_op_spawn @164 ; END diff --git a/src/gpgme.h.in b/src/gpgme.h.in index 050051a4..655602df 100644 --- a/src/gpgme.h.in +++ b/src/gpgme.h.in @@ -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); + /* Key management functions. */ struct _gpgme_op_keylist_result diff --git a/src/libgpgme.vers b/src/libgpgme.vers index 4db1d408..39663c1c 100644 --- a/src/libgpgme.vers +++ b/src/libgpgme.vers @@ -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 index 00000000..e3454f3b --- /dev/null +++ b/src/spawn.c @@ -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 +#endif +#include + +#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); +}