/* engine.c - GPGME engine support.
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003, 2004, 2006, 2009, 2010 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "gpgme.h"
#include "util.h"
#include "sema.h"
#include "ops.h"
#include "debug.h"
#include "engine.h"
#include "engine-backend.h"
struct engine
{
struct engine_ops *ops;
void *engine;
};
static struct engine_ops *engine_ops[] =
{
&_gpgme_engine_ops_gpg, /* OpenPGP. */
&_gpgme_engine_ops_gpgsm, /* CMS. */
&_gpgme_engine_ops_gpgconf, /* gpg-conf. */
&_gpgme_engine_ops_assuan, /* Low-Level Assuan. */
&_gpgme_engine_ops_g13, /* Crypto VFS. */
#ifdef ENABLE_UISERVER
&_gpgme_engine_ops_uiserver, /* UI-Server. */
#else
NULL,
#endif
&_gpgme_engine_ops_spawn
};
/* The engine info. */
static gpgme_engine_info_t engine_info;
DEFINE_STATIC_LOCK (engine_info_lock);
/* Get the file name of the engine for PROTOCOL. */
static const char *
engine_get_file_name (gpgme_protocol_t proto)
{
if (proto > DIM (engine_ops))
return NULL;
if (engine_ops[proto] && engine_ops[proto]->get_file_name)
return (*engine_ops[proto]->get_file_name) ();
else
return NULL;
}
/* Get the standard home dir of the engine for PROTOCOL. */
static const char *
engine_get_home_dir (gpgme_protocol_t proto)
{
if (proto > DIM (engine_ops))
return NULL;
if (engine_ops[proto] && engine_ops[proto]->get_home_dir)
return (*engine_ops[proto]->get_home_dir) ();
else
return NULL;
}
/* Get a malloced string containing the version number of the engine
for PROTOCOL. */
static char *
engine_get_version (gpgme_protocol_t proto, const char *file_name)
{
if (proto > DIM (engine_ops))
return NULL;
if (engine_ops[proto] && engine_ops[proto]->get_version)
return (*engine_ops[proto]->get_version) (file_name);
else
return NULL;
}
/* Get the required version number of the engine for PROTOCOL. */
static const char *
engine_get_req_version (gpgme_protocol_t proto)
{
if (proto > DIM (engine_ops))
return NULL;
if (engine_ops[proto] && engine_ops[proto]->get_req_version)
return (*engine_ops[proto]->get_req_version) ();
else
return NULL;
}
/* Verify the version requirement for the engine for PROTOCOL. */
gpgme_error_t
gpgme_engine_check_version (gpgme_protocol_t proto)
{
gpgme_error_t err;
gpgme_engine_info_t info;
int result;
LOCK (engine_info_lock);
info = engine_info;
if (!info)
{
/* Make sure it is initialized. */
UNLOCK (engine_info_lock);
err = gpgme_get_engine_info (&info);
if (err)
return err;
LOCK (engine_info_lock);
}
while (info && info->protocol != proto)
info = info->next;
if (!info)
result = 0;
else
result = _gpgme_compare_versions (info->version,
info->req_version);
UNLOCK (engine_info_lock);
return result ? 0 : trace_gpg_error (GPG_ERR_INV_ENGINE);
}
/* Release the engine info INFO. */
void
_gpgme_engine_info_release (gpgme_engine_info_t info)
{
while (info)
{
gpgme_engine_info_t next_info = info->next;
assert (info->file_name);
free (info->file_name);
if (info->home_dir)
free (info->home_dir);
if (info->version)
free (info->version);
free (info);
info = next_info;
}
}
/* Get the information about the configured and installed engines. A
pointer to the first engine in the statically allocated linked list
is returned in *INFO. If an error occurs, it is returned. The
returned data is valid until the next gpgme_set_engine_info. */
gpgme_error_t
gpgme_get_engine_info (gpgme_engine_info_t *info)
{
gpgme_error_t err;
LOCK (engine_info_lock);
if (!engine_info)
{
gpgme_engine_info_t *lastp = &engine_info;
gpgme_protocol_t proto_list[] = { GPGME_PROTOCOL_OpenPGP,
GPGME_PROTOCOL_CMS,
GPGME_PROTOCOL_GPGCONF,
GPGME_PROTOCOL_ASSUAN,
GPGME_PROTOCOL_G13,
GPGME_PROTOCOL_UISERVER,
GPGME_PROTOCOL_SPAWN };
unsigned int proto;
err = 0;
for (proto = 0; proto < DIM (proto_list); proto++)
{
const char *ofile_name = engine_get_file_name (proto_list[proto]);
const char *ohome_dir = engine_get_home_dir (proto_list[proto]);
char *file_name;
char *home_dir;
if (!ofile_name)
continue;
file_name = strdup (ofile_name);
if (!file_name)
err = gpg_error_from_syserror ();
if (ohome_dir)
{
home_dir = strdup (ohome_dir);
if (!home_dir && !err)
err = gpg_error_from_syserror ();
}
else
home_dir = NULL;
*lastp = malloc (sizeof (*engine_info));
if (!*lastp && !err)
err = gpg_error_from_syserror ();
if (err)
{
_gpgme_engine_info_release (engine_info);
engine_info = NULL;
if (file_name)
free (file_name);
if (home_dir)
free (home_dir);
UNLOCK (engine_info_lock);
return err;
}
(*lastp)->protocol = proto_list[proto];
(*lastp)->file_name = file_name;
(*lastp)->home_dir = home_dir;
(*lastp)->version = engine_get_version (proto_list[proto], NULL);
(*lastp)->req_version = engine_get_req_version (proto_list[proto]);
(*lastp)->next = NULL;
lastp = &(*lastp)->next;
}
}
*info = engine_info;
UNLOCK (engine_info_lock);
return 0;
}
/* Get a deep copy of the engine info and return it in INFO. */
gpgme_error_t
_gpgme_engine_info_copy (gpgme_engine_info_t *r_info)
{
gpgme_error_t err = 0;
gpgme_engine_info_t info;
gpgme_engine_info_t new_info;
gpgme_engine_info_t *lastp;
LOCK (engine_info_lock);
info = engine_info;
if (!info)
{
/* Make sure it is initialized. */
UNLOCK (engine_info_lock);
err = gpgme_get_engine_info (&info);
if (err)
return err;
LOCK (engine_info_lock);
}
new_info = NULL;
lastp = &new_info;
while (info)
{
char *file_name;
char *home_dir;
char *version;
assert (info->file_name);
file_name = strdup (info->file_name);
if (!file_name)
err = gpg_error_from_syserror ();
if (info->home_dir)
{
home_dir = strdup (info->home_dir);
if (!home_dir && !err)
err = gpg_error_from_syserror ();
}
else
home_dir = NULL;
if (info->version)
{
version = strdup (info->version);
if (!version && !err)
err = gpg_error_from_syserror ();
}
else
version = NULL;
*lastp = malloc (sizeof (*engine_info));
if (!*lastp && !err)
err = gpg_error_from_syserror ();
if (err)
{
_gpgme_engine_info_release (new_info);
if (file_name)
free (file_name);
if (home_dir)
free (home_dir);
if (version)
free (version);
UNLOCK (engine_info_lock);
return err;
}
(*lastp)->protocol = info->protocol;
(*lastp)->file_name = file_name;
(*lastp)->home_dir = home_dir;
(*lastp)->version = version;
(*lastp)->req_version = info->req_version;
(*lastp)->next = NULL;
lastp = &(*lastp)->next;
info = info->next;
}
*r_info = new_info;
UNLOCK (engine_info_lock);
return 0;
}
/* Set the engine info for the info list INFO, protocol PROTO, to the
file name FILE_NAME and the home directory HOME_DIR. */
gpgme_error_t
_gpgme_set_engine_info (gpgme_engine_info_t info, gpgme_protocol_t proto,
const char *file_name, const char *home_dir)
{
char *new_file_name;
char *new_home_dir;
/* FIXME: Use some PROTO_MAX definition. */
if (proto > DIM (engine_ops))
return gpg_error (GPG_ERR_INV_VALUE);
while (info && info->protocol != proto)
info = info->next;
if (!info)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
/* Prepare new members. */
if (file_name)
new_file_name = strdup (file_name);
else
{
const char *ofile_name = engine_get_file_name (proto);
assert (ofile_name);
new_file_name = strdup (ofile_name);
}
if (!new_file_name)
return gpg_error_from_syserror ();
if (home_dir)
{
new_home_dir = strdup (home_dir);
if (!new_home_dir)
{
free (new_file_name);
return gpg_error_from_syserror ();
}
}
else
{
const char *ohome_dir = engine_get_home_dir (proto);
if (ohome_dir)
{
new_home_dir = strdup (ohome_dir);
if (!new_home_dir)
{
free (new_file_name);
return gpg_error_from_syserror ();
}
}
else
new_home_dir = NULL;
}
/* Remove the old members. */
assert (info->file_name);
free (info->file_name);
if (info->home_dir)
free (info->home_dir);
if (info->version)
free (info->version);
/* Install the new members. */
info->file_name = new_file_name;
info->home_dir = new_home_dir;
info->version = engine_get_version (proto, new_file_name);
return 0;
}
/* Set the default engine info for the protocol PROTO to the file name
FILE_NAME and the home directory HOME_DIR. */
gpgme_error_t
gpgme_set_engine_info (gpgme_protocol_t proto,
const char *file_name, const char *home_dir)
{
gpgme_error_t err;
gpgme_engine_info_t info;
LOCK (engine_info_lock);
info = engine_info;
if (!info)
{
/* Make sure it is initialized. */
UNLOCK (engine_info_lock);
err = gpgme_get_engine_info (&info);
if (err)
return err;
LOCK (engine_info_lock);
}
err = _gpgme_set_engine_info (info, proto, file_name, home_dir);
UNLOCK (engine_info_lock);
return err;
}
gpgme_error_t
_gpgme_engine_new (gpgme_engine_info_t info, engine_t *r_engine)
{
engine_t engine;
if (!info->file_name || !info->version)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
engine = calloc (1, sizeof *engine);
if (!engine)
return gpg_error_from_syserror ();
engine->ops = engine_ops[info->protocol];
if (engine->ops->new)
{
gpgme_error_t err;
err = (*engine->ops->new) (&engine->engine,
info->file_name, info->home_dir);
if (err)
{
free (engine);
return err;
}
}
else
engine->engine = NULL;
*r_engine = engine;
return 0;
}
gpgme_error_t
_gpgme_engine_reset (engine_t engine)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->reset)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->reset) (engine->engine);
}
void
_gpgme_engine_release (engine_t engine)
{
if (!engine)
return;
if (engine->ops->release)
(*engine->ops->release) (engine->engine);
free (engine);
}
/* Set a status callback which is used to monitor the status values
* before they are passed to a handler set with
* _gpgme_engine_set_status_handler. */
void
_gpgme_engine_set_status_cb (engine_t engine,
gpgme_status_cb_t cb, void *cb_value)
{
if (!engine)
return;
if (engine->ops->set_status_cb)
(*engine->ops->set_status_cb) (engine->engine, cb, cb_value);
}
void
_gpgme_engine_set_status_handler (engine_t engine,
engine_status_handler_t fnc, void *fnc_value)
{
if (!engine)
return;
if (engine->ops->set_status_handler)
(*engine->ops->set_status_handler) (engine->engine, fnc, fnc_value);
}
gpgme_error_t
_gpgme_engine_set_command_handler (engine_t engine,
engine_command_handler_t fnc,
void *fnc_value,
gpgme_data_t linked_data)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->set_command_handler)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->set_command_handler) (engine->engine,
fnc, fnc_value, linked_data);
}
gpgme_error_t
_gpgme_engine_set_colon_line_handler (engine_t engine,
engine_colon_line_handler_t fnc,
void *fnc_value)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->set_colon_line_handler)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->set_colon_line_handler) (engine->engine,
fnc, fnc_value);
}
gpgme_error_t
_gpgme_engine_set_locale (engine_t engine, int category,
const char *value)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->set_locale)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->set_locale) (engine->engine, category, value);
}
gpgme_error_t
_gpgme_engine_set_protocol (engine_t engine, gpgme_protocol_t protocol)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->set_protocol)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->set_protocol) (engine->engine, protocol);
}
gpgme_error_t
_gpgme_engine_op_decrypt (engine_t engine, gpgme_data_t ciph,
gpgme_data_t plain)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->decrypt)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->decrypt) (engine->engine, ciph, plain);
}
gpgme_error_t
_gpgme_engine_op_decrypt_verify (engine_t engine, gpgme_data_t ciph,
gpgme_data_t plain)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->decrypt_verify)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->decrypt_verify) (engine->engine, ciph, plain);
}
gpgme_error_t
_gpgme_engine_op_delete (engine_t engine, gpgme_key_t key,
int allow_secret)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->delete)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->delete) (engine->engine, key, allow_secret);
}
gpgme_error_t
_gpgme_engine_op_edit (engine_t engine, int type, gpgme_key_t key,
gpgme_data_t out, gpgme_ctx_t ctx /* FIXME */)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->edit)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->edit) (engine->engine, type, key, out, ctx);
}
gpgme_error_t
_gpgme_engine_op_encrypt (engine_t engine, gpgme_key_t recp[],
gpgme_encrypt_flags_t flags,
gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->encrypt)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->encrypt) (engine->engine, recp, flags, plain, ciph,
use_armor);
}
gpgme_error_t
_gpgme_engine_op_encrypt_sign (engine_t engine, gpgme_key_t recp[],
gpgme_encrypt_flags_t flags,
gpgme_data_t plain, gpgme_data_t ciph,
int use_armor, gpgme_ctx_t ctx /* FIXME */)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->encrypt_sign)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->encrypt_sign) (engine->engine, recp, flags,
plain, ciph, use_armor, ctx);
}
gpgme_error_t
_gpgme_engine_op_export (engine_t engine, const char *pattern,
gpgme_export_mode_t mode, gpgme_data_t keydata,
int use_armor)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->export)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->export) (engine->engine, pattern, mode,
keydata, use_armor);
}
gpgme_error_t
_gpgme_engine_op_export_ext (engine_t engine, const char *pattern[],
unsigned int reserved, gpgme_data_t keydata,
int use_armor)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->export_ext)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->export_ext) (engine->engine, pattern, reserved,
keydata, use_armor);
}
gpgme_error_t
_gpgme_engine_op_genkey (engine_t engine, gpgme_data_t help_data,
int use_armor, gpgme_data_t pubkey,
gpgme_data_t seckey)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->genkey)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->genkey) (engine->engine, help_data, use_armor,
pubkey, seckey);
}
gpgme_error_t
_gpgme_engine_op_import (engine_t engine, gpgme_data_t keydata,
gpgme_key_t *keyarray)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->import)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->import) (engine->engine, keydata, keyarray);
}
gpgme_error_t
_gpgme_engine_op_keylist (engine_t engine, const char *pattern,
int secret_only, gpgme_keylist_mode_t mode,
int engine_flags)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->keylist)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->keylist) (engine->engine, pattern, secret_only, mode,
engine_flags);
}
gpgme_error_t
_gpgme_engine_op_keylist_ext (engine_t engine, const char *pattern[],
int secret_only, int reserved,
gpgme_keylist_mode_t mode, int engine_flags)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->keylist_ext)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->keylist_ext) (engine->engine, pattern, secret_only,
reserved, mode, engine_flags);
}
gpgme_error_t
_gpgme_engine_op_sign (engine_t engine, gpgme_data_t in, gpgme_data_t out,
gpgme_sig_mode_t mode, int use_armor,
int use_textmode, int include_certs,
gpgme_ctx_t ctx /* FIXME */)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->sign)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->sign) (engine->engine, in, out, mode, use_armor,
use_textmode, include_certs, ctx);
}
gpgme_error_t
_gpgme_engine_op_trustlist (engine_t engine, const char *pattern)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->trustlist)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->trustlist) (engine->engine, pattern);
}
gpgme_error_t
_gpgme_engine_op_verify (engine_t engine, gpgme_data_t sig,
gpgme_data_t signed_text, gpgme_data_t plaintext)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->verify)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->verify) (engine->engine, sig, signed_text, plaintext);
}
gpgme_error_t
_gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output,
unsigned int flags)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->getauditlog)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->getauditlog) (engine->engine, output, flags);
}
gpgme_error_t
_gpgme_engine_op_assuan_transact (engine_t engine,
const char *command,
gpgme_assuan_data_cb_t data_cb,
void *data_cb_value,
gpgme_assuan_inquire_cb_t inq_cb,
void *inq_cb_value,
gpgme_assuan_status_cb_t status_cb,
void *status_cb_value)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->opassuan_transact)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->opassuan_transact) (engine->engine,
command,
data_cb, data_cb_value,
inq_cb, inq_cb_value,
status_cb, status_cb_value);
}
gpgme_error_t
_gpgme_engine_op_conf_load (engine_t engine, gpgme_conf_comp_t *conf_p)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->conf_load)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->conf_load) (engine->engine, conf_p);
}
gpgme_error_t
_gpgme_engine_op_conf_save (engine_t engine, gpgme_conf_comp_t conf)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->conf_save)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->conf_save) (engine->engine, conf);
}
void
_gpgme_engine_set_io_cbs (engine_t engine, gpgme_io_cbs_t io_cbs)
{
if (!engine)
return;
(*engine->ops->set_io_cbs) (engine->engine, io_cbs);
}
void
_gpgme_engine_io_event (engine_t engine,
gpgme_event_io_t type, void *type_data)
{
if (!engine)
return;
(*engine->ops->io_event) (engine->engine, type, type_data);
}
/* Cancel the session and the pending operation if any. */
gpgme_error_t
_gpgme_engine_cancel (engine_t engine)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->cancel)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->cancel) (engine->engine);
}
/* Cancel the pending operation, but not the complete session. */
gpgme_error_t
_gpgme_engine_cancel_op (engine_t engine)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->cancel_op)
return 0;
return (*engine->ops->cancel_op) (engine->engine);
}
/* Change the passphrase for KEY. */
gpgme_error_t
_gpgme_engine_op_passwd (engine_t engine, gpgme_key_t key,
unsigned int flags)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->passwd)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
return (*engine->ops->passwd) (engine->engine, key, flags);
}
/* Set the pinentry mode for ENGINE to MODE. */
gpgme_error_t
_gpgme_engine_set_pinentry_mode (engine_t engine, gpgme_pinentry_mode_t mode)
{
if (!engine)
return gpg_error (GPG_ERR_INV_VALUE);
if (!engine->ops->set_pinentry_mode)
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
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,
unsigned int flags)
{
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, flags);
}