
* src/genrandom.c: New. * src/Makefile.am: Add new file. * src/engine-backend.h (struct engine_ops): Add func ptr getdirect. Adjust all engine_ops. * src/engine-gpg.c (gpg_getdirect): New. (_gpgme_engine_ops_gpg): Connect new handler. * src/gpgme.h.in (gpgme_random_mode_t): New. (GPGME_RANDOM_MODE_NORMAL): New. (GPGME_RANDOM_MODE_ZBASE32): New. (gpgme_op_random_bytes): New public function * src/libgpgme.vers: Add function. * src/gpgme.def: Add function. * tests/run-genrandom.c: New. * tests/Makefile.am: Add new file. -- This is a first take on this the mode parameter allows to extend the function if ever needed. Due to the gpg calling and fd setup overhead this function is not yet very fast but its purpose is to get "approved" random bytes. We might eventually extend it to keep a small internal cache of random numbers and get for example 128 random bytes directly from gpg and deliver only the few required. GnuPG-bug-id: 6694
857 lines
22 KiB
C
857 lines
22 KiB
C
/* engine-assuan.c - Low-level Assuan protocol engine
|
||
* Copyright (C) 2009 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 <https://gnu.org/licenses/>.
|
||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||
*/
|
||
|
||
/*
|
||
Note: This engine requires a modern Assuan server which uses
|
||
gpg-error codes. In particular there is no backward compatible
|
||
mapping of old Assuan error codes implemented.
|
||
*/
|
||
|
||
|
||
#if HAVE_CONFIG_H
|
||
#include <config.h>
|
||
#endif
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#ifdef HAVE_SYS_TYPES_H
|
||
# include <sys/types.h>
|
||
#endif
|
||
#include <assert.h>
|
||
#ifdef HAVE_UNISTD_H
|
||
# include <unistd.h>
|
||
#endif
|
||
#ifdef HAVE_LOCALE_H
|
||
#include <locale.h>
|
||
#endif
|
||
#include <errno.h>
|
||
|
||
#include "gpgme.h"
|
||
#include "util.h"
|
||
#include "ops.h"
|
||
#include "wait.h"
|
||
#include "priv-io.h"
|
||
#include "sema.h"
|
||
|
||
#include "assuan.h"
|
||
#include "debug.h"
|
||
|
||
#include "engine-backend.h"
|
||
|
||
|
||
typedef struct
|
||
{
|
||
int fd; /* FD we talk about. */
|
||
int server_fd;/* Server FD for this connection. */
|
||
int dir; /* Inbound/Outbound, maybe given implicit? */
|
||
void *data; /* Handler-specific data. */
|
||
void *tag; /* ID from the user for gpgme_remove_io_callback. */
|
||
} iocb_data_t;
|
||
|
||
/* Engine instance data. */
|
||
struct engine_llass
|
||
{
|
||
assuan_context_t assuan_ctx;
|
||
|
||
int lc_ctype_set;
|
||
int lc_messages_set;
|
||
|
||
iocb_data_t status_cb;
|
||
|
||
struct gpgme_io_cbs io_cbs;
|
||
|
||
/* Hack for old opassuan.c interface, see there the result struct. */
|
||
gpg_error_t last_op_err;
|
||
|
||
/* User provided callbacks. */
|
||
struct {
|
||
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;
|
||
} user;
|
||
|
||
/* Option flags. */
|
||
struct {
|
||
int gpg_agent:1; /* Assume this is a gpg-agent connection. */
|
||
} opt;
|
||
|
||
char request_origin[10]; /* Copy from the CTX. */
|
||
};
|
||
typedef struct engine_llass *engine_llass_t;
|
||
|
||
|
||
gpg_error_t _gpgme_engine_assuan_last_op_err (void *engine)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
return llass->last_op_err;
|
||
}
|
||
|
||
|
||
/* Prototypes. */
|
||
static void llass_io_event (void *engine,
|
||
gpgme_event_io_t type, void *type_data);
|
||
|
||
|
||
|
||
|
||
|
||
/* return the default home directory. */
|
||
static const char *
|
||
llass_get_home_dir (void)
|
||
{
|
||
/* For this engine the home directory is not a filename but a string
|
||
used to convey options. The exclamation mark is a marker to show
|
||
that this is not a directory name. Options are strings delimited
|
||
by a space. The only option defined for now is GPG_AGENT to
|
||
enable GPG_AGENT specific commands to send to the server at
|
||
connection startup. */
|
||
return "!GPG_AGENT";
|
||
}
|
||
|
||
static char *
|
||
llass_get_version (const char *file_name)
|
||
{
|
||
(void)file_name;
|
||
return NULL;
|
||
}
|
||
|
||
|
||
static const char *
|
||
llass_get_req_version (void)
|
||
{
|
||
return NULL;
|
||
}
|
||
|
||
|
||
static void
|
||
close_notify_handler (int fd, void *opaque)
|
||
{
|
||
engine_llass_t llass = opaque;
|
||
|
||
assert (fd != -1);
|
||
if (llass->status_cb.fd == fd)
|
||
{
|
||
if (llass->status_cb.tag)
|
||
llass->io_cbs.remove (llass->status_cb.tag);
|
||
llass->status_cb.fd = -1;
|
||
llass->status_cb.tag = NULL;
|
||
}
|
||
}
|
||
|
||
|
||
|
||
static gpgme_error_t
|
||
llass_cancel (void *engine)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
|
||
if (!llass)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
if (llass->status_cb.fd != -1)
|
||
_gpgme_io_close (llass->status_cb.fd);
|
||
|
||
if (llass->assuan_ctx)
|
||
{
|
||
assuan_release (llass->assuan_ctx);
|
||
llass->assuan_ctx = NULL;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
llass_cancel_op (void *engine)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
|
||
if (!llass)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
if (llass->status_cb.fd != -1)
|
||
_gpgme_io_close (llass->status_cb.fd);
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
static void
|
||
llass_release (void *engine)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
|
||
if (!llass)
|
||
return;
|
||
|
||
llass_cancel (engine);
|
||
|
||
free (llass);
|
||
}
|
||
|
||
|
||
/* Create a new instance. If HOME_DIR is NULL standard options for use
|
||
with gpg-agent are issued. */
|
||
static gpgme_error_t
|
||
llass_new (void **engine, const char *file_name, const char *home_dir,
|
||
const char *version)
|
||
{
|
||
gpgme_error_t err = 0;
|
||
engine_llass_t llass;
|
||
char *optstr;
|
||
char *env_tty = NULL;
|
||
|
||
(void)version; /* Not yet used. */
|
||
|
||
llass = calloc (1, sizeof *llass);
|
||
if (!llass)
|
||
return gpg_error_from_syserror ();
|
||
|
||
llass->status_cb.fd = -1;
|
||
llass->status_cb.dir = 1;
|
||
llass->status_cb.tag = 0;
|
||
llass->status_cb.data = llass;
|
||
|
||
/* Parse_options. */
|
||
if (home_dir && *home_dir == '!')
|
||
{
|
||
home_dir++;
|
||
/* Very simple parser only working for the one option we support. */
|
||
/* Note that wk promised to write a regression test if this
|
||
parser will be extended. */
|
||
if (!strncmp (home_dir, "GPG_AGENT", 9)
|
||
&& (!home_dir[9] || home_dir[9] == ' '))
|
||
llass->opt.gpg_agent = 1;
|
||
}
|
||
|
||
err = assuan_new_ext (&llass->assuan_ctx, GPG_ERR_SOURCE_GPGME,
|
||
&_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
|
||
NULL);
|
||
if (err)
|
||
goto leave;
|
||
assuan_ctx_set_system_hooks (llass->assuan_ctx, &_gpgme_assuan_system_hooks);
|
||
assuan_set_flag (llass->assuan_ctx, ASSUAN_CONVEY_COMMENTS, 1);
|
||
|
||
err = assuan_socket_connect (llass->assuan_ctx, file_name, 0, 0);
|
||
if (err)
|
||
goto leave;
|
||
|
||
if (llass->opt.gpg_agent)
|
||
{
|
||
char *dft_display = NULL;
|
||
|
||
err = _gpgme_getenv ("DISPLAY", &dft_display);
|
||
if (err)
|
||
goto leave;
|
||
if (dft_display)
|
||
{
|
||
if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
free (dft_display);
|
||
goto leave;
|
||
}
|
||
free (dft_display);
|
||
|
||
err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
|
||
NULL, NULL, NULL);
|
||
gpgrt_free (optstr);
|
||
if (err)
|
||
goto leave;
|
||
}
|
||
}
|
||
|
||
if (llass->opt.gpg_agent)
|
||
err = _gpgme_getenv ("GPG_TTY", &env_tty);
|
||
|
||
if (llass->opt.gpg_agent && (isatty (1) || env_tty || err))
|
||
{
|
||
int rc = 0;
|
||
char dft_ttyname[64];
|
||
char *dft_ttytype = NULL;
|
||
|
||
if (err)
|
||
goto leave;
|
||
else if (env_tty)
|
||
{
|
||
snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty);
|
||
free (env_tty);
|
||
}
|
||
else
|
||
rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname));
|
||
|
||
/* Even though isatty() returns 1, ttyname_r() may fail in many
|
||
ways, e.g., when /dev/pts is not accessible under chroot. */
|
||
if (!rc)
|
||
{
|
||
if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
goto leave;
|
||
}
|
||
err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL,
|
||
NULL, NULL, NULL);
|
||
gpgrt_free (optstr);
|
||
if (err)
|
||
goto leave;
|
||
|
||
err = _gpgme_getenv ("TERM", &dft_ttytype);
|
||
if (err)
|
||
goto leave;
|
||
if (dft_ttytype)
|
||
{
|
||
if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0)
|
||
{
|
||
err = gpg_error_from_syserror ();
|
||
free (dft_ttytype);
|
||
goto leave;
|
||
}
|
||
free (dft_ttytype);
|
||
|
||
err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
|
||
NULL, NULL, NULL, NULL);
|
||
gpgrt_free (optstr);
|
||
if (err)
|
||
goto leave;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
/* Under Windows we need to use AllowSetForegroundWindow. Tell
|
||
llass to tell us when it needs it. */
|
||
if (!err && llass->opt.gpg_agent)
|
||
{
|
||
err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify",
|
||
NULL, NULL, NULL, NULL, NULL, NULL);
|
||
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
|
||
err = 0; /* This work only with recent gpg-agents. */
|
||
}
|
||
#endif /*HAVE_W32_SYSTEM*/
|
||
|
||
|
||
leave:
|
||
/* Close the server ends of the pipes (because of this, we must use
|
||
the stored server_fd_str in the function start). Our ends are
|
||
closed in llass_release(). */
|
||
|
||
if (err)
|
||
llass_release (llass);
|
||
else
|
||
*engine = llass;
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
/* Copy flags from CTX into the engine object. */
|
||
static void
|
||
llass_set_engine_flags (void *engine, const gpgme_ctx_t ctx)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
|
||
if (ctx->request_origin)
|
||
{
|
||
if (strlen (ctx->request_origin) + 1 > sizeof llass->request_origin)
|
||
strcpy (llass->request_origin, "xxx"); /* Too long - force error */
|
||
else
|
||
strcpy (llass->request_origin, ctx->request_origin);
|
||
}
|
||
else
|
||
*llass->request_origin = 0;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
llass_set_locale (void *engine, int category, const char *value)
|
||
{
|
||
gpgme_error_t err;
|
||
engine_llass_t llass = engine;
|
||
char *optstr;
|
||
const char *catstr;
|
||
|
||
if (!llass->opt.gpg_agent)
|
||
return 0;
|
||
|
||
/* FIXME: If value is NULL, we need to reset the option to default.
|
||
But we can't do this. So we error out here. gpg-agent needs
|
||
support for this. */
|
||
if (0)
|
||
;
|
||
#ifdef LC_CTYPE
|
||
else if (category == LC_CTYPE)
|
||
{
|
||
catstr = "lc-ctype";
|
||
if (!value && llass->lc_ctype_set)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (value)
|
||
llass->lc_ctype_set = 1;
|
||
}
|
||
#endif
|
||
#ifdef LC_MESSAGES
|
||
else if (category == LC_MESSAGES)
|
||
{
|
||
catstr = "lc-messages";
|
||
if (!value && llass->lc_messages_set)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
if (value)
|
||
llass->lc_messages_set = 1;
|
||
}
|
||
#endif /* LC_MESSAGES */
|
||
else
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
/* FIXME: Reset value to default. */
|
||
if (!value)
|
||
return 0;
|
||
|
||
if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0)
|
||
err = gpg_error_from_syserror ();
|
||
else
|
||
{
|
||
err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL,
|
||
NULL, NULL, NULL, NULL);
|
||
gpgrt_free (optstr);
|
||
}
|
||
return err;
|
||
}
|
||
|
||
|
||
/* This is the inquiry callback. It handles stuff which ee need to
|
||
handle here and passes everything on to the user callback. */
|
||
static gpgme_error_t
|
||
inquire_cb (engine_llass_t llass, const char *keyword, const char *args)
|
||
{
|
||
gpg_error_t err, err2;
|
||
gpgme_data_t data = NULL;
|
||
char buf[1024];
|
||
gpgme_ssize_t n;
|
||
|
||
if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED"))
|
||
{
|
||
_gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10));
|
||
}
|
||
|
||
if (llass->user.inq_cb)
|
||
{
|
||
err = llass->user.inq_cb (llass->user.inq_cb_value,
|
||
keyword, args, &data);
|
||
if (!err && data)
|
||
{
|
||
while ((n = gpgme_data_read (data, buf, sizeof buf)) > 0)
|
||
{
|
||
err = assuan_send_data (llass->assuan_ctx, buf, n);
|
||
if (err)
|
||
break;
|
||
}
|
||
/* Tell the caller that we are finished with the data
|
||
* object. The error code from assuan_send_data has
|
||
* priority over the one from the cleanup function. */
|
||
err2 = llass->user.inq_cb (llass->user.inq_cb_value,
|
||
NULL, NULL, &data);
|
||
if (!err)
|
||
err = err2;
|
||
}
|
||
}
|
||
else
|
||
err = 0;
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
llass_status_handler (void *opaque, int fd)
|
||
{
|
||
struct io_cb_data *data = (struct io_cb_data *) opaque;
|
||
engine_llass_t llass = (engine_llass_t) data->handler_value;
|
||
gpgme_error_t err = 0;
|
||
char *line;
|
||
size_t linelen;
|
||
|
||
do
|
||
{
|
||
err = assuan_read_line (llass->assuan_ctx, &line, &linelen);
|
||
if (err)
|
||
{
|
||
/* Reading a full line may not be possible when
|
||
communicating over a socket in nonblocking mode. In this
|
||
case, we are done for now. */
|
||
if (gpg_err_code (err) == GPG_ERR_EAGAIN)
|
||
{
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: EAGAIN reading assuan line (ignored)", fd);
|
||
err = 0;
|
||
continue;
|
||
}
|
||
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: error reading assuan line: %s",
|
||
fd, gpg_strerror (err));
|
||
}
|
||
else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ')
|
||
{
|
||
char *src = line + 2;
|
||
char *end = line + linelen;
|
||
char *dst = src;
|
||
|
||
linelen = 0;
|
||
while (src < end)
|
||
{
|
||
if (*src == '%' && src + 2 < end)
|
||
{
|
||
/* Handle escaped characters. */
|
||
++src;
|
||
*dst++ = _gpgme_hextobyte (src);
|
||
src += 2;
|
||
}
|
||
else
|
||
*dst++ = *src++;
|
||
|
||
linelen++;
|
||
}
|
||
|
||
src = line + 2;
|
||
if (linelen && llass->user.data_cb)
|
||
err = llass->user.data_cb (llass->user.data_cb_value,
|
||
src, linelen);
|
||
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: D inlinedata; status from cb: %s",
|
||
fd, (llass->user.data_cb ?
|
||
(err? gpg_strerror (err):"ok"):"no callback"));
|
||
}
|
||
else if (linelen >= 3
|
||
&& line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
|
||
&& (line[3] == '\0' || line[3] == ' '))
|
||
{
|
||
/* END received. Tell the data callback. */
|
||
if (llass->user.data_cb)
|
||
err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0);
|
||
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: END line; status from cb: %s",
|
||
fd, (llass->user.data_cb ?
|
||
(err? gpg_strerror (err):"ok"):"no callback"));
|
||
}
|
||
else if (linelen > 2 && line[0] == 'S' && line[1] == ' ')
|
||
{
|
||
char *args;
|
||
char *src;
|
||
|
||
for (src=line+2; *src == ' '; src++)
|
||
;
|
||
|
||
args = strchr (src, ' ');
|
||
if (!args)
|
||
args = line + linelen; /* Let it point to an empty string. */
|
||
else
|
||
*(args++) = 0;
|
||
|
||
while (*args == ' ')
|
||
args++;
|
||
|
||
if (llass->user.status_cb)
|
||
err = llass->user.status_cb (llass->user.status_cb_value,
|
||
src, args);
|
||
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: S line (%s) - status from cb: %s",
|
||
fd, line+2, (llass->user.status_cb ?
|
||
(err? gpg_strerror (err):"ok"):"no callback"));
|
||
}
|
||
else if (linelen >= 7
|
||
&& line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
|
||
&& line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
|
||
&& line[6] == 'E'
|
||
&& (line[7] == '\0' || line[7] == ' '))
|
||
{
|
||
char *src;
|
||
char *args;
|
||
|
||
for (src=line+7; *src == ' '; src++)
|
||
;
|
||
|
||
args = strchr (src, ' ');
|
||
if (!args)
|
||
args = line + linelen; /* Let it point to an empty string. */
|
||
else
|
||
*(args++) = 0;
|
||
|
||
while (*args == ' ')
|
||
args++;
|
||
|
||
err = inquire_cb (llass, src, args);
|
||
if (!err)
|
||
{
|
||
/* Flush and send END. */
|
||
err = assuan_send_data (llass->assuan_ctx, NULL, 0);
|
||
}
|
||
else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
|
||
{
|
||
/* Flush and send CANcel. */
|
||
err = assuan_send_data (llass->assuan_ctx, NULL, 1);
|
||
}
|
||
}
|
||
else if (linelen >= 3
|
||
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
|
||
&& (line[3] == '\0' || line[3] == ' '))
|
||
{
|
||
if (line[3] == ' ')
|
||
err = atoi (line+4);
|
||
else
|
||
err = gpg_error (GPG_ERR_GENERAL);
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: ERR line: %s",
|
||
fd, err ? gpg_strerror (err) : "ok");
|
||
|
||
/* Command execution errors are not fatal, as we use
|
||
a session based protocol. */
|
||
data->op_err = err;
|
||
llass->last_op_err = err;
|
||
|
||
/* The caller will do the rest (namely, call cancel_op,
|
||
which closes status_fd). */
|
||
return 0;
|
||
}
|
||
else if (linelen >= 2
|
||
&& line[0] == 'O' && line[1] == 'K'
|
||
&& (line[2] == '\0' || line[2] == ' '))
|
||
{
|
||
TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass,
|
||
"fd 0x%x: OK line", fd);
|
||
|
||
llass->last_op_err = 0;
|
||
|
||
_gpgme_io_close (llass->status_cb.fd);
|
||
return 0;
|
||
}
|
||
else
|
||
{
|
||
/* Comment line or invalid line. */
|
||
}
|
||
|
||
}
|
||
while (!err && assuan_pending_line (llass->assuan_ctx));
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler)
|
||
{
|
||
gpgme_error_t err;
|
||
|
||
TRACE_BEG (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass,
|
||
"fd=%d, dir %d", iocbd->fd, iocbd->dir);
|
||
err = (*llass->io_cbs.add) (llass->io_cbs.add_priv,
|
||
iocbd->fd, iocbd->dir,
|
||
handler, iocbd->data, &iocbd->tag);
|
||
if (err)
|
||
return TRACE_ERR (err);
|
||
if (!iocbd->dir)
|
||
/* FIXME Kludge around poll() problem. */
|
||
err = _gpgme_io_set_nonblocking (iocbd->fd);
|
||
return TRACE_ERR (err);
|
||
}
|
||
|
||
|
||
static gpgme_error_t
|
||
start (engine_llass_t llass, const char *command)
|
||
{
|
||
gpgme_error_t err;
|
||
assuan_fd_t afdlist[5];
|
||
int fdlist[5];
|
||
int nfds;
|
||
int i;
|
||
|
||
if (*llass->request_origin && llass->opt.gpg_agent)
|
||
{
|
||
char *cmd;
|
||
|
||
cmd = _gpgme_strconcat ("OPTION pretend-request-origin=",
|
||
llass->request_origin, NULL);
|
||
if (!cmd)
|
||
return gpg_error_from_syserror ();
|
||
err = assuan_transact (llass->assuan_ctx, cmd, NULL, NULL, NULL,
|
||
NULL, NULL, NULL);
|
||
free (cmd);
|
||
if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION)
|
||
return err;
|
||
}
|
||
|
||
/* We need to know the fd used by assuan for reads. We do this by
|
||
using the assumption that the first returned fd from
|
||
assuan_get_active_fds() is always this one. */
|
||
nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */,
|
||
afdlist, DIM (afdlist));
|
||
if (nfds < 1)
|
||
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
|
||
/* For now... */
|
||
for (i = 0; i < nfds; i++)
|
||
fdlist[i] = (int) afdlist[i];
|
||
|
||
/* We "duplicate" the file descriptor, so we can close it here (we
|
||
can't close fdlist[0], as that is closed by libassuan, and
|
||
closing it here might cause libassuan to close some unrelated FD
|
||
later). Alternatively, we could special case status_fd and
|
||
register/unregister it manually as needed, but this increases
|
||
code duplication and is more complicated as we can not use the
|
||
close notifications etc. A third alternative would be to let
|
||
Assuan know that we closed the FD, but that complicates the
|
||
Assuan interface. */
|
||
|
||
llass->status_cb.fd = _gpgme_io_dup (fdlist[0]);
|
||
if (llass->status_cb.fd < 0)
|
||
return gpg_error_from_syserror ();
|
||
|
||
if (_gpgme_io_set_close_notify (llass->status_cb.fd,
|
||
close_notify_handler, llass))
|
||
{
|
||
_gpgme_io_close (llass->status_cb.fd);
|
||
llass->status_cb.fd = -1;
|
||
return gpg_error (GPG_ERR_GENERAL);
|
||
}
|
||
|
||
err = add_io_cb (llass, &llass->status_cb, llass_status_handler);
|
||
if (!err)
|
||
err = assuan_write_line (llass->assuan_ctx, command);
|
||
|
||
/* FIXME: If *command == '#' no answer is expected. */
|
||
|
||
if (!err)
|
||
llass_io_event (llass, GPGME_EVENT_START, NULL);
|
||
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
static gpgme_error_t
|
||
llass_transact (void *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)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
gpgme_error_t err;
|
||
|
||
if (!llass || !command || !*command)
|
||
return gpg_error (GPG_ERR_INV_VALUE);
|
||
|
||
llass->user.data_cb = data_cb;
|
||
llass->user.data_cb_value = data_cb_value;
|
||
llass->user.inq_cb = inq_cb;
|
||
llass->user.inq_cb_value = inq_cb_value;
|
||
llass->user.status_cb = status_cb;
|
||
llass->user.status_cb_value = status_cb_value;
|
||
|
||
err = start (llass, command);
|
||
return err;
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
llass->io_cbs = *io_cbs;
|
||
}
|
||
|
||
|
||
static void
|
||
llass_io_event (void *engine, gpgme_event_io_t type, void *type_data)
|
||
{
|
||
engine_llass_t llass = engine;
|
||
|
||
TRACE (DEBUG_ENGINE, "gpgme:llass_io_event", llass,
|
||
"event %p, type %d, type_data %p",
|
||
llass->io_cbs.event, type, type_data);
|
||
if (llass->io_cbs.event)
|
||
(*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data);
|
||
}
|
||
|
||
|
||
struct engine_ops _gpgme_engine_ops_assuan =
|
||
{
|
||
/* Static functions. */
|
||
_gpgme_get_default_agent_socket,
|
||
llass_get_home_dir,
|
||
llass_get_version,
|
||
llass_get_req_version,
|
||
llass_new,
|
||
|
||
/* Member functions. */
|
||
llass_release,
|
||
NULL, /* reset */
|
||
NULL, /* set_status_cb */
|
||
NULL, /* set_status_handler */
|
||
NULL, /* set_command_handler */
|
||
NULL, /* set_colon_line_handler */
|
||
llass_set_locale,
|
||
NULL, /* set_protocol */
|
||
llass_set_engine_flags,
|
||
NULL, /* decrypt */
|
||
NULL, /* delete */
|
||
NULL, /* edit */
|
||
NULL, /* encrypt */
|
||
NULL, /* encrypt_sign */
|
||
NULL, /* export */
|
||
NULL, /* export_ext */
|
||
NULL, /* genkey */
|
||
NULL, /* import */
|
||
NULL, /* keylist */
|
||
NULL, /* keylist_ext */
|
||
NULL, /* keylist_data */
|
||
NULL, /* keysign */
|
||
NULL, /* revsig */
|
||
NULL, /* tofu_policy */
|
||
NULL, /* sign */
|
||
NULL, /* verify */
|
||
NULL, /* getauditlog */
|
||
NULL, /* setexpire */
|
||
NULL, /* setownertrust */
|
||
llass_transact, /* opassuan_transact */
|
||
NULL, /* getdirect */
|
||
NULL, /* conf_load */
|
||
NULL, /* conf_save */
|
||
NULL, /* conf_dir */
|
||
NULL, /* query_swdb */
|
||
llass_set_io_cbs,
|
||
llass_io_event,
|
||
llass_cancel,
|
||
llass_cancel_op,
|
||
NULL, /* passwd */
|
||
NULL, /* set_pinentry_mode */
|
||
NULL /* opspawn */
|
||
};
|