diff --git a/src/Makefile.am b/src/Makefile.am
index 79028a17..781441cc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,171 +1,171 @@
# Copyright (C) 2000 Werner Koch (dd9jn)
# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 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 .
# SPDX-License-Identifier: LGPL-2.1-or-later
## Process this file with automake to produce Makefile.in
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = gpgme.pc gpgme-glib.pc
EXTRA_DIST = gpgme-config.in gpgme.m4 libgpgme.vers ChangeLog-2011 \
gpgme.h.in versioninfo.rc.in gpgme.def \
gpgme.pc.in gpgme-glib.pc.in
bin_SCRIPTS = gpgme-config
m4datadir = $(datadir)/aclocal
m4data_DATA = gpgme.m4
nodist_include_HEADERS = gpgme.h
bin_PROGRAMS = gpgme-tool gpgme-json
if BUILD_W32_GLIB
ltlib_gpgme_glib = libgpgme-glib.la
else
ltlib_gpgme_glib =
endif
lib_LTLIBRARIES = libgpgme.la $(ltlib_gpgme_glib)
if HAVE_LD_VERSION_SCRIPT
libgpgme_version_script_cmd = -Wl,--version-script=$(srcdir)/libgpgme.vers
else
libgpgme_version_script_cmd =
endif
if HAVE_DOSISH_SYSTEM
system_components = w32-util.c
system_components_not_extra = w32-io.c
else
system_components = ath.h posix-util.c posix-io.c
system_components_not_extra =
endif
if HAVE_UISERVER
uiserver_components = engine-uiserver.c
else
uiserver_components =
endif
# These are the source files common to all library versions. We used
# to build a non-installed library for that, but that does not work
# correctly on all platforms (in particular, one can not specify the
# right linking order with libtool, as the non-installed version has
# unresolved symbols to the thread module.
main_sources = \
util.h conversion.c b64dec.c get-env.c context.h ops.h \
parsetlv.c parsetlv.h \
mbox-util.c mbox-util.h \
data.h data.c data-fd.c data-stream.c data-mem.c data-user.c \
data-estream.c \
data-compat.c data-identify.c \
signers.c sig-notation.c \
fdtable.c fdtable.h \
- wait.c wait-global.c wait-private.c wait-user.c wait.h \
+ wait.c wait.h \
op-support.c \
encrypt.c encrypt-sign.c decrypt.c decrypt-verify.c verify.c \
sign.c passphrase.c progress.c \
key.c keylist.c keysign.c trust-item.c trustlist.c tofupolicy.c \
import.c export.c genkey.c delete.c edit.c getauditlog.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 queryswdb.c \
sema.h priv-io.h $(system_components) sys-util.h dirinfo.c \
debug.c debug.h gpgme.c version.c error.c \
ath.h ath.c
libgpgme_la_SOURCES = $(main_sources) $(system_components_not_extra)
if BUILD_W32_GLIB
libgpgme_glib_la_SOURCES = $(main_sources) w32-glib-io.c
endif
# We use a global CFLAGS setting for all libraries
# versions, because then every object file is only compiled once.
AM_CFLAGS = @LIBASSUAN_CFLAGS@ @GLIB_CFLAGS@
gpgme_tool_SOURCES = gpgme-tool.c argparse.c argparse.h
gpgme_tool_LDADD = libgpgme.la @LIBASSUAN_LIBS@
gpgme_json_SOURCES = gpgme-json.c cJSON.c cJSON.h
gpgme_json_LDADD = -lm libgpgme.la $(GPG_ERROR_LIBS)
if HAVE_W32_SYSTEM
# Windows provides us with an endless stream of Tough Love. To spawn
# processes with a controlled set of inherited handles, we need a
# wrapper process.
libexec_PROGRAMS = gpgme-w32spawn
RCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES)
LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE)
SUFFIXES = .rc .lo
.rc.lo:
$(LTRCCOMPILE) -i "$<" -o "$@"
gpgme_res = versioninfo.lo
no_undefined = -no-undefined
export_symbols = -export-symbols $(srcdir)/gpgme.def
extra_ltoptions = -XCClinker -static-libgcc
install-def-file:
-$(INSTALL) -d $(DESTDIR)$(libdir)
$(INSTALL) $(srcdir)/gpgme.def $(DESTDIR)$(libdir)/gpgme.def
uninstall-def-file:
-rm $(DESTDIR)$(libdir)/gpgme.def
gpgme_deps = $(gpgme_res) gpgme.def
else
gpgme_res =
no_undefined =
export_symbols =
extra_ltoptions =
install-def-file:
uninstall-def-file:
gpgme_deps =
endif
libgpgme_la_LDFLAGS = $(no_undefined) $(export_symbols) $(extra_ltoptions) \
$(libgpgme_version_script_cmd) -version-info \
@LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
libgpgme_la_DEPENDENCIES = @LTLIBOBJS@ $(srcdir)/libgpgme.vers $(gpgme_deps)
libgpgme_la_LIBADD = $(gpgme_res) @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
@GPG_ERROR_LIBS@
if BUILD_W32_GLIB
libgpgme_glib_la_LDFLAGS = \
$(no_undefined) $(export_symbols) $(extra_ltoptions) \
$(libgpgme_version_script_cmd) -version-info \
@LIBGPGME_LT_CURRENT@:@LIBGPGME_LT_REVISION@:@LIBGPGME_LT_AGE@
libgpgme_glib_la_DEPENDENCIES = @LTLIBOBJS@ \
$(srcdir)/libgpgme.vers $(gpgme_deps)
libgpgme_glib_la_LIBADD = $(gpgme_res) @LIBASSUAN_LIBS@ @LTLIBOBJS@ \
@GPG_ERROR_LIBS@ @GLIB_LIBS@
endif
install-data-local: install-def-file
uninstall-local: uninstall-def-file
diff --git a/src/context.h b/src/context.h
index 7333d2d5..4142b684 100644
--- a/src/context.h
+++ b/src/context.h
@@ -1,216 +1,214 @@
/* context.h - Definitions for a GPGME context.
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003, 2004, 2005, 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 .
*/
#ifndef CONTEXT_H
#define CONTEXT_H
#include
#include "gpgme.h"
#include "engine.h"
#include "wait.h"
#include "sema.h"
extern gpgme_error_t _gpgme_selftest;
/* Operations might require to remember arbitrary information and data
objects during invocations of the status handler. The
ctx_op_data structure provides a generic framework to hook in
such additional data. */
typedef enum
{
OPDATA_DECRYPT, OPDATA_SIGN, OPDATA_ENCRYPT, OPDATA_PASSPHRASE,
OPDATA_IMPORT, OPDATA_GENKEY, OPDATA_KEYLIST, OPDATA_EDIT,
OPDATA_VERIFY, OPDATA_TRUSTLIST, OPDATA_ASSUAN, OPDATA_VFS_MOUNT,
OPDATA_PASSWD, OPDATA_EXPORT, OPDATA_KEYSIGN, OPDATA_TOFU_POLICY,
OPDATA_QUERY_SWDB
} ctx_op_data_id_t;
/* "gpgmeres" in ASCII. */
#define CTX_OP_DATA_MAGIC 0x736572656d677067ULL
struct ctx_op_data
{
/* A magic word just to make sure people don't deallocate something
that ain't a result structure. */
unsigned long long magic;
/* The next element in the linked list, or NULL if this is the last
element. Used by op data structures linked into a context. */
struct ctx_op_data *next;
/* The type of the hook data, which can be used by a routine to
lookup the hook data. */
ctx_op_data_id_t type;
/* The function to release HOOK and all its associated resources.
Can be NULL if no special deallocation routine is necessary. */
void (*cleanup) (void *hook);
/* The hook that points to the operation data. */
void *hook;
/* The number of outstanding references. */
int references;
};
typedef struct ctx_op_data *ctx_op_data_t;
/* The context defines an environment in which crypto operations can
* be performed (sequentially). */
struct gpgme_context
{
DECLARE_LOCK (lock);
/* The unique serial number of this context object. This is used
* for a weak reference of the context. Using the address of the
* context is not always possible becuase it might have already been
* freed and reused. */
uint64_t serial;
/* A link to the next context. We keep all contex object in the
* linked list to so that we are abale to find a context by its
* serial number. */
gpgme_ctx_t next_ctx;
/* True if the context was canceled asynchronously. */
int canceled;
/* The engine info for this context. */
gpgme_engine_info_t engine_info;
/* The protocol used by this context. */
gpgme_protocol_t protocol;
/* The running engine process. */
engine_t engine;
/* Engine's sub protocol. */
gpgme_protocol_t sub_protocol;
/* True if armor mode should be used. */
unsigned int use_armor : 1;
/* True if text mode should be used. */
unsigned int use_textmode : 1;
/* True if offline mode should be used. */
unsigned int offline : 1;
/* True if a status callback shall be called for nearly all status
* lines. */
unsigned int full_status : 1;
/* The Tofu info has a human readable string which is presented to
* the user in a directly usable format. By enabling this flag the
* unmodified string, as received form gpg, will be returned. */
unsigned int raw_description : 1;
/* True if session keys should be exported upon decryption. */
unsigned int export_session_keys : 1;
/* True if a Pinentry was launched during the last operation. This
* flag is cleared with each operation. */
unsigned int redraw_suggested : 1;
/* True if the option --auto-key-retrieve shall be passed to gpg. */
unsigned int auto_key_retrieve : 1;
/* Do not use the symmtric encryption passphrase cache. */
unsigned int no_symkey_cache : 1;
/* Pass --ignore-mdc-error to gpg. Note that this flag is reset
* after the operation. */
unsigned int ignore_mdc_error : 1;
/* Flags for keylist mode. */
gpgme_keylist_mode_t keylist_mode;
/* The current pinentry mode. */
gpgme_pinentry_mode_t pinentry_mode;
/* Number of certs to be included. */
unsigned int include_certs;
/* The actual number of keys in SIGNERS, the allocated size of the
* array, and the array with the signing keys. */
unsigned int signers_len;
unsigned int signers_size;
gpgme_key_t *signers;
/* The signature notations for this context. */
gpgme_sig_notation_t sig_notations;
/* The sender's addr-spec or NULL. */
char *sender;
/* The gpg specific override session key or NULL. */
char *override_session_key;
/* The optional request origin. */
char *request_origin;
/* The optional auto key locate options. */
char *auto_key_locate;
/* The locale for the pinentry. */
char *lc_ctype;
char *lc_messages;
/* The optional trust-model override. */
char *trust_model;
/* The operation data hooked into the context. */
ctx_op_data_t op_data;
/* The user provided passphrase callback and its hook value. */
gpgme_passphrase_cb_t passphrase_cb;
void *passphrase_cb_value;
/* The user provided progress callback and its hook value. */
gpgme_progress_cb_t progress_cb;
void *progress_cb_value;
/* The user provided status callback and its hook value. */
gpgme_status_cb_t status_cb;
void *status_cb_value;
- /* A list of file descriptors in active use by the current
- operation. */
- struct fd_table fdt;
- struct gpgme_io_cbs io_cbs;
+ /* User specific I/O callbacks. */
+ struct gpgme_io_cbs user_io_cbs;
};
/* Macro to retrieve the serial number. Returns 0 if CTX is NULL. */
#define CTXSERIAL(ctx) (ctx? (unsigned long)ctx->serial : 0)
/*-- gpgme.c --*/
gpg_error_t _gpgme_get_ctx (uint64_t serial, gpgme_ctx_t *r_ctx);
-gpgme_error_t _gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err,
+gpgme_error_t _gpgme_cancel_with_err (uint64_t serial, gpg_error_t ctx_err,
gpg_error_t op_err);
#endif /* CONTEXT_H */
diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c
index 9ecdbe47..086ed32d 100644
--- a/src/engine-gpgsm.c
+++ b/src/engine-gpgsm.c
@@ -1,2346 +1,2352 @@
/* engine-gpgsm.c - GpgSM engine.
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#include
#ifdef HAVE_UNISTD_H
# include
#endif
#ifdef HAVE_LOCALE_H
#include
#endif
#include /* FIXME */
#include "gpgme.h"
#include "util.h"
#include "ops.h"
#include "wait.h"
#include "priv-io.h"
#include "sema.h"
#include "data.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. */
char server_fd_str[15]; /* Same as SERVER_FD but as a string. We
need this because _gpgme_io_fd2str can't
be used on a closed descriptor. */
} iocb_data_t;
struct engine_gpgsm
{
assuan_context_t assuan_ctx;
int lc_ctype_set;
int lc_messages_set;
iocb_data_t status_cb;
/* Input, output etc are from the servers perspective. */
iocb_data_t input_cb;
gpgme_data_t input_helper_data; /* Input helper data object. */
void *input_helper_memory; /* Input helper memory block. */
iocb_data_t output_cb;
iocb_data_t message_cb;
iocb_data_t diag_cb;
struct
{
engine_status_handler_t fnc;
void *fnc_value;
gpgme_status_cb_t mon_cb;
void *mon_cb_value;
} status;
struct
{
engine_colon_line_handler_t fnc;
void *fnc_value;
struct
{
char *line;
int linesize;
int linelen;
} attic;
int any; /* any data line seen */
} colon;
gpgme_data_t inline_data; /* Used to collect D lines. */
char request_origin[10];
struct gpgme_io_cbs io_cbs;
/* Memory data containing diagnostics (--logger-fd) of gpgsm */
gpgme_data_t diagnostics;
};
typedef struct engine_gpgsm *engine_gpgsm_t;
static void gpgsm_io_event (void *engine,
gpgme_event_io_t type, void *type_data);
static char *
gpgsm_get_version (const char *file_name)
{
return _gpgme_get_program_version (file_name ? file_name
: _gpgme_get_default_gpgsm_name ());
}
static const char *
gpgsm_get_req_version (void)
{
return "2.0.4";
}
static gpg_error_t
close_notify_handler (int fd, void *opaque)
{
engine_gpgsm_t gpgsm = opaque;
TRACE_BEG (DEBUG_SYSIO, "gpgsm:close_notify_handler", NULL,
"fd=%d", fd);
assert (fd != -1);
if (gpgsm->status_cb.fd == fd)
{
if (gpgsm->status_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->status_cb.tag);
gpgsm->status_cb.fd = -1;
gpgsm->status_cb.tag = NULL;
/* Because the server keeps on running as long as the
* gpgme_ctx_t is valid the diag fd will not receive a close and
* thus the operation gets stuck trying to read the diag fd.
* The status fd however is closed right after it received the
* "OK" from the command. So we use this event to also close
* the diag fd. */
TRACE_LOG ("closing diag fd");
_gpgme_io_close (gpgsm->diag_cb.fd);
}
else if (gpgsm->input_cb.fd == fd)
{
if (gpgsm->input_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->input_cb.tag);
gpgsm->input_cb.fd = -1;
gpgsm->input_cb.tag = NULL;
if (gpgsm->input_helper_data)
{
gpgme_data_release (gpgsm->input_helper_data);
gpgsm->input_helper_data = NULL;
}
if (gpgsm->input_helper_memory)
{
free (gpgsm->input_helper_memory);
gpgsm->input_helper_memory = NULL;
}
TRACE_LOG ("ready with input_fd");
}
else if (gpgsm->output_cb.fd == fd)
{
if (gpgsm->output_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->output_cb.tag);
gpgsm->output_cb.fd = -1;
gpgsm->output_cb.tag = NULL;
TRACE_LOG ("ready with output_fd");
}
else if (gpgsm->message_cb.fd == fd)
{
if (gpgsm->message_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->message_cb.tag);
gpgsm->message_cb.fd = -1;
gpgsm->message_cb.tag = NULL;
TRACE_LOG ("ready with message_fd");
}
else if (gpgsm->diag_cb.fd == fd)
{
if (gpgsm->diag_cb.tag)
(*gpgsm->io_cbs.remove) (gpgsm->diag_cb.tag);
gpgsm->diag_cb.fd = -1;
gpgsm->diag_cb.tag = NULL;
TRACE_LOG ("ready with diag_fd");
}
TRACE_SUC ("");
return 0;
}
/* This is the default inquiry callback. We use it to handle the
Pinentry notifications. */
static gpgme_error_t
default_inq_cb (engine_gpgsm_t gpgsm, const char *line)
{
(void)gpgsm;
if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
{
_gpgme_allow_set_foreground_window ((pid_t)strtoul (line+17, NULL, 10));
}
return 0;
}
static gpgme_error_t
gpgsm_cancel (void *engine)
{
engine_gpgsm_t gpgsm = engine;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (gpgsm->status_cb.fd != -1)
_gpgme_io_close (gpgsm->status_cb.fd);
if (gpgsm->input_cb.fd != -1)
_gpgme_io_close (gpgsm->input_cb.fd);
if (gpgsm->output_cb.fd != -1)
_gpgme_io_close (gpgsm->output_cb.fd);
if (gpgsm->message_cb.fd != -1)
_gpgme_io_close (gpgsm->message_cb.fd);
if (gpgsm->diag_cb.fd != -1)
_gpgme_io_close (gpgsm->diag_cb.fd);
if (gpgsm->assuan_ctx)
{
assuan_release (gpgsm->assuan_ctx);
gpgsm->assuan_ctx = NULL;
}
return 0;
}
static void
gpgsm_release (void *engine)
{
engine_gpgsm_t gpgsm = engine;
if (!gpgsm)
return;
gpgsm_cancel (engine);
gpgme_data_release (gpgsm->diagnostics);
free (gpgsm->colon.attic.line);
free (gpgsm);
}
static gpgme_error_t
gpgsm_new (void **engine, const char *file_name, const char *home_dir,
const char *version)
{
gpgme_error_t err = 0;
engine_gpgsm_t gpgsm;
const char *pgmname;
const char *argv[7];
char *diag_fd_str = NULL;
int argc;
int fds[2];
int child_fds[5];
int nchild_fds;
char *dft_display = NULL;
char dft_ttyname[64];
char *env_tty = NULL;
char *dft_ttytype = NULL;
char *optstr;
unsigned int connect_flags;
(void)version; /* Not yet used. */
gpgsm = calloc (1, sizeof *gpgsm);
if (!gpgsm)
return gpg_error_from_syserror ();
gpgsm->status_cb.fd = -1;
gpgsm->status_cb.dir = 1;
gpgsm->status_cb.tag = 0;
gpgsm->status_cb.data = gpgsm;
gpgsm->input_cb.fd = -1;
gpgsm->input_cb.dir = 0;
gpgsm->input_cb.tag = 0;
gpgsm->input_cb.server_fd = -1;
*gpgsm->input_cb.server_fd_str = 0;
gpgsm->output_cb.fd = -1;
gpgsm->output_cb.dir = 1;
gpgsm->output_cb.tag = 0;
gpgsm->output_cb.server_fd = -1;
*gpgsm->output_cb.server_fd_str = 0;
gpgsm->message_cb.fd = -1;
gpgsm->message_cb.dir = 0;
gpgsm->message_cb.tag = 0;
gpgsm->message_cb.server_fd = -1;
*gpgsm->message_cb.server_fd_str = 0;
gpgsm->diag_cb.fd = -1;
gpgsm->diag_cb.dir = 1;
gpgsm->diag_cb.tag = 0;
gpgsm->diag_cb.server_fd = -1;
*gpgsm->diag_cb.server_fd_str = 0;
gpgsm->status.fnc = 0;
gpgsm->colon.fnc = 0;
gpgsm->colon.attic.line = 0;
gpgsm->colon.attic.linesize = 0;
gpgsm->colon.attic.linelen = 0;
gpgsm->colon.any = 0;
gpgsm->inline_data = NULL;
gpgsm->io_cbs.add = NULL;
gpgsm->io_cbs.add_priv = NULL;
gpgsm->io_cbs.remove = NULL;
gpgsm->io_cbs.event = NULL;
gpgsm->io_cbs.event_priv = NULL;
if (_gpgme_io_pipe (fds, 1) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->diag_cb.fd = fds[0];
gpgsm->diag_cb.server_fd = fds[1];
#if USE_DESCRIPTOR_PASSING
child_fds[0] = gpgsm->diag_cb.server_fd;
child_fds[1] = -1;
nchild_fds = 2;
connect_flags = ASSUAN_PIPE_CONNECT_FDPASSING;
#else /*!USE_DESCRIPTOR_PASSING*/
if (_gpgme_io_pipe (fds, 0) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->input_cb.fd = fds[1];
gpgsm->input_cb.server_fd = fds[0];
if (_gpgme_io_pipe (fds, 1) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->output_cb.fd = fds[0];
gpgsm->output_cb.server_fd = fds[1];
if (_gpgme_io_pipe (fds, 0) < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
gpgsm->message_cb.fd = fds[1];
gpgsm->message_cb.server_fd = fds[0];
child_fds[0] = gpgsm->input_cb.server_fd;
child_fds[1] = gpgsm->output_cb.server_fd;
child_fds[2] = gpgsm->message_cb.server_fd;
child_fds[3] = gpgsm->diag_cb.server_fd;
child_fds[4] = -1;
nchild_fds = 5;
connect_flags = 0;
#endif /*!USE_DESCRIPTOR_PASSING*/
pgmname = file_name ? file_name : _gpgme_get_default_gpgsm_name ();
argc = 0;
argv[argc++] = _gpgme_get_basename (pgmname);
if (home_dir)
{
argv[argc++] = "--homedir";
argv[argc++] = home_dir;
}
/* Set up diagnostics */
err = gpgme_data_new (&gpgsm->diagnostics);
if (err)
goto leave;
gpgsm->diag_cb.data = gpgsm->diagnostics;
argv[argc++] = "--logger-fd";
if (gpgrt_asprintf (&diag_fd_str, "%i", gpgsm->diag_cb.server_fd) == -1)
{
err = gpg_error_from_syserror ();
goto leave;
}
argv[argc++] = diag_fd_str;
argv[argc++] = "--server";
argv[argc++] = NULL;
err = assuan_new_ext (&gpgsm->assuan_ctx, GPG_ERR_SOURCE_GPGME,
&_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb,
NULL);
if (err)
goto leave;
assuan_ctx_set_system_hooks (gpgsm->assuan_ctx, &_gpgme_assuan_system_hooks);
{
assuan_fd_t achild_fds[5];
int i;
/* For now... */
for (i = 0; i < nchild_fds; i++)
achild_fds[i] = (assuan_fd_t) child_fds[i];
err = assuan_pipe_connect (gpgsm->assuan_ctx, pgmname, argv,
achild_fds, NULL, NULL, connect_flags);
/* FIXME: Check whether our Windows code still updates the list.*/
for (i = 0; i < nchild_fds; i++)
child_fds[i] = (int) achild_fds[i];
}
#if !USE_DESCRIPTOR_PASSING
/* On Windows, handles are inserted in the spawned process with
DuplicateHandle, and child_fds contains the server-local names
for the inserted handles when assuan_pipe_connect returns. */
if (!err)
{
/* Note: We don't use _gpgme_io_fd2str here. On W32 the
returned handles are real W32 system handles, not whatever
GPGME uses internally (which may be a system handle, a C
library handle or a GLib/Qt channel. Confusing, yes, but
remember these are server-local names, so they are not part
of GPGME at all. */
snprintf (gpgsm->input_cb.server_fd_str,
sizeof gpgsm->input_cb.server_fd_str, "%d", child_fds[0]);
snprintf (gpgsm->output_cb.server_fd_str,
sizeof gpgsm->output_cb.server_fd_str, "%d", child_fds[1]);
snprintf (gpgsm->message_cb.server_fd_str,
sizeof gpgsm->message_cb.server_fd_str, "%d", child_fds[2]);
snprintf (gpgsm->diag_cb.server_fd_str,
sizeof gpgsm->diag_cb.server_fd_str, "%d", child_fds[3]);
}
#endif
if (err)
goto leave;
err = _gpgme_getenv ("DISPLAY", &dft_display);
if (err)
goto leave;
if (dft_display)
{
if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0)
{
free (dft_display);
err = gpg_error_from_syserror ();
goto leave;
}
free (dft_display);
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL, NULL,
NULL, NULL, NULL);
gpgrt_free (optstr);
if (err)
goto leave;
}
err = _gpgme_getenv ("GPG_TTY", &env_tty);
if (isatty (1) || env_tty || err)
{
int rc = 0;
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 (gpgsm->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)
{
free (dft_ttytype);
err = gpg_error_from_syserror ();
goto leave;
}
free (dft_ttytype);
err = assuan_transact (gpgsm->assuan_ctx, optstr, NULL, NULL,
NULL, NULL, NULL, NULL);
gpgrt_free (optstr);
if (err)
goto leave;
}
}
}
/* Ask gpgsm to enable the audit log support. */
if (!err)
{
err = assuan_transact (gpgsm->assuan_ctx, "OPTION enable-audit-log=1",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* This is an optional feature of gpgsm. */
}
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use AllowSetForegroundWindow. Tell
gpgsm to tell us when it needs it. */
if (!err)
{
err = assuan_transact (gpgsm->assuan_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* This is a new feature of gpgsm. */
}
#endif /*HAVE_W32_SYSTEM*/
#if !USE_DESCRIPTOR_PASSING
if (!err
&& (_gpgme_fdtable_add_close_notify (gpgsm->input_cb.fd,
close_notify_handler, gpgsm)
|| _gpgme_fdtable_add_close_notify (gpgsm->output_cb.fd,
close_notify_handler, gpgsm)
|| _gpgme_fdtable_add_close_notify (gpgsm->message_cb.fd,
close_notify_handler, gpgsm)))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif
if (!err && _gpgme_fdtable_add_close_notify (gpgsm->diag_cb.fd,
close_notify_handler, gpgsm))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
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 gpgsm_release(). */
#if !USE_DESCRIPTOR_PASSING
if (gpgsm->input_cb.server_fd != -1)
_gpgme_io_close (gpgsm->input_cb.server_fd);
if (gpgsm->output_cb.server_fd != -1)
_gpgme_io_close (gpgsm->output_cb.server_fd);
if (gpgsm->message_cb.server_fd != -1)
_gpgme_io_close (gpgsm->message_cb.server_fd);
if (gpgsm->diag_cb.server_fd != -1)
_gpgme_io_close (gpgsm->diag_cb.server_fd);
#endif
if (err)
gpgsm_release (gpgsm);
else
*engine = gpgsm;
free (diag_fd_str);
return err;
}
/* Copy flags from CTX into the engine object. */
static void
gpgsm_set_engine_flags (void *engine, const gpgme_ctx_t ctx)
{
engine_gpgsm_t gpgsm = engine;
if (ctx->request_origin)
{
if (strlen (ctx->request_origin) + 1 > sizeof gpgsm->request_origin)
strcpy (gpgsm->request_origin, "xxx"); /* Too long - force error */
else
strcpy (gpgsm->request_origin, ctx->request_origin);
}
else
*gpgsm->request_origin = 0;
}
static gpgme_error_t
gpgsm_set_locale (void *engine, int category, const char *value)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *optstr;
const char *catstr;
/* FIXME: If value is NULL, we need to reset the option to default.
But we can't do this. So we error out here. GPGSM needs support
for this. */
if (0)
;
#ifdef LC_CTYPE
else if (category == LC_CTYPE)
{
catstr = "lc-ctype";
if (!value && gpgsm->lc_ctype_set)
return gpg_error (GPG_ERR_INV_VALUE);
if (value)
gpgsm->lc_ctype_set = 1;
}
#endif
#ifdef LC_MESSAGES
else if (category == LC_MESSAGES)
{
catstr = "lc-messages";
if (!value && gpgsm->lc_messages_set)
return gpg_error (GPG_ERR_INV_VALUE);
if (value)
gpgsm->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 (gpgsm->assuan_ctx, optstr, NULL, NULL,
NULL, NULL, NULL, NULL);
gpgrt_free (optstr);
}
return err;
}
static gpgme_error_t
gpgsm_assuan_simple_command (engine_gpgsm_t gpgsm, const char *cmd,
engine_status_handler_t status_fnc,
void *status_fnc_value)
{
assuan_context_t ctx = gpgsm->assuan_ctx;
gpg_error_t err, cb_err;
char *line;
size_t linelen;
err = assuan_write_line (ctx, cmd);
if (err)
return err;
cb_err = 0;
do
{
err = assuan_read_line (ctx, &line, &linelen);
if (err)
break;
if (*line == '#' || !linelen)
continue;
if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
break;
else if (linelen >= 4
&& line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
&& line[3] == ' ')
{
/* We prefer a callback generated error because that one is
more related to gpgme and thus probably more important
than the error returned by the engine. */
err = cb_err? cb_err : atoi (&line[4]);
cb_err = 0;
}
else if (linelen >= 2
&& line[0] == 'S' && line[1] == ' ')
{
/* After an error from a status callback we skip all further
status lines. */
if (!cb_err)
{
char *rest;
gpgme_status_code_t r;
rest = strchr (line + 2, ' ');
if (!rest)
rest = line + linelen; /* set to an empty string */
else
*(rest++) = 0;
r = _gpgme_parse_status (line + 2);
if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
{
/* Note that we call the monitor even if we do
* not know the status code (r < 0). */
cb_err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
line + 2, rest);
}
if (r >= 0 && status_fnc && !cb_err)
cb_err = status_fnc (status_fnc_value, r, rest);
}
}
else
{
/* Invalid line or INQUIRY. We can't do anything else than
to stop. As with ERR we prefer a status callback
generated error code, though. */
err = cb_err ? cb_err : gpg_error (GPG_ERR_GENERAL);
cb_err = 0;
}
}
while (!err);
/* We only want the first error from the status handler, thus we
* take the one saved in CB_ERR. */
if (!err && cb_err)
err = cb_err;
return err;
}
typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t;
static void
gpgsm_clear_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type)
{
#if !USE_DESCRIPTOR_PASSING
switch (fd_type)
{
case INPUT_FD:
_gpgme_io_close (gpgsm->input_cb.fd);
break;
case OUTPUT_FD:
_gpgme_io_close (gpgsm->output_cb.fd);
break;
case MESSAGE_FD:
_gpgme_io_close (gpgsm->message_cb.fd);
break;
}
#else
(void)gpgsm;
(void)fd_type;
#endif
}
#define COMMANDLINELEN 40
static gpgme_error_t
gpgsm_set_fd (engine_gpgsm_t gpgsm, fd_type_t fd_type, const char *opt)
{
gpg_error_t err = 0;
char line[COMMANDLINELEN];
const char *which;
iocb_data_t *iocb_data;
#if USE_DESCRIPTOR_PASSING
int dir;
#endif
switch (fd_type)
{
case INPUT_FD:
which = "INPUT";
iocb_data = &gpgsm->input_cb;
break;
case OUTPUT_FD:
which = "OUTPUT";
iocb_data = &gpgsm->output_cb;
break;
case MESSAGE_FD:
which = "MESSAGE";
iocb_data = &gpgsm->message_cb;
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
#if USE_DESCRIPTOR_PASSING
dir = iocb_data->dir;
/* We try to short-cut the communication by giving GPGSM direct
access to the file descriptor, rather than using a pipe. */
iocb_data->server_fd = _gpgme_data_get_fd (iocb_data->data);
if (iocb_data->server_fd < 0)
{
int fds[2];
if (_gpgme_io_pipe (fds, dir) < 0)
return gpg_error_from_syserror ();
iocb_data->fd = dir ? fds[0] : fds[1];
iocb_data->server_fd = dir ? fds[1] : fds[0];
if (_gpgme_fdtable_add_close_notify (iocb_data->fd,
close_notify_handler, gpgsm))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave_set_fd;
}
}
err = assuan_sendfd (gpgsm->assuan_ctx, iocb_data->server_fd);
if (err)
goto leave_set_fd;
_gpgme_io_close (iocb_data->server_fd);
iocb_data->server_fd = -1;
if (opt)
snprintf (line, COMMANDLINELEN, "%s FD %s", which, opt);
else
snprintf (line, COMMANDLINELEN, "%s FD", which);
#else
if (opt)
snprintf (line, COMMANDLINELEN, "%s FD=%s %s",
which, iocb_data->server_fd_str, opt);
else
snprintf (line, COMMANDLINELEN, "%s FD=%s",
which, iocb_data->server_fd_str);
#endif
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
#if USE_DESCRIPTOR_PASSING
leave_set_fd:
if (err)
{
_gpgme_io_close (iocb_data->fd);
iocb_data->fd = -1;
if (iocb_data->server_fd != -1)
{
_gpgme_io_close (iocb_data->server_fd);
iocb_data->server_fd = -1;
}
}
#endif
return err;
}
static const char *
map_data_enc (gpgme_data_t d)
{
switch (gpgme_data_get_encoding (d))
{
case GPGME_DATA_ENCODING_NONE:
break;
case GPGME_DATA_ENCODING_BINARY:
return "--binary";
case GPGME_DATA_ENCODING_BASE64:
return "--base64";
case GPGME_DATA_ENCODING_ARMOR:
return "--armor";
default:
break;
}
return NULL;
}
static gpgme_error_t
status_handler (void *opaque, int fd)
{
struct io_cb_data *data = (struct io_cb_data *) opaque;
engine_gpgsm_t gpgsm = (engine_gpgsm_t) data->handler_value;
gpgme_error_t err = 0;
char *line;
size_t linelen;
do
{
err = assuan_read_line (gpgsm->assuan_ctx, &line, &linelen);
if (err)
{
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: error from assuan (%d) getting status line : %s",
+ "fd=%d: error from assuan (%d) getting status line : %s",
fd, err, gpg_strerror (err));
}
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]);
if (! err)
err = gpg_error (GPG_ERR_GENERAL);
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: ERR line - mapped to: %s",
+ "fd=%d: ERR line - mapped to: %s",
fd, err ? gpg_strerror (err) : "ok");
/* Try our best to terminate the connection friendly. */
/* assuan_write_line (gpgsm->assuan_ctx, "BYE"); */
}
else if (linelen >= 2
&& line[0] == 'O' && line[1] == 'K'
&& (line[2] == '\0' || line[2] == ' '))
{
if (gpgsm->status.fnc)
{
char emptystring[1] = {0};
err = gpgsm->status.fnc (gpgsm->status.fnc_value,
GPGME_STATUS_EOF, emptystring);
if (gpg_err_code (err) == GPG_ERR_FALSE)
err = 0; /* Drop special error code. */
}
if (!err && gpgsm->colon.fnc && gpgsm->colon.any)
{
/* We must tell a colon function about the EOF. We do
this only when we have seen any data lines. Note
that this inlined use of colon data lines will
eventually be changed into using a regular data
channel. */
gpgsm->colon.any = 0;
err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, NULL);
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: OK line - final status: %s",
+ "fd=%d: OK line - final status: %s",
fd, err ? gpg_strerror (err) : "ok");
_gpgme_io_close (gpgsm->status_cb.fd);
return err;
}
else if (linelen > 2
&& line[0] == 'D' && line[1] == ' '
&& gpgsm->colon.fnc)
{
/* We are using the colon handler even for plain inline data
- strange name for that function but for historic reasons
we keep it. */
/* FIXME We can't use this for binary data because we
assume this is a string. For the current usage of colon
output it is correct. */
char *src = line + 2;
char *end = line + linelen;
char *dst;
char **aline = &gpgsm->colon.attic.line;
int *alinelen = &gpgsm->colon.attic.linelen;
if (gpgsm->colon.attic.linesize < *alinelen + linelen + 1)
{
char *newline = realloc (*aline, *alinelen + linelen + 1);
if (!newline)
err = gpg_error_from_syserror ();
else
{
*aline = newline;
gpgsm->colon.attic.linesize = *alinelen + linelen + 1;
}
}
if (!err)
{
dst = *aline + *alinelen;
while (!err && src < end)
{
if (*src == '%' && src + 2 < end)
{
/* Handle escaped characters. */
++src;
*dst = _gpgme_hextobyte (src);
(*alinelen)++;
src += 2;
}
else
{
*dst = *src++;
(*alinelen)++;
}
if (*dst == '\n')
{
/* Terminate the pending line, pass it to the colon
handler and reset it. */
gpgsm->colon.any = 1;
if (*alinelen > 1 && *(dst - 1) == '\r')
dst--;
*dst = '\0';
/* FIXME How should we handle the return code? */
err = gpgsm->colon.fnc (gpgsm->colon.fnc_value, *aline);
if (!err)
{
dst = *aline;
*alinelen = 0;
}
}
else
dst++;
}
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: D line; final status: %s",
+ "fd=%d: D line; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
&& line[0] == 'D' && line[1] == ' '
&& gpgsm->inline_data)
{
char *src = line + 2;
char *end = line + linelen;
char *dst = src;
gpgme_ssize_t nwritten;
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;
while (linelen > 0)
{
nwritten = gpgme_data_write (gpgsm->inline_data, src, linelen);
if (nwritten <= 0 || nwritten > linelen)
{
err = gpg_error_from_syserror ();
break;
}
src += nwritten;
linelen -= nwritten;
}
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: D inlinedata; final status: %s",
+ "fd=%d: D inlinedata; final status: %s",
fd, err? gpg_strerror (err):"ok");
}
else if (linelen > 2
&& line[0] == 'S' && line[1] == ' ')
{
char *rest;
gpgme_status_code_t r;
rest = strchr (line + 2, ' ');
if (!rest)
rest = line + linelen; /* set to an empty string */
else
*(rest++) = 0;
r = _gpgme_parse_status (line + 2);
if (gpgsm->status.mon_cb && r != GPGME_STATUS_PROGRESS)
{
/* Note that we call the monitor even if we do
* not know the status code (r < 0). */
err = gpgsm->status.mon_cb (gpgsm->status.mon_cb_value,
line + 2, rest);
}
else
err = 0;
if (r >= 0 && !err)
{
if (gpgsm->status.fnc)
{
err = gpgsm->status.fnc (gpgsm->status.fnc_value, r, rest);
if (gpg_err_code (err) == GPG_ERR_FALSE)
err = 0; /* Drop special error code. */
}
}
else
fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest);
TRACE (DEBUG_CTX, "gpgme:status_handler", gpgsm,
- "fd 0x%x: S line (%s) - final status: %s",
+ "fd=%d: S line (%s) - final status: %s",
fd, line+2, err? gpg_strerror (err):"ok");
}
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 *keyword = line+7;
while (*keyword == ' ')
keyword++;;
default_inq_cb (gpgsm, keyword);
assuan_write_line (gpgsm->assuan_ctx, "END");
}
}
while (!err && assuan_pending_line (gpgsm->assuan_ctx));
return err;
}
static gpgme_error_t
-add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler)
+add_io_cb (engine_gpgsm_t gpgsm, iocb_data_t *iocbd, gpgme_io_cb_t handler,
+ const char *handler_desc)
{
gpgme_error_t err;
- TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm,
- "fd=%d, dir %d", iocbd->fd, iocbd->dir);
+ TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", NULL,
+ "fd=%d, dir %d (%s-handler)",
+ iocbd->fd, iocbd->dir, handler_desc);
err = (*gpgsm->io_cbs.add) (gpgsm->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_gpgsm_t gpgsm, const char *command)
{
gpgme_error_t err;
assuan_fd_t afdlist[5];
int fdlist[5];
int nfds;
int i;
if (*gpgsm->request_origin)
{
char *cmd;
cmd = _gpgme_strconcat ("OPTION request-origin=",
gpgsm->request_origin, NULL);
if (!cmd)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, cmd, 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 (gpgsm->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. */
gpgsm->status_cb.fd = _gpgme_io_dup (fdlist[0]);
if (gpgsm->status_cb.fd < 0)
return gpg_error_from_syserror ();
if (_gpgme_fdtable_add_close_notify (gpgsm->status_cb.fd,
close_notify_handler, gpgsm))
{
_gpgme_io_close (gpgsm->status_cb.fd);
gpgsm->status_cb.fd = -1;
return gpg_error (GPG_ERR_GENERAL);
}
- err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler);
+ err = add_io_cb (gpgsm, &gpgsm->status_cb, status_handler, "status");
if (!err && gpgsm->input_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->input_cb,
+ _gpgme_data_outbound_handler, "outbound");
if (!err && gpgsm->output_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->output_cb,
+ _gpgme_data_inbound_handler, "inbound");
if (!err && gpgsm->message_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->message_cb,
+ _gpgme_data_outbound_handler, "outbound");
if (!err && gpgsm->diag_cb.fd != -1)
- err = add_io_cb (gpgsm, &gpgsm->diag_cb, _gpgme_data_inbound_handler);
+ err = add_io_cb (gpgsm, &gpgsm->diag_cb,
+ _gpgme_data_inbound_handler, "inbound");
if (!err)
err = assuan_write_line (gpgsm->assuan_ctx, command);
if (!err)
gpgsm_io_event (gpgsm, GPGME_EVENT_START, NULL);
return err;
}
#if USE_DESCRIPTOR_PASSING
static gpgme_error_t
gpgsm_reset (void *engine)
{
engine_gpgsm_t gpgsm = engine;
/* IF we have an active connection we must send a reset because we
need to reset the list of signers. Note that RESET does not
reset OPTION commands. */
return (gpgsm->assuan_ctx
? gpgsm_assuan_simple_command (gpgsm, "RESET", NULL, NULL)
: 0);
}
#endif
static gpgme_error_t
gpgsm_decrypt (void *engine,
gpgme_decrypt_flags_t flags,
gpgme_data_t ciph, gpgme_data_t plain,
int export_session_key, const char *override_session_key,
int auto_key_retrieve)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)flags;
/* gpgsm is not capable of exporting session keys right now, so we
* will ignore this if requested. */
(void)export_session_key;
(void)override_session_key;
/* --auto-key-retrieve is also not supported. */
(void)auto_key_retrieve;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = ciph;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
gpgsm->output_cb.data = plain;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
if (err)
return gpg_error (GPG_ERR_GENERAL); /* FIXME */
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (engine, "DECRYPT");
return err;
}
static gpgme_error_t
gpgsm_delete (void *engine, gpgme_key_t key, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *fpr = key->subkeys ? key->subkeys->fpr : NULL;
char *linep = fpr;
char *line;
int length = 8; /* "DELKEYS " */
(void)flags;
if (!fpr)
return gpg_error (GPG_ERR_INV_VALUE);
while (*linep)
{
length++;
if (*linep == '%' || *linep == ' ' || *linep == '+')
length += 2;
linep++;
}
length++;
/* Fixme: Ugly quoting code below - use some standard function. */
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "DELKEYS ");
linep = &line[8];
while (*fpr)
{
switch (*fpr)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *fpr;
break;
}
fpr++;
}
*linep = '\0';
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
set_recipients (engine_gpgsm_t gpgsm, gpgme_key_t recp[])
{
gpgme_error_t err = 0;
char *line;
int linelen;
int invalid_recipients = 0;
int i;
/* FIXME: Uyse a membuf etc to build up LINE. */
linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */
line = malloc (10 + 40 + 1);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "RECIPIENT ");
for (i =0; !err && recp[i]; i++)
{
char *fpr;
int newlen;
if (!recp[i]->subkeys || !recp[i]->subkeys->fpr)
{
invalid_recipients++;
continue;
}
fpr = recp[i]->subkeys->fpr;
newlen = 11 + strlen (fpr);
if (linelen < newlen)
{
char *newline = realloc (line, newlen);
if (! newline)
{
int saved_err = gpg_error_from_syserror ();
free (line);
return saved_err;
}
line = newline;
linelen = newlen;
}
strcpy (&line[10], fpr);
err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc,
gpgsm->status.fnc_value);
/* FIXME: This requires more work. */
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
invalid_recipients++;
else if (err)
{
free (line);
return err;
}
}
free (line);
return gpg_error (invalid_recipients
? GPG_ERR_UNUSABLE_PUBKEY : GPG_ERR_NO_ERROR);
}
/* Take recipients from the LF delimited STRING and send RECIPIENT
* commands to gpgsm. */
static gpgme_error_t
set_recipients_from_string (engine_gpgsm_t gpgsm, const char *string)
{
gpg_error_t err = 0;
char *line = NULL;
int ignore = 0;
int any = 0;
const char *s;
int n;
do
{
while (*string == ' ' || *string == '\t')
string++;
if (!*string)
break;
s = strchr (string, '\n');
if (s)
n = s - string;
else
n = strlen (string);
while (n && (string[n-1] == ' ' || string[n-1] == '\t'))
n--;
if (!ignore && n == 2 && !memcmp (string, "--", 2))
ignore = 1;
else if (!ignore && n > 2 && !memcmp (string, "--", 2))
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
else if (n) /* Not empty - use it. */
{
gpgrt_free (line);
if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0)
err = gpg_error_from_syserror ();
else
{
err = gpgsm_assuan_simple_command (gpgsm, line, gpgsm->status.fnc,
gpgsm->status.fnc_value);
if (!err)
any = 1;
}
}
string += n + !!s;
}
while (!err);
if (!err && !any)
err = gpg_error (GPG_ERR_MISSING_KEY);
gpgrt_free (line);
return err;
}
static gpgme_error_t
gpgsm_encrypt (void *engine, gpgme_key_t recp[], const char *recpstring,
gpgme_encrypt_flags_t flags,
gpgme_data_t plain, gpgme_data_t ciph, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (!recp && !recpstring) /* Symmetric only */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if ((flags & GPGME_ENCRYPT_NO_ENCRYPT_TO))
{
err = gpgsm_assuan_simple_command (gpgsm,
"OPTION no-encrypt-to", NULL, NULL);
if (err)
return err;
}
gpgsm->input_cb.data = plain;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = ciph;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
if (!recp && recpstring)
err = set_recipients_from_string (gpgsm, recpstring);
else
err = set_recipients (gpgsm, recp);
if (!err)
err = start (gpgsm, "ENCRYPT");
return err;
}
static gpgme_error_t
gpgsm_export (void *engine, const char *pattern, gpgme_export_mode_t mode,
gpgme_data_t keydata, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
char *cmd;
const char *s1, *s2;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (!pattern)
pattern = "";
s1 = s2 = "";
if ((mode & GPGME_EXPORT_MODE_SECRET))
{
s1 = "--secret ";
if ((mode & GPGME_EXPORT_MODE_RAW))
s2 = "--raw ";
else if ((mode & GPGME_EXPORT_MODE_PKCS12))
s2 = "--pkcs12 ";
}
cmd = _gpgme_strconcat ("EXPORT ", s1, s2, pattern, NULL);
if (!cmd)
return gpg_error_from_syserror ();
gpgsm->output_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, cmd);
free (cmd);
return err;
}
static gpgme_error_t
gpgsm_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode,
gpgme_data_t keydata, int use_armor)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
char *line;
/* Length is "EXPORT " + "--secret " + "--pkcs12 " + p + '\0'. */
int length = 7 + 9 + 9 + 1;
char *linep;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
/*FIXME: Replace by membuf and a quoting function. */
if (pattern && *pattern)
{
const char **pat = pattern;
while (*pat)
{
const char *patlet = *pat;
while (*patlet)
{
length++;
if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
length += 2;
patlet++;
}
pat++;
length++;
}
}
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
strcpy (line, "EXPORT ");
if ((mode & GPGME_EXPORT_MODE_SECRET))
{
strcat (line, "--secret ");
if ((mode & GPGME_EXPORT_MODE_RAW))
strcat (line, "--raw ");
else if ((mode & GPGME_EXPORT_MODE_PKCS12))
strcat (line, "--pkcs12 ");
}
linep = &line[strlen (line)];
if (pattern && *pattern)
{
while (*pattern)
{
const char *patlet = *pattern;
while (*patlet)
{
switch (*patlet)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *patlet;
break;
}
patlet++;
}
pattern++;
if (*pattern)
*linep++ = ' ';
}
}
*linep = '\0';
gpgsm->output_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_genkey (void *engine,
const char *userid, const char *algo,
unsigned long reserved, unsigned long expires,
gpgme_key_t key, unsigned int flags,
gpgme_data_t help_data, unsigned int extraflags,
gpgme_data_t pubkey, gpgme_data_t seckey)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)reserved;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (help_data)
{
if (!pubkey || seckey)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = help_data;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = pubkey;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD,
(extraflags & GENKEY_EXTRAFLAG_ARMOR)? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, "GENKEY");
return err;
}
(void)userid;
(void)algo;
(void)expires;
(void)key;
(void)flags;
/* The new interface has not yet been implemented, */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
static gpgme_error_t
gpgsm_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
gpgme_data_encoding_t dataenc;
int idx;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
if (keydata && keyarray)
return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed. */
dataenc = gpgme_data_get_encoding (keydata);
if (keyarray)
{
size_t buflen;
char *buffer, *p;
/* Fist check whether the engine already features the
--re-import option. */
err = gpgsm_assuan_simple_command
(gpgsm, "GETINFO cmd_has_option IMPORT re-import", NULL, NULL);
if (err)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* Create an internal data object with a list of all
fingerprints. The data object and its memory (to avoid an
extra copy by gpgme_data_new_from_mem) are stored in two
variables which are released by the close_notify_handler. */
for (idx=0, buflen=0; keyarray[idx]; idx++)
{
if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
&& keyarray[idx]->subkeys
&& keyarray[idx]->subkeys->fpr
&& *keyarray[idx]->subkeys->fpr)
buflen += strlen (keyarray[idx]->subkeys->fpr) + 1;
}
/* Allocate a buffer with extra space for the trailing Nul
introduced by the use of stpcpy. */
buffer = malloc (buflen+1);
if (!buffer)
return gpg_error_from_syserror ();
for (idx=0, p = buffer; keyarray[idx]; idx++)
{
if (keyarray[idx]->protocol == GPGME_PROTOCOL_CMS
&& keyarray[idx]->subkeys
&& keyarray[idx]->subkeys->fpr
&& *keyarray[idx]->subkeys->fpr)
p = stpcpy (stpcpy (p, keyarray[idx]->subkeys->fpr), "\n");
}
err = gpgme_data_new_from_mem (&gpgsm->input_helper_data,
buffer, buflen, 0);
if (err)
{
free (buffer);
return err;
}
gpgsm->input_helper_memory = buffer;
gpgsm->input_cb.data = gpgsm->input_helper_data;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
{
gpgme_data_release (gpgsm->input_helper_data);
gpgsm->input_helper_data = NULL;
free (gpgsm->input_helper_memory);
gpgsm->input_helper_memory = NULL;
return err;
}
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
return start (gpgsm, "IMPORT --re-import");
}
else if (dataenc == GPGME_DATA_ENCODING_URL
|| dataenc == GPGME_DATA_ENCODING_URL0
|| dataenc == GPGME_DATA_ENCODING_URLESC)
{
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else
{
gpgsm->input_cb.data = keydata;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
return start (gpgsm, "IMPORT");
}
}
static gpgme_error_t
gpgsm_keylist (void *engine, const char *pattern, int secret_only,
gpgme_keylist_mode_t mode, int engine_flags)
{
engine_gpgsm_t gpgsm = engine;
char *line;
gpgme_error_t err;
int list_mode = 0;
if (mode & GPGME_KEYLIST_MODE_LOCAL)
list_mode |= 1;
if (mode & GPGME_KEYLIST_MODE_EXTERN)
list_mode |= 2;
if (!pattern)
pattern = "";
/* Hack to make sure that the agent is started. Only if the agent
has been started an application may connect to the agent via
GPGME_PROTOCOL_ASSUAN - for example to look for smartcards. We
do this only if a secret key listing has been requested. In
general this is not needed because a secret key listing starts
the agent. However on a fresh installation no public keys are
available and thus there is no need for gpgsm to ask the agent
whether a secret key exists for the public key. */
if (secret_only || (mode & GPGME_KEYLIST_MODE_WITH_SECRET))
gpgsm_assuan_simple_command (gpgsm, "GETINFO agent-check", NULL, NULL);
/* Always send list-mode option because RESET does not reset it. */
if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
gpgrt_free (line);
if (err)
return err;
/* Always send key validation because RESET does not reset it. */
/* Use the validation mode if requested. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_VALIDATE)?
"OPTION with-validation=1":
"OPTION with-validation=0" ,
NULL, NULL);
/* Include the ephemeral keys if requested. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_EPHEMERAL)?
"OPTION with-ephemeral-keys=1":
"OPTION with-ephemeral-keys=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
"OPTION with-secret=1":
"OPTION with-secret=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
"OPTION offline=1":
"OPTION offline=0" ,
NULL, NULL);
if (secret_only)
line = _gpgme_strconcat ("LISTSECRETKEYS ", pattern, NULL);
else
line = _gpgme_strconcat ("LISTKEYS ", pattern, NULL);
if (!line)
return gpg_error_from_syserror ();
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_keylist_ext (void *engine, const char *pattern[], int secret_only,
int reserved, gpgme_keylist_mode_t mode, int engine_flags)
{
engine_gpgsm_t gpgsm = engine;
char *line;
gpgme_error_t err;
/* Length is "LISTSECRETKEYS " + p + '\0'. */
int length = 15 + 1;
char *linep;
int any_pattern = 0;
int list_mode = 0;
if (reserved)
return gpg_error (GPG_ERR_INV_VALUE);
if (mode & GPGME_KEYLIST_MODE_LOCAL)
list_mode |= 1;
if (mode & GPGME_KEYLIST_MODE_EXTERN)
list_mode |= 2;
/* Always send list-mode option because RESET does not reset it. */
if (gpgrt_asprintf (&line, "OPTION list-mode=%d", (list_mode & 3)) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, line, NULL, NULL);
gpgrt_free (line);
if (err)
return err;
/* Always send key validation because RESET does not reset it. */
/* Use the validation mode if required. We don't check for an error
yet because this is a pretty fresh gpgsm features. */
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_VALIDATE)?
"OPTION with-validation=1":
"OPTION with-validation=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(mode & GPGME_KEYLIST_MODE_WITH_SECRET)?
"OPTION with-secret=1":
"OPTION with-secret=0" ,
NULL, NULL);
gpgsm_assuan_simple_command (gpgsm,
(engine_flags & GPGME_ENGINE_FLAG_OFFLINE)?
"OPTION offline=1":
"OPTION offline=0" ,
NULL, NULL);
/* FIXME: Repalce by membuf and quoting function. */
if (pattern && *pattern)
{
const char **pat = pattern;
while (*pat)
{
const char *patlet = *pat;
while (*patlet)
{
length++;
if (*patlet == '%' || *patlet == ' ' || *patlet == '+')
length += 2;
patlet++;
}
pat++;
length++;
}
}
line = malloc (length);
if (!line)
return gpg_error_from_syserror ();
if (secret_only)
{
strcpy (line, "LISTSECRETKEYS ");
linep = &line[15];
}
else
{
strcpy (line, "LISTKEYS ");
linep = &line[9];
}
if (pattern && *pattern)
{
while (*pattern)
{
const char *patlet = *pattern;
while (*patlet)
{
switch (*patlet)
{
case '%':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '5';
break;
case ' ':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = '0';
break;
case '+':
*(linep++) = '%';
*(linep++) = '2';
*(linep++) = 'B';
break;
default:
*(linep++) = *patlet;
break;
}
patlet++;
}
any_pattern = 1;
*linep++ = ' ';
pattern++;
}
}
if (any_pattern)
linep--;
*linep = '\0';
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
free (line);
return err;
}
static gpgme_error_t
gpgsm_sign (void *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 */)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *assuan_cmd;
int i;
gpgme_key_t key;
(void)use_textmode;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
/* FIXME: This does not work as RESET does not reset it so we can't
revert back to default. */
if (include_certs != GPGME_INCLUDE_CERTS_DEFAULT)
{
/* FIXME: Make sure that if we run multiple operations, that we
can reset any previously set value in case the default is
requested. */
if (gpgrt_asprintf (&assuan_cmd,
"OPTION include-certs %i", include_certs) < 0)
return gpg_error_from_syserror ();
err = gpgsm_assuan_simple_command (gpgsm, assuan_cmd, NULL, NULL);
gpgrt_free (assuan_cmd);
if (err)
return err;
}
for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++)
{
const char *s = key->subkeys ? key->subkeys->fpr : NULL;
if (s && strlen (s) < 80)
{
char buf[100];
strcpy (stpcpy (buf, "SIGNER "), s);
err = gpgsm_assuan_simple_command (gpgsm, buf,
gpgsm->status.fnc,
gpgsm->status.fnc_value);
}
else
err = gpg_error (GPG_ERR_INV_VALUE);
gpgme_key_unref (key);
if (err)
return err;
}
gpgsm->input_cb.data = in;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
gpgsm->output_cb.data = out;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, use_armor ? "--armor"
: map_data_enc (gpgsm->output_cb.data));
if (err)
return err;
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, mode == GPGME_SIG_MODE_DETACH
? "SIGN --detached" : "SIGN");
return err;
}
static gpgme_error_t
gpgsm_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text,
gpgme_data_t plaintext, gpgme_ctx_t ctx)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
(void)ctx;
if (!gpgsm)
return gpg_error (GPG_ERR_INV_VALUE);
gpgsm->input_cb.data = sig;
err = gpgsm_set_fd (gpgsm, INPUT_FD, map_data_enc (gpgsm->input_cb.data));
if (err)
return err;
if (!signed_text)
{
/* Normal or cleartext signature. */
if (plaintext)
{
gpgsm->output_cb.data = plaintext;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
}
else
{
/* No output requested. */
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
}
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
}
else
{
/* Detached signature. */
gpgsm->message_cb.data = signed_text;
err = gpgsm_set_fd (gpgsm, MESSAGE_FD, 0);
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
}
gpgsm->inline_data = NULL;
if (!err)
err = start (gpgsm, "VERIFY");
return err;
}
/* Send the GETAUDITLOG command. The result is saved to a gpgme data
object. */
static gpgme_error_t
gpgsm_getauditlog (void *engine, gpgme_data_t output, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err = 0;
if (!gpgsm || !output)
return gpg_error (GPG_ERR_INV_VALUE);
if ((flags & GPGME_AUDITLOG_DIAG) && (flags & GPGME_AUDITLOG_HTML))
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if ((flags & GPGME_AUDITLOG_DIAG))
{
char buf[BUFFER_SIZE];
int nread;
int any_written = 0;
gpgme_data_rewind (gpgsm->diagnostics);
while ((nread = gpgme_data_read (gpgsm->diagnostics,
buf, BUFFER_SIZE)) > 0)
{
any_written = 1;
if (gpgme_data_write (output, buf, nread) == -1)
return gpg_error_from_syserror ();
}
if (!any_written)
return gpg_error (GPG_ERR_NO_DATA);
if (nread == -1)
return gpg_error_from_syserror ();
gpgme_data_rewind (output);
return 0;
}
if (!gpgsm->assuan_ctx)
return gpg_error (GPG_ERR_INV_VALUE);
#if USE_DESCRIPTOR_PASSING
gpgsm->output_cb.data = output;
err = gpgsm_set_fd (gpgsm, OUTPUT_FD, 0);
if (err)
return err;
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
# define CMD "GETAUDITLOG"
#else
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = output;
# define CMD "GETAUDITLOG --data"
#endif
err = start (gpgsm, (flags & GPGME_AUDITLOG_HTML)? CMD " --html" : CMD);
return err;
}
/* This sets a status callback for monitoring status lines before they
* are passed to a caller set handler. */
static void
gpgsm_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->status.mon_cb = cb;
gpgsm->status.mon_cb_value = cb_value;
}
static void
gpgsm_set_status_handler (void *engine, engine_status_handler_t fnc,
void *fnc_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->status.fnc = fnc;
gpgsm->status.fnc_value = fnc_value;
}
static gpgme_error_t
gpgsm_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc,
void *fnc_value)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->colon.fnc = fnc;
gpgsm->colon.fnc_value = fnc_value;
gpgsm->colon.any = 0;
return 0;
}
static void
gpgsm_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs)
{
engine_gpgsm_t gpgsm = engine;
gpgsm->io_cbs = *io_cbs;
}
static void
gpgsm_io_event (void *engine, gpgme_event_io_t type, void *type_data)
{
engine_gpgsm_t gpgsm = engine;
TRACE (DEBUG_ENGINE, "gpgme:gpgsm_io_event", gpgsm,
"event %p, type %d, type_data %p",
gpgsm->io_cbs.event, type, type_data);
if (gpgsm->io_cbs.event)
(*gpgsm->io_cbs.event) (gpgsm->io_cbs.event_priv, type, type_data);
}
static gpgme_error_t
gpgsm_passwd (void *engine, gpgme_key_t key, unsigned int flags)
{
engine_gpgsm_t gpgsm = engine;
gpgme_error_t err;
char *line;
(void)flags;
if (!key || !key->subkeys || !key->subkeys->fpr)
return gpg_error (GPG_ERR_INV_CERT_OBJ);
if (gpgrt_asprintf (&line, "PASSWD -- %s", key->subkeys->fpr) < 0)
return gpg_error_from_syserror ();
gpgsm_clear_fd (gpgsm, OUTPUT_FD);
gpgsm_clear_fd (gpgsm, INPUT_FD);
gpgsm_clear_fd (gpgsm, MESSAGE_FD);
gpgsm->inline_data = NULL;
err = start (gpgsm, line);
gpgrt_free (line);
return err;
}
struct engine_ops _gpgme_engine_ops_gpgsm =
{
/* Static functions. */
_gpgme_get_default_gpgsm_name,
NULL,
gpgsm_get_version,
gpgsm_get_req_version,
gpgsm_new,
/* Member functions. */
gpgsm_release,
#if USE_DESCRIPTOR_PASSING
gpgsm_reset,
#else
NULL, /* reset */
#endif
gpgsm_set_status_cb,
gpgsm_set_status_handler,
NULL, /* set_command_handler */
gpgsm_set_colon_line_handler,
gpgsm_set_locale,
NULL, /* set_protocol */
gpgsm_set_engine_flags,
gpgsm_decrypt,
gpgsm_delete, /* decrypt_verify */
NULL, /* edit */
gpgsm_encrypt,
NULL, /* encrypt_sign */
gpgsm_export,
gpgsm_export_ext,
gpgsm_genkey,
gpgsm_import,
gpgsm_keylist,
gpgsm_keylist_ext,
NULL, /* keylist_data */
NULL, /* keysign */
NULL, /* tofu_policy */
gpgsm_sign,
NULL, /* trustlist */
gpgsm_verify,
gpgsm_getauditlog,
NULL, /* opassuan_transact */
NULL, /* conf_load */
NULL, /* conf_save */
NULL, /* conf_dir */
NULL, /* query_swdb */
gpgsm_set_io_cbs,
gpgsm_io_event,
gpgsm_cancel,
NULL, /* cancel_op */
gpgsm_passwd,
NULL, /* set_pinentry_mode */
NULL /* opspawn */
};
diff --git a/src/fdtable.c b/src/fdtable.c
index 2e682dea..de240fc3 100644
--- a/src/fdtable.c
+++ b/src/fdtable.c
@@ -1,205 +1,680 @@
/* fdtable.c - Keep track of file descriptors.
* Copyright (C) 2019 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include "gpgme.h"
#include "debug.h"
#include "context.h"
#include "fdtable.h"
/* The table to hold information about file descriptors. Currently we
* use a linear search and extend the table as needed. Eventually we
* may swicth to a hash table and allocate items on the fly. */
struct fdtable_item_s
{
int fd; /* -1 indicates an unused entry. */
+ uint64_t owner; /* The S/N of the context owning this FD. */
+
+ /* ACTIVE is set if this fd is in the global event loop, has an
+ * active callback (.io_cb), and has seen the start event. */
+ unsigned int active:1;
+ /* DONE is set if this fd was previously active but is not active
+ * any longer, either because is finished successfully or its I/O
+ * callback returned an error. Note that ACTIVE and DONE should
+ * never both be set. */
+ unsigned int done:1;
+
+ /* Infos for io_select. */
+ unsigned int for_read:1;
+ unsigned int for_write:1;
+ unsigned int signaled:1;
+
+ /* We are in a closing handler. Note that while this flag is active
+ * the remove code holds an index into the table. Thus we better
+ * make sure that the index won't change. Or change the removal
+ * code to re-find the fd. */
+ unsigned int closing:1;
+
+ /* We are currently running the IO callback. */
+ unsigned int io_cb_running:1;
+
+ /* The I/O callback handler with its value context. */
+ struct {
+ gpgme_io_cb_t cb;
+ void *value;
+ } io_cb;
+
+ /* The error code and the operational error for the done status. */
+ gpg_error_t done_status;
+ gpg_error_t done_op_err;
+
/* The callback to be called before the descriptor is actually closed. */
struct {
fdtable_handler_t handler;
void *value;
} close_notify;
};
typedef struct fdtable_item_s *fdtable_item_t;
/* The actual table, its size and the lock to guard access. */
static fdtable_item_t fdtable;
static unsigned int fdtablesize;
DEFINE_STATIC_LOCK (fdtable_lock);
/* Insert FD into our file descriptor table. This function checks
* that FD is not yet in the table. On success 0 is returned; if FD
* is already in the table GPG_ERR_DUP_KEY is returned. Other error
* codes may also be returned. */
gpg_error_t
_gpgme_fdtable_insert (int fd)
{
gpg_error_t err;
int firstunused, idx;
TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
if (fd < 0 )
return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
LOCK (fdtable_lock);
firstunused = -1;
for (idx=0; idx < fdtablesize; idx++)
if (fdtable[idx].fd == -1)
{
if (firstunused == -1)
firstunused = idx;
}
else if (fdtable[idx].fd == fd)
{
err = gpg_error (GPG_ERR_DUP_KEY);
goto leave;
}
if (firstunused == -1)
{
/* We need to increase the size of the table. The approach we
* take is straightforward to minimize the risk of bugs. */
fdtable_item_t newtbl;
size_t newsize = fdtablesize + 64;
newtbl = calloc (newsize, sizeof *newtbl);
if (!newtbl)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (idx=0; idx < fdtablesize; idx++)
newtbl[idx] = fdtable[idx];
for (; idx < newsize; idx++)
newtbl[idx].fd = -1;
free (fdtable);
fdtable = newtbl;
idx = fdtablesize;
fdtablesize = newsize;
}
else
idx = firstunused;
fdtable[idx].fd = fd;
+ fdtable[idx].owner = 0;
+ fdtable[idx].active = 0;
+ fdtable[idx].done = 0;
+ fdtable[idx].for_read = 0;
+ fdtable[idx].for_write = 0;
+ fdtable[idx].signaled = 0;
+ fdtable[idx].closing = 0;
+ fdtable[idx].io_cb_running = 0;
+ fdtable[idx].io_cb.cb = NULL;
+ fdtable[idx].io_cb.value = NULL;
fdtable[idx].close_notify.handler = NULL;
fdtable[idx].close_notify.value = NULL;
err = 0;
leave:
UNLOCK (fdtable_lock);
return TRACE_ERR (err);
}
/* Add the close notification HANDLER to the table under the key FD.
* FD must exist. VALUE is a pointer passed to the handler along with
* the FD. */
gpg_error_t
_gpgme_fdtable_add_close_notify (int fd,
fdtable_handler_t handler, void *value)
{
gpg_error_t err;
int idx;
TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
if (fd < 0 )
return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
LOCK (fdtable_lock);
for (idx=0; idx < fdtablesize; idx++)
if (fdtable[idx].fd == fd)
break;
if (idx == fdtablesize)
{
err = gpg_error (GPG_ERR_NO_KEY);
goto leave;
}
if (fdtable[idx].close_notify.handler)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
fdtable[idx].close_notify.handler = handler;
fdtable[idx].close_notify.value = value;
err = 0;
leave:
UNLOCK (fdtable_lock);
return TRACE_ERR (err);
}
+/* Set the I/O callback for the FD. FD must already exist otherwise
+ * GPG_ERR_NO_KEY is returned. OWNER is the serial of the owning
+ * context. If DIRECTION is 1 the callback wants to read from it; if
+ * it is 0 the callback want to write to it. CB is the actual
+ * callback and CB_VALUE the values passed to that callback. If a
+ * callback as already been set GPG_ERR_DUP_VALUE is returned. To
+ * remove the handler, FD and OWNER must be passed as usual but CB be
+ * passed as NULL.
+ */
+gpg_error_t
+_gpgme_fdtable_set_io_cb (int fd, uint64_t owner, int direction,
+ gpgme_io_cb_t cb, void *cb_value)
+{
+ gpg_error_t err;
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d ctx=%lu dir=%d",
+ fd, (unsigned long)owner, direction);
+
+ if (fd < 0 || !owner)
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ if (cb)
+ {
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd)
+ break;
+ if (idx == fdtablesize)
+ {
+ err = gpg_error (GPG_ERR_NO_KEY);
+ TRACE_LOG ("with_cb: fd=%d owner=%lu", fd, (unsigned long)owner);
+ goto leave;
+ }
+
+ if (fdtable[idx].io_cb.cb)
+ {
+ err = gpg_error (GPG_ERR_DUP_VALUE);
+ goto leave;
+ }
+
+ fdtable[idx].owner = owner;
+
+ fdtable[idx].for_read = (direction == 1);
+ fdtable[idx].for_write = (direction == 0);
+ fdtable[idx].signaled = 0;
+
+ fdtable[idx].io_cb.cb = cb;
+ fdtable[idx].io_cb.value = cb_value;
+ }
+ else /* Remove. */
+ {
+ /* We compare also the owner as a cross-check. */
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd == fd && fdtable[idx].owner == owner)
+ break;
+ if (idx == fdtablesize)
+ {
+ err = gpg_error (GPG_ERR_NO_KEY);
+ TRACE_LOG ("remove: fd=%d owner=%lu", fd, (unsigned long)owner);
+ for (idx=0; idx < fdtablesize; idx++)
+ TRACE_LOG (" TBL: fd=%d owner=%lu", fdtable[idx].fd, (unsigned long)fdtable[idx].owner);
+ goto leave;
+ }
+
+ fdtable[idx].for_read = 0;
+ fdtable[idx].for_write = 0;
+ fdtable[idx].signaled = 0;
+
+ fdtable[idx].io_cb.cb = NULL;
+ fdtable[idx].io_cb.value = NULL;
+ fdtable[idx].owner = 0;
+ }
+ err = 0;
+
+ leave:
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (err);
+}
+
+
+/* Set all FDs of OWNER into the active state. */
+gpg_error_t
+_gpgme_fdtable_set_active (uint64_t owner)
+{
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ if (!owner )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner
+ && fdtable[idx].io_cb.cb)
+ {
+ fdtable[idx].active = 1;
+ fdtable[idx].done = 0;
+ }
+
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (0);
+}
+
+
+/* Set all FDs of OWNER into the done state. STATUS and OP_ERR are
+ * recorded. */
+gpg_error_t
+_gpgme_fdtable_set_done (uint64_t owner, gpg_error_t status, gpg_error_t op_err)
+{
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ if (!owner )
+ return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner
+ && fdtable[idx].active)
+ {
+ fdtable[idx].active = 0;
+ fdtable[idx].done = 1;
+ fdtable[idx].done_status = status;
+ fdtable[idx].done_op_err = op_err;
+ }
+
+ UNLOCK (fdtable_lock);
+ return TRACE_ERR (0);
+}
+
+
+/* Walk over all fds in FDS and copy the signaled flag if set. It
+ * does not clear any signal flag in the global table. */
+void
+_gpgme_fdtable_set_signaled (io_select_t fds, unsigned int nfds)
+{
+ int idx;
+ unsigned int n, count;
+
+ if (!nfds)
+ return;
+
+ /* FIXME: Highly inefficient code in case of large select lists. */
+ count = 0;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ {
+ if (fdtable[idx].fd == -1)
+ continue;
+ for (n = 0; n < nfds; n++)
+ if (fdtable[idx].fd == fds[n].fd)
+ {
+ if (fds[n].signaled && !fdtable[idx].signaled)
+ {
+ fdtable[idx].signaled = 1;
+ count++; /* Only for tracing. */
+ }
+ break;
+ }
+ }
+ UNLOCK (fdtable_lock);
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "fds newly signaled=%u", count);
+}
+
+
/* Remove FD from the table after calling the close handler. Note
* that at the time the close handler is called the FD has been
* removed form the table. Thus the close handler may not access the
* fdtable anymore and assume that FD is still there. Callers may
* want to handle the error code GPG_ERR_NO_KEY which indicates that
* FD is not anymore or not yet in the table. */
gpg_error_t
_gpgme_fdtable_remove (int fd)
{
gpg_error_t err;
int idx;
fdtable_handler_t handler;
void *handlervalue;
TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "fd=%d", fd);
if (fd < 0 )
return TRACE_ERR (gpg_error (GPG_ERR_INV_ARG));
LOCK (fdtable_lock);
for (idx=0; idx < fdtablesize; idx++)
if (fdtable[idx].fd == fd)
break;
if (idx == fdtablesize)
{
UNLOCK (fdtable_lock);
return TRACE_ERR (gpg_error (GPG_ERR_NO_KEY));
}
+ TRACE_LOG ("removal of fd=%d owner=%lu (closing=%d)",
+ fdtable[idx].fd, (unsigned long)fdtable[idx].owner,
+ fdtable[idx].closing);
+
handler = fdtable[idx].close_notify.handler;
fdtable[idx].close_notify.handler = NULL;
handlervalue = fdtable[idx].close_notify.value;
fdtable[idx].close_notify.value = NULL;
- fdtable[idx].fd = -1;
+
+ /* The handler might call into the fdtable again, so of we have a
+ * handler we can't immediately close it but instead record the fact
+ * and remove the entry from the table only after the handler has
+ * been run. */
+ if (handler)
+ fdtable[idx].closing = 1;
+ else if (!fdtable[idx].closing)
+ fdtable[idx].fd = -1;
UNLOCK (fdtable_lock);
- err = handler? handler (fd, handlervalue) : 0;
+ if (handler)
+ {
+ err = handler (fd, handlervalue);
+ LOCK (fdtable_lock);
+ TRACE_LOG ("final removal of fd=%d owner=%lu (closing=%d)",
+ fdtable[idx].fd, (unsigned long)fdtable[idx].owner,
+ fdtable[idx].closing);
+ fdtable[idx].fd = -1;
+ UNLOCK (fdtable_lock);
+ }
+ else
+ err = 0;
return TRACE_ERR (err);
}
+
+
+/* Return the number of active I/O callbacks for OWNER or for all if
+ * OWNER is 0. */
+unsigned int
+_gpgme_fdtable_io_cb_count (uint64_t owner)
+{
+ int idx;
+ unsigned int count = 0;
+
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner))
+ count++;
+ UNLOCK (fdtable_lock);
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu count=%u",
+ (unsigned long)owner, count);
+ return count;
+}
+
+
+/* Run all signaled IO callbacks of OWNER or all signaled callbacks if
+ * OWNER is 0. Returns an error code on the first real error
+ * encountered. If R_OP_ERR is not NULL an optional operational error
+ * can be stored tehre. For EOF the respective flags are set. */
+gpg_error_t
+_gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err)
+{
+ gpg_error_t err;
+ int idx;
+ int fd;
+ gpgme_io_cb_t iocb;
+ struct io_cb_data iocb_data;
+ uint64_t serial;
+ unsigned int cb_count;
+ gpgme_ctx_t actx;
+
+ if (r_op_err)
+ *r_op_err = 0;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", owner);
+
+ for (;;)
+ {
+ fd = -1;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner)
+ && fdtable[idx].signaled)
+ {
+ fd = fdtable[idx].fd;
+ serial = fdtable[idx].owner;
+ iocb = fdtable[idx].io_cb.cb;
+ iocb_data.handler_value = fdtable[idx].io_cb.value;
+ iocb_data.op_err = 0;
+ fdtable[idx].signaled = 0;
+ if (iocb)
+ {
+ fdtable[idx].io_cb_running = 1;
+ break;
+ }
+ }
+ UNLOCK (fdtable_lock);
+ if (fd == -1)
+ break; /* No more callbacks found. */
+
+ /* If the context object is still valid and has not been
+ * canceled, we run the I/O callback. */
+ err = _gpgme_get_ctx (serial, &actx);
+ if (!err)
+ {
+ err = iocb (&iocb_data, fd);
+ if (err)
+ TRACE_LOG ("iocb(fd=%d) err=%s", fd, gpg_strerror (err));
+ }
+
+ /* Clear the running flag and while we are at it also count the
+ * remaining callbacks. */
+ cb_count = 0;
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ {
+ if (fdtable[idx].fd == -1)
+ continue;
+ if (fdtable[idx].fd == fd)
+ fdtable[idx].io_cb_running = 0;
+ if (fdtable[idx].owner == serial)
+ cb_count++;
+ }
+ UNLOCK (fdtable_lock);
+
+ /* Handle errors or success from the IO callback. In the error
+ * case we close all fds belonging to the same context. In the
+ * success case we check whether any callback is left and only
+ * if that is not the case, tell the engine that we are done.
+ * The latter indirectly sets the fd into the done state. */
+ if (err)
+ {
+ _gpgme_cancel_with_err (serial, err, 0);
+ return TRACE_ERR (err);
+ }
+ else if (iocb_data.op_err)
+ {
+ /* An operational error occurred. Cancel the current
+ * operation but not the session, and signal it. */
+ _gpgme_cancel_with_err (serial, 0, iocb_data.op_err);
+
+ /* NOTE: This relies on the operational error being
+ * generated after the operation really has completed, for
+ * example after no further status line output is generated.
+ * Otherwise the following I/O will spill over into the next
+ * operation. */
+ if (r_op_err)
+ *r_op_err = iocb_data.op_err;
+ return TRACE_ERR (0);
+ }
+ else if (!cb_count && actx)
+ {
+ struct gpgme_io_event_done_data data = { 0, 0 };
+ _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
+ }
+ }
+
+ return TRACE_ERR (0);
+}
+
+
+/* Retrieve a list of file descriptors owned by OWNER, or with OWNER
+ * being 0 of all fds, and store that list as a new array at R_FDS.
+ * Return the number of FDS in that list or 0 if none were selected.
+ * FLAGS give further selection flags:
+ * FDTABLE_FLAG_ACTIVE - Only those with the active flag set.
+ * FDTABLE_FLAG_DONE - Only those with the done flag set.
+ * FDTABLE_FLAG_FOR_READ - Only those with the readable FDs.
+ * FDTABLE_FLAG_FOR_WRITE - Only those with the writable FDs.
+ * FDTABLE_FLAG_SIGNALED - Only those with the signaled flag set.
+ * FDTABLE_FLAG_NOT_SIGNALED - Only those with the signaled flag cleared.
+ * FDTABLE_FLAG_CLEAR - Clear the signaled flag..
+ */
+unsigned int
+_gpgme_fdtable_get_fds (io_select_t *r_fds, uint64_t owner, unsigned int flags)
+{
+ int idx;
+ unsigned int count = 0;
+ io_select_t fds;
+
+ *r_fds = NULL;
+ gpg_err_set_errno (0);
+ /* We take an easy approach and allocate the array at the size of
+ * the entire fdtable. */
+ fds = calloc (fdtablesize, sizeof *fds);
+ if (!fds)
+ return 0;
+
+ LOCK (fdtable_lock);
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner))
+ {
+ if ((flags & FDTABLE_FLAG_ACTIVE) && !fdtable[idx].active)
+ continue;
+ if ((flags & FDTABLE_FLAG_DONE) && !fdtable[idx].done)
+ continue;
+ if ((flags & FDTABLE_FLAG_FOR_READ) && !fdtable[idx].for_read)
+ continue;
+ if ((flags & FDTABLE_FLAG_FOR_WRITE) && !fdtable[idx].for_write)
+ continue;
+ if ((flags & FDTABLE_FLAG_SIGNALED) && !fdtable[idx].signaled)
+ continue;
+ if ((flags & FDTABLE_FLAG_NOT_SIGNALED) && fdtable[idx].signaled)
+ continue;
+
+ if (fdtable[idx].io_cb_running || fdtable[idx].closing)
+ continue; /* The callback has not yet finished or we are
+ * already closing. Does not make sense to allow
+ * selecting on it. */
+
+ fds[count].fd = fdtable[idx].fd;
+ fds[count].for_read = fdtable[idx].for_read;
+ fds[count].for_write = fdtable[idx].for_write;
+ fds[count].signaled =
+ (flags & FDTABLE_FLAG_SIGNALED)? 0 : fdtable[idx].signaled;
+
+ count++;
+ }
+
+ UNLOCK (fdtable_lock);
+ *r_fds = fds;
+
+ TRACE (DEBUG_SYSIO, __func__, NULL, "ctx=%lu count=%u",
+ (unsigned long)owner, count);
+ return count;
+}
+
+
+/* If OWNER is 0 return the status info of the first fd with the done
+ * flag set. If OWNER is not 0 search for a matching owner with the
+ * done flag set and return its status info. Returns the serial
+ * number of the context found. */
+uint64_t
+_gpgme_fdtable_get_done (uint64_t owner,
+ gpg_error_t *r_status, gpg_error_t *r_op_err)
+{
+ uint64_t serial = 0;
+ int idx;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu", (unsigned long)owner);
+
+ LOCK (fdtable_lock);
+
+ for (idx=0; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && (!owner || fdtable[idx].owner == owner)
+ && fdtable[idx].done)
+ {
+ /* Found. If an owner has been given also clear the done
+ * flags from all other fds of this owner. Note that they
+ * have the same status info anyway. */
+ *r_status = fdtable[idx].done_status;
+ *r_op_err = fdtable[idx].done_op_err;
+ fdtable[idx].done = 0;
+ serial = fdtable[idx].owner;
+ if (owner)
+ {
+ for (; idx < fdtablesize; idx++)
+ if (fdtable[idx].fd != -1 && fdtable[idx].owner == owner)
+ fdtable[idx].done = 0;
+ }
+ break;
+ }
+
+ UNLOCK (fdtable_lock);
+
+ TRACE_SUC ("ctx=%lu", (unsigned long)serial);
+ return serial;
+}
diff --git a/src/fdtable.h b/src/fdtable.h
index 6038b249..6f621849 100644
--- a/src/fdtable.h
+++ b/src/fdtable.h
@@ -1,43 +1,81 @@
/* fdtable.h - Keep track of file descriptors.
* Copyright (C) 2019 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef GPGME_FDTABLE_H
#define GPGME_FDTABLE_H
+#include "priv-io.h"
+
+/* Flags used by _gpgme_fdtable_get_fds. */
+#define FDTABLE_FLAG_ACTIVE 1 /* Only those with the active flag set. */
+#define FDTABLE_FLAG_DONE 2 /* Only those with the done flag set */
+#define FDTABLE_FLAG_FOR_READ 4 /* Only those with the signaled flag set. */
+#define FDTABLE_FLAG_FOR_WRITE 8 /* Only those with the for_read flag set. */
+#define FDTABLE_FLAG_SIGNALED 16 /* Only those with the signaled flag set. */
+#define FDTABLE_FLAG_NOT_SIGNALED 32 /* Ditto reversed. */
+#define FDTABLE_FLAG_CLEAR 128 /* Clear the signaled flag. */
+
+
/* The handler type associated with an FD. It is called with the FD
* and the registered pointer. The handler may return an error code
* but there is no guarantee that this code is used; in particular
* errors from close notifications can't inhibit the the closing. */
typedef gpg_error_t (*fdtable_handler_t) (int, void*);
/* Insert a new FD into the table. */
gpg_error_t _gpgme_fdtable_insert (int fd);
/* Add a close notification handler to the FD item. */
gpg_error_t _gpgme_fdtable_add_close_notify (int fd,
fdtable_handler_t handler,
void *value);
+/* Set or remove the I/O callback. */
+gpg_error_t _gpgme_fdtable_set_io_cb (int fd, uint64_t owner, int direction,
+ gpgme_io_cb_t cb, void *cb_value);
+
+/* Set all FDs of OWNER into the active state. */
+gpg_error_t _gpgme_fdtable_set_active (uint64_t owner);
+
+/* Set all FDs of OWNER into the done state. */
+gpg_error_t _gpgme_fdtable_set_done (uint64_t owner,
+ gpg_error_t status, gpg_error_t op_err);
+
+/* Walk over all FDS and copy the signaled flag if set. */
+void _gpgme_fdtable_set_signaled (io_select_t fds, unsigned int nfds);
/* Remove FD from the table. This also runs the close handlers. */
gpg_error_t _gpgme_fdtable_remove (int fd);
+/* Return the number of active I/O callbacks for OWNER. */
+unsigned int _gpgme_fdtable_io_cb_count (uint64_t owner);
+
+/* Run all the signaled IO callbacks of OWNER. */
+gpg_error_t _gpgme_fdtable_run_io_cbs (uint64_t owner, gpg_error_t *r_op_err);
+
+/* Return a list of FDs matching the OWNER and FLAGS. */
+unsigned int _gpgme_fdtable_get_fds (io_select_t *r_fds,
+ uint64_t owner, unsigned int flags);
+
+/* Return the status info for the entry of OWNER. */
+uint64_t _gpgme_fdtable_get_done (uint64_t owner, gpg_error_t *r_status,
+ gpg_error_t *r_op_err);
#endif /*GPGME_FDTABLE_H*/
diff --git a/src/gpgme.c b/src/gpgme.c
index 283290f4..19489023 100644
--- a/src/gpgme.c
+++ b/src/gpgme.c
@@ -1,1383 +1,1389 @@
/* gpgme.c - GnuPG Made Easy.
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2012,
* 2014, 2015, 2019 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#ifdef HAVE_LOCALE_H
#include
#endif
#include "util.h"
#include "context.h"
#include "ops.h"
#include "wait.h"
#include "debug.h"
#include "priv-io.h"
#include "sys-util.h"
#include "mbox-util.h"
/* The lock used to protect access to the default locale, the global
* serial counter, and the list of context objects. */
DEFINE_STATIC_LOCK (context_list_lock);
/* The default locale. Access is protected by CONTEXT_LIST_LOCK. */
static char *def_lc_ctype;
static char *def_lc_messages;
/* A serial number to identify a context. To make debugging easier by
* distinguishing this from the data object s/n we initialize it with
* an arbitrary offset. Debug output of this should be done using
* decimal notation. Updates are protected by CONTEXT_LIST_LOCK. */
static uint64_t last_ctx_serial = 200000;
/* A linked list of all context objects. Protected by
* CONTEXT_LIST_LOCK. */
static gpgme_ctx_t context_list;
gpgme_error_t _gpgme_selftest = GPG_ERR_NOT_OPERATIONAL;
/* Protects all reference counters in result structures. All other
* accesses to a result structure are read only. */
DEFINE_STATIC_LOCK (result_ref_lock);
/* Set the global flag NAME to VALUE. Return 0 on success. Note that
this function does not use gpgme_error and thus a non-zero return
value merely means "error". Certain flags may be set before
gpgme_check_version is called. See the manual for a description of
supported flags. The caller must assure that this function is
called only by one thread at a time. */
int
gpgme_set_global_flag (const char *name, const char *value)
{
if (!name || !value)
return -1;
else if (!strcmp (name, "debug"))
return _gpgme_debug_set_debug_envvar (value);
else if (!strcmp (name, "disable-gpgconf"))
{
_gpgme_dirinfo_disable_gpgconf ();
return 0;
}
else if (!strcmp (name, "require-gnupg"))
return _gpgme_set_engine_minimal_version (value);
else if (!strcmp (name, "gpgconf-name"))
return _gpgme_set_default_gpgconf_name (value);
else if (!strcmp (name, "gpg-name"))
return _gpgme_set_default_gpg_name (value);
else if (!strcmp (name, "w32-inst-dir"))
return _gpgme_set_override_inst_dir (value);
else
return -1;
}
/* Create a new context as an environment for GPGME crypto
operations. */
gpgme_error_t
gpgme_new (gpgme_ctx_t *r_ctx)
{
gpgme_error_t err;
gpgme_ctx_t ctx;
TRACE_BEG (DEBUG_CTX, "gpgme_new", NULL, "");
if (_gpgme_selftest)
return TRACE_ERR (_gpgme_selftest);
if (!r_ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
ctx = calloc (1, sizeof *ctx);
if (!ctx)
return TRACE_ERR (gpg_error_from_syserror ());
INIT_LOCK (ctx->lock);
err = _gpgme_engine_info_copy (&ctx->engine_info);
if (!err && !ctx->engine_info)
err = gpg_error (GPG_ERR_NO_ENGINE);
if (err)
{
free (ctx);
return TRACE_ERR (err);
}
ctx->keylist_mode = GPGME_KEYLIST_MODE_LOCAL;
ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
ctx->protocol = GPGME_PROTOCOL_OpenPGP;
ctx->sub_protocol = GPGME_PROTOCOL_DEFAULT;
- _gpgme_fd_table_init (&ctx->fdt);
LOCK (context_list_lock);
if (def_lc_ctype)
{
ctx->lc_ctype = strdup (def_lc_ctype);
if (!ctx->lc_ctype)
{
int saved_err = gpg_error_from_syserror ();
UNLOCK (context_list_lock);
_gpgme_engine_info_release (ctx->engine_info);
free (ctx);
return TRACE_ERR (saved_err);
}
}
else
def_lc_ctype = NULL;
if (def_lc_messages)
{
ctx->lc_messages = strdup (def_lc_messages);
if (!ctx->lc_messages)
{
int saved_err = gpg_error_from_syserror ();
UNLOCK (context_list_lock);
if (ctx->lc_ctype)
free (ctx->lc_ctype);
_gpgme_engine_info_release (ctx->engine_info);
free (ctx);
return TRACE_ERR (saved_err);
}
}
else
def_lc_messages = NULL;
ctx->serial = ++last_ctx_serial;
/* Put the object itno a list. */
ctx->next_ctx = context_list;
context_list = ctx;
UNLOCK (context_list_lock);
*r_ctx = ctx;
TRACE_SUC ("ctx=%lu (%p)", CTXSERIAL(ctx), ctx);
return 0;
}
/* Check the status of the context described by SERIAL.
* Returns:
* 0 - All fine
* GPG_ERR_CANCELED - Context operation has been canceled.
* GPG_ERR_NO_OBJ - Context ist not anymore known.
* If R_CTX is not NULl and the context exists, it is stored there.
*/
gpg_error_t
_gpgme_get_ctx (uint64_t serial, gpgme_ctx_t *r_ctx)
{
gpg_error_t err;
gpgme_ctx_t ctx = NULL;
LOCK (context_list_lock);
for (ctx = context_list; ctx; ctx = ctx->next_ctx)
if (ctx->serial == serial)
break;
UNLOCK (context_list_lock);
if (ctx)
{
/* FIXME: The lock is only used for the canceled flag. We
* should remove it and rely on the global
* context_list_lock. */
LOCK (ctx->lock);
err = ctx->canceled? gpg_error (GPG_ERR_CANCELED) : 0;
UNLOCK (ctx->lock);
}
else
err = gpg_error (GPG_ERR_NO_OBJ);
if (r_ctx)
*r_ctx = ctx;
return err;
}
+
+
+
+/* Cancel the context indetified with SERIAL. Pass CTX_ERR or OP_ERR
+ * down to the engine. */
gpgme_error_t
-_gpgme_cancel_with_err (gpgme_ctx_t ctx, gpg_error_t ctx_err,
+_gpgme_cancel_with_err (uint64_t serial, gpg_error_t ctx_err,
gpg_error_t op_err)
{
gpgme_error_t err;
+ gpgme_ctx_t ctx;
struct gpgme_io_event_done_data data;
TRACE_BEG (DEBUG_CTX, "_gpgme_cancel_with_err", NULL,
"ctx=%lu ctx_err=%i op_err=%i",
- CTXSERIAL (ctx), ctx_err, op_err);
+ (unsigned long)serial, ctx_err, op_err);
- if (ctx_err)
- {
- err = _gpgme_engine_cancel (ctx->engine);
- if (err)
- return TRACE_ERR (err);
- }
+ LOCK (context_list_lock);
+ for (ctx = context_list; ctx; ctx = ctx->next_ctx)
+ if (ctx->serial == serial)
+ break;
+ UNLOCK (context_list_lock);
+
+ if (!ctx)
+ err = gpg_error (GPG_ERR_NO_OBJ);
+ else if (ctx_err)
+ err = _gpgme_engine_cancel (ctx->engine);
else
+ err = _gpgme_engine_cancel_op (ctx->engine);
+
+ if (!err)
{
- err = _gpgme_engine_cancel_op (ctx->engine);
- if (err)
- return TRACE_ERR (err);
+ data.err = ctx_err;
+ data.op_err = op_err;
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
}
-
- data.err = ctx_err;
- data.op_err = op_err;
-
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
-
- return TRACE_ERR (0);
+ return TRACE_ERR (err);
}
/* Cancel a pending asynchronous operation. */
gpgme_error_t
gpgme_cancel (gpgme_ctx_t ctx)
{
gpg_error_t err;
TRACE_BEG (DEBUG_CTX, "gpgme_cancel", NULL, "ctx=%lu", CTXSERIAL (ctx));
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
- err = _gpgme_cancel_with_err (ctx, gpg_error (GPG_ERR_CANCELED), 0);
+ err = _gpgme_cancel_with_err (ctx->serial, gpg_error (GPG_ERR_CANCELED), 0);
return TRACE_ERR (err);
}
/* Cancel a pending operation asynchronously. */
gpgme_error_t
gpgme_cancel_async (gpgme_ctx_t ctx)
{
TRACE_BEG (DEBUG_CTX, "gpgme_cancel_async", NULL,
"ctx=%lu", CTXSERIAL (ctx));
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
LOCK (ctx->lock);
ctx->canceled = 1;
UNLOCK (ctx->lock);
return TRACE_ERR (0);
}
/* Release all resources associated with the given context. */
void
gpgme_release (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_release", NULL, "ctx=%lu", CTXSERIAL (ctx));
if (!ctx)
return;
LOCK (context_list_lock);
if (context_list == ctx)
{
context_list = ctx->next_ctx;
ctx->next_ctx = NULL;
}
else
{
gpgme_ctx_t tmpctx;
for (tmpctx = context_list; tmpctx; tmpctx = tmpctx->next_ctx)
if (tmpctx->next_ctx == ctx)
{
tmpctx->next_ctx = ctx->next_ctx;
ctx->next_ctx = NULL;
break;
}
}
UNLOCK (context_list_lock);
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
- _gpgme_fd_table_deinit (&ctx->fdt);
+ /* FIXME: Remove stale FDs belonging to us? */
_gpgme_release_result (ctx);
_gpgme_signers_clear (ctx);
_gpgme_sig_notation_clear (ctx);
free (ctx->sender);
free (ctx->signers);
free (ctx->lc_ctype);
free (ctx->lc_messages);
free (ctx->override_session_key);
free (ctx->request_origin);
free (ctx->auto_key_locate);
free (ctx->trust_model);
_gpgme_engine_info_release (ctx->engine_info);
ctx->engine_info = NULL;
DESTROY_LOCK (ctx->lock);
free (ctx);
}
void
gpgme_result_ref (void *result)
{
struct ctx_op_data *data;
if (! result)
return;
data = (void*)((char*)result - sizeof (struct ctx_op_data));
assert (data->magic == CTX_OP_DATA_MAGIC);
LOCK (result_ref_lock);
data->references++;
UNLOCK (result_ref_lock);
}
void
gpgme_result_unref (void *result)
{
struct ctx_op_data *data;
if (! result)
return;
data = (void*)((char*)result - sizeof (struct ctx_op_data));
assert (data->magic == CTX_OP_DATA_MAGIC);
LOCK (result_ref_lock);
if (--data->references)
{
UNLOCK (result_ref_lock);
return;
}
UNLOCK (result_ref_lock);
if (data->cleanup)
(*data->cleanup) (data->hook);
free (data);
}
void
_gpgme_release_result (gpgme_ctx_t ctx)
{
struct ctx_op_data *data = ctx->op_data;
while (data)
{
struct ctx_op_data *next_data = data->next;
data->next = NULL;
gpgme_result_unref (data->hook);
data = next_data;
}
ctx->op_data = NULL;
}
/* Note that setting the protocol will intentionally not fail if the
* engine is not available. */
gpgme_error_t
gpgme_set_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
{
TRACE_BEG (DEBUG_CTX, "gpgme_set_protocol", NULL, "ctx=%lu protocol=%i (%s)",
CTXSERIAL (ctx), protocol, gpgme_get_protocol_name (protocol)
? gpgme_get_protocol_name (protocol) : "invalid");
if (protocol != GPGME_PROTOCOL_OpenPGP
&& protocol != GPGME_PROTOCOL_CMS
&& protocol != GPGME_PROTOCOL_GPGCONF
&& protocol != GPGME_PROTOCOL_ASSUAN
&& protocol != GPGME_PROTOCOL_G13
&& protocol != GPGME_PROTOCOL_UISERVER
&& protocol != GPGME_PROTOCOL_SPAWN)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
if (ctx->protocol != protocol)
{
/* Shut down the engine when switching protocols. */
if (ctx->engine)
{
TRACE_LOG ("releasing ctx->engine=%p", ctx->engine);
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
}
ctx->protocol = protocol;
}
return TRACE_ERR (0);
}
gpgme_protocol_t
gpgme_get_protocol (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_protocol", NULL,
"ctx=%lu protocol=%i (%s)", CTXSERIAL (ctx), ctx->protocol,
gpgme_get_protocol_name (ctx->protocol)
? gpgme_get_protocol_name (ctx->protocol) : "invalid");
return ctx->protocol;
}
gpgme_error_t
gpgme_set_sub_protocol (gpgme_ctx_t ctx, gpgme_protocol_t protocol)
{
TRACE (DEBUG_CTX, "gpgme_set_sub_protocol", NULL, "ctx=%lu protocol=%i (%s)",
CTXSERIAL (ctx), protocol, gpgme_get_protocol_name (protocol)
? gpgme_get_protocol_name (protocol) : "invalid");
if (!ctx)
return gpg_error (GPG_ERR_INV_VALUE);
ctx->sub_protocol = protocol;
return 0;
}
gpgme_protocol_t
gpgme_get_sub_protocol (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_sub_protocol", NULL,
"ctx=%lu sub_protocol=%i (%s)",
CTXSERIAL (ctx), ctx->sub_protocol,
gpgme_get_protocol_name (ctx->sub_protocol)
? gpgme_get_protocol_name (ctx->sub_protocol) : "invalid");
return ctx->sub_protocol;
}
const char *
gpgme_get_protocol_name (gpgme_protocol_t protocol)
{
switch (protocol)
{
case GPGME_PROTOCOL_OpenPGP:
return "OpenPGP";
case GPGME_PROTOCOL_CMS:
return "CMS";
case GPGME_PROTOCOL_GPGCONF:
return "GPGCONF";
case GPGME_PROTOCOL_ASSUAN:
return "Assuan";
case GPGME_PROTOCOL_G13:
return "G13";
case GPGME_PROTOCOL_UISERVER:
return "UIServer";
case GPGME_PROTOCOL_SPAWN:
return "Spawn";
case GPGME_PROTOCOL_DEFAULT:
return "default";
case GPGME_PROTOCOL_UNKNOWN:
return "unknown";
default:
return NULL;
}
}
/* Store the sender's address in the context. ADDRESS is addr-spec of
* mailbox but my also be a complete mailbox, in which case this
* function extracts the addr-spec from it. Returns 0 on success or
* an error code if no valid addr-spec could be extracted from
* ADDRESS. */
gpgme_error_t
gpgme_set_sender (gpgme_ctx_t ctx, const char *address)
{
char *p = NULL;
TRACE_BEG (DEBUG_CTX, "gpgme_set_sender", NULL, "ctx=%lu sender='%s'",
CTXSERIAL (ctx), address?address:"(null)");
if (!ctx || (address && !(p = _gpgme_mailbox_from_userid (address))))
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
free (ctx->sender);
ctx->sender = p;
return TRACE_ERR (0);
}
/* Return the sender's address (addr-spec part) from the context or
* NULL if none was set. The returned value is valid as long as the
* CTX is valid and gpgme_set_sender has not been used. */
const char *
gpgme_get_sender (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_sender", NULL, "ctx=%lu sender='%s'",
CTXSERIAL (ctx), ctx?ctx->sender:"");
return ctx->sender;
}
/* Enable or disable the use of an ascii armor for all output. */
void
gpgme_set_armor (gpgme_ctx_t ctx, int use_armor)
{
TRACE (DEBUG_CTX, "gpgme_set_armor", NULL, "ctx=%lu use_armor=%i (%s)",
CTXSERIAL (ctx), use_armor, use_armor ? "yes" : "no");
if (!ctx)
return;
ctx->use_armor = !!use_armor;
}
/* Return the state of the armor flag. */
int
gpgme_get_armor (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_armor", NULL, "ctx=%lu use_armor=%i (%s)",
CTXSERIAL (ctx), ctx->use_armor, ctx->use_armor ? "yes" : "no");
return ctx->use_armor;
}
/* Set the flag NAME for CTX to VALUE. Please consult the manual for
* a description of the flags.
*/
gpgme_error_t
gpgme_set_ctx_flag (gpgme_ctx_t ctx, const char *name, const char *value)
{
gpgme_error_t err = 0;
int abool;
TRACE (DEBUG_CTX, "gpgme_set_ctx_flag", NULL,
"ctx=%lu name='%s' value='%s'",
CTXSERIAL (ctx), name? name:"(null)", value?value:"(null)");
abool = (value && *value)? !!atoi (value) : 0;
if (!ctx || !name || !value)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (!strcmp (name, "redraw"))
{
ctx->redraw_suggested = abool;
}
else if (!strcmp (name, "full-status"))
{
ctx->full_status = abool;
}
else if (!strcmp (name, "raw-description"))
{
ctx->raw_description = abool;
}
else if (!strcmp (name, "export-session-key"))
{
ctx->export_session_keys = abool;
}
else if (!strcmp (name, "override-session-key"))
{
free (ctx->override_session_key);
ctx->override_session_key = strdup (value);
if (!ctx->override_session_key)
err = gpg_error_from_syserror ();
}
else if (!strcmp (name, "auto-key-retrieve"))
{
ctx->auto_key_retrieve = abool;
}
else if (!strcmp (name, "request-origin"))
{
free (ctx->request_origin);
ctx->request_origin = strdup (value);
if (!ctx->request_origin)
err = gpg_error_from_syserror ();
}
else if (!strcmp (name, "no-symkey-cache"))
{
ctx->no_symkey_cache = abool;
}
else if (!strcmp (name, "ignore-mdc-error"))
{
ctx->ignore_mdc_error = abool;
}
else if (!strcmp (name, "auto-key-locate"))
{
free (ctx->auto_key_locate);
ctx->auto_key_locate = strdup (value);
if (!ctx->auto_key_locate)
err = gpg_error_from_syserror ();
}
else if (!strcmp (name, "trust-model"))
{
free (ctx->trust_model);
ctx->trust_model = strdup (value);
if (!ctx->trust_model)
err = gpg_error_from_syserror ();
}
else
err = gpg_error (GPG_ERR_UNKNOWN_NAME);
return err;
}
/* Get the context flag named NAME. See gpgme_set_ctx_flag for a list
* of valid names. If the NAME is unknown NULL is returned. For a
* boolean flag an empty string is returned for False and the string
* "1" for True; thus either atoi or a simple string test can be
* used. */
const char *
gpgme_get_ctx_flag (gpgme_ctx_t ctx, const char *name)
{
if (!ctx || !name)
return NULL;
else if (!strcmp (name, "redraw"))
{
return ctx->redraw_suggested? "1":"";
}
else if (!strcmp (name, "full-status"))
{
return ctx->full_status? "1":"";
}
else if (!strcmp (name, "raw-description"))
{
return ctx->raw_description? "1":"";
}
else if (!strcmp (name, "export-session-key"))
{
return ctx->export_session_keys? "1":"";
}
else if (!strcmp (name, "override-session-key"))
{
return ctx->override_session_key? ctx->override_session_key : "";
}
else if (!strcmp (name, "auto-key-retrieve"))
{
return ctx->auto_key_retrieve? "1":"";
}
else if (!strcmp (name, "request-origin"))
{
return ctx->request_origin? ctx->request_origin : "";
}
else if (!strcmp (name, "no-symkey-cache"))
{
return ctx->no_symkey_cache? "1":"";
}
else if (!strcmp (name, "ignore-mdc-error"))
{
return ctx->ignore_mdc_error? "1":"";
}
else if (!strcmp (name, "auto-key-locate"))
{
return ctx->auto_key_locate? ctx->auto_key_locate : "";
}
else
return NULL;
}
/* Enable or disable the use of the special textmode. Textmode is for
example used for the RFC2015 signatures; note that the updated RFC
3156 mandates that the MUA does some preparations so that textmode
is not needed anymore. */
void
gpgme_set_textmode (gpgme_ctx_t ctx, int use_textmode)
{
TRACE (DEBUG_CTX, "gpgme_set_textmode", NULL, "ctx=%lu use_textmode=%i (%s)",
CTXSERIAL (ctx), use_textmode, use_textmode ? "yes" : "no");
if (!ctx)
return;
ctx->use_textmode = !!use_textmode;
}
/* Return the state of the textmode flag. */
int
gpgme_get_textmode (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_textmode", NULL, "ctx=%lu use_textmode=%i (%s)",
CTXSERIAL (ctx), ctx->use_textmode, ctx->use_textmode ? "yes" : "no");
return ctx->use_textmode;
}
/* Enable offline mode for this context. In offline mode dirmngr
will be disabled. */
void
gpgme_set_offline (gpgme_ctx_t ctx, int offline)
{
TRACE (DEBUG_CTX, "gpgme_set_offline", NULL, "ctx=%lu offline=%i (%s)",
CTXSERIAL (ctx), offline, offline ? "yes" : "no");
if (!ctx)
return;
ctx->offline = !!offline;
}
/* Return the state of the offline flag. */
int
gpgme_get_offline (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_offline", NULL, "ctx=%lu offline=%i (%s)",
CTXSERIAL (ctx), ctx->offline, ctx->offline ? "yes" : "no");
return ctx->offline;
}
/* Set the number of certifications to include in an S/MIME message.
The default is GPGME_INCLUDE_CERTS_DEFAULT. -1 means all certs,
and -2 means all certs except the root cert. */
void
gpgme_set_include_certs (gpgme_ctx_t ctx, int nr_of_certs)
{
if (!ctx)
return;
if (nr_of_certs == GPGME_INCLUDE_CERTS_DEFAULT)
ctx->include_certs = GPGME_INCLUDE_CERTS_DEFAULT;
else if (nr_of_certs < -2)
ctx->include_certs = -2;
else
ctx->include_certs = nr_of_certs;
TRACE (DEBUG_CTX, "gpgme_set_include_certs", NULL, "ctx=%lu nr_of_certs=%i%s",
CTXSERIAL (ctx),
nr_of_certs, nr_of_certs == ctx->include_certs ? "" : " (-2)");
}
/* Get the number of certifications to include in an S/MIME
message. */
int
gpgme_get_include_certs (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_include_certs", NULL, "ctx=%lu include_certs=%i",
CTXSERIAL (ctx), ctx->include_certs);
return ctx->include_certs;
}
/* This function changes the default behaviour of the keylisting
functions. MODE is a bitwise-OR of the GPGME_KEYLIST_* flags. The
default mode is GPGME_KEYLIST_MODE_LOCAL. */
gpgme_error_t
gpgme_set_keylist_mode (gpgme_ctx_t ctx, gpgme_keylist_mode_t mode)
{
TRACE (DEBUG_CTX, "gpgme_set_keylist_mode", NULL, "ctx=%lu keylist_mode=0x%x",
CTXSERIAL (ctx), mode);
if (!ctx)
return gpg_error (GPG_ERR_INV_VALUE);
ctx->keylist_mode = mode;
return 0;
}
/* This function returns the default behaviour of the keylisting
functions. */
gpgme_keylist_mode_t
gpgme_get_keylist_mode (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_keylist_mode", NULL,
"ctx=%lu keylist_mode=0x%x", CTXSERIAL (ctx), ctx->keylist_mode);
return ctx->keylist_mode;
}
/* Set the pinentry mode for CTX to MODE. */
gpgme_error_t
gpgme_set_pinentry_mode (gpgme_ctx_t ctx, gpgme_pinentry_mode_t mode)
{
TRACE (DEBUG_CTX, "gpgme_set_pinentry_mode", NULL, "ctx=%lu pinentry_mode=%u",
CTXSERIAL (ctx), (unsigned int)mode);
if (!ctx)
return gpg_error (GPG_ERR_INV_VALUE);
switch (mode)
{
case GPGME_PINENTRY_MODE_DEFAULT:
case GPGME_PINENTRY_MODE_ASK:
case GPGME_PINENTRY_MODE_CANCEL:
case GPGME_PINENTRY_MODE_ERROR:
case GPGME_PINENTRY_MODE_LOOPBACK:
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
ctx->pinentry_mode = mode;
return 0;
}
/* Get the pinentry mode of CTX. */
gpgme_pinentry_mode_t
gpgme_get_pinentry_mode (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_get_pinentry_mode", NULL, "ctx=%lu pinentry_mode=%u",
CTXSERIAL (ctx), (unsigned int)ctx->pinentry_mode);
return ctx->pinentry_mode;
}
/* This function sets a callback function to be used to pass a
passphrase to gpg. */
void
gpgme_set_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t cb,
void *cb_value)
{
TRACE (DEBUG_CTX, "gpgme_set_passphrase_cb", NULL,
"ctx=%lu passphrase_cb=%p/%p", CTXSERIAL (ctx), cb, cb_value);
if (!ctx)
return;
ctx->passphrase_cb = cb;
ctx->passphrase_cb_value = cb_value;
}
/* This function returns the callback function to be used to pass a
passphrase to the crypto engine. */
void
gpgme_get_passphrase_cb (gpgme_ctx_t ctx, gpgme_passphrase_cb_t *r_cb,
void **r_cb_value)
{
TRACE (DEBUG_CTX, "gpgme_get_passphrase_cb", NULL,
"ctx=%lu passphrase_cb=%p/%p",
CTXSERIAL (ctx), ctx->passphrase_cb, ctx->passphrase_cb_value);
if (r_cb)
*r_cb = ctx->passphrase_cb;
if (r_cb_value)
*r_cb_value = ctx->passphrase_cb_value;
}
/* This function sets a callback function to be used as a progress
indicator. */
void
gpgme_set_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t cb, void *cb_value)
{
TRACE (DEBUG_CTX, "gpgme_set_progress_cb", NULL, "ctx=%lu progress_cb=%p/%p",
CTXSERIAL (ctx), cb, cb_value);
if (!ctx)
return;
ctx->progress_cb = cb;
ctx->progress_cb_value = cb_value;
}
/* This function returns the callback function to be used as a
progress indicator. */
void
gpgme_get_progress_cb (gpgme_ctx_t ctx, gpgme_progress_cb_t *r_cb,
void **r_cb_value)
{
TRACE (DEBUG_CTX, "gpgme_get_progress_cb", NULL, "ctx=%lu progress_cb=%p/%p",
CTXSERIAL (ctx), ctx->progress_cb, ctx->progress_cb_value);
if (r_cb)
*r_cb = ctx->progress_cb;
if (r_cb_value)
*r_cb_value = ctx->progress_cb_value;
}
/* This function sets a callback function to be used as a status
message forwarder. */
void
gpgme_set_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t cb, void *cb_value)
{
TRACE (DEBUG_CTX, "gpgme_set_status_cb", NULL, "ctx=%lu status_cb=%p/%p",
CTXSERIAL (ctx), cb, cb_value);
if (!ctx)
return;
ctx->status_cb = cb;
ctx->status_cb_value = cb_value;
}
/* This function returns the callback function to be used as a
status message forwarder. */
void
gpgme_get_status_cb (gpgme_ctx_t ctx, gpgme_status_cb_t *r_cb,
void **r_cb_value)
{
TRACE (DEBUG_CTX, "gpgme_get_status_cb", NULL, "ctx=%lu status_cb=%p/%p",
CTXSERIAL (ctx),
ctx ? ctx->status_cb : NULL, ctx ? ctx->status_cb_value : NULL);
if (r_cb)
*r_cb = NULL;
if (r_cb_value)
*r_cb_value = NULL;
if (!ctx || !ctx->status_cb)
return;
if (r_cb)
*r_cb = ctx->status_cb;
if (r_cb_value)
*r_cb_value = ctx->status_cb_value;
}
/* Set the I/O callback functions for CTX to IO_CBS. */
void
gpgme_set_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
{
if (!ctx)
return;
if (io_cbs)
{
TRACE (DEBUG_CTX, "gpgme_set_io_cbs", NULL,
"ctx=%lu io_cbs=%p (add=%p/%p, remove=%p, event=%p/%p",
CTXSERIAL (ctx),
io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove,
io_cbs->event, io_cbs->event_priv);
- ctx->io_cbs = *io_cbs;
+ ctx->user_io_cbs = *io_cbs;
}
else
{
TRACE (DEBUG_CTX, "gpgme_set_io_cbs", NULL,
"ctx=%lu io_cbs=%p (default)", CTXSERIAL (ctx), io_cbs);
- ctx->io_cbs.add = NULL;
- ctx->io_cbs.add_priv = NULL;
- ctx->io_cbs.remove = NULL;
- ctx->io_cbs.event = NULL;
- ctx->io_cbs.event_priv = NULL;
+ ctx->user_io_cbs.add = NULL;
+ ctx->user_io_cbs.add_priv = NULL;
+ ctx->user_io_cbs.remove = NULL;
+ ctx->user_io_cbs.event = NULL;
+ ctx->user_io_cbs.event_priv = NULL;
}
}
/* This function provides access to the internal read function; it is
normally not used. */
gpgme_ssize_t
gpgme_io_read (int fd, void *buffer, size_t count)
{
int ret;
TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_read", NULL,
"fd=%d buffer=%p count=%zu", fd, buffer, count);
ret = _gpgme_io_read (fd, buffer, count);
return TRACE_SYSRES (ret);
}
/* This function provides access to the internal write function. It
is to be used by user callbacks to return data to gpgme. See
gpgme_passphrase_cb_t and gpgme_edit_cb_t. */
gpgme_ssize_t
gpgme_io_write (int fd, const void *buffer, size_t count)
{
int ret;
TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_write", NULL,
"fd=%d buffer=%p count=%zu", fd, buffer, count);
ret = _gpgme_io_write (fd, buffer, count);
return TRACE_SYSRES (ret);
}
/* This function provides access to the internal write function. It
is to be used by user callbacks to return data to gpgme. See
gpgme_passphrase_cb_t and gpgme_edit_cb_t. Note that this is a
variant of gpgme_io_write which guarantees that all COUNT bytes are
written or an error is return. Returns: 0 on success or -1 on
error and the sets errno. */
int
gpgme_io_writen (int fd, const void *buffer_arg, size_t count)
{
const char *buffer = buffer_arg;
int ret = 0;
TRACE_BEG (DEBUG_GLOBAL, "gpgme_io_writen", NULL,
"fd=%d buffer=%p count=%zu", fd, buffer, count);
while (count)
{
ret = _gpgme_io_write (fd, buffer, count);
if (ret < 0)
break;
buffer += ret;
count -= ret;
ret = 0;
}
return TRACE_SYSRES (ret);
}
/* This function returns the callback function for I/O. */
void
gpgme_get_io_cbs (gpgme_ctx_t ctx, gpgme_io_cbs_t io_cbs)
{
TRACE (DEBUG_CTX, "gpgme_get_io_cbs", NULL,
"ctx=%lu io_cbs=%p ctx->io_cbs.add=%p/%p .remove=%p, .event=%p/%p",
CTXSERIAL (ctx),
io_cbs, io_cbs->add, io_cbs->add_priv, io_cbs->remove,
io_cbs->event, io_cbs->event_priv);
- *io_cbs = ctx->io_cbs;
+ *io_cbs = ctx->user_io_cbs;
}
/* This function sets the locale for the context CTX, or the default
locale if CTX is a null pointer. */
gpgme_error_t
gpgme_set_locale (gpgme_ctx_t ctx, int category, const char *value)
{
int failed = 0;
char *new_lc_ctype = NULL;
char *new_lc_messages = NULL;
TRACE_BEG (DEBUG_CTX, "gpgme_set_locale", NULL,
"ctx=%lu category=%i value=%s",
CTXSERIAL (ctx), category, value ? value : "(null)");
#define PREPARE_ONE_LOCALE(lcat, ucat) \
if (!failed && value \
&& (category == LC_ALL || category == LC_ ## ucat)) \
{ \
new_lc_ ## lcat = strdup (value); \
if (!new_lc_ ## lcat) \
failed = 1; \
}
#ifdef LC_CTYPE
PREPARE_ONE_LOCALE (ctype, CTYPE);
#endif
#ifdef LC_MESSAGES
PREPARE_ONE_LOCALE (messages, MESSAGES);
#endif
if (failed)
{
int saved_err = gpg_error_from_syserror ();
if (new_lc_ctype)
free (new_lc_ctype);
if (new_lc_messages)
free (new_lc_messages);
return TRACE_ERR (saved_err);
}
#define SET_ONE_LOCALE(lcat, ucat) \
if (category == LC_ALL || category == LC_ ## ucat) \
{ \
if (ctx) \
{ \
if (ctx->lc_ ## lcat) \
free (ctx->lc_ ## lcat); \
ctx->lc_ ## lcat = new_lc_ ## lcat; \
} \
else \
{ \
if (def_lc_ ## lcat) \
free (def_lc_ ## lcat); \
def_lc_ ## lcat = new_lc_ ## lcat; \
} \
}
if (!ctx)
LOCK (context_list_lock);
#ifdef LC_CTYPE
SET_ONE_LOCALE (ctype, CTYPE);
#endif
#ifdef LC_MESSAGES
SET_ONE_LOCALE (messages, MESSAGES);
#endif
if (!ctx)
UNLOCK (context_list_lock);
return TRACE_ERR (0);
}
/* Get the information about the configured engines. A pointer to the
first engine in the statically allocated linked list is returned.
The returned data is valid until the next gpgme_ctx_set_engine_info. */
gpgme_engine_info_t
gpgme_ctx_get_engine_info (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_ctx_get_engine_info", NULL,
"ctx=%lu engine_info=%p", CTXSERIAL (ctx), ctx->engine_info);
return ctx->engine_info;
}
/* Set the engine info for the context CTX, protocol PROTO, to the
file name FILE_NAME and the home directory HOME_DIR. */
gpgme_error_t
gpgme_ctx_set_engine_info (gpgme_ctx_t ctx, gpgme_protocol_t proto,
const char *file_name, const char *home_dir)
{
gpgme_error_t err;
TRACE_BEG (DEBUG_CTX, "gpgme_ctx_set_engine_info", NULL,
"ctx=%lu protocol=%i (%s), file_name=%s, home_dir=%s",
CTXSERIAL (ctx), proto, gpgme_get_protocol_name (proto)
? gpgme_get_protocol_name (proto) : "unknown",
file_name ? file_name : "(default)",
home_dir ? home_dir : "(default)");
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
/* Shut down the engine when changing engine info. */
if (ctx->engine)
{
TRACE_LOG ("releasing ctx->engine=%p", ctx->engine);
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
}
err = _gpgme_set_engine_info (ctx->engine_info, proto,
file_name, home_dir);
return TRACE_ERR (err);
}
/* Clear all notation data from the context. */
void
_gpgme_sig_notation_clear (gpgme_ctx_t ctx)
{
gpgme_sig_notation_t notation;
if (!ctx)
return;
notation = ctx->sig_notations;
while (notation)
{
gpgme_sig_notation_t next_notation = notation->next;
_gpgme_sig_notation_free (notation);
notation = next_notation;
}
ctx->sig_notations = NULL;
}
void
gpgme_sig_notation_clear (gpgme_ctx_t ctx)
{
TRACE (DEBUG_CTX, "gpgme_sig_notation_clear", NULL, "ctx=%lu",
CTXSERIAL (ctx));
if (!ctx)
return;
_gpgme_sig_notation_clear (ctx);
}
/* Add the human-readable notation data with name NAME and value VALUE
to the context CTX, using the flags FLAGS. If NAME is NULL, then
VALUE should be a policy URL. The flag
GPGME_SIG_NOTATION_HUMAN_READABLE is forced to be true for notation
data, and false for policy URLs. */
gpgme_error_t
gpgme_sig_notation_add (gpgme_ctx_t ctx, const char *name,
const char *value, gpgme_sig_notation_flags_t flags)
{
gpgme_error_t err;
gpgme_sig_notation_t notation;
gpgme_sig_notation_t *lastp;
TRACE_BEG (DEBUG_CTX, "gpgme_sig_notation_add", NULL,
"ctx=%lu name=%s value=%s flags=0x%x",
CTXSERIAL (ctx),
name ? name : "(null)", value ? value : "(null)",
flags);
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
if (name)
flags |= GPGME_SIG_NOTATION_HUMAN_READABLE;
else
flags &= ~GPGME_SIG_NOTATION_HUMAN_READABLE;
err = _gpgme_sig_notation_create (¬ation, name, name ? strlen (name) : 0,
value, value ? strlen (value) : 0, flags);
if (err)
return TRACE_ERR (err);
lastp = &ctx->sig_notations;
while (*lastp)
lastp = &(*lastp)->next;
*lastp = notation;
return TRACE_ERR (0);
}
/* Get the sig notations for this context. */
gpgme_sig_notation_t
gpgme_sig_notation_get (gpgme_ctx_t ctx)
{
if (!ctx)
{
TRACE (DEBUG_CTX, "gpgme_sig_notation_get", NULL, "ctx=%lu",
CTXSERIAL (ctx));
return NULL;
}
TRACE (DEBUG_CTX, "gpgme_sig_notation_get", NULL,
"ctx=%lu sig_notations=%p", CTXSERIAL (ctx), ctx->sig_notations);
return ctx->sig_notations;
}
/* Return a public key algorithm string made of the algorithm and size
or the curve name. May return NULL on error. Caller must free the
result using gpgme_free. */
char *
gpgme_pubkey_algo_string (gpgme_subkey_t subkey)
{
const char *prefix = NULL;
char *result;
if (!subkey)
{
gpg_err_set_errno (EINVAL);
return NULL;
}
switch (subkey->pubkey_algo)
{
case GPGME_PK_RSA:
case GPGME_PK_RSA_E:
case GPGME_PK_RSA_S: prefix = "rsa"; break;
case GPGME_PK_ELG_E: prefix = "elg"; break;
case GPGME_PK_DSA: prefix = "dsa"; break;
case GPGME_PK_ELG: prefix = "xxx"; break;
case GPGME_PK_ECC:
case GPGME_PK_ECDH:
case GPGME_PK_ECDSA:
case GPGME_PK_EDDSA: prefix = ""; break;
}
if (prefix && *prefix)
{
char buffer[40];
snprintf (buffer, sizeof buffer, "%s%u", prefix, subkey->length);
result = strdup (buffer);
}
else if (prefix && subkey->curve && *subkey->curve)
result = strdup (subkey->curve);
else if (prefix)
result = strdup ("E_error");
else
result = strdup ("unknown");
return result;
}
const char *
gpgme_pubkey_algo_name (gpgme_pubkey_algo_t algo)
{
switch (algo)
{
case GPGME_PK_RSA: return "RSA";
case GPGME_PK_RSA_E: return "RSA-E";
case GPGME_PK_RSA_S: return "RSA-S";
case GPGME_PK_ELG_E: return "ELG-E";
case GPGME_PK_DSA: return "DSA";
case GPGME_PK_ECC: return "ECC";
case GPGME_PK_ELG: return "ELG";
case GPGME_PK_ECDSA: return "ECDSA";
case GPGME_PK_ECDH: return "ECDH";
case GPGME_PK_EDDSA: return "EdDSA";
default: return NULL;
}
}
const char *
gpgme_hash_algo_name (gpgme_hash_algo_t algo)
{
switch (algo)
{
case GPGME_MD_MD5:
return "MD5";
case GPGME_MD_SHA1:
return "SHA1";
case GPGME_MD_RMD160:
return "RIPEMD160";
case GPGME_MD_MD2:
return "MD2";
case GPGME_MD_TIGER:
return "TIGER192";
case GPGME_MD_HAVAL:
return "HAVAL";
case GPGME_MD_SHA256:
return "SHA256";
case GPGME_MD_SHA384:
return "SHA384";
case GPGME_MD_SHA512:
return "SHA512";
case GPGME_MD_SHA224:
return "SHA224";
case GPGME_MD_MD4:
return "MD4";
case GPGME_MD_CRC32:
return "CRC32";
case GPGME_MD_CRC32_RFC1510:
return "CRC32RFC1510";
case GPGME_MD_CRC24_RFC2440:
return "CRC24RFC2440";
default:
return NULL;
}
}
diff --git a/src/op-support.c b/src/op-support.c
index 6affb5e4..874827e1 100644
--- a/src/op-support.c
+++ b/src/op-support.c
@@ -1,433 +1,433 @@
/* op-support.c - Supporting functions.
* Copyright (C) 2002, 2003, 2004, 2007 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#ifdef HAVE_LOCALE_H
#include
#endif
#include "gpgme.h"
#include "context.h"
#include "ops.h"
#include "util.h"
#include "debug.h"
#if GPG_ERROR_VERSION_NUMBER < 0x011700 /* 1.23 */
# define GPG_ERR_SUBKEYS_EXP_OR_REV 217
#endif
gpgme_error_t
_gpgme_op_data_lookup (gpgme_ctx_t ctx, ctx_op_data_id_t type, void **hook,
int size, void (*cleanup) (void *))
{
struct ctx_op_data *data;
if (!ctx)
return gpg_error (GPG_ERR_INV_VALUE);
data = ctx->op_data;
while (data && data->type != type)
data = data->next;
if (!data)
{
if (size < 0)
{
*hook = NULL;
return 0;
}
data = calloc (1, sizeof (struct ctx_op_data) + size);
if (!data)
return gpg_error_from_syserror ();
data->magic = CTX_OP_DATA_MAGIC;
data->next = ctx->op_data;
data->type = type;
data->cleanup = cleanup;
data->hook = (void *) (((char *) data) + sizeof (struct ctx_op_data));
data->references = 1;
ctx->op_data = data;
}
*hook = data->hook;
return 0;
}
/* type is: 0: asynchronous operation (use global or user event loop).
1: synchronous operation (always use private event loop).
2: asynchronous private operation (use private or user
event loop).
256: Modification flag to suppress the engine reset.
*/
gpgme_error_t
_gpgme_op_reset (gpgme_ctx_t ctx, int type)
{
gpgme_error_t err = 0;
struct gpgme_io_cbs io_cbs;
int no_reset = (type & 256);
int reuse_engine = 0;
type &= 255;
_gpgme_release_result (ctx);
LOCK (ctx->lock);
ctx->canceled = 0;
ctx->redraw_suggested = 0;
UNLOCK (ctx->lock);
if (ctx->engine && no_reset)
reuse_engine = 1;
else if (ctx->engine)
{
/* Attempt to reset an existing engine. */
err = _gpgme_engine_reset (ctx->engine);
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
{
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
}
}
if (!ctx->engine)
{
gpgme_engine_info_t info;
info = ctx->engine_info;
while (info && info->protocol != ctx->protocol)
info = info->next;
if (!info)
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
/* Create an engine object. */
err = _gpgme_engine_new (info, &ctx->engine);
if (err)
return err;
}
if (!reuse_engine)
{
err = 0;
#ifdef LC_CTYPE
err = _gpgme_engine_set_locale (ctx->engine, LC_CTYPE, ctx->lc_ctype);
#endif
#ifdef LC_MESSAGES
if (!err)
err = _gpgme_engine_set_locale (ctx->engine,
LC_MESSAGES, ctx->lc_messages);
#endif
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = 0;
_gpgme_engine_set_engine_flags (ctx->engine, ctx);
if (!err)
{
err = _gpgme_engine_set_pinentry_mode (ctx->engine,
ctx->pinentry_mode);
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED)
err = 0;
}
if (!err && ctx->status_cb && ctx->full_status)
{
_gpgme_engine_set_status_cb (ctx->engine,
ctx->status_cb, ctx->status_cb_value);
}
if (err)
{
_gpgme_engine_release (ctx->engine);
ctx->engine = NULL;
return err;
}
}
if (ctx->sub_protocol != GPGME_PROTOCOL_DEFAULT)
{
err = _gpgme_engine_set_protocol (ctx->engine, ctx->sub_protocol);
if (err)
return err;
}
- if (type == 1 || (type == 2 && !ctx->io_cbs.add))
+ if (type == 1 || (type == 2 && !ctx->user_io_cbs.add))
{
/* Use private event loop. */
io_cbs.add = _gpgme_add_io_cb;
io_cbs.add_priv = ctx;
io_cbs.remove = _gpgme_remove_io_cb;
io_cbs.event = _gpgme_wait_private_event_cb;
io_cbs.event_priv = ctx;
}
- else if (! ctx->io_cbs.add)
+ else if (!ctx->user_io_cbs.add)
{
/* Use global event loop. */
io_cbs.add = _gpgme_add_io_cb;
io_cbs.add_priv = ctx;
io_cbs.remove = _gpgme_remove_io_cb;
io_cbs.event = _gpgme_wait_global_event_cb;
io_cbs.event_priv = ctx;
}
else
{
/* Use user event loop. */
- io_cbs.add = _gpgme_wait_user_add_io_cb;
+ io_cbs.add = _gpgme_add_io_cb_user;
io_cbs.add_priv = ctx;
- io_cbs.remove = _gpgme_wait_user_remove_io_cb;
+ io_cbs.remove = _gpgme_remove_io_cb_user;
io_cbs.event = _gpgme_wait_user_event_cb;
io_cbs.event_priv = ctx;
}
_gpgme_engine_set_io_cbs (ctx->engine, &io_cbs);
return err;
}
/* Parse the INV_RECP or INV_SNDR status line in ARGS and return the
result in KEY. If KC_FPR (from the KEY_CONSIDERED status line) is
not NULL take the KC_FLAGS in account. */
gpgme_error_t
_gpgme_parse_inv_recp (char *args, int for_signing,
const char *kc_fpr, unsigned int kc_flags,
gpgme_invalid_key_t *key)
{
gpgme_invalid_key_t inv_key;
char *tail;
long int reason;
(void)for_signing;
inv_key = calloc (1, sizeof (*inv_key));
if (!inv_key)
return gpg_error_from_syserror ();
inv_key->next = NULL;
gpg_err_set_errno (0);
reason = strtol (args, &tail, 0);
if (errno || args == tail || (*tail && *tail != ' '))
{
/* The crypto backend does not behave. */
free (inv_key);
return trace_gpg_error (GPG_ERR_INV_ENGINE);
}
switch (reason)
{
case 0:
if (kc_fpr && (kc_flags & 2))
inv_key->reason = gpg_error (GPG_ERR_SUBKEYS_EXP_OR_REV);
else
inv_key->reason = gpg_error (GPG_ERR_GENERAL);
break;
case 1:
inv_key->reason = gpg_error (GPG_ERR_NO_PUBKEY);
break;
case 2:
inv_key->reason = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
break;
case 3:
inv_key->reason = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
break;
case 4:
inv_key->reason = gpg_error (GPG_ERR_CERT_REVOKED);
break;
case 5:
inv_key->reason = gpg_error (GPG_ERR_CERT_EXPIRED);
break;
case 6:
inv_key->reason = gpg_error (GPG_ERR_NO_CRL_KNOWN);
break;
case 7:
inv_key->reason = gpg_error (GPG_ERR_CRL_TOO_OLD);
break;
case 8:
inv_key->reason = gpg_error (GPG_ERR_NO_POLICY_MATCH);
break;
case 9:
inv_key->reason = gpg_error (GPG_ERR_NO_SECKEY);
break;
case 10:
inv_key->reason = gpg_error (GPG_ERR_PUBKEY_NOT_TRUSTED);
break;
case 11:
inv_key->reason = gpg_error (GPG_ERR_MISSING_CERT);
break;
case 12:
inv_key->reason = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
break;
case 13:
inv_key->reason = gpg_error (252); /*GPG_ERR_KEY_DISABLED*/
break;
case 14:
inv_key->reason = gpg_error (GPG_ERR_INV_USER_ID);
break;
default:
inv_key->reason = gpg_error (GPG_ERR_GENERAL);
break;
}
while (*tail && *tail == ' ')
tail++;
if (*tail)
{
inv_key->fpr = strdup (tail);
if (!inv_key->fpr)
{
free (inv_key);
return gpg_error_from_syserror ();
}
}
*key = inv_key;
return 0;
}
/* Parse a KEY_CONSIDERED status line in ARGS and store the
* fingerprint and the flags at R_FPR and R_FLAGS. The caller must
* free the value at R_FPR on success. */
gpgme_error_t
_gpgme_parse_key_considered (const char *args,
char **r_fpr, unsigned int *r_flags)
{
char *pend;
size_t n;
*r_fpr = NULL;
pend = strchr (args, ' ');
if (!pend || pend == args)
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Bogus status line. */
n = pend - args;
*r_fpr = malloc (n + 1);
if (!*r_fpr)
return gpg_error_from_syserror ();
memcpy (*r_fpr, args, n);
(*r_fpr)[n] = 0;
args = pend + 1;
gpg_err_set_errno (0);
*r_flags = strtoul (args, &pend, 0);
if (errno || args == pend || (*pend && *pend != ' '))
{
free (*r_fpr);
*r_fpr = NULL;
return trace_gpg_error (GPG_ERR_INV_ENGINE);
}
return 0;
}
/* Parse the PLAINTEXT status line in ARGS and return the result in
FILENAMEP. */
gpgme_error_t
_gpgme_parse_plaintext (char *args, char **filenamep, int *r_mime)
{
char *tail;
while (*args == ' ')
args++;
if (*args == '\0')
return 0;
/* First argument is file type (a one byte uppercase hex value). */
if (args[0] == '6' && args[1] == 'D')
*r_mime = 1;
while (*args != ' ' && *args != '\0')
args++;
while (*args == ' ')
args++;
if (*args == '\0')
return 0;
/* Second argument is the timestamp. */
while (*args != ' ' && *args != '\0')
args++;
while (*args == ' ')
args++;
if (*args == '\0')
return 0;
tail = args;
while (*tail != ' ' && *tail != '\0')
tail++;
*tail = '\0';
if (filenamep && *args != '\0')
{
char *filename = strdup (args);
if (!filename)
return gpg_error_from_syserror ();
*filenamep = filename;
}
return 0;
}
/* Parse a FAILURE status line and return the error code. ARGS is
* modified to contain the location part. Note that for now we ignore
* failure codes with a location of gpg-exit; they are too trouble
* some. Instead we should eventually record that error in the
* context and provide a function to return a fuller error
* description; this could then also show the location of the error
* (e.g. "option- parser") to make it easier for the user to detect
* the actual error. */
gpgme_error_t
_gpgme_parse_failure (char *args)
{
char *where, *which;
if (!strncmp (args, "gpg-exit", 8))
return 0;
where = strchr (args, ' ');
if (!where)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
*where = '\0';
which = where + 1;
where = strchr (which, ' ');
if (where)
*where = '\0';
return atoi (which);
}
diff --git a/src/posix-io.c b/src/posix-io.c
index b754ac34..5d9edf10 100644
--- a/src/posix-io.c
+++ b/src/posix-io.c
@@ -1,872 +1,877 @@
/* posix-io.c - Posix I/O functions
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2004, 2005, 2007, 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#ifdef HAVE_STDINT_H
# include
#endif
#include
#include
#include
#include
#include
#ifdef HAVE_UNISTD_H
# include
#endif
#ifdef HAVE_SYS_TIME_H
# include
#endif
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#include
#ifdef HAVE_SYS_UIO_H
# include
#endif
#include
#include
#ifdef USE_LINUX_GETDENTS
# include
# include
# include
#endif /*USE_LINUX_GETDENTS*/
#include "util.h"
#include "priv-io.h"
#include "sema.h"
#include "ath.h"
#include "fdtable.h"
#include "debug.h"
#ifdef USE_LINUX_GETDENTS
/* This is not declared in public headers; getdents64(2) says that we must
* define it ourselves. */
struct linux_dirent64
{
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[];
};
# define DIR_BUF_SIZE 1024
#endif /*USE_LINUX_GETDENTS*/
/* Return true if FD is valid file descriptor. */
#if 0
int
_gpgme_is_fd_valid (int fd)
{
int dir_fd;
char dir_buf[DIR_BUF_SIZE];
struct linux_dirent64 *dir_entry;
int r, pos, x;
const char *s;
int result = 0;
dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
if (dir_fd != -1)
{
for (;;)
{
r = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE);
if (r == -1)
break; /* Ooops */
if (r == 0)
break;
for (pos = 0; pos < r; pos += dir_entry->d_reclen)
{
dir_entry = (struct linux_dirent64 *) (dir_buf + pos);
s = dir_entry->d_name;
if (*s < '0' || *s > '9')
continue;
/* atoi is not guaranteed to be async-signal-safe. */
for (x = 0; *s >= '0' && *s <= '9'; s++)
x = x * 10 + (*s - '0');
if (*s)
continue; /* Does not look like a file descriptor. */
if (x == fd)
{
result = 1;
goto leave;
}
}
}
leave:
close (dir_fd);
}
return result;
}
#endif /*0*/
void
_gpgme_io_subsystem_init (void)
{
struct sigaction act;
sigaction (SIGPIPE, NULL, &act);
if (act.sa_handler == SIG_DFL)
{
act.sa_handler = SIG_IGN;
sigemptyset (&act.sa_mask);
act.sa_flags = 0;
sigaction (SIGPIPE, &act, NULL);
}
}
/* Write the printable version of FD to the buffer BUF of length
BUFLEN. The printable version is the representation on the command
line that the child process expects. */
int
_gpgme_io_fd2str (char *buf, int buflen, int fd)
{
return snprintf (buf, buflen, "%d", fd);
}
int
_gpgme_io_read (int fd, void *buffer, size_t count)
{
int nread;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", NULL,
"fd=%d buffer=%p count=%zu", fd, buffer, count);
do
{
nread = _gpgme_ath_read (fd, buffer, count);
}
while (nread == -1 && errno == EINTR);
TRACE_LOGBUFX (buffer, nread);
return TRACE_SYSRES (nread);
}
int
_gpgme_io_write (int fd, const void *buffer, size_t count)
{
int nwritten;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", NULL,
"fd=%d buffer=%p count=%zu", fd, buffer, count);
TRACE_LOGBUFX (buffer, count);
do
{
nwritten = _gpgme_ath_write (fd, buffer, count);
}
while (nwritten == -1 && errno == EINTR);
return TRACE_SYSRES (nwritten);
}
int
_gpgme_io_pipe (int filedes[2], int inherit_idx)
{
gpg_error_t err;
int res;
int saved_errno;
int i;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", NULL,
"inherit_idx=%i (GPGME uses it for %s)",
inherit_idx, inherit_idx ? "reading" : "writing");
res = pipe (filedes);
if (res < 0)
return TRACE_SYSRES (res);
/* FIXME: Should get the old flags first. */
res = fcntl (filedes[1 - inherit_idx], F_SETFD, FD_CLOEXEC);
saved_errno = errno;
if (res < 0)
{
close (filedes[0]);
close (filedes[1]);
}
errno = saved_errno;
if (res)
return TRACE_SYSRES (res);
for (i=0; i < 2; i++)
{
err = _gpgme_fdtable_insert (filedes[i]);
if (err)
{
TRACE_LOG ("fdtable_insert failed for fd=%d: %s\n",
filedes[i], gpg_strerror (err));
close (filedes[0]);
close (filedes[1]);
gpg_err_set_errno (EIO);
return TRACE_SYSRES (-1);
}
}
TRACE_SUC ("read fd=%d write fd=%d", filedes[0], filedes[1]);
return 0;
}
int
_gpgme_io_close (int fd)
{
gpg_error_t err;
int res;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", NULL, "fd=%d", fd);
if (fd == -1)
return TRACE_SYSRES (0); /* Igore invalid FDs. */
/* First remove from the table which also runs the close handlers.
* Having the FD not (yet) in the table is possible and thus we
* ignore that error code. */
err = _gpgme_fdtable_remove (fd);
if (err && gpg_err_code (err) != GPG_ERR_NO_KEY)
{
TRACE_LOG ("fdtable_remove failed for fd=%d: %s\n",
fd, gpg_strerror (err));
gpg_err_set_errno (EINVAL);
return TRACE_SYSRES (-1);
}
/* Then do the close. */
res = close (fd);
return TRACE_SYSRES (res);
}
int
_gpgme_io_set_nonblocking (int fd)
{
int flags;
int res;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", NULL, "fd=%d", fd);
flags = fcntl (fd, F_GETFL, 0);
if (flags == -1)
return TRACE_SYSRES (-1);
flags |= O_NONBLOCK;
res = fcntl (fd, F_SETFL, flags);
return TRACE_SYSRES (res);
}
static long int
get_max_fds (void)
{
const char *source = NULL;
long int fds = -1;
int rc;
/* Under Linux we can figure out the highest used file descriptor by
* reading /proc/self/fd. This is in the common cases much faster
* than for example doing 4096 close calls where almost all of them
* will fail.
*
* We can't use the normal opendir/readdir/closedir interface between
* fork and exec in a multi-threaded process because opendir uses
* malloc and thus a mutex which may deadlock with a malloc in another
* thread. However, the underlying getdents system call is safe. */
#ifdef USE_LINUX_GETDENTS
{
int dir_fd;
char dir_buf[DIR_BUF_SIZE];
struct linux_dirent64 *dir_entry;
int r, pos;
const char *s;
int x;
dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
if (dir_fd != -1)
{
for (;;)
{
r = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE);
if (r == -1)
{
/* Fall back to other methods. */
fds = -1;
break;
}
if (r == 0)
break;
for (pos = 0; pos < r; pos += dir_entry->d_reclen)
{
dir_entry = (struct linux_dirent64 *) (dir_buf + pos);
s = dir_entry->d_name;
if (*s < '0' || *s > '9')
continue;
/* atoi is not guaranteed to be async-signal-safe. */
for (x = 0; *s >= '0' && *s <= '9'; s++)
x = x * 10 + (*s - '0');
if (!*s && x > fds && x != dir_fd)
fds = x;
}
}
close (dir_fd);
}
if (fds != -1)
{
fds++;
source = "/proc";
}
}
#endif /*USE_LINUX_GETDENTS*/
#ifdef RLIMIT_NOFILE
if (fds == -1)
{
struct rlimit rl;
rc = getrlimit (RLIMIT_NOFILE, &rl);
if (rc == 0)
{
source = "RLIMIT_NOFILE";
fds = rl.rlim_max;
}
}
#endif
#ifdef RLIMIT_OFILE
if (fds == -1)
{
struct rlimit rl;
rc = getrlimit (RLIMIT_OFILE, &rl);
if (rc == 0)
{
source = "RLIMIT_OFILE";
fds = rl.rlim_max;
}
}
#endif
#ifdef _SC_OPEN_MAX
if (fds == -1)
{
long int scres;
scres = sysconf (_SC_OPEN_MAX);
if (scres >= 0)
{
source = "_SC_OPEN_MAX";
return scres;
}
}
#endif
#ifdef OPEN_MAX
if (fds == -1)
{
source = "OPEN_MAX";
fds = OPEN_MAX;
}
#endif
#if !defined(RLIMIT_NOFILE) && !defined(RLIMIT_OFILE) \
&& !defined(_SC_OPEN_MAX) && !defined(OPEN_MAX)
#warning "No known way to get the maximum number of file descriptors."
#endif
if (fds == -1)
{
source = "arbitrary";
/* Arbitrary limit. */
fds = 1024;
}
/* AIX returns INT32_MAX instead of a proper value. We assume that
* this is always an error and use a more reasonable limit. */
#ifdef INT32_MAX
if (fds == INT32_MAX)
{
source = "aix-fix";
fds = 1024;
}
#endif
TRACE (DEBUG_SYSIO, "gpgme:max_fds", NULL, "max fds=%ld (%s)", fds, source);
return fds;
}
int
_gpgme_io_waitpid (int pid, int hang, int *r_status, int *r_signal)
{
int status;
pid_t ret;
*r_status = 0;
*r_signal = 0;
do
ret = _gpgme_ath_waitpid (pid, &status, hang? 0 : WNOHANG);
while (ret == (pid_t)(-1) && errno == EINTR);
if (ret == pid)
{
if (WIFSIGNALED (status))
{
*r_status = 4; /* Need some value here. */
*r_signal = WTERMSIG (status);
}
else if (WIFEXITED (status))
*r_status = WEXITSTATUS (status);
else
*r_status = 4; /* Oops. */
return 1;
}
return 0;
}
/* Returns 0 on success, -1 on error. */
int
_gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
struct spawn_fd_item_s *fd_list,
void (*atfork) (void *opaque, int reserved),
void *atforkvalue, pid_t *r_pid)
{
pid_t pid;
int i;
int status;
int signo;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", NULL,
"path=%s", path);
i = 0;
while (argv[i])
{
TRACE_LOG ("argv[%2i] = %s", i, argv[i]);
i++;
}
for (i = 0; fd_list[i].fd != -1; i++)
{
if (fd_list[i].dup_to == -1)
TRACE_LOG ("fd[%i] = 0x%x", i, fd_list[i].fd);
else
TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i,fd_list[i].fd,fd_list[i].dup_to);
}
pid = fork ();
if (pid == -1)
return TRACE_SYSRES (-1);
if (!pid)
{
/* Intermediate child to prevent zombie processes. */
if ((pid = fork ()) == 0)
{
/* Child. */
int max_fds = -1;
int fd;
int seen_stdin = 0;
int seen_stdout = 0;
int seen_stderr = 0;
if (atfork)
atfork (atforkvalue, 0);
/* First close all fds which will not be inherited. If we
* have closefrom(2) we first figure out the highest fd we
* do not want to close, then call closefrom, and on success
* use the regular code to close all fds up to the start
* point of closefrom. Note that Solaris' and FreeBSD's closefrom do
* not return errors. */
#ifdef HAVE_CLOSEFROM
{
fd = -1;
for (i = 0; fd_list[i].fd != -1; i++)
if (fd_list[i].fd > fd)
fd = fd_list[i].fd;
fd++;
#if defined(__sun) || defined(__FreeBSD__)
closefrom (fd);
max_fds = fd;
#else /*!__sun */
while ((i = closefrom (fd)) && errno == EINTR)
;
if (!i || errno == EBADF)
max_fds = fd;
#endif /*!__sun*/
}
#endif /*HAVE_CLOSEFROM*/
if (max_fds == -1)
max_fds = get_max_fds ();
for (fd = 0; fd < max_fds; fd++)
{
for (i = 0; fd_list[i].fd != -1; i++)
if (fd_list[i].fd == fd)
break;
if (fd_list[i].fd == -1)
close (fd);
}
/* And now dup and close those to be duplicated. */
for (i = 0; fd_list[i].fd != -1; i++)
{
int child_fd;
int res;
if (fd_list[i].dup_to != -1)
child_fd = fd_list[i].dup_to;
else
child_fd = fd_list[i].fd;
if (child_fd == 0)
seen_stdin = 1;
else if (child_fd == 1)
seen_stdout = 1;
else if (child_fd == 2)
seen_stderr = 1;
if (fd_list[i].dup_to == -1)
continue;
res = dup2 (fd_list[i].fd, fd_list[i].dup_to);
if (res < 0)
{
#if 0
/* FIXME: The debug file descriptor is not
dup'ed anyway, so we can't see this. */
TRACE_LOG ("dup2 failed in child: %s\n",
strerror (errno));
#endif
_exit (8);
}
close (fd_list[i].fd);
}
if (! seen_stdin || ! seen_stdout || !seen_stderr)
{
fd = open ("/dev/null", O_RDWR);
if (fd == -1)
{
/* The debug file descriptor is not dup'ed, so we
can't do a trace output. */
_exit (8);
}
/* Make sure that the process has connected stdin. */
if (! seen_stdin && fd != 0)
{
if (dup2 (fd, 0) == -1)
_exit (8);
}
if (! seen_stdout && fd != 1)
{
if (dup2 (fd, 1) == -1)
_exit (8);
}
if (! seen_stderr && fd != 2)
{
if (dup2 (fd, 2) == -1)
_exit (8);
}
if (fd != 0 && fd != 1 && fd != 2)
close (fd);
}
execv (path, (char *const *) argv);
/* Hmm: in that case we could write a special status code to the
status-pipe. */
_exit (8);
/* End child. */
}
if (pid == -1)
_exit (1);
else
_exit (0);
}
TRACE_LOG ("waiting for child process pid=%i", pid);
_gpgme_io_waitpid (pid, 1, &status, &signo);
if (status)
return TRACE_SYSRES (-1);
for (i = 0; fd_list[i].fd != -1; i++)
{
if (! (flags & IOSPAWN_FLAG_NOCLOSE))
_gpgme_io_close (fd_list[i].fd);
/* No handle translation. */
fd_list[i].peer_name = fd_list[i].fd;
}
if (r_pid)
*r_pid = pid;
return TRACE_SYSRES (0);
}
-/* Select on the list of fds. Returns: -1 = error, 0 = timeout or
- nothing to select, > 0 = number of signaled fds. */
+/* Select on the list of fds.
+ *
+ * Returns: -1 = error,
+ * 0 = timeout or nothing to select,
+ * > 0 = number of signaled fds.
+ */
int
-_gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
+_gpgme_io_select (io_select_t fds, unsigned int nfds, int nonblock)
{
fd_set readfds;
fd_set writefds;
unsigned int i;
int any;
int max_fd;
int n;
int count;
/* Use a 1s timeout. */
struct timeval timeout = { 1, 0 };
void *dbg_help = NULL;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", NULL,
- "nfds=%zu, nonblock=%u", nfds, nonblock);
+ "nfds=%u, nonblock=%u", nfds, nonblock);
FD_ZERO (&readfds);
FD_ZERO (&writefds);
max_fd = 0;
if (nonblock)
timeout.tv_sec = 0;
TRACE_SEQ (dbg_help, "select on [ ");
any = 0;
for (i = 0; i < nfds; i++)
{
if (fds[i].fd == -1)
continue;
if (fds[i].for_read)
{
if (fds[i].fd >= FD_SETSIZE)
{
TRACE_END (dbg_help, " -BAD- ]");
gpg_err_set_errno (EMFILE);
return TRACE_SYSRES (-1);
}
assert (!FD_ISSET (fds[i].fd, &readfds));
FD_SET (fds[i].fd, &readfds);
if (fds[i].fd > max_fd)
max_fd = fds[i].fd;
TRACE_ADD1 (dbg_help, "r=%d ", fds[i].fd);
any = 1;
}
else if (fds[i].for_write)
{
if (fds[i].fd >= FD_SETSIZE)
{
TRACE_END (dbg_help, " -BAD- ]");
gpg_err_set_errno (EMFILE);
return TRACE_SYSRES (-1);
}
assert (!FD_ISSET (fds[i].fd, &writefds));
FD_SET (fds[i].fd, &writefds);
if (fds[i].fd > max_fd)
max_fd = fds[i].fd;
TRACE_ADD1 (dbg_help, "w=%d ", fds[i].fd);
any = 1;
}
fds[i].signaled = 0;
}
TRACE_END (dbg_help, "]");
if (!any)
return TRACE_SYSRES (0);
do
{
count = _gpgme_ath_select (max_fd + 1, &readfds, &writefds, NULL,
&timeout);
}
while (count < 0 && errno == EINTR);
if (count < 0)
return TRACE_SYSRES (-1);
TRACE_SEQ (dbg_help, "select OK [ ");
if (TRACE_ENABLED (dbg_help))
{
for (i = 0; i <= max_fd; i++)
{
if (FD_ISSET (i, &readfds))
TRACE_ADD1 (dbg_help, "r=%d ", i);
if (FD_ISSET (i, &writefds))
TRACE_ADD1 (dbg_help, "w=%d ", i);
}
TRACE_END (dbg_help, "]");
}
/* The variable N is used to optimize it a little bit. */
for (n = count, i = 0; i < nfds && n; i++)
{
if (fds[i].fd == -1)
;
else if (fds[i].for_read)
{
if (FD_ISSET (fds[i].fd, &readfds))
{
fds[i].signaled = 1;
n--;
}
}
else if (fds[i].for_write)
{
if (FD_ISSET (fds[i].fd, &writefds))
{
fds[i].signaled = 1;
n--;
}
}
}
return TRACE_SYSRES (count);
}
+
int
_gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags)
{
int nread;
int saved_errno;
struct iovec *iov;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_recvmsg", NULL,
"fd=%d msg=%p flags=%i", fd, msg, flags);
nread = 0;
iov = msg->msg_iov;
while (iov < msg->msg_iov + msg->msg_iovlen)
{
nread += iov->iov_len;
iov++;
}
TRACE_LOG ("about to receive %d bytes", nread);
do
{
nread = _gpgme_ath_recvmsg (fd, msg, flags);
}
while (nread == -1 && errno == EINTR);
saved_errno = errno;
if (nread > 0)
{
int nr = nread;
iov = msg->msg_iov;
while (nr > 0)
{
int len = nr > iov->iov_len ? iov->iov_len : nr;
TRACE_LOGBUFX (msg->msg_iov->iov_base, len);
iov++;
nr -= len;
}
}
errno = saved_errno;
return TRACE_SYSRES (nread);
}
int
_gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags)
{
int nwritten;
struct iovec *iov;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_sendmsg", NULL,
"fd=%d msg=%p flags=%i", fd, msg, flags);
nwritten = 0;
iov = msg->msg_iov;
while (iov < msg->msg_iov + msg->msg_iovlen)
{
nwritten += iov->iov_len;
iov++;
}
TRACE_LOG ("about to receive %d bytes", nwritten);
iov = msg->msg_iov;
while (nwritten > 0)
{
int len = nwritten > iov->iov_len ? iov->iov_len : nwritten;
TRACE_LOGBUFX (msg->msg_iov->iov_base, len);
iov++;
nwritten -= len;
}
do
{
nwritten = _gpgme_ath_sendmsg (fd, msg, flags);
}
while (nwritten == -1 && errno == EINTR);
return TRACE_SYSRES (nwritten);
}
int
_gpgme_io_dup (int fd)
{
gpg_error_t err;
int new_fd;
do
new_fd = dup (fd);
while (new_fd == -1 && errno == EINTR);
TRACE (DEBUG_SYSIO, __func__, NULL, "fd=%d -> fd=%d", fd, new_fd);
err = _gpgme_fdtable_insert (new_fd);
if (err)
{
TRACE (DEBUG_SYSIO, __func__, NULL,
"fdtable_insert failed for fd=%d: %s\n",
new_fd, gpg_strerror (err));
close (new_fd);
new_fd = -1;
gpg_err_set_errno (EIO);
}
return new_fd;
}
int
_gpgme_io_socket (int domain, int type, int proto)
{
int res;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", NULL,
"domain=%d type=%i proto=%i", domain, type, proto);
res = socket (domain, type, proto);
return TRACE_SYSRES (res);
}
int
_gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen)
{
int res;
TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", NULL,
"fd=%d addr=%p addrlen=%i", fd, addr, addrlen);
do
res = ath_connect (fd, addr, addrlen);
while (res == -1 && errno == EINTR);
return TRACE_SYSRES (res);
}
diff --git a/src/priv-io.h b/src/priv-io.h
index f40cdffc..27cc07eb 100644
--- a/src/priv-io.h
+++ b/src/priv-io.h
@@ -1,114 +1,115 @@
/* priv-io.h - Interface to the private I/O functions.
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003, 2004, 2005 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#ifndef IO_H
#define IO_H
#ifdef HAVE_W32_SYSTEM
# include
# include
#else
# include
#endif
/* For pid_t. */
#ifdef HAVE_SYS_TYPES_H
# include
#endif
/* A single file descriptor passed to spawn. For child fds, dup_to
specifies the fd it should become in the child, but only 0, 1 and 2
are valid values (due to a limitation in the W32 code). As return
value, the PEER_NAME fields specify the name of the file
descriptor in the spawned process, or -1 if no change. If ARG_LOC
is not 0, it specifies the index in the argument vector of the
program which contains a numerical representation of the file
descriptor for translation purposes. */
struct spawn_fd_item_s
{
int fd;
int dup_to;
int peer_name;
int arg_loc;
};
-struct io_select_fd_s
+struct io_select_s
{
int fd;
- int for_read;
- int for_write;
- int signaled;
- void *opaque;
+ unsigned int for_read:1;
+ unsigned int for_write:1;
+ unsigned int signaled:1;
};
+typedef struct io_select_s *io_select_t;
+
/* These function are either defined in posix-io.c or w32-io.c. */
void _gpgme_io_subsystem_init (void);
int _gpgme_io_socket (int namespace, int style, int protocol);
int _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen);
int _gpgme_io_read (int fd, void *buffer, size_t count);
int _gpgme_io_write (int fd, const void *buffer, size_t count);
int _gpgme_io_pipe (int filedes[2], int inherit_idx);
int _gpgme_io_close (int fd);
int _gpgme_io_set_nonblocking (int fd);
/* Under Windows do not allocate a console. */
#define IOSPAWN_FLAG_DETACHED 1
/* A flag to tell the spawn function to allow the child process to set
the foreground window. */
#define IOSPAWN_FLAG_ALLOW_SET_FG 2
/* Don't close any child FDs. */
#define IOSPAWN_FLAG_NOCLOSE 4
/* Set show window to true for windows */
#define IOSPAWN_FLAG_SHOW_WINDOW 8
/* Spawn the executable PATH with ARGV as arguments. After forking
close all fds except for those in FD_LIST in the child, then
optionally dup() the child fds. Finally, all fds in the list are
closed in the parent. */
int _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
struct spawn_fd_item_s *fd_list,
void (*atfork) (void *opaque, int reserved),
void *atforkvalue, pid_t *r_pid);
-int _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock);
+int _gpgme_io_select (io_select_t fds, unsigned int nfds, int nonblock);
/* Write the printable version of FD to the buffer BUF of length
BUFLEN. The printable version is the representation on the command
line that the child process expects. */
int _gpgme_io_fd2str (char *buf, int buflen, int fd);
/* Duplicate a file descriptor. This is more restrictive than dup():
it assumes that the resulting file descriptors are essentially
co-equal (for example, no private offset), which is true for pipes
and sockets (but not files) under Unix with the standard dup()
function. Basically, this function is used to reference count the
status output file descriptor shared between GPGME and libassuan
(in engine-gpgsm.c). */
int _gpgme_io_dup (int fd);
#ifndef HAVE_W32_SYSTEM
int _gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags);
int _gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags);
int _gpgme_io_waitpid (int pid, int hang, int *r_status, int *r_signal);
#endif
#endif /* IO_H */
diff --git a/src/verify.c b/src/verify.c
index 8aa9d281..f6bc23c4 100644
--- a/src/verify.c
+++ b/src/verify.c
@@ -1,1394 +1,1394 @@
/* verify.c - Signature verification.
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2003, 2004, 2005 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include "gpgme.h"
#include "debug.h"
#include "util.h"
#include "context.h"
#include "ops.h"
typedef struct
{
struct _gpgme_op_verify_result result;
/* The error code from a FAILURE status line or 0. */
gpg_error_t failure_code;
gpgme_signature_t current_sig;
int did_prepare_new_sig;
int only_newsig_seen;
int plaintext_seen;
int conflict_user_seen;
} *op_data_t;
static void
release_op_data (void *hook)
{
op_data_t opd = (op_data_t) hook;
gpgme_signature_t sig = opd->result.signatures;
while (sig)
{
gpgme_signature_t next = sig->next;
gpgme_sig_notation_t notation = sig->notations;
while (notation)
{
gpgme_sig_notation_t next_nota = notation->next;
_gpgme_sig_notation_free (notation);
notation = next_nota;
}
if (sig->fpr)
free (sig->fpr);
if (sig->pka_address)
free (sig->pka_address);
if (sig->key)
gpgme_key_unref (sig->key);
free (sig);
sig = next;
}
if (opd->result.file_name)
free (opd->result.file_name);
}
gpgme_verify_result_t
gpgme_op_verify_result (gpgme_ctx_t ctx)
{
void *hook;
op_data_t opd;
gpgme_error_t err;
gpgme_signature_t sig;
TRACE_BEG (DEBUG_CTX, "gpgme_op_verify_result", ctx, "");
err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL);
opd = hook;
if (err || !opd)
{
TRACE_SUC ("result=(null)");
return NULL;
}
/* It is possible that we saw a new signature only followed by an
ERROR line for that. In particular a missing X.509 key triggers
this. In this case it is surprising that the summary field has
not been updated. We fix it here by explicitly looking for this
case. The real fix would be to have GPGME emit ERRSIG. */
for (sig = opd->result.signatures; sig; sig = sig->next)
{
if (!sig->summary)
{
switch (gpg_err_code (sig->status))
{
case GPG_ERR_KEY_EXPIRED:
sig->summary |= GPGME_SIGSUM_KEY_EXPIRED;
break;
case GPG_ERR_NO_PUBKEY:
sig->summary |= GPGME_SIGSUM_KEY_MISSING;
break;
default:
break;
}
}
}
/* Now for some tracing stuff. */
if (_gpgme_debug_trace ())
{
int i;
for (sig = opd->result.signatures, i = 0; sig; sig = sig->next, i++)
{
TRACE_LOG ("sig[%i] = fpr %s, summary 0x%x, status %s",
i, sig->fpr, sig->summary, gpg_strerror (sig->status));
TRACE_LOG ("sig[%i] = timestamps 0x%lx/0x%lx flags:%s%s%s",
i, sig->timestamp, sig->exp_timestamp,
sig->wrong_key_usage ? "wrong key usage" : "",
sig->pka_trust == 1 ? "pka bad"
: (sig->pka_trust == 2 ? "pka_okay" : "pka RFU"),
sig->chain_model ? "chain model" : "");
TRACE_LOG ("sig[%i] = validity 0x%x (%s), algos %s/%s",
i, sig->validity, gpg_strerror (sig->validity_reason),
gpgme_pubkey_algo_name (sig->pubkey_algo),
gpgme_hash_algo_name (sig->hash_algo));
if (sig->pka_address)
{
TRACE_LOG ("sig[%i] = PKA address %s", i, sig->pka_address);
}
if (sig->notations)
{
TRACE_LOG ("sig[%i] = has notations (not shown)", i);
}
}
}
TRACE_SUC ("result=%p", &opd->result);
return &opd->result;
}
/* Build a summary vector from RESULT. */
static void
calc_sig_summary (gpgme_signature_t sig)
{
unsigned long sum = 0;
/* Calculate the red/green flag. */
if (sig->validity == GPGME_VALIDITY_FULL
|| sig->validity == GPGME_VALIDITY_ULTIMATE)
{
if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR
|| gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED
|| gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED)
sum |= GPGME_SIGSUM_GREEN;
}
else if (sig->validity == GPGME_VALIDITY_NEVER)
{
if (gpg_err_code (sig->status) == GPG_ERR_NO_ERROR
|| gpg_err_code (sig->status) == GPG_ERR_SIG_EXPIRED
|| gpg_err_code (sig->status) == GPG_ERR_KEY_EXPIRED)
sum |= GPGME_SIGSUM_RED;
}
else if (gpg_err_code (sig->status) == GPG_ERR_BAD_SIGNATURE)
sum |= GPGME_SIGSUM_RED;
/* FIXME: handle the case when key and message are expired. */
switch (gpg_err_code (sig->status))
{
case GPG_ERR_SIG_EXPIRED:
sum |= GPGME_SIGSUM_SIG_EXPIRED;
break;
case GPG_ERR_KEY_EXPIRED:
sum |= GPGME_SIGSUM_KEY_EXPIRED;
break;
case GPG_ERR_NO_PUBKEY:
sum |= GPGME_SIGSUM_KEY_MISSING;
break;
case GPG_ERR_CERT_REVOKED:
sum |= GPGME_SIGSUM_KEY_REVOKED;
break;
case GPG_ERR_BAD_SIGNATURE:
case GPG_ERR_NO_ERROR:
break;
default:
sum |= GPGME_SIGSUM_SYS_ERROR;
break;
}
/* Now look at the certain reason codes. */
switch (gpg_err_code (sig->validity_reason))
{
case GPG_ERR_CRL_TOO_OLD:
if (sig->validity == GPGME_VALIDITY_UNKNOWN)
sum |= GPGME_SIGSUM_CRL_TOO_OLD;
break;
case GPG_ERR_CERT_REVOKED:
/* Note that this is a second way to set this flag. It may also
have been set due to a sig->status of STATUS_REVKEYSIG from
parse_new_sig. */
sum |= GPGME_SIGSUM_KEY_REVOKED;
break;
default:
break;
}
/* Check other flags. */
if (sig->wrong_key_usage)
sum |= GPGME_SIGSUM_BAD_POLICY;
/* Set the valid flag when the signature is unquestionable
valid. (The test is identical to if(sum == GPGME_SIGSUM_GREEN)). */
if ((sum & GPGME_SIGSUM_GREEN) && !(sum & ~GPGME_SIGSUM_GREEN))
sum |= GPGME_SIGSUM_VALID;
sig->summary = sum;
}
static gpgme_error_t
prepare_new_sig (op_data_t opd)
{
gpgme_signature_t sig;
if (opd->only_newsig_seen && opd->current_sig)
{
/* We have only seen the NEWSIG status and nothing else - we
better skip this signature therefore and reuse it for the
next possible signature. */
sig = opd->current_sig;
memset (sig, 0, sizeof *sig);
assert (opd->result.signatures == sig);
}
else
{
sig = calloc (1, sizeof (*sig));
if (!sig)
return gpg_error_from_syserror ();
if (!opd->result.signatures)
opd->result.signatures = sig;
if (opd->current_sig)
opd->current_sig->next = sig;
opd->current_sig = sig;
}
opd->did_prepare_new_sig = 1;
opd->only_newsig_seen = 0;
return 0;
}
static gpgme_error_t
parse_new_sig (op_data_t opd, gpgme_status_code_t code, char *args,
gpgme_protocol_t protocol)
{
gpgme_signature_t sig;
char *end = strchr (args, ' ');
char *tail;
int got_fpr = 0;
if (end)
{
*end = '\0';
end++;
}
if (!opd->did_prepare_new_sig)
{
gpg_error_t err;
err = prepare_new_sig (opd);
if (err)
return err;
}
assert (opd->did_prepare_new_sig);
opd->did_prepare_new_sig = 0;
assert (opd->current_sig);
sig = opd->current_sig;
/* FIXME: We should set the source of the state. */
switch (code)
{
case GPGME_STATUS_GOODSIG:
sig->status = gpg_error (GPG_ERR_NO_ERROR);
break;
case GPGME_STATUS_EXPSIG:
sig->status = gpg_error (GPG_ERR_SIG_EXPIRED);
break;
case GPGME_STATUS_EXPKEYSIG:
sig->status = gpg_error (GPG_ERR_KEY_EXPIRED);
break;
case GPGME_STATUS_BADSIG:
sig->status = gpg_error (GPG_ERR_BAD_SIGNATURE);
break;
case GPGME_STATUS_REVKEYSIG:
sig->status = gpg_error (GPG_ERR_CERT_REVOKED);
break;
case GPGME_STATUS_ERRSIG:
/* Parse the pubkey algo. */
if (!end)
goto parse_err_sig_fail;
gpg_err_set_errno (0);
sig->pubkey_algo = _gpgme_map_pk_algo (strtol (end, &tail, 0), protocol);
if (errno || end == tail || *tail != ' ')
goto parse_err_sig_fail;
end = tail;
while (*end == ' ')
end++;
/* Parse the hash algo. */
if (!*end)
goto parse_err_sig_fail;
gpg_err_set_errno (0);
sig->hash_algo = strtol (end, &tail, 0);
if (errno || end == tail || *tail != ' ')
goto parse_err_sig_fail;
end = tail;
while (*end == ' ')
end++;
/* Skip the sig class. */
end = strchr (end, ' ');
if (!end)
goto parse_err_sig_fail;
while (*end == ' ')
end++;
/* Parse the timestamp. */
sig->timestamp = _gpgme_parse_timestamp (end, &tail);
if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' '))
return trace_gpg_error (GPG_ERR_INV_ENGINE);
end = tail;
while (*end == ' ')
end++;
/* Parse the return code. */
if (!*end)
goto parse_err_sig_fail;
gpg_err_set_errno (0);
sig->status = strtoul (end, &tail, 10);
if (errno || end == tail || (*tail && *tail != ' '))
goto parse_err_sig_fail;
if (!*tail)
goto parse_err_sig_ok;
end = tail;
while (*end == ' ')
end++;
/* Parse the new fingerprint (from the ISSUER_FPR subpacket). */
if (!*end || (*end == '-' && (end[1] == ' ' || !end[1])))
goto parse_err_sig_ok; /* Okay (just trailing spaces). */
sig->fpr = strdup (end);
if (!sig->fpr)
return gpg_error_from_syserror ();
got_fpr = 1;
goto parse_err_sig_ok;
parse_err_sig_fail:
sig->status = gpg_error (GPG_ERR_GENERAL);
parse_err_sig_ok:
break;
default:
return gpg_error (GPG_ERR_GENERAL);
}
if (*args && !got_fpr)
{
sig->fpr = strdup (args);
if (!sig->fpr)
return gpg_error_from_syserror ();
}
return 0;
}
static gpgme_error_t
parse_valid_sig (gpgme_signature_t sig, char *args, gpgme_protocol_t protocol)
{
char *end = strchr (args, ' ');
if (end)
{
*end = '\0';
end++;
}
if (!*args)
/* We require at least the fingerprint. */
return gpg_error (GPG_ERR_GENERAL);
if (sig->fpr)
free (sig->fpr);
sig->fpr = strdup (args);
if (!sig->fpr)
return gpg_error_from_syserror ();
/* Skip the creation date. */
end = strchr (end, ' ');
if (end)
{
char *tail;
sig->timestamp = _gpgme_parse_timestamp (end, &tail);
if (sig->timestamp == -1 || end == tail || (*tail && *tail != ' '))
return trace_gpg_error (GPG_ERR_INV_ENGINE);
end = tail;
sig->exp_timestamp = _gpgme_parse_timestamp (end, &tail);
if (sig->exp_timestamp == -1 || end == tail || (*tail && *tail != ' '))
return trace_gpg_error (GPG_ERR_INV_ENGINE);
end = tail;
while (*end == ' ')
end++;
/* Skip the signature version. */
end = strchr (end, ' ');
if (end)
{
while (*end == ' ')
end++;
/* Skip the reserved field. */
end = strchr (end, ' ');
if (end)
{
/* Parse the pubkey algo. */
gpg_err_set_errno (0);
sig->pubkey_algo = _gpgme_map_pk_algo (strtol (end, &tail, 0),
protocol);
if (errno || end == tail || *tail != ' ')
return trace_gpg_error (GPG_ERR_INV_ENGINE);
end = tail;
while (*end == ' ')
end++;
if (*end)
{
/* Parse the hash algo. */
gpg_err_set_errno (0);
sig->hash_algo = strtol (end, &tail, 0);
if (errno || end == tail || *tail != ' ')
return trace_gpg_error (GPG_ERR_INV_ENGINE);
end = tail;
}
}
}
}
return 0;
}
static gpgme_error_t
parse_notation (gpgme_signature_t sig, gpgme_status_code_t code, char *args)
{
gpgme_error_t err;
gpgme_sig_notation_t *lastp = &sig->notations;
gpgme_sig_notation_t notation = sig->notations;
char *p;
if (code == GPGME_STATUS_NOTATION_NAME || code == GPGME_STATUS_POLICY_URL)
{
p = strchr (args, ' ');
if (p)
*p = '\0';
/* FIXME: We could keep a pointer to the last notation in the list. */
while (notation && notation->value)
{
lastp = ¬ation->next;
notation = notation->next;
}
if (notation)
/* There is another notation name without data for the
previous one. The crypto backend misbehaves. */
return trace_gpg_error (GPG_ERR_INV_ENGINE);
err = _gpgme_sig_notation_create (¬ation, NULL, 0, NULL, 0, 0);
if (err)
return err;
if (code == GPGME_STATUS_NOTATION_NAME)
{
err = _gpgme_decode_percent_string (args, ¬ation->name, 0, 0);
if (err)
{
_gpgme_sig_notation_free (notation);
return err;
}
notation->name_len = strlen (notation->name);
/* Set default flags for use with older gpg versions which
* do not emit a NOTATIONS_FLAG line. */
notation->flags = GPGME_SIG_NOTATION_HUMAN_READABLE;
notation->human_readable = 1;
}
else
{
/* This is a policy URL. */
err = _gpgme_decode_percent_string (args, ¬ation->value, 0, 0);
if (err)
{
_gpgme_sig_notation_free (notation);
return err;
}
notation->value_len = strlen (notation->value);
}
*lastp = notation;
}
else if (code == GPGME_STATUS_NOTATION_FLAGS)
{
char *field[2];
while (notation && notation->next)
{
lastp = ¬ation->next;
notation = notation->next;
}
if (!notation || !notation->name)
{ /* There are notation flags without a previous notation name.
* The crypto backend misbehaves. */
return trace_gpg_error (GPG_ERR_INV_ENGINE);
}
if (_gpgme_split_fields (args, field, DIM (field)) < 2)
{ /* Required args missing. */
return trace_gpg_error (GPG_ERR_INV_ENGINE);
}
notation->flags = 0;
if (atoi (field[0]))
{
notation->flags |= GPGME_SIG_NOTATION_CRITICAL;
notation->critical = 1;
}
if (atoi (field[1]))
{
notation->flags |= GPGME_SIG_NOTATION_HUMAN_READABLE;
notation->human_readable = 1;
}
}
else if (code == GPGME_STATUS_NOTATION_DATA)
{
int len = strlen (args) + 1;
char *dest;
/* FIXME: We could keep a pointer to the last notation in the list. */
while (notation && notation->next)
{
lastp = ¬ation->next;
notation = notation->next;
}
if (!notation || !notation->name)
/* There is notation data without a previous notation
name. The crypto backend misbehaves. */
return trace_gpg_error (GPG_ERR_INV_ENGINE);
if (!notation->value)
{
dest = notation->value = malloc (len);
if (!dest)
return gpg_error_from_syserror ();
}
else
{
int cur_len = strlen (notation->value);
dest = realloc (notation->value, len + strlen (notation->value));
if (!dest)
return gpg_error_from_syserror ();
notation->value = dest;
dest += cur_len;
}
err = _gpgme_decode_percent_string (args, &dest, len, 0);
if (err)
return err;
notation->value_len += strlen (dest);
}
else
return trace_gpg_error (GPG_ERR_INV_ENGINE);
return 0;
}
static gpgme_error_t
parse_trust (gpgme_signature_t sig, gpgme_status_code_t code, char *args)
{
char *end = strchr (args, ' ');
if (end)
*end = '\0';
switch (code)
{
case GPGME_STATUS_TRUST_UNDEFINED:
default:
sig->validity = GPGME_VALIDITY_UNKNOWN;
break;
case GPGME_STATUS_TRUST_NEVER:
sig->validity = GPGME_VALIDITY_NEVER;
break;
case GPGME_STATUS_TRUST_MARGINAL:
sig->validity = GPGME_VALIDITY_MARGINAL;
break;
case GPGME_STATUS_TRUST_FULLY:
case GPGME_STATUS_TRUST_ULTIMATE:
sig->validity = GPGME_VALIDITY_FULL;
break;
}
sig->validity_reason = 0;
sig->chain_model = 0;
if (*args)
{
sig->validity_reason = atoi (args);
while (*args && *args != ' ')
args++;
if (*args)
{
while (*args == ' ')
args++;
if (!strncmp (args, "chain", 2) && (args[2] == ' ' || !args[2]))
sig->chain_model = 1;
}
}
return 0;
}
/* Parse a TOFU_USER line and put the info into SIG. */
static gpgme_error_t
parse_tofu_user (gpgme_signature_t sig, char *args, gpgme_protocol_t protocol)
{
gpg_error_t err;
char *tail;
gpgme_user_id_t uid;
gpgme_tofu_info_t ti;
char *fpr = NULL;
char *address = NULL;
tail = strchr (args, ' ');
if (!tail || tail == args)
{
err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No fingerprint. */
goto leave;
}
*tail++ = 0;
fpr = strdup (args);
if (!fpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (sig->key && sig->key->fpr && strcmp (sig->key->fpr, fpr))
{
/* GnuPG since 2.1.17 emits multiple TOFU_USER lines with
different fingerprints in case of conflicts for a signature. */
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
args = tail;
tail = strchr (args, ' ');
if (tail == args)
{
err = trace_gpg_error (GPG_ERR_INV_ENGINE); /* No addr-spec. */
goto leave;
}
if (tail)
*tail = 0;
err = _gpgme_decode_percent_string (args, &address, 0, 0);
if (err)
goto leave;
if (!sig->key)
{
err = _gpgme_key_new (&sig->key);
if (err)
goto leave;
sig->key->fpr = fpr;
sig->key->protocol = protocol;
fpr = NULL;
}
else if (!sig->key->fpr)
{
err = trace_gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
err = _gpgme_key_append_name (sig->key, address, 0);
if (err)
goto leave;
uid = sig->key->_last_uid;
assert (uid);
ti = calloc (1, sizeof *ti);
if (!ti)
{
err = gpg_error_from_syserror ();
goto leave;
}
uid->tofu = ti;
leave:
free (fpr);
free (address);
return err;
}
/* Parse a TOFU_STATS line and store it in the last tofu info of SIG.
*
* TOFU_STATS \
* [ [ ]]
*/
static gpgme_error_t
parse_tofu_stats (gpgme_signature_t sig, char *args)
{
gpgme_error_t err;
gpgme_tofu_info_t ti;
char *field[8];
int nfields;
unsigned long uval;
if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu))
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */
if (ti->signfirst || ti->signcount || ti->validity || ti->policy)
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */
nfields = _gpgme_split_fields (args, field, DIM (field));
if (nfields < 3)
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Required args missing. */
/* Note that we allow a value of up to 7 which is what we can store
* in the ti->validity. */
err = _gpgme_strtoul_field (field[0], &uval);
if (err || uval > 7)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
ti->validity = uval;
/* Parse the sign-count. */
err = _gpgme_strtoul_field (field[1], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
if (uval > USHRT_MAX)
uval = USHRT_MAX;
ti->signcount = uval;
/* Parse the encr-count. */
err = _gpgme_strtoul_field (field[2], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
if (uval > USHRT_MAX)
uval = USHRT_MAX;
ti->encrcount = uval;
if (nfields == 3)
return 0; /* All mandatory fields parsed. */
/* Parse the policy. */
if (!strcmp (field[3], "none"))
ti->policy = GPGME_TOFU_POLICY_NONE;
else if (!strcmp (field[3], "auto"))
ti->policy = GPGME_TOFU_POLICY_AUTO;
else if (!strcmp (field[3], "good"))
ti->policy = GPGME_TOFU_POLICY_GOOD;
else if (!strcmp (field[3], "bad"))
ti->policy = GPGME_TOFU_POLICY_BAD;
else if (!strcmp (field[3], "ask"))
ti->policy = GPGME_TOFU_POLICY_ASK;
else /* "unknown" and invalid policy strings. */
ti->policy = GPGME_TOFU_POLICY_UNKNOWN;
if (nfields == 4)
return 0; /* No more optional fields. */
/* Parse first and last seen timestamps (none or both are required). */
if (nfields < 6)
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* "tm2" missing. */
err = _gpgme_strtoul_field (field[4], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
ti->signfirst = uval;
err = _gpgme_strtoul_field (field[5], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
ti->signlast = uval;
if (nfields > 7)
{
/* This condition is only to allow for gpg 2.1.15 - can
* eventually be removed. */
err = _gpgme_strtoul_field (field[6], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
ti->encrfirst = uval;
err = _gpgme_strtoul_field (field[7], &uval);
if (err)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
ti->encrlast = uval;
}
return 0;
}
/* Parse a TOFU_STATS_LONG line and store it in the last tofu info of SIG. */
static gpgme_error_t
parse_tofu_stats_long (gpgme_signature_t sig, char *args, int raw)
{
gpgme_error_t err;
gpgme_tofu_info_t ti;
char *p;
if (!sig->key || !sig->key->_last_uid || !(ti = sig->key->_last_uid->tofu))
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* No TOFU_USER seen. */
if (ti->description)
return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Already set. */
err = _gpgme_decode_percent_string (args, &ti->description, 0, 0);
if (err)
return err;
/* Remove the non-breaking spaces. */
if (!raw)
{
for (p = ti->description; *p; p++)
if (*p == '~')
*p = ' ';
}
return 0;
}
/* Parse an error status line and if SET_STATUS is true update the
result status as appropriate. With SET_STATUS being false, only
check for an error. */
static gpgme_error_t
parse_error (gpgme_signature_t sig, char *args, int set_status)
{
gpgme_error_t err;
char *where = strchr (args, ' ');
char *which;
if (where)
{
*where = '\0';
which = where + 1;
where = strchr (which, ' ');
if (where)
*where = '\0';
where = args;
}
else
return trace_gpg_error (GPG_ERR_INV_ENGINE);
err = atoi (which);
if (!strcmp (where, "proc_pkt.plaintext")
&& gpg_err_code (err) == GPG_ERR_BAD_DATA)
{
/* This indicates a double plaintext. The only solid way to
- handle this is by failing the oepration. */
+ handle this is by failing the operation. */
return gpg_error (GPG_ERR_BAD_DATA);
}
else if (!set_status)
;
else if (!strcmp (where, "verify.findkey"))
sig->status = err;
else if (!strcmp (where, "verify.keyusage")
&& gpg_err_code (err) == GPG_ERR_WRONG_KEY_USAGE)
sig->wrong_key_usage = 1;
return 0;
}
gpgme_error_t
_gpgme_verify_status_handler (void *priv, gpgme_status_code_t code, char *args)
{
gpgme_ctx_t ctx = (gpgme_ctx_t) priv;
gpgme_error_t err;
void *hook;
op_data_t opd;
gpgme_signature_t sig;
char *end;
err = _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook, -1, NULL);
opd = hook;
if (err)
return err;
sig = opd->current_sig;
switch (code)
{
case GPGME_STATUS_NEWSIG:
if (sig)
calc_sig_summary (sig);
err = prepare_new_sig (opd);
opd->only_newsig_seen = 1;
opd->conflict_user_seen = 0;
return err;
case GPGME_STATUS_GOODSIG:
case GPGME_STATUS_EXPSIG:
case GPGME_STATUS_EXPKEYSIG:
case GPGME_STATUS_BADSIG:
case GPGME_STATUS_ERRSIG:
case GPGME_STATUS_REVKEYSIG:
if (sig && !opd->did_prepare_new_sig)
calc_sig_summary (sig);
opd->only_newsig_seen = 0;
return parse_new_sig (opd, code, args, ctx->protocol);
case GPGME_STATUS_VALIDSIG:
opd->only_newsig_seen = 0;
return sig ? parse_valid_sig (sig, args, ctx->protocol)
: trace_gpg_error (GPG_ERR_INV_ENGINE);
case GPGME_STATUS_NODATA:
opd->only_newsig_seen = 0;
if (!sig)
return gpg_error (GPG_ERR_NO_DATA);
sig->status = gpg_error (GPG_ERR_NO_DATA);
break;
case GPGME_STATUS_UNEXPECTED:
opd->only_newsig_seen = 0;
if (!sig)
return gpg_error (GPG_ERR_GENERAL);
sig->status = gpg_error (GPG_ERR_NO_DATA);
break;
case GPGME_STATUS_NOTATION_NAME:
case GPGME_STATUS_NOTATION_FLAGS:
case GPGME_STATUS_NOTATION_DATA:
case GPGME_STATUS_POLICY_URL:
opd->only_newsig_seen = 0;
return sig ? parse_notation (sig, code, args)
: trace_gpg_error (GPG_ERR_INV_ENGINE);
case GPGME_STATUS_TRUST_UNDEFINED:
case GPGME_STATUS_TRUST_NEVER:
case GPGME_STATUS_TRUST_MARGINAL:
case GPGME_STATUS_TRUST_FULLY:
case GPGME_STATUS_TRUST_ULTIMATE:
opd->only_newsig_seen = 0;
return sig ? parse_trust (sig, code, args)
: trace_gpg_error (GPG_ERR_INV_ENGINE);
case GPGME_STATUS_PKA_TRUST_BAD:
case GPGME_STATUS_PKA_TRUST_GOOD:
opd->only_newsig_seen = 0;
/* Check that we only get one of these status codes per
signature; if not the crypto backend misbehaves. */
if (!sig || sig->pka_trust || sig->pka_address)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
sig->pka_trust = code == GPGME_STATUS_PKA_TRUST_GOOD? 2 : 1;
end = strchr (args, ' ');
if (end)
*end = 0;
sig->pka_address = strdup (args);
break;
case GPGME_STATUS_TOFU_USER:
opd->only_newsig_seen = 0;
if (!sig)
return trace_gpg_error (GPG_ERR_INV_ENGINE);
err = parse_tofu_user (sig, args, ctx->protocol);
/* gpg emits TOFU User lines for each conflicting key.
* GPGME does not expose this to have a clean API and
* a GPGME user can do a keylisting with the address
* normalisation.
* So when a duplicated TOFU_USER line is encountered
* we ignore the conflicting tofu stats emitted afterwards.
*/
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
{
opd->conflict_user_seen = 1;
break;
}
opd->conflict_user_seen = 0;
return trace_gpg_error (err);
case GPGME_STATUS_TOFU_STATS:
opd->only_newsig_seen = 0;
if (opd->conflict_user_seen)
break;
return sig ? parse_tofu_stats (sig, args)
/* */ : trace_gpg_error (GPG_ERR_INV_ENGINE);
case GPGME_STATUS_TOFU_STATS_LONG:
opd->only_newsig_seen = 0;
if (opd->conflict_user_seen)
break;
return sig ? parse_tofu_stats_long (sig, args, ctx->raw_description)
/* */ : trace_gpg_error (GPG_ERR_INV_ENGINE);
case GPGME_STATUS_ERROR:
opd->only_newsig_seen = 0;
/* Some error stati are informational, so we don't return an
error code if we are not ready to process this status. */
return parse_error (sig, args, !!sig );
case GPGME_STATUS_FAILURE:
opd->failure_code = _gpgme_parse_failure (args);
break;
case GPGME_STATUS_EOF:
if (sig && !opd->did_prepare_new_sig)
calc_sig_summary (sig);
if (opd->only_newsig_seen && sig)
{
gpgme_signature_t sig2;
/* The last signature has no valid information - remove it
from the list. */
assert (!sig->next);
if (sig == opd->result.signatures)
opd->result.signatures = NULL;
else
{
for (sig2 = opd->result.signatures; sig2; sig2 = sig2->next)
if (sig2->next == sig)
{
sig2->next = NULL;
break;
}
}
/* Note that there is no need to release the members of SIG
because we won't be here if they have been set. */
free (sig);
opd->current_sig = NULL;
}
opd->only_newsig_seen = 0;
if (opd->failure_code)
return opd->failure_code;
break;
case GPGME_STATUS_PLAINTEXT:
if (++opd->plaintext_seen > 1)
return gpg_error (GPG_ERR_BAD_DATA);
{
int mime = 0;
err = _gpgme_parse_plaintext (args, &opd->result.file_name, &mime);
if (err)
return err;
opd->result.is_mime = !!mime;
}
break;
case GPGME_STATUS_VERIFICATION_COMPLIANCE_MODE:
PARSE_COMPLIANCE_FLAGS (args, opd->current_sig);
break;
default:
break;
}
return 0;
}
static gpgme_error_t
verify_status_handler (void *priv, gpgme_status_code_t code, char *args)
{
gpgme_error_t err;
err = _gpgme_progress_status_handler (priv, code, args);
if (!err)
err = _gpgme_verify_status_handler (priv, code, args);
return err;
}
gpgme_error_t
_gpgme_op_verify_init_result (gpgme_ctx_t ctx)
{
void *hook;
op_data_t opd;
return _gpgme_op_data_lookup (ctx, OPDATA_VERIFY, &hook,
sizeof (*opd), release_op_data);
}
static gpgme_error_t
verify_start (gpgme_ctx_t ctx, int synchronous, gpgme_data_t sig,
gpgme_data_t signed_text, gpgme_data_t plaintext)
{
gpgme_error_t err;
err = _gpgme_op_reset (ctx, synchronous);
if (err)
return err;
err = _gpgme_op_verify_init_result (ctx);
if (err)
return err;
_gpgme_engine_set_status_handler (ctx->engine, verify_status_handler, ctx);
if (!sig)
return gpg_error (GPG_ERR_NO_DATA);
return _gpgme_engine_op_verify (ctx->engine, sig, signed_text, plaintext,
ctx);
}
/* Decrypt ciphertext CIPHER and make a signature verification within
CTX and store the resulting plaintext in PLAIN. */
gpgme_error_t
gpgme_op_verify_start (gpgme_ctx_t ctx, gpgme_data_t sig,
gpgme_data_t signed_text, gpgme_data_t plaintext)
{
gpg_error_t err;
TRACE_BEG (DEBUG_CTX, "gpgme_op_verify_start", ctx,
"sig=%p, signed_text=%p, plaintext=%p",
sig, signed_text, plaintext);
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
err = verify_start (ctx, 0, sig, signed_text, plaintext);
return TRACE_ERR (err);
}
/* Decrypt ciphertext CIPHER and make a signature verification within
CTX and store the resulting plaintext in PLAIN. */
gpgme_error_t
gpgme_op_verify (gpgme_ctx_t ctx, gpgme_data_t sig, gpgme_data_t signed_text,
gpgme_data_t plaintext)
{
gpgme_error_t err;
TRACE_BEG (DEBUG_CTX, "gpgme_op_verify", ctx,
"sig=%p, signed_text=%p, plaintext=%p",
sig, signed_text, plaintext);
if (!ctx)
return TRACE_ERR (gpg_error (GPG_ERR_INV_VALUE));
err = verify_start (ctx, 1, sig, signed_text, plaintext);
if (!err)
err = _gpgme_wait_one (ctx);
return TRACE_ERR (err);
}
/* Compatibility interfaces. */
/* Get the key used to create signature IDX in CTX and return it in
R_KEY. */
gpgme_error_t
gpgme_get_sig_key (gpgme_ctx_t ctx, int idx, gpgme_key_t *r_key)
{
gpgme_verify_result_t result;
gpgme_signature_t sig;
if (!ctx)
return gpg_error (GPG_ERR_INV_VALUE);
result = gpgme_op_verify_result (ctx);
sig = result->signatures;
while (sig && idx)
{
sig = sig->next;
idx--;
}
if (!sig || idx)
return gpg_error (GPG_ERR_EOF);
return gpgme_get_key (ctx, sig->fpr, r_key, 0);
}
/* Retrieve the signature status of signature IDX in CTX after a
successful verify operation in R_STAT (if non-null). The creation
time stamp of the signature is returned in R_CREATED (if non-null).
The function returns a string containing the fingerprint. */
const char *
gpgme_get_sig_status (gpgme_ctx_t ctx, int idx,
_gpgme_sig_stat_t *r_stat, time_t *r_created)
{
gpgme_verify_result_t result;
gpgme_signature_t sig;
result = gpgme_op_verify_result (ctx);
sig = result->signatures;
while (sig && idx)
{
sig = sig->next;
idx--;
}
if (!sig || idx)
return NULL;
if (r_stat)
{
switch (gpg_err_code (sig->status))
{
case GPG_ERR_NO_ERROR:
*r_stat = GPGME_SIG_STAT_GOOD;
break;
case GPG_ERR_BAD_SIGNATURE:
*r_stat = GPGME_SIG_STAT_BAD;
break;
case GPG_ERR_NO_PUBKEY:
*r_stat = GPGME_SIG_STAT_NOKEY;
break;
case GPG_ERR_NO_DATA:
*r_stat = GPGME_SIG_STAT_NOSIG;
break;
case GPG_ERR_SIG_EXPIRED:
*r_stat = GPGME_SIG_STAT_GOOD_EXP;
break;
case GPG_ERR_KEY_EXPIRED:
*r_stat = GPGME_SIG_STAT_GOOD_EXPKEY;
break;
default:
*r_stat = GPGME_SIG_STAT_ERROR;
break;
}
}
if (r_created)
*r_created = sig->timestamp;
return sig->fpr;
}
/* Retrieve certain attributes of a signature. IDX is the index
number of the signature after a successful verify operation. WHAT
is an attribute where GPGME_ATTR_EXPIRE is probably the most useful
one. WHATIDX is to be passed as 0 for most attributes . */
unsigned long
gpgme_get_sig_ulong_attr (gpgme_ctx_t ctx, int idx,
_gpgme_attr_t what, int whatidx)
{
gpgme_verify_result_t result;
gpgme_signature_t sig;
(void)whatidx;
result = gpgme_op_verify_result (ctx);
sig = result->signatures;
while (sig && idx)
{
sig = sig->next;
idx--;
}
if (!sig || idx)
return 0;
switch (what)
{
case GPGME_ATTR_CREATED:
return sig->timestamp;
case GPGME_ATTR_EXPIRE:
return sig->exp_timestamp;
case GPGME_ATTR_VALIDITY:
return (unsigned long) sig->validity;
case GPGME_ATTR_SIG_STATUS:
switch (gpg_err_code (sig->status))
{
case GPG_ERR_NO_ERROR:
return GPGME_SIG_STAT_GOOD;
case GPG_ERR_BAD_SIGNATURE:
return GPGME_SIG_STAT_BAD;
case GPG_ERR_NO_PUBKEY:
return GPGME_SIG_STAT_NOKEY;
case GPG_ERR_NO_DATA:
return GPGME_SIG_STAT_NOSIG;
case GPG_ERR_SIG_EXPIRED:
return GPGME_SIG_STAT_GOOD_EXP;
case GPG_ERR_KEY_EXPIRED:
return GPGME_SIG_STAT_GOOD_EXPKEY;
default:
return GPGME_SIG_STAT_ERROR;
}
case GPGME_ATTR_SIG_SUMMARY:
return sig->summary;
default:
break;
}
return 0;
}
const char *
gpgme_get_sig_string_attr (gpgme_ctx_t ctx, int idx,
_gpgme_attr_t what, int whatidx)
{
gpgme_verify_result_t result;
gpgme_signature_t sig;
result = gpgme_op_verify_result (ctx);
sig = result->signatures;
while (sig && idx)
{
sig = sig->next;
idx--;
}
if (!sig || idx)
return NULL;
switch (what)
{
case GPGME_ATTR_FPR:
return sig->fpr;
case GPGME_ATTR_ERRTOK:
if (whatidx == 1)
return sig->wrong_key_usage ? "Wrong_Key_Usage" : "";
else
return "";
default:
break;
}
return NULL;
}
diff --git a/src/wait-global.c b/src/wait-global.c
deleted file mode 100644
index e88962e0..00000000
--- a/src/wait-global.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/* wait-global.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 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 .
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include
-#endif
-#include
-#include
-#include
-#include
-
-#include "gpgme.h"
-#include "sema.h"
-#include "util.h"
-#include "context.h"
-#include "wait.h"
-#include "priv-io.h"
-#include "ops.h"
-#include "debug.h"
-
-/* The global event loop is used for all asynchronous operations
- (except key listing) for which no user I/O callbacks are specified.
-
- A context sets up its initial I/O callbacks and then sends the
- GPGME_EVENT_START event. After that, it is added to the global
- list of active contexts.
-
- The gpgme_wait function contains a select() loop over all file
- descriptors in all active contexts. If an error occurs, it closes
- all fds in that context and moves the context to the global done
- list. Likewise, if a context has removed all I/O callbacks, it is
- moved to the global done list.
-
- All contexts in the global done list are eligible for being
- returned by gpgme_wait if requested by the caller. */
-
-/* The ctx_list_lock protects the list of active and done contexts.
- Insertion into any of these lists is only allowed when the lock is
- held. This allows a muli-threaded program to loop over gpgme_wait
- and in parallel start asynchronous gpgme operations.
-
- However, the fd tables in the contexts are not protected by this
- lock. They are only allowed to change either before the context is
- added to the active list (ie, before the start event is signalled)
- or in a callback handler. */
-DEFINE_STATIC_LOCK (ctx_list_lock);
-
-/* A ctx_list_item is an item in the global list of active or done
- contexts. */
-struct ctx_list_item
-{
- /* Every ctx_list_item is an element in a doubly linked list. The
- list pointers are protected by the ctx_list_lock. */
- struct ctx_list_item *next;
- struct ctx_list_item *prev;
-
- gpgme_ctx_t ctx;
- /* The status is set when the ctx is moved to the done list. */
- gpgme_error_t status;
- gpgme_error_t op_err;
-};
-
-/* The active list contains all contexts that are in the global event
- loop, have active I/O callbacks, and have already seen the start
- event. */
-static struct ctx_list_item *ctx_active_list;
-
-/* The done list contains all contexts that have previously been
- active but now are not active any longer, either because they
- finished successfully or an I/O callback returned an error. The
- status field in the list item contains the error value (or 0 if
- successful). */
-static struct ctx_list_item *ctx_done_list;
-
-
-/* Enter the context CTX into the active list. */
-static gpgme_error_t
-ctx_active (gpgme_ctx_t ctx)
-{
- struct ctx_list_item *li = malloc (sizeof (struct ctx_list_item));
- if (!li)
- return gpg_error_from_syserror ();
- li->ctx = ctx;
-
- LOCK (ctx_list_lock);
- /* Add LI to active list. */
- li->next = ctx_active_list;
- li->prev = NULL;
- if (ctx_active_list)
- ctx_active_list->prev = li;
- ctx_active_list = li;
- UNLOCK (ctx_list_lock);
- return 0;
-}
-
-
-/* Enter the context CTX into the done list with status STATUS. */
-static void
-ctx_done (gpgme_ctx_t ctx, gpgme_error_t status, gpgme_error_t op_err)
-{
- struct ctx_list_item *li;
-
- LOCK (ctx_list_lock);
- li = ctx_active_list;
- while (li && li->ctx != ctx)
- li = li->next;
- assert (li);
-
- /* Remove LI from active list. */
- if (li->next)
- li->next->prev = li->prev;
- if (li->prev)
- li->prev->next = li->next;
- else
- ctx_active_list = li->next;
-
- li->status = status;
- li->op_err = op_err;
-
- /* Add LI to done list. */
- li->next = ctx_done_list;
- li->prev = NULL;
- if (ctx_done_list)
- ctx_done_list->prev = li;
- ctx_done_list = li;
- UNLOCK (ctx_list_lock);
-}
-
-
-/* Find finished context CTX (or any context if CTX is NULL) and
- return its status in STATUS after removing it from the done list.
- If a matching context could be found, return it. Return NULL if no
- context could be found. */
-static gpgme_ctx_t
-ctx_wait (gpgme_ctx_t ctx, gpgme_error_t *status, gpgme_error_t *op_err)
-{
- struct ctx_list_item *li;
-
- LOCK (ctx_list_lock);
- li = ctx_done_list;
- if (ctx)
- {
- /* A specific context is requested. */
- while (li && li->ctx != ctx)
- li = li->next;
- }
- if (li)
- {
- ctx = li->ctx;
- if (status)
- *status = li->status;
- if (op_err)
- *op_err = li->op_err;
-
- /* Remove LI from done list. */
- if (li->next)
- li->next->prev = li->prev;
- if (li->prev)
- li->prev->next = li->next;
- else
- ctx_done_list = li->next;
- free (li);
- }
- else
- ctx = NULL;
- UNLOCK (ctx_list_lock);
- return ctx;
-}
-
-
-/* Internal I/O callback functions. */
-
-/* The add_io_cb and remove_io_cb handlers are shared with the private
- event loops. */
-
-void
-_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
- void *type_data)
-{
- gpgme_ctx_t ctx = (gpgme_ctx_t) data;
-
- assert (ctx);
-
- switch (type)
- {
- case GPGME_EVENT_START:
- {
- gpgme_error_t err = ctx_active (ctx);
-
- if (err)
- /* An error occurred. Close all fds in this context, and
- send the error in a done event. */
- _gpgme_cancel_with_err (ctx, err, 0);
- }
- break;
-
- case GPGME_EVENT_DONE:
- {
- gpgme_io_event_done_data_t done_data =
- (gpgme_io_event_done_data_t) type_data;
-
- ctx_done (ctx, done_data->err, done_data->op_err);
- }
- break;
-
- case GPGME_EVENT_NEXT_KEY:
- assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
- break;
-
- case GPGME_EVENT_NEXT_TRUSTITEM:
- assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
- break;
-
- default:
- assert (!"Unexpected event");
- break;
- }
-}
-
-
-
-/* Perform asynchronous operations in the global event loop (ie, any
- asynchronous operation except key listing and trustitem listing
- operations). If CTX is not a null pointer, the function will
- return if the asynchronous operation in the context CTX finished.
- Otherwise the function will return if any asynchronous operation
- finished. If HANG is zero, the function will not block for a long
- time. Otherwise the function does not return until an operation
- matching CTX finished.
-
- If a matching context finished, it is returned, and *STATUS is set
- to the error value of the operation in that context. Otherwise, if
- the timeout expires, NULL is returned and *STATUS is 0. If an
- error occurs, NULL is returned and *STATUS is set to the error
- value. */
-gpgme_ctx_t
-gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
- gpgme_error_t *op_err, int hang)
-{
- do
- {
- unsigned int i = 0;
- struct ctx_list_item *li;
- struct fd_table fdt;
- int nr;
-
- /* Collect the active file descriptors. */
- LOCK (ctx_list_lock);
- for (li = ctx_active_list; li; li = li->next)
- i += li->ctx->fdt.size;
- fdt.fds = malloc (i * sizeof (struct io_select_fd_s));
- if (!fdt.fds)
- {
- int saved_err = gpg_error_from_syserror ();
- UNLOCK (ctx_list_lock);
- if (status)
- *status = saved_err;
- if (op_err)
- *op_err = 0;
- return NULL;
- }
- fdt.size = i;
- i = 0;
- for (li = ctx_active_list; li; li = li->next)
- {
- memcpy (&fdt.fds[i], li->ctx->fdt.fds,
- li->ctx->fdt.size * sizeof (struct io_select_fd_s));
- i += li->ctx->fdt.size;
- }
- UNLOCK (ctx_list_lock);
-
- nr = _gpgme_io_select (fdt.fds, fdt.size, 0);
- if (nr < 0)
- {
- int saved_err = gpg_error_from_syserror ();
- free (fdt.fds);
- if (status)
- *status = saved_err;
- if (op_err)
- *op_err = 0;
- return NULL;
- }
-
- for (i = 0; i < fdt.size && nr; i++)
- {
- if (fdt.fds[i].fd != -1 && fdt.fds[i].signaled)
- {
- gpgme_ctx_t ictx;
- gpgme_error_t err = 0;
- gpgme_error_t local_op_err = 0;
- struct wait_item_s *item;
-
- assert (nr);
- nr--;
-
- item = (struct wait_item_s *) fdt.fds[i].opaque;
- assert (item);
- ictx = item->ctx;
- assert (ictx);
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (!err)
- err = _gpgme_run_io_cb (&fdt.fds[i], 0, &local_op_err);
- if (err || local_op_err)
- {
- /* An error occurred. Close all fds in this context,
- and signal it. */
- _gpgme_cancel_with_err (ictx, err, local_op_err);
-
- /* Break out of the loop, and retry the select()
- from scratch, because now all fds should be
- gone. */
- break;
- }
- }
- }
- free (fdt.fds);
-
- /* Now some contexts might have finished successfully. */
- LOCK (ctx_list_lock);
- retry:
- for (li = ctx_active_list; li; li = li->next)
- {
- gpgme_ctx_t actx = li->ctx;
-
- for (i = 0; i < actx->fdt.size; i++)
- if (actx->fdt.fds[i].fd != -1)
- break;
- if (i == actx->fdt.size)
- {
- struct gpgme_io_event_done_data data;
- data.err = 0;
- data.op_err = 0;
-
- /* FIXME: This does not perform too well. We have to
- release the lock because the I/O event handler
- acquires it to remove the context from the active
- list. Two alternative strategies are worth
- considering: Either implement the DONE event handler
- here in a lock-free manner, or save a list of all
- contexts to be released and call the DONE events
- afterwards. */
- UNLOCK (ctx_list_lock);
- _gpgme_engine_io_event (actx->engine, GPGME_EVENT_DONE, &data);
- LOCK (ctx_list_lock);
- goto retry;
- }
- }
- UNLOCK (ctx_list_lock);
-
- {
- gpgme_ctx_t dctx = ctx_wait (ctx, status, op_err);
-
- if (dctx)
- {
- ctx = dctx;
- hang = 0;
- }
- else if (!hang)
- {
- ctx = NULL;
- if (status)
- *status = 0;
- if (op_err)
- *op_err = 0;
- }
- }
- }
- while (hang);
-
- return ctx;
-}
-
-
-gpgme_ctx_t
-gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
-{
- return gpgme_wait_ext (ctx, status, NULL, hang);
-}
diff --git a/src/wait-private.c b/src/wait-private.c
deleted file mode 100644
index 417cb2b3..00000000
--- a/src/wait-private.c
+++ /dev/null
@@ -1,180 +0,0 @@
-/* wait-private.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 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 .
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include
-#endif
-#include
-#include
-
-#include "gpgme.h"
-#include "context.h"
-#include "wait.h"
-#include "ops.h"
-#include "priv-io.h"
-#include "util.h"
-#include "debug.h"
-
-
-/* The private event loops are used for all blocking operations, and
- for the key and trust item listing operations. They are completely
- separated from each other. */
-
-
-/* Internal I/O callback functions. */
-
-/* The add_io_cb and remove_io_cb handlers are shared with the global
- event loops. */
-
-void
-_gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
- void *type_data)
-{
- switch (type)
- {
- case GPGME_EVENT_START:
- /* Nothing to do here, as the wait routine is called after the
- initialization is finished. */
- break;
-
- case GPGME_EVENT_DONE:
- break;
-
- case GPGME_EVENT_NEXT_KEY:
- _gpgme_op_keylist_event_cb (data, type, type_data);
- break;
-
- case GPGME_EVENT_NEXT_TRUSTITEM:
- _gpgme_op_trustlist_event_cb (data, type, type_data);
- break;
- }
-}
-
-
-/* If COND is a null pointer, wait until the blocking operation in CTX
- finished and return its error value. Otherwise, wait until COND is
- satisfied or the operation finished. */
-gpgme_error_t
-_gpgme_wait_on_condition (gpgme_ctx_t ctx, volatile int *cond,
- gpgme_error_t *op_err_p)
-{
- gpgme_error_t err = 0;
- int hang = 1;
-
- if (op_err_p)
- *op_err_p = 0;
-
- do
- {
- int nr = _gpgme_io_select (ctx->fdt.fds, ctx->fdt.size, 0);
- unsigned int i;
-
- if (nr < 0)
- {
- /* An error occurred. Close all fds in this context, and
- signal it. */
- err = gpg_error_from_syserror ();
- _gpgme_cancel_with_err (ctx, err, 0);
-
- return err;
- }
-
- for (i = 0; i < ctx->fdt.size && nr; i++)
- {
- if (ctx->fdt.fds[i].fd != -1 && ctx->fdt.fds[i].signaled)
- {
- gpgme_error_t op_err = 0;
-
- ctx->fdt.fds[i].signaled = 0;
- assert (nr);
- nr--;
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (!err)
- err = _gpgme_run_io_cb (&ctx->fdt.fds[i], 0, &op_err);
- if (err)
- {
- /* An error occurred. Close all fds in this context,
- and signal it. */
- _gpgme_cancel_with_err (ctx, err, 0);
-
- return err;
- }
- else if (op_err)
- {
- /* An operational error occurred. Cancel the current
- operation but not the session, and signal it. */
- _gpgme_cancel_with_err (ctx, 0, op_err);
-
- /* NOTE: This relies on the operational error being
- generated after the operation really has
- completed, for example after no further status
- line output is generated. Otherwise the
- following I/O will spill over into the next
- operation. */
- if (op_err_p)
- *op_err_p = op_err;
- return 0;
- }
- }
- }
-
- for (i = 0; i < ctx->fdt.size; i++)
- if (ctx->fdt.fds[i].fd != -1)
- break;
- if (i == ctx->fdt.size)
- {
- struct gpgme_io_event_done_data data;
- data.err = 0;
- data.op_err = 0;
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
- hang = 0;
- }
- if (cond && *cond)
- hang = 0;
- }
- while (hang);
-
- return 0;
-}
-
-
-/* Wait until the blocking operation in context CTX has finished and
- return the error value. This variant can not be used for
- session-based protocols. */
-gpgme_error_t
-_gpgme_wait_one (gpgme_ctx_t ctx)
-{
- return _gpgme_wait_on_condition (ctx, NULL, NULL);
-}
-
-/* Wait until the blocking operation in context CTX has finished and
- return the error value. This is the right variant to use for
- sesion-based protocols. */
-gpgme_error_t
-_gpgme_wait_one_ext (gpgme_ctx_t ctx, gpgme_error_t *op_err)
-{
- return _gpgme_wait_on_condition (ctx, NULL, op_err);
-}
diff --git a/src/wait-user.c b/src/wait-user.c
deleted file mode 100644
index 2a42170c..00000000
--- a/src/wait-user.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/* wait-user.c
- * Copyright (C) 2000 Werner Koch (dd9jn)
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 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 .
- * SPDX-License-Identifier: LGPL-2.1-or-later
- */
-
-#if HAVE_CONFIG_H
-#include
-#endif
-#include
-
-#include "gpgme.h"
-#include "context.h"
-#include "priv-io.h"
-#include "wait.h"
-#include "ops.h"
-#include "debug.h"
-
-
-/* The user event loops are used for all asynchronous operations for
- which a user callback is defined. */
-
-
-/* Internal I/O Callbacks. */
-
-gpgme_error_t
-_gpgme_user_io_cb_handler (void *data, int fd)
-{
- gpgme_error_t err = 0;
- gpgme_error_t op_err = 0;
- struct tag *tag = (struct tag *) data;
- gpgme_ctx_t ctx;
-
- (void)fd;
-
- assert (data);
- ctx = tag->ctx;
- assert (ctx);
-
- LOCK (ctx->lock);
- if (ctx->canceled)
- err = gpg_error (GPG_ERR_CANCELED);
- UNLOCK (ctx->lock);
-
- if (! err)
- err = _gpgme_run_io_cb (&ctx->fdt.fds[tag->idx], 0, &op_err);
- if (err || op_err)
- _gpgme_cancel_with_err (ctx, err, op_err);
- else
- {
- unsigned int i;
-
- for (i = 0; i < ctx->fdt.size; i++)
- if (ctx->fdt.fds[i].fd != -1)
- break;
-
- if (i == ctx->fdt.size)
- {
- struct gpgme_io_event_done_data done_data;
-
- done_data.err = 0;
- done_data.op_err = 0;
- _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &done_data);
- }
- }
- return 0;
-}
-
-
-/* Register the file descriptor FD with the handler FNC (which gets
- FNC_DATA as its first argument) for the direction DIR. DATA should
- be the context for which the fd is added. R_TAG will hold the tag
- that can be used to remove the fd. */
-gpgme_error_t
-_gpgme_wait_user_add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc,
- void *fnc_data, void **r_tag)
-{
- gpgme_ctx_t ctx = (gpgme_ctx_t) data;
- struct tag *tag;
- gpgme_error_t err;
-
- assert (ctx);
- err = _gpgme_add_io_cb (data, fd, dir, fnc, fnc_data, r_tag);
- if (err)
- return err;
- tag = *r_tag;
- assert (tag);
- err = (*ctx->io_cbs.add) (ctx->io_cbs.add_priv, fd, dir,
- _gpgme_user_io_cb_handler, *r_tag,
- &tag->user_tag);
- if (err)
- _gpgme_remove_io_cb (*r_tag);
- return err;
-}
-
-
-void
-_gpgme_wait_user_remove_io_cb (void *data)
-{
- struct tag *tag = (struct tag *) data;
- gpgme_ctx_t ctx;
-
- assert (tag);
- ctx = tag->ctx;
-
- (*ctx->io_cbs.remove) (tag->user_tag);
- _gpgme_remove_io_cb (data);
-}
-
-
-void
-_gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type, void *type_data)
-{
- gpgme_ctx_t ctx = data;
-
- if (ctx->io_cbs.event)
- (*ctx->io_cbs.event) (ctx->io_cbs.event_priv, type, type_data);
-}
diff --git a/src/wait.c b/src/wait.c
index 52eb6f4d..7418f00d 100644
--- a/src/wait.c
+++ b/src/wait.c
@@ -1,227 +1,477 @@
/* wait.c
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007 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 .
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#if HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#ifdef HAVE_SYS_TYPES_H
# include
#endif
#include "util.h"
#include "context.h"
#include "ops.h"
#include "wait.h"
#include "sema.h"
#include "priv-io.h"
#include "engine.h"
#include "debug.h"
+#include "fdtable.h"
-
-void
-_gpgme_fd_table_init (fd_table_t fdt)
-{
- fdt->fds = NULL;
- fdt->size = 0;
-}
-void
-_gpgme_fd_table_deinit (fd_table_t fdt)
+/* Wrapper for the user wait handler to match the exported prototype.
+ * This is used by _gpgme_add_io_cb_user. */
+static gpg_error_t
+user_io_cb_handler (void *data, int fd)
{
- if (fdt->fds)
- free (fdt->fds);
-}
+ struct io_cb_tag_s *tag = data;
+ gpg_error_t err;
+ uint64_t serial;
+ gpgme_ctx_t ctx;
+ gpg_error_t op_err;
+ (void)fd;
-/* XXX We should keep a marker and roll over for speed. */
-static gpgme_error_t
-fd_table_put (fd_table_t fdt, int fd, int dir, void *opaque, int *idx)
-{
- unsigned int i, j;
- struct io_select_fd_s *new_fds;
+ assert (data);
+ serial = tag->serial;
+ assert (serial);
- for (i = 0; i < fdt->size; i++)
+ err = _gpgme_fdtable_run_io_cbs (serial, &op_err);
+ if (err || op_err)
+ ;
+ else if (!_gpgme_fdtable_io_cb_count (serial))
{
- if (fdt->fds[i].fd == -1)
- break;
+ /* No more active callbacks - emit a DONE. */
+ struct gpgme_io_event_done_data done_data = { 0, 0 };
+ _gpgme_get_ctx (serial, &ctx);
+ if (ctx)
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &done_data);
}
- if (i == fdt->size)
- {
-#define FDT_ALLOCSIZE 10
- new_fds = realloc (fdt->fds, (fdt->size + FDT_ALLOCSIZE)
- * sizeof (*new_fds));
- if (!new_fds)
- return gpg_error_from_syserror ();
-
- fdt->fds = new_fds;
- fdt->size += FDT_ALLOCSIZE;
- for (j = 0; j < FDT_ALLOCSIZE; j++)
- fdt->fds[i + j].fd = -1;
- }
-
- fdt->fds[i].fd = fd;
- fdt->fds[i].for_read = (dir == 1);
- fdt->fds[i].for_write = (dir == 0);
- fdt->fds[i].signaled = 0;
- fdt->fds[i].opaque = opaque;
- *idx = i;
return 0;
}
+
/* Register the file descriptor FD with the handler FNC (which gets
FNC_DATA as its first argument) for the direction DIR. DATA should
be the context for which the fd is added. R_TAG will hold the tag
- that can be used to remove the fd. */
+ that can be used to remove the fd. This function is used for the
+ global and the private wait loops. */
gpgme_error_t
_gpgme_add_io_cb (void *data, int fd, int dir, gpgme_io_cb_t fnc,
void *fnc_data, void **r_tag)
{
gpgme_error_t err;
gpgme_ctx_t ctx = (gpgme_ctx_t) data;
- fd_table_t fdt;
- struct wait_item_s *item;
- struct tag *tag;
+ struct io_cb_tag_s *tag;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu fd=%d, dir %d",
+ CTXSERIAL (ctx), fd, dir);
+
+ if (!fnc)
+ return gpg_error (GPG_ERR_INV_ARG);
assert (fnc);
assert (ctx);
- fdt = &ctx->fdt;
- assert (fdt);
-
- tag = malloc (sizeof *tag);
+ tag = calloc (1, sizeof *tag);
if (!tag)
return gpg_error_from_syserror ();
- tag->ctx = ctx;
-
- /* Allocate a structure to hold information about the handler. */
- item = calloc (1, sizeof *item);
- if (!item)
- {
- free (tag);
- return gpg_error_from_syserror ();
- }
- item->ctx = ctx;
- item->dir = dir;
- item->handler = fnc;
- item->handler_value = fnc_data;
+ tag->serial = ctx->serial;
+ tag->fd = fd;
- err = fd_table_put (fdt, fd, dir, item, &tag->idx);
+ err = _gpgme_fdtable_set_io_cb (fd, ctx->serial, dir, fnc, fnc_data);
if (err)
{
free (tag);
- free (item);
- return err;
+ return TRACE_ERR (err);
}
- TRACE (DEBUG_CTX, "_gpgme_add_io_cb", NULL,
- "ctx=%lu fd=%d dir=%d -> tag=%p", CTXSERIAL (ctx), fd, dir, tag);
-
*r_tag = tag;
+
+ TRACE_SUC ("tag=%p", tag);
return 0;
}
+/* Register the file descriptor FD with the handler FNC (which gets
+ FNC_DATA as its first argument) for the direction DIR. DATA should
+ be the context for which the fd is added. R_TAG will hold the tag
+ that can be used to remove the fd. This function is used for the
+ user wait loops. */
+gpg_error_t
+_gpgme_add_io_cb_user (void *data, int fd, int dir, gpgme_io_cb_t fnc,
+ void *fnc_data, void **r_tag)
+{
+ gpgme_ctx_t ctx = (gpgme_ctx_t) data;
+ struct io_cb_tag_s *tag;
+ gpgme_error_t err;
+
+ TRACE_BEG (DEBUG_SYSIO, __func__, NULL, "ctx=%lu fd=%d, dir %d",
+ CTXSERIAL (ctx), fd, dir);
+
+ assert (ctx);
+ err = _gpgme_add_io_cb (data, fd, dir, fnc, fnc_data, r_tag);
+ if (err)
+ return TRACE_ERR (err);
+ tag = *r_tag;
+ assert (tag);
+
+ err = ctx->user_io_cbs.add (ctx->user_io_cbs.add_priv, fd, dir,
+ user_io_cb_handler, *r_tag,
+ &tag->user_tag);
+ if (err)
+ _gpgme_remove_io_cb (*r_tag);
+ return TRACE_ERR (err);
+}
+
+
+/* This function is used for the global and the private wait loops. */
void
_gpgme_remove_io_cb (void *data)
{
- struct tag *tag = data;
+ struct io_cb_tag_s *tag = data;
+ gpg_error_t err;
+
+ assert (tag);
+
+ err = _gpgme_fdtable_set_io_cb (tag->fd, tag->serial, 0, NULL, NULL);
+ if (err)
+ {
+ TRACE (DEBUG_CTX, __func__, NULL, "tag=%p (ctx=%lu fd=%d) failed: %s",
+ tag, tag->serial, tag->fd, gpg_strerror (err));
+ }
+ else
+ {
+ TRACE (DEBUG_CTX, __func__, NULL, "tag=%p (ctx=%lu fd=%d) done",
+ tag, tag->serial, tag->fd);
+ }
+ free (tag);
+}
+
+
+/* This function is used for the user wait loops. */
+void
+_gpgme_remove_io_cb_user (void *data)
+{
+ struct io_cb_tag_s *tag = data;
gpgme_ctx_t ctx;
- fd_table_t fdt;
- int idx;
assert (tag);
- ctx = tag->ctx;
+ _gpgme_get_ctx (tag->serial, &ctx);
+
+ if (ctx)
+ ctx->user_io_cbs.remove (tag->user_tag);
+ _gpgme_remove_io_cb (data);
+}
+
+
+
+/* The internal I/O callback function used for the global event loop.
+ That loop is used for all asynchronous operations (except key
+ listing) for which no user I/O callbacks are specified.
+
+ A context sets up its initial I/O callbacks and then sends the
+ GPGME_EVENT_START event. After that, it is added to the global
+ list of active contexts.
+
+ The gpgme_wait function contains a select() loop over all file
+ descriptors in all active contexts. If an error occurs, it closes
+ all fds in that context and moves the context to the global done
+ list. Likewise, if a context has removed all I/O callbacks, it is
+ moved to the global done list.
+
+ All contexts in the global done list are eligible for being
+ returned by gpgme_wait if requested by the caller. */
+void
+_gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data)
+{
+ gpgme_ctx_t ctx = (gpgme_ctx_t) data;
+ gpg_error_t err;
+
assert (ctx);
- fdt = &ctx->fdt;
- assert (fdt);
- idx = tag->idx;
- TRACE (DEBUG_CTX, "_gpgme_remove_io_cb", NULL,
- "ctx=%lu setting fd=%d (item=%p data=%p) done",
- CTXSERIAL (ctx),
- fdt->fds[idx].fd,
- fdt->fds[idx].opaque, data);
+ switch (type)
+ {
+ case GPGME_EVENT_START:
+ {
+ err = _gpgme_fdtable_set_active (ctx->serial);
+ if (err)
+ /* An error occurred. Close all fds in this context, and
+ send the error in a done event. */
+ _gpgme_cancel_with_err (ctx->serial, err, 0);
+ }
+ break;
- free (fdt->fds[idx].opaque);
- free (tag);
+ case GPGME_EVENT_DONE:
+ {
+ gpgme_io_event_done_data_t done_data =
+ (gpgme_io_event_done_data_t) type_data;
+
+ _gpgme_fdtable_set_done (ctx->serial,
+ done_data->err, done_data->op_err);
+ }
+ break;
+
+ case GPGME_EVENT_NEXT_KEY:
+ assert (!"Unexpected event GPGME_EVENT_NEXT_KEY");
+ break;
+
+ case GPGME_EVENT_NEXT_TRUSTITEM:
+ assert (!"Unexpected event GPGME_EVENT_NEXT_TRUSTITEM");
+ break;
+
+ default:
+ assert (!"Unexpected event");
+ break;
+ }
+}
+
+
+/* The internal I/O callback function used for private event loops.
+ * The private event loops are used for all blocking operations, and
+ * for the key and trust item listing operations. They are completely
+ * separated from each other. */
+void
+_gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data)
+{
+ switch (type)
+ {
+ case GPGME_EVENT_START:
+ /* Nothing to do here, as the wait routine is called after the
+ initialization is finished. */
+ break;
+
+ case GPGME_EVENT_DONE:
+ break;
+
+ case GPGME_EVENT_NEXT_KEY:
+ _gpgme_op_keylist_event_cb (data, type, type_data);
+ break;
+
+ case GPGME_EVENT_NEXT_TRUSTITEM:
+ _gpgme_op_trustlist_event_cb (data, type, type_data);
+ break;
+ }
+}
+
+
+/* The internal I/O callback function used for user event loops. User
+ * event loops are used for all asynchronous operations for which a
+ * user callback is defined. */
+void
+_gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type, void *type_data)
+{
+ gpgme_ctx_t ctx = data;
- /* Free the table entry. */
- fdt->fds[idx].fd = -1;
- fdt->fds[idx].for_read = 0;
- fdt->fds[idx].for_write = 0;
- fdt->fds[idx].opaque = NULL;
+ if (ctx->user_io_cbs.event)
+ ctx->user_io_cbs.event (ctx->user_io_cbs.event_priv, type, type_data);
}
+
-/* This is slightly embarrassing. The problem is that running an I/O
- callback _may_ influence the status of other file descriptors. Our
- own event loops could compensate for that, but the external event
- loops cannot. FIXME: We may still want to optimize this a bit when
- we are called from our own event loops. So if CHECKED is 1, the
- check is skipped. FIXME: Give an example on how the status of other
- fds can be influenced. */
+/* Perform asynchronous operations in the global event loop (ie, any
+ asynchronous operation except key listing and trustitem listing
+ operations). If CTX is not a null pointer, the function will
+ return if the asynchronous operation in the context CTX finished.
+ Otherwise the function will return if any asynchronous operation
+ finished. If HANG is zero, the function will not block for a long
+ time. Otherwise the function does not return until an operation
+ matching CTX finished.
+
+ If a matching context finished, it is returned, and *STATUS is set
+ to the error value of the operation in that context. Otherwise, if
+ the timeout expires, NULL is returned and *STATUS is 0. If an
+ error occurs, NULL is returned and *STATUS is set to the error
+ value. */
+gpgme_ctx_t
+gpgme_wait_ext (gpgme_ctx_t ctx, gpgme_error_t *status,
+ gpgme_error_t *op_err, int hang)
+{
+ gpg_error_t err;
+ io_select_t fds = NULL;
+ unsigned int nfds;
+ int nr;
+ uint64_t serial;
+
+ do
+ {
+ /* Get all fds of CTX (or all if CTX is NULL) we want to wait
+ * for and which are in the active state. */
+ free (fds);
+ nfds = _gpgme_fdtable_get_fds (&fds, ctx? ctx->serial : 0,
+ ( FDTABLE_FLAG_ACTIVE
+ | FDTABLE_FLAG_CLEAR));
+ if (!nfds)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_MISSING_ERRNO)
+ {
+ if (status)
+ *status = err;
+ if (op_err)
+ *op_err = 0;
+ free (fds);
+ return NULL;
+ }
+ /* Nothing to select. Run the select anyway, so that we use
+ * its timeout. */
+ }
+
+ nr = _gpgme_io_select (fds, nfds, 0);
+ if (nr < 0)
+ {
+ if (status)
+ *status = gpg_error_from_syserror ();
+ if (op_err)
+ *op_err = 0;
+ free (fds);
+ return NULL;
+ }
+ _gpgme_fdtable_set_signaled (fds, nfds);
+
+ _gpgme_fdtable_run_io_cbs (ctx? ctx->serial : 0, NULL);
+ serial = _gpgme_fdtable_get_done (ctx? ctx->serial : 0, status, op_err);
+ if (serial)
+ {
+ _gpgme_get_ctx (serial, &ctx);
+ hang = 0;
+ }
+ else if (!hang)
+ {
+ ctx = NULL;
+ if (status)
+ *status = 0;
+ if (op_err)
+ *op_err = 0;
+ }
+ }
+ while (hang);
+
+ free (fds);
+ return ctx;
+}
+
+
+gpgme_ctx_t
+gpgme_wait (gpgme_ctx_t ctx, gpgme_error_t *status, int hang)
+{
+ return gpgme_wait_ext (ctx, status, NULL, hang);
+}
+
+
+
+/* If COND is a null pointer, wait until the blocking operation in CTX
+ finished and return its error value. Otherwise, wait until COND is
+ satisfied or the operation finished. */
gpgme_error_t
-_gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked,
- gpgme_error_t *op_err)
+_gpgme_wait_on_condition (gpgme_ctx_t ctx, volatile int *cond,
+ gpgme_error_t *r_op_err)
{
- struct wait_item_s *item;
- struct io_cb_data iocb_data;
- gpgme_error_t err;
+ gpgme_error_t err = 0;
+ int hang = 1;
+ io_select_t fds = NULL;
+ unsigned int nfds;
+ int op_err;
+ int nr;
+
+ if (r_op_err)
+ *r_op_err = 0;
- item = (struct wait_item_s *) an_fds->opaque;
- assert (item);
+ if (!ctx)
+ return gpg_error (GPG_ERR_INV_VALUE);
- if (!checked)
+ do
{
- int nr;
- struct io_select_fd_s fds;
-
- TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "need to check");
- fds = *an_fds;
- fds.signaled = 0;
- /* Just give it a quick poll. */
- nr = _gpgme_io_select (&fds, 1, 1);
- assert (nr <= 1);
+ /* Get all fds of CTX we want to wait for. */
+ free (fds);
+ nfds = _gpgme_fdtable_get_fds (&fds, ctx->serial,
+ FDTABLE_FLAG_CLEAR);
+ if (!nfds)
+ {
+ err = gpg_error_from_syserror ();
+ if (gpg_err_code (err) != GPG_ERR_MISSING_ERRNO)
+ {
+ free (fds);
+ return err;
+ }
+ /* Nothing to select. Run the select anyway, so that we use
+ * its timeout. */
+ }
+
+ nr = _gpgme_io_select (fds, nfds, 0);
if (nr < 0)
- return gpg_error_from_syserror ();
- else if (nr == 0)
+ {
+ /* An error occurred. Close all fds in this context, and
+ signal it. */
+ err = gpg_error_from_syserror ();
+ _gpgme_cancel_with_err (ctx->serial, err, 0);
+ free (fds);
+ return err;
+ }
+ _gpgme_fdtable_set_signaled (fds, nfds);
+
+ err = _gpgme_fdtable_run_io_cbs (ctx->serial, r_op_err);
+ if (err || (r_op_err && *r_op_err))
{
- /* The status changed in the meantime, there is nothing left
- * to do. */
- return 0;
+ free (fds);
+ return err;
}
+
+ if (!_gpgme_fdtable_io_cb_count (ctx->serial))
+ {
+ struct gpgme_io_event_done_data data = {0, 0};
+ _gpgme_engine_io_event (ctx->engine, GPGME_EVENT_DONE, &data);
+ hang = 0;
+ }
+ if (cond && *cond)
+ hang = 0;
}
+ while (hang);
- TRACE (DEBUG_CTX, "_gpgme_run_io_cb", item, "handler (%p, %d)",
- item->handler_value, an_fds->fd);
+ free (fds);
+ return 0;
+}
- iocb_data.handler_value = item->handler_value;
- iocb_data.op_err = 0;
- err = item->handler (&iocb_data, an_fds->fd);
- *op_err = iocb_data.op_err;
- return err;
+/* Wait until the blocking operation in context CTX has finished and
+ return the error value. This variant can not be used for
+ session-based protocols. */
+gpgme_error_t
+_gpgme_wait_one (gpgme_ctx_t ctx)
+{
+ return _gpgme_wait_on_condition (ctx, NULL, NULL);
+}
+
+/* Wait until the blocking operation in context CTX has finished and
+ return the error value. This is the right variant to use for
+ sesion-based protocols. */
+gpgme_error_t
+_gpgme_wait_one_ext (gpgme_ctx_t ctx, gpgme_error_t *op_err)
+{
+ return _gpgme_wait_on_condition (ctx, NULL, op_err);
}
diff --git a/src/wait.h b/src/wait.h
index 4beac6e1..b7584cf5 100644
--- a/src/wait.h
+++ b/src/wait.h
@@ -1,96 +1,84 @@
/* wait.h - Definitions for the wait queue interface.
Copyright (C) 2000 Werner Koch (dd9jn)
- Copyright (C) 2001, 2002, 2003, 2004 g10 Code GmbH
+ Copyright (C) 2001, 2002, 2003, 2004. 2019 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA. */
#ifndef WAIT_H
#define WAIT_H
#include "gpgme.h"
#include "sema.h"
-struct fd_table
+/* A registered fd handler can be removed using the tag that
+ * identifies it. In the public API that tag is an an opaque
+ * pointer. */
+struct io_cb_tag_s
{
- struct io_select_fd_s *fds;
- size_t size;
-};
-typedef struct fd_table *fd_table_t;
+ /* The s/n of the context for which the fd was registered. */
+ uint64_t serial;
-/* Wait items are hooked into the io_select_fd_s to connect an fd with
- a callback handler. */
-struct wait_item_s
-{
- gpgme_ctx_t ctx;
- gpgme_io_cb_t handler;
- void *handler_value;
- int dir;
-};
-
-/* A registered fd handler is removed later using the tag that
- identifies it. */
-struct tag
-{
- /* The context for which the fd was registered. */
- gpgme_ctx_t ctx;
-
- /* The index into the fd table for this context. */
- int idx;
+ /* The actual fd. */
+ int fd;
/* This is used by the wrappers for the user event loop. */
void *user_tag;
-};
+ /* A string used describing the data. This is used for tracing. */
+ const char *desc;
+};
-void _gpgme_fd_table_init (fd_table_t fdt);
-void _gpgme_fd_table_deinit (fd_table_t fdt);
gpgme_error_t _gpgme_add_io_cb (void *data, int fd, int dir,
- gpgme_io_cb_t fnc, void *fnc_data, void **r_tag);
+ gpgme_io_cb_t fnc, void *fnc_data,
+ void **r_tag);
+gpgme_error_t _gpgme_add_io_cb_user (void *data, int fd, int dir,
+ gpgme_io_cb_t fnc, void *fnc_data,
+ void **r_tag);
+
void _gpgme_remove_io_cb (void *tag);
-void _gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
- void *type_data);
+void _gpgme_remove_io_cb_user (void *tag);
+
+
void _gpgme_wait_global_event_cb (void *data, gpgme_event_io_t type,
void *type_data);
-gpgme_error_t _gpgme_wait_user_add_io_cb (void *data, int fd, int dir,
- gpgme_io_cb_t fnc, void *fnc_data,
- void **r_tag);
-void _gpgme_wait_user_remove_io_cb (void *tag);
+
+void _gpgme_wait_private_event_cb (void *data, gpgme_event_io_t type,
+ void *type_data);
void _gpgme_wait_user_event_cb (void *data, gpgme_event_io_t type,
void *type_data);
-gpgme_error_t _gpgme_run_io_cb (struct io_select_fd_s *an_fds, int checked,
- gpgme_error_t *err);
+
/* Session based interfaces require to make a distinction between IPC
errors and operational errors. To glue this into the old
interface, I/O handlers (esp. the status handler) are called with a
struct as the opaque value that contains the handlers opaque value
but also a field for the operational error to be returned. */
struct io_cb_data
{
/* If this is the first field, the old internal code will still work. */
void *handler_value;
/* The I/O callback can pass an operational error here. */
gpgme_error_t op_err;
};
#endif /* WAIT_H */