diff --git a/src/Makefile.am b/src/Makefile.am index d85a85c9..79028a17 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,170 +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 \ 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/engine-assuan.c b/src/engine-assuan.c index 497397db..0cca9e59 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -1,844 +1,845 @@ /* engine-assuan.c - Low-level Assuan protocol engine * Copyright (C) 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* Note: This engine requires a modern Assuan server which uses gpg-error codes. In particular there is no backward compatible mapping of old Assuan error codes implemented. */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ } iocb_data_t; /* Engine instance data. */ struct engine_llass { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; struct gpgme_io_cbs io_cbs; /* Hack for old opassuan.c interface, see there the result struct. */ gpg_error_t last_op_err; /* User provided callbacks. */ struct { gpgme_assuan_data_cb_t data_cb; void *data_cb_value; gpgme_assuan_inquire_cb_t inq_cb; void *inq_cb_value; gpgme_assuan_status_cb_t status_cb; void *status_cb_value; } user; /* Option flags. */ struct { int gpg_agent:1; /* Assume this is a gpg-agent connection. */ } opt; char request_origin[10]; /* Copy from the CTX. */ }; typedef struct engine_llass *engine_llass_t; gpg_error_t _gpgme_engine_assuan_last_op_err (void *engine) { engine_llass_t llass = engine; return llass->last_op_err; } /* Prototypes. */ static void llass_io_event (void *engine, gpgme_event_io_t type, void *type_data); /* return the default home directory. */ static const char * llass_get_home_dir (void) { /* For this engine the home directory is not a filename but a string used to convey options. The exclamation mark is a marker to show that this is not a directory name. Options are strings delimited by a space. The only option defined for now is GPG_AGENT to enable GPG_AGENT specific commands to send to the server at connection startup. */ return "!GPG_AGENT"; } static char * llass_get_version (const char *file_name) { (void)file_name; return NULL; } static const char * llass_get_req_version (void) { return NULL; } -static void +static gpg_error_t close_notify_handler (int fd, void *opaque) { engine_llass_t llass = opaque; assert (fd != -1); if (llass->status_cb.fd == fd) { if (llass->status_cb.tag) llass->io_cbs.remove (llass->status_cb.tag); llass->status_cb.fd = -1; llass->status_cb.tag = NULL; } + return 0; } static gpgme_error_t llass_cancel (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); if (llass->assuan_ctx) { assuan_release (llass->assuan_ctx); llass->assuan_ctx = NULL; } return 0; } static gpgme_error_t llass_cancel_op (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); return 0; } static void llass_release (void *engine) { engine_llass_t llass = engine; if (!llass) return; llass_cancel (engine); free (llass); } /* Create a new instance. If HOME_DIR is NULL standard options for use with gpg-agent are issued. */ static gpgme_error_t llass_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_llass_t llass; char *optstr; char *env_tty = NULL; (void)version; /* Not yet used. */ llass = calloc (1, sizeof *llass); if (!llass) return gpg_error_from_syserror (); llass->status_cb.fd = -1; llass->status_cb.dir = 1; llass->status_cb.tag = 0; llass->status_cb.data = llass; /* Parse_options. */ if (home_dir && *home_dir == '!') { home_dir++; /* Very simple parser only working for the one option we support. */ /* Note that wk promised to write a regression test if this parser will be extended. */ if (!strncmp (home_dir, "GPG_AGENT", 9) && (!home_dir[9] || home_dir[9] == ' ')) llass->opt.gpg_agent = 1; } err = assuan_new_ext (&llass->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (llass->assuan_ctx, &_gpgme_assuan_system_hooks); assuan_set_flag (llass->assuan_ctx, ASSUAN_CONVEY_COMMENTS, 1); err = assuan_socket_connect (llass->assuan_ctx, file_name, 0, 0); if (err) goto leave; if (llass->opt.gpg_agent) { char *dft_display = NULL; err = _gpgme_getenv ("DISPLAY", &dft_display); if (err) goto leave; if (dft_display) { if (gpgrt_asprintf (&optstr, "OPTION display=%s", dft_display) < 0) { err = gpg_error_from_syserror (); free (dft_display); goto leave; } free (dft_display); err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } if (llass->opt.gpg_agent) err = _gpgme_getenv ("GPG_TTY", &env_tty); if (llass->opt.gpg_agent && (isatty (1) || env_tty || err)) { int rc = 0; char dft_ttyname[64]; char *dft_ttytype = NULL; if (err) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else rc = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); /* Even though isatty() returns 1, ttyname_r() may fail in many ways, e.g., when /dev/pts is not accessible under chroot. */ if (!rc) { if (gpgrt_asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) { err = gpg_error_from_syserror (); goto leave; } err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { err = gpg_error_from_syserror (); free (dft_ttytype); goto leave; } free (dft_ttytype); err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell llass to tell us when it needs it. */ if (!err && llass->opt.gpg_agent) { err = assuan_transact (llass->assuan_ctx, "OPTION allow-pinentry-notify", NULL, NULL, NULL, NULL, NULL, NULL); if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION) err = 0; /* This work only with recent gpg-agents. */ } #endif /*HAVE_W32_SYSTEM*/ leave: /* Close the server ends of the pipes (because of this, we must use the stored server_fd_str in the function start). Our ends are closed in llass_release(). */ if (err) llass_release (llass); else *engine = llass; return err; } /* Copy flags from CTX into the engine object. */ static void llass_set_engine_flags (void *engine, const gpgme_ctx_t ctx) { engine_llass_t llass = engine; if (ctx->request_origin) { if (strlen (ctx->request_origin) + 1 > sizeof llass->request_origin) strcpy (llass->request_origin, "xxx"); /* Too long - force error */ else strcpy (llass->request_origin, ctx->request_origin); } else *llass->request_origin = 0; } static gpgme_error_t llass_set_locale (void *engine, int category, const char *value) { gpgme_error_t err; engine_llass_t llass = engine; char *optstr; const char *catstr; if (!llass->opt.gpg_agent) return 0; /* FIXME: If value is NULL, we need to reset the option to default. But we can't do this. So we error out here. gpg-agent needs support for this. */ if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && llass->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) llass->lc_ctype_set = 1; } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && llass->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) llass->lc_messages_set = 1; } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); /* FIXME: Reset value to default. */ if (!value) return 0; if (gpgrt_asprintf (&optstr, "OPTION %s=%s", catstr, value) < 0) err = gpg_error_from_syserror (); else { err = assuan_transact (llass->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } /* This is the inquiry callback. It handles stuff which ee need to handle here and passes everything on to the user callback. */ static gpgme_error_t inquire_cb (engine_llass_t llass, const char *keyword, const char *args) { gpg_error_t err; if (llass->opt.gpg_agent && !strcmp (keyword, "PINENTRY_LAUNCHED")) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); } if (llass->user.inq_cb) { gpgme_data_t data = NULL; err = llass->user.inq_cb (llass->user.inq_cb_value, keyword, args, &data); if (!err && data) { /* FIXME: Returning data is not yet implemented. However we need to allow the caller to cleanup his data object. Thus we run the callback in finish mode immediately. */ err = llass->user.inq_cb (llass->user.inq_cb_value, NULL, NULL, &data); } } else err = 0; return err; } static gpgme_error_t llass_status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_llass_t llass = (engine_llass_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (llass->assuan_ctx, &line, &linelen); if (err) { /* Reading a full line may not be possible when communicating over a socket in nonblocking mode. In this case, we are done for now. */ if (gpg_err_code (err) == GPG_ERR_EAGAIN) { TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: EAGAIN reading assuan line (ignored)", fd); err = 0; continue; } TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: error reading assuan line: %s", fd, gpg_strerror (err)); } else if (linelen >= 2 && line[0] == 'D' && line[1] == ' ') { char *src = line + 2; char *end = line + linelen; char *dst = src; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; if (linelen && llass->user.data_cb) err = llass->user.data_cb (llass->user.data_cb_value, src, linelen); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: D inlinedata; status from cb: %s", fd, (llass->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'N' && line[2] == 'D' && (line[3] == '\0' || line[3] == ' ')) { /* END received. Tell the data callback. */ if (llass->user.data_cb) err = llass->user.data_cb (llass->user.data_cb_value, NULL, 0); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: END line; status from cb: %s", fd, (llass->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *args; char *src; for (src=line+2; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; if (llass->user.status_cb) err = llass->user.status_cb (llass->user.status_cb_value, src, args); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: S line (%s) - status from cb: %s", fd, line+2, (llass->user.status_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *src; char *args; for (src=line+7; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; err = inquire_cb (llass, src, args); if (!err) { /* Flush and send END. */ err = assuan_send_data (llass->assuan_ctx, NULL, 0); } else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) { /* Flush and send CANcel. */ err = assuan_send_data (llass->assuan_ctx, NULL, 1); } } else if (linelen >= 3 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && (line[3] == '\0' || line[3] == ' ')) { if (line[3] == ' ') err = atoi (line+4); else err = gpg_error (GPG_ERR_GENERAL); TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: ERR line: %s", fd, err ? gpg_strerror (err) : "ok"); /* Command execution errors are not fatal, as we use a session based protocol. */ data->op_err = err; llass->last_op_err = err; /* The caller will do the rest (namely, call cancel_op, which closes status_fd). */ return 0; } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { TRACE (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: OK line", fd); llass->last_op_err = 0; _gpgme_io_close (llass->status_cb.fd); return 0; } else { /* Comment line or invalid line. */ } } while (!err && assuan_pending_line (llass->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_llass_t llass, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-assuan:add_io_cb", llass, "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*llass->io_cbs.add) (llass->io_cbs.add_priv, iocbd->fd, iocbd->dir, handler, iocbd->data, &iocbd->tag); if (err) return TRACE_ERR (err); if (!iocbd->dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (iocbd->fd); return TRACE_ERR (err); } static gpgme_error_t start (engine_llass_t llass, const char *command) { gpgme_error_t err; assuan_fd_t afdlist[5]; int fdlist[5]; int nfds; int i; if (*llass->request_origin && llass->opt.gpg_agent) { char *cmd; cmd = _gpgme_strconcat ("OPTION pretend-request-origin=", llass->request_origin, NULL); if (!cmd) return gpg_error_from_syserror (); err = assuan_transact (llass->assuan_ctx, cmd, NULL, NULL, NULL, NULL, NULL, NULL); free (cmd); if (err && gpg_err_code (err) != GPG_ERR_UNKNOWN_OPTION) return err; } /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */, afdlist, DIM (afdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* For now... */ for (i = 0; i < nfds; i++) fdlist[i] = (int) afdlist[i]; /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ llass->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (llass->status_cb.fd < 0) return gpg_error_from_syserror (); - if (_gpgme_io_set_close_notify (llass->status_cb.fd, - close_notify_handler, llass)) + if (_gpgme_fdtable_add_close_notify (llass->status_cb.fd, + close_notify_handler, llass)) { _gpgme_io_close (llass->status_cb.fd); llass->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (llass, &llass->status_cb, llass_status_handler); if (!err) err = assuan_write_line (llass->assuan_ctx, command); /* FIXME: If *command == '#' no answer is expected. */ if (!err) llass_io_event (llass, GPGME_EVENT_START, NULL); return err; } static gpgme_error_t llass_transact (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { engine_llass_t llass = engine; gpgme_error_t err; if (!llass || !command || !*command) return gpg_error (GPG_ERR_INV_VALUE); llass->user.data_cb = data_cb; llass->user.data_cb_value = data_cb_value; llass->user.inq_cb = inq_cb; llass->user.inq_cb_value = inq_cb_value; llass->user.status_cb = status_cb; llass->user.status_cb_value = status_cb_value; err = start (llass, command); return err; } static void llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_llass_t llass = engine; llass->io_cbs = *io_cbs; } static void llass_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_llass_t llass = engine; TRACE (DEBUG_ENGINE, "gpgme:llass_io_event", llass, "event %p, type %d, type_data %p", llass->io_cbs.event, type, type_data); if (llass->io_cbs.event) (*llass->io_cbs.event) (llass->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_assuan = { /* Static functions. */ _gpgme_get_default_agent_socket, llass_get_home_dir, llass_get_version, llass_get_req_version, llass_new, /* Member functions. */ llass_release, NULL, /* reset */ NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ llass_set_locale, NULL, /* set_protocol */ llass_set_engine_flags, NULL, /* decrypt */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ llass_transact, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ llass_set_io_cbs, llass_io_event, llass_cancel, llass_cancel_op, NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine-backend.h b/src/engine-backend.h index 4f33da1c..9bf54bc1 100644 --- a/src/engine-backend.h +++ b/src/engine-backend.h @@ -1,188 +1,189 @@ /* engine-backend.h - A crypto backend for the engine interface. Copyright (C) 2002, 2003, 2004, 2009 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see . */ #ifndef ENGINE_BACKEND_H #define ENGINE_BACKEND_H #include "engine.h" +#include "fdtable.h" struct engine_ops { /* Static functions. */ /* Return the default file name for the binary of this engine. */ const char *(*get_file_name) (void); /* Return the default home dir for the binary of this engine. If this function pointer is not set, the standard default home dir of the engine is used. */ const char *(*get_home_dir) (void); /* Returns a malloced string containing the version of the engine with the given binary file name (or the default if FILE_NAME is NULL. */ char *(*get_version) (const char *file_name); /* Returns a statically allocated string containing the required version. */ const char *(*get_req_version) (void); gpgme_error_t (*new) (void **r_engine, const char *file_name, const char *home_dir, const char *version); /* Member functions. */ void (*release) (void *engine); gpgme_error_t (*reset) (void *engine); void (*set_status_cb) (void *engine, gpgme_status_cb_t cb, void *cb_value); void (*set_status_handler) (void *engine, engine_status_handler_t fnc, void *fnc_value); gpgme_error_t (*set_command_handler) (void *engine, engine_command_handler_t fnc, void *fnc_value); gpgme_error_t (*set_colon_line_handler) (void *engine, engine_colon_line_handler_t fnc, void *fnc_value); gpgme_error_t (*set_locale) (void *engine, int category, const char *value); gpgme_error_t (*set_protocol) (void *engine, gpgme_protocol_t protocol); void (*set_engine_flags) (void *engine, gpgme_ctx_t ctx); gpgme_error_t (*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); gpgme_error_t (*delete) (void *engine, gpgme_key_t key, unsigned int flags); gpgme_error_t (*edit) (void *engine, int type, gpgme_key_t key, gpgme_data_t out, gpgme_ctx_t ctx /* FIXME */); gpgme_error_t (*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); gpgme_error_t (*encrypt_sign) (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, gpgme_ctx_t ctx /* FIXME */); gpgme_error_t (*export) (void *engine, const char *pattern, gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor); gpgme_error_t (*export_ext) (void *engine, const char *pattern[], gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor); gpgme_error_t (*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); gpgme_error_t (*import) (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray); gpgme_error_t (*keylist) (void *engine, const char *pattern, int secret_only, gpgme_keylist_mode_t mode, int engine_flags); gpgme_error_t (*keylist_ext) (void *engine, const char *pattern[], int secret_only, int reserved, gpgme_keylist_mode_t mode, int engine_flags); gpgme_error_t (*keylist_data) (void *engine, gpgme_data_t data); gpgme_error_t (*keysign) (void *engine, gpgme_key_t key, const char *userid, unsigned long expires, unsigned int flags, gpgme_ctx_t ctx); gpgme_error_t (*tofu_policy) (void *engine, gpgme_key_t key, gpgme_tofu_policy_t policy); gpgme_error_t (*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 */); gpgme_error_t (*trustlist) (void *engine, const char *pattern); gpgme_error_t (*verify) (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_ctx_t ctx); gpgme_error_t (*getauditlog) (void *engine, gpgme_data_t output, unsigned int flags); gpgme_error_t (*opassuan_transact) (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value); gpgme_error_t (*conf_load) (void *engine, gpgme_conf_comp_t *conf_p); gpgme_error_t (*conf_save) (void *engine, gpgme_conf_comp_t conf); gpgme_error_t (*conf_dir) (void *engine, const char *what, char **result); gpgme_error_t (*query_swdb) (void *engine, const char *name, const char *iversion, gpgme_query_swdb_result_t result); void (*set_io_cbs) (void *engine, gpgme_io_cbs_t io_cbs); void (*io_event) (void *engine, gpgme_event_io_t type, void *type_data); /* Cancel the whole engine session. */ gpgme_error_t (*cancel) (void *engine); /* Cancel only the current operation, not the whole session. */ gpgme_error_t (*cancel_op) (void *engine); /* Change the passphrase for KEY. */ gpgme_error_t (*passwd) (void *engine, gpgme_key_t key, unsigned int flags); /* Set the pinentry mode. */ gpgme_error_t (*set_pinentry_mode) (void *engine, gpgme_pinentry_mode_t mode); /* The spawn command. */ gpgme_error_t (*opspawn) (void * engine, const char *file, const char *argv[], gpgme_data_t datain, gpgme_data_t dataout, gpgme_data_t dataerr, unsigned int flags); }; extern struct engine_ops _gpgme_engine_ops_gpg; /* OpenPGP. */ extern struct engine_ops _gpgme_engine_ops_gpgsm; /* CMS. */ extern struct engine_ops _gpgme_engine_ops_gpgconf; /* gpg-conf. */ extern struct engine_ops _gpgme_engine_ops_assuan; /* Low-level Assuan. */ extern struct engine_ops _gpgme_engine_ops_g13; /* Crypto VFS. */ #ifdef ENABLE_UISERVER extern struct engine_ops _gpgme_engine_ops_uiserver; #endif extern struct engine_ops _gpgme_engine_ops_spawn; /* Spawn engine. */ /* Prototypes for extra functions in engine-gpgconf.c */ gpgme_error_t _gpgme_conf_arg_new (gpgme_conf_arg_t *arg_p, gpgme_conf_type_t type, const void *value); void _gpgme_conf_arg_release (gpgme_conf_arg_t arg, gpgme_conf_type_t type); gpgme_error_t _gpgme_conf_opt_change (gpgme_conf_opt_t opt, int reset, gpgme_conf_arg_t arg); void _gpgme_conf_release (gpgme_conf_comp_t conf); gpgme_error_t _gpgme_conf_load (void *engine, gpgme_conf_comp_t *conf_p); #endif /* ENGINE_BACKEND_H */ diff --git a/src/engine-g13.c b/src/engine-g13.c index 19dd8f47..7d448595 100644 --- a/src/engine-g13.c +++ b/src/engine-g13.c @@ -1,824 +1,825 @@ /* engine-g13.c - G13 engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * 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 #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ 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_g13 { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; struct gpgme_io_cbs io_cbs; /* User provided callbacks. */ struct { gpgme_assuan_data_cb_t data_cb; void *data_cb_value; gpgme_assuan_inquire_cb_t inq_cb; void *inq_cb_value; gpgme_assuan_status_cb_t status_cb; void *status_cb_value; } user; }; typedef struct engine_g13 *engine_g13_t; static void g13_io_event (void *engine, gpgme_event_io_t type, void *type_data); static char * g13_get_version (const char *file_name) { return _gpgme_get_program_version (file_name ? file_name : _gpgme_get_default_g13_name ()); } static const char * g13_get_req_version (void) { return "2.1.0"; } -static void +static gpg_error_t close_notify_handler (int fd, void *opaque) { engine_g13_t g13 = opaque; assert (fd != -1); if (g13->status_cb.fd == fd) { if (g13->status_cb.tag) (*g13->io_cbs.remove) (g13->status_cb.tag); g13->status_cb.fd = -1; g13->status_cb.tag = NULL; } + return 0; } /* This is the default inquiry callback. We use it to handle the Pinentry notifications. */ static gpgme_error_t default_inq_cb (engine_g13_t g13, const char *keyword, const char *args) { gpg_error_t err; if (!strcmp (keyword, "PINENTRY_LAUNCHED")) { _gpgme_allow_set_foreground_window ((pid_t)strtoul (args, NULL, 10)); } if (g13->user.inq_cb) { gpgme_data_t data = NULL; err = g13->user.inq_cb (g13->user.inq_cb_value, keyword, args, &data); if (!err && data) { /* FIXME: Returning data is not yet implemented. However we need to allow the caller to cleanup his data object. Thus we run the callback in finish mode immediately. */ err = g13->user.inq_cb (g13->user.inq_cb_value, NULL, NULL, &data); } } else err = 0; return err; } static gpgme_error_t g13_cancel (void *engine) { engine_g13_t g13 = engine; if (!g13) return gpg_error (GPG_ERR_INV_VALUE); if (g13->status_cb.fd != -1) _gpgme_io_close (g13->status_cb.fd); if (g13->assuan_ctx) { assuan_release (g13->assuan_ctx); g13->assuan_ctx = NULL; } return 0; } static gpgme_error_t g13_cancel_op (void *engine) { engine_g13_t g13 = engine; if (!g13) return gpg_error (GPG_ERR_INV_VALUE); if (g13->status_cb.fd != -1) _gpgme_io_close (g13->status_cb.fd); return 0; } static void g13_release (void *engine) { engine_g13_t g13 = engine; if (!g13) return; g13_cancel (engine); free (g13); } static gpgme_error_t g13_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_g13_t g13; const char *pgmname; int argc; const char *argv[5]; char *dft_display = NULL; char dft_ttyname[64]; char *env_tty = NULL; char *dft_ttytype = NULL; char *optstr; (void)version; /* Not yet used. */ g13 = calloc (1, sizeof *g13); if (!g13) return gpg_error_from_syserror (); g13->status_cb.fd = -1; g13->status_cb.dir = 1; g13->status_cb.tag = 0; g13->status_cb.data = g13; pgmname = file_name ? file_name : _gpgme_get_default_g13_name (); argc = 0; argv[argc++] = _gpgme_get_basename (pgmname); if (home_dir) { argv[argc++] = "--homedir"; argv[argc++] = home_dir; } argv[argc++] = "--server"; argv[argc++] = NULL; err = assuan_new_ext (&g13->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (g13->assuan_ctx, &_gpgme_assuan_system_hooks); #if USE_DESCRIPTOR_PASSING err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv, NULL, NULL, NULL, ASSUAN_PIPE_CONNECT_FDPASSING); #else err = assuan_pipe_connect (g13->assuan_ctx, pgmname, argv, NULL, NULL, NULL, 0); #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 (g13->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 (g13->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 (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell g13 to tell us when it needs it. */ if (!err) { err = assuan_transact (g13->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 g13. */ } #endif /*HAVE_W32_SYSTEM*/ leave: if (err) g13_release (g13); else *engine = g13; return err; } static gpgme_error_t g13_set_locale (void *engine, int category, const char *value) { engine_g13_t g13 = 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. G13 needs support for this. */ if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && g13->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) g13->lc_ctype_set = 1; } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && g13->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) g13->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 (g13->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } #if USE_DESCRIPTOR_PASSING static gpgme_error_t g13_assuan_simple_command (assuan_context_t ctx, const char *cmd, engine_status_handler_t status_fnc, void *status_fnc_value) { gpg_error_t err; char *line; size_t linelen; (void)status_fnc; (void)status_fnc_value; err = assuan_write_line (ctx, cmd); if (err) return err; do { err = assuan_read_line (ctx, &line, &linelen); if (err) return err; if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) return 0; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') err = atoi (&line[4]); else if (linelen >= 2 && line[0] == 'S' && line[1] == ' ') { char *rest; rest = strchr (line + 2, ' '); if (!rest) rest = line + linelen; /* set to an empty string */ else *(rest++) = 0; /* Nothing to do with status lines. */ } else err = gpg_error (GPG_ERR_GENERAL); } while (!err); return err; } #endif static gpgme_error_t status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_g13_t g13 = (engine_g13_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (g13->assuan_ctx, &line, &linelen); if (err) { /* Try our best to terminate the connection friendly. */ /* assuan_write_line (g13->assuan_ctx, "BYE"); */ TRACE (DEBUG_CTX, "gpgme:status_handler", g13, "fd 0x%x: error reading assuan line: %s", fd, 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", g13, "fd 0x%x: ERR line: %s", fd, err ? gpg_strerror (err) : "ok"); /* Command execution errors are not fatal, as we use a session based protocol. */ data->op_err = err; /* The caller will do the rest (namely, call cancel_op, which closes status_fd). */ return 0; } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { TRACE (DEBUG_CTX, "gpgme:status_handler", g13, "fd 0x%x: OK line", fd); _gpgme_io_close (g13->status_cb.fd); return 0; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ') { /* 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 = src; linelen = 0; while (src < end) { if (*src == '%' && src + 2 < end) { /* Handle escaped characters. */ ++src; *dst++ = _gpgme_hextobyte (src); src += 2; } else *dst++ = *src++; linelen++; } src = line + 2; if (linelen && g13->user.data_cb) err = g13->user.data_cb (g13->user.data_cb_value, src, linelen); else err = 0; TRACE (DEBUG_CTX, "gpgme:g13_status_handler", g13, "fd 0x%x: D inlinedata; status from cb: %s", fd, (g13->user.data_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen > 2 && line[0] == 'S' && line[1] == ' ') { char *src; char *args; src = line + 2; while (*src == ' ') src++; args = strchr (line + 2, ' '); if (!args) args = line + linelen; /* set to an empty string */ else *(args++) = 0; while (*args == ' ') args++; if (g13->user.status_cb) err = g13->user.status_cb (g13->user.status_cb_value, src, args); else err = 0; TRACE (DEBUG_CTX, "gpgme:g13_status_handler", g13, "fd 0x%x: S line (%s) - status from cb: %s", fd, line+2, (g13->user.status_cb ? (err? gpg_strerror (err):"ok"):"no callback")); } else if (linelen >= 7 && line[0] == 'I' && line[1] == 'N' && line[2] == 'Q' && line[3] == 'U' && line[4] == 'I' && line[5] == 'R' && line[6] == 'E' && (line[7] == '\0' || line[7] == ' ')) { char *src; char *args; for (src=line+7; *src == ' '; src++) ; args = strchr (src, ' '); if (!args) args = line + linelen; /* Let it point to an empty string. */ else *(args++) = 0; while (*args == ' ') args++; err = default_inq_cb (g13, src, args); if (!err) { /* Flush and send END. */ err = assuan_send_data (g13->assuan_ctx, NULL, 0); } else if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED) { /* Flush and send CANcel. */ err = assuan_send_data (g13->assuan_ctx, NULL, 1); } assuan_write_line (g13->assuan_ctx, "END"); } } while (!err && assuan_pending_line (g13->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_g13_t g13, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-g13:add_io_cb", g13, "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*g13->io_cbs.add) (g13->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_g13_t g13, const char *command) { gpgme_error_t err; assuan_fd_t afdlist[5]; int fdlist[5]; int nfds; int i; /* 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 (g13->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. */ g13->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (g13->status_cb.fd < 0) return gpg_error_from_syserror (); - if (_gpgme_io_set_close_notify (g13->status_cb.fd, - close_notify_handler, g13)) + if (_gpgme_fdtable_add_close_notify (g13->status_cb.fd, + close_notify_handler, g13)) { _gpgme_io_close (g13->status_cb.fd); g13->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (g13, &g13->status_cb, status_handler); if (!err) err = assuan_write_line (g13->assuan_ctx, command); if (!err) g13_io_event (g13, GPGME_EVENT_START, NULL); return err; } #if USE_DESCRIPTOR_PASSING static gpgme_error_t g13_reset (void *engine) { engine_g13_t g13 = engine; /* We must send a reset because we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ return g13_assuan_simple_command (g13->assuan_ctx, "RESET", NULL, NULL); } #endif static gpgme_error_t g13_transact (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { engine_g13_t g13 = engine; gpgme_error_t err; if (!g13 || !command || !*command) return gpg_error (GPG_ERR_INV_VALUE); g13->user.data_cb = data_cb; g13->user.data_cb_value = data_cb_value; g13->user.inq_cb = inq_cb; g13->user.inq_cb_value = inq_cb_value; g13->user.status_cb = status_cb; g13->user.status_cb_value = status_cb_value; err = start (g13, command); return err; } static void g13_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_g13_t g13 = engine; g13->io_cbs = *io_cbs; } static void g13_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_g13_t g13 = engine; TRACE (DEBUG_ENGINE, "gpgme:g13_io_event", g13, "event %p, type %d, type_data %p", g13->io_cbs.event, type, type_data); if (g13->io_cbs.event) (*g13->io_cbs.event) (g13->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_g13 = { /* Static functions. */ _gpgme_get_default_g13_name, NULL, g13_get_version, g13_get_req_version, g13_new, /* Member functions. */ g13_release, #if USE_DESCRIPTOR_PASSING g13_reset, #else NULL, /* reset */ #endif NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ g13_set_locale, NULL, /* set_protocol */ NULL, /* set_engine_flags */ NULL, /* decrypt */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ g13_transact, NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ g13_set_io_cbs, g13_io_event, g13_cancel, g13_cancel_op, NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine-gpg.c b/src/engine-gpg.c index dc2d9455..cce80d8d 100644 --- a/src/engine-gpg.c +++ b/src/engine-gpg.c @@ -1,3421 +1,3423 @@ /* engine-gpg.c - Gpg Engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, * 2009, 2010, 2012, 2013 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_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "context.h" /*temp hack until we have GpmeData methods to do I/O */ #include "priv-io.h" #include "sema.h" #include "debug.h" #include "data.h" #include "mbox-util.h" #include "engine-backend.h" /* This type is used to build a list of gpg arguments and data sources/sinks. */ struct arg_and_data_s { struct arg_and_data_s *next; gpgme_data_t data; /* If this is not NULL, use arg below. */ int inbound; /* True if this is used for reading from gpg. */ int dup_to; int print_fd; /* Print the fd number and not the special form of it. */ int *arg_locp; /* Write back the argv idx of this argument when building command line to this location. */ char arg[1]; /* Used if data above is not used. */ }; struct fd_data_map_s { gpgme_data_t data; int inbound; /* true if this is used for reading from gpg */ int dup_to; int fd; /* the fd to use */ int peer_fd; /* the other side of the pipe */ int arg_loc; /* The index into the argv for translation purposes. */ void *tag; }; /* NB.: R_LINE is allocated an gpgrt function and thus gpgrt_free * shall be used to release it. This takes care of custom memory * allocators and avoids problems on Windows with different runtimes * used for libgpg-error/gpgrt and gpgme. */ typedef gpgme_error_t (*colon_preprocessor_t) (char *line, char **rline); struct engine_gpg { char *file_name; char *version; char *lc_messages; char *lc_ctype; struct arg_and_data_s *arglist; struct arg_and_data_s **argtail; struct { int fd[2]; int arg_loc; size_t bufsize; char *buffer; size_t readpos; int eof; engine_status_handler_t fnc; void *fnc_value; gpgme_status_cb_t mon_cb; void *mon_cb_value; void *tag; } status; /* This is a kludge - see the comment at colon_line_handler. */ struct { int fd[2]; int arg_loc; size_t bufsize; char *buffer; size_t readpos; int eof; engine_colon_line_handler_t fnc; /* this indicate use of this structrue */ void *fnc_value; void *tag; colon_preprocessor_t preprocess_fnc; } colon; char **argv; struct fd_data_map_s *fd_data_map; /* stuff needed for interactive (command) mode */ struct { int used; int fd; void *cb_data; int idx; /* Index in fd_data_map */ gpgme_status_code_t code; /* last code */ char *keyword; /* what has been requested (malloced) */ engine_command_handler_t fnc; void *fnc_value; } cmd; struct gpgme_io_cbs io_cbs; gpgme_pinentry_mode_t pinentry_mode; char request_origin[10]; char *auto_key_locate; char *trust_model; struct { unsigned int no_symkey_cache : 1; unsigned int offline : 1; unsigned int ignore_mdc_error : 1; } flags; /* NULL or the data object fed to --override_session_key-fd. */ gpgme_data_t override_session_key; /* Memory data containing diagnostics (--logger-fd) of gpg */ gpgme_data_t diagnostics; }; typedef struct engine_gpg *engine_gpg_t; static void gpg_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_gpg_t gpg = engine; TRACE (DEBUG_ENGINE, "gpgme:gpg_io_event", gpg, "event %p, type %d, type_data %p", gpg->io_cbs.event, type, type_data); if (gpg->io_cbs.event) (*gpg->io_cbs.event) (gpg->io_cbs.event_priv, type, type_data); } -static void +static gpg_error_t close_notify_handler (int fd, void *opaque) { engine_gpg_t gpg = opaque; assert (fd != -1); if (gpg->status.fd[0] == fd) { if (gpg->status.tag) (*gpg->io_cbs.remove) (gpg->status.tag); gpg->status.fd[0] = -1; } else if (gpg->status.fd[1] == fd) gpg->status.fd[1] = -1; else if (gpg->colon.fd[0] == fd) { if (gpg->colon.tag) (*gpg->io_cbs.remove) (gpg->colon.tag); gpg->colon.fd[0] = -1; } else if (gpg->colon.fd[1] == fd) gpg->colon.fd[1] = -1; else if (gpg->cmd.fd == fd) gpg->cmd.fd = -1; else if (gpg->fd_data_map) { int i; for (i = 0; gpg->fd_data_map[i].data; i++) { if (gpg->fd_data_map[i].fd == fd) { if (gpg->fd_data_map[i].tag) (*gpg->io_cbs.remove) (gpg->fd_data_map[i].tag); gpg->fd_data_map[i].fd = -1; break; } if (gpg->fd_data_map[i].peer_fd == fd) { gpg->fd_data_map[i].peer_fd = -1; break; } } } + return 0; } /* If FRONT is true, push at the front of the list. Use this for options added late in the process. */ static gpgme_error_t _add_arg (engine_gpg_t gpg, const char *prefix, const char *arg, size_t arglen, int front, int *arg_locp) { struct arg_and_data_s *a; size_t prefixlen = prefix? strlen (prefix) : 0; assert (gpg); assert (arg); a = malloc (sizeof *a + prefixlen + arglen); if (!a) return gpg_error_from_syserror (); a->data = NULL; a->dup_to = -1; a->arg_locp = arg_locp; if (prefixlen) memcpy (a->arg, prefix, prefixlen); memcpy (a->arg + prefixlen, arg, arglen); a->arg[prefixlen + arglen] = 0; if (front) { a->next = gpg->arglist; if (!gpg->arglist) { /* If this is the first argument, we need to update the tail pointer. */ gpg->argtail = &a->next; } gpg->arglist = a; } else { a->next = NULL; *gpg->argtail = a; gpg->argtail = &a->next; } return 0; } static gpgme_error_t add_arg_ext (engine_gpg_t gpg, const char *arg, int front) { return _add_arg (gpg, NULL, arg, strlen (arg), front, NULL); } static gpgme_error_t add_arg_with_locp (engine_gpg_t gpg, const char *arg, int *locp) { return _add_arg (gpg, NULL, arg, strlen (arg), 0, locp); } static gpgme_error_t add_arg (engine_gpg_t gpg, const char *arg) { return _add_arg (gpg, NULL, arg, strlen (arg), 0, NULL); } static gpgme_error_t add_arg_pfx (engine_gpg_t gpg, const char *prefix, const char *arg) { return _add_arg (gpg, prefix, arg, strlen (arg), 0, NULL); } static gpgme_error_t add_arg_len (engine_gpg_t gpg, const char *prefix, const char *arg, size_t arglen) { return _add_arg (gpg, prefix, arg, arglen, 0, NULL); } static gpgme_error_t add_data (engine_gpg_t gpg, gpgme_data_t data, int dup_to, int inbound) { struct arg_and_data_s *a; assert (gpg); assert (data); a = malloc (sizeof *a - 1); if (!a) return gpg_error_from_syserror (); a->next = NULL; a->data = data; a->inbound = inbound; a->arg_locp = NULL; if (dup_to == -2) { a->print_fd = 1; a->dup_to = -1; } else { a->print_fd = 0; a->dup_to = dup_to; } *gpg->argtail = a; gpg->argtail = &a->next; return 0; } /* Return true if the engine's version is at least VERSION. */ static int have_gpg_version (engine_gpg_t gpg, const char *version) { return _gpgme_compare_versions (gpg->version, version); } static char * gpg_get_version (const char *file_name) { return _gpgme_get_program_version (file_name ? file_name : _gpgme_get_default_gpg_name ()); } static const char * gpg_get_req_version (void) { return "1.4.0"; } static void free_argv (char **argv) { int i; for (i = 0; argv[i]; i++) free (argv[i]); free (argv); } static void free_fd_data_map (struct fd_data_map_s *fd_data_map) { int i; if (!fd_data_map) return; for (i = 0; fd_data_map[i].data; i++) { if (fd_data_map[i].fd != -1) _gpgme_io_close (fd_data_map[i].fd); if (fd_data_map[i].peer_fd != -1) _gpgme_io_close (fd_data_map[i].peer_fd); /* Don't release data because this is only a reference. */ } free (fd_data_map); } static gpgme_error_t gpg_cancel (void *engine) { engine_gpg_t gpg = engine; if (!gpg) return gpg_error (GPG_ERR_INV_VALUE); /* If gpg may be waiting for a cmd, close the cmd fd first. On Windows, close operations block on the reader/writer thread. */ if (gpg->cmd.used) { if (gpg->cmd.fd != -1) _gpgme_io_close (gpg->cmd.fd); else if (gpg->fd_data_map && gpg->fd_data_map[gpg->cmd.idx].fd != -1) _gpgme_io_close (gpg->fd_data_map[gpg->cmd.idx].fd); } if (gpg->status.fd[0] != -1) _gpgme_io_close (gpg->status.fd[0]); if (gpg->status.fd[1] != -1) _gpgme_io_close (gpg->status.fd[1]); if (gpg->colon.fd[0] != -1) _gpgme_io_close (gpg->colon.fd[0]); if (gpg->colon.fd[1] != -1) _gpgme_io_close (gpg->colon.fd[1]); if (gpg->fd_data_map) { free_fd_data_map (gpg->fd_data_map); gpg->fd_data_map = NULL; } return 0; } static void gpg_release (void *engine) { engine_gpg_t gpg = engine; if (!gpg) return; gpg_cancel (engine); if (gpg->file_name) free (gpg->file_name); if (gpg->version) free (gpg->version); if (gpg->lc_messages) free (gpg->lc_messages); if (gpg->lc_ctype) free (gpg->lc_ctype); while (gpg->arglist) { struct arg_and_data_s *next = gpg->arglist->next; free (gpg->arglist); gpg->arglist = next; } if (gpg->status.buffer) free (gpg->status.buffer); if (gpg->colon.buffer) free (gpg->colon.buffer); if (gpg->argv) free_argv (gpg->argv); if (gpg->cmd.keyword) free (gpg->cmd.keyword); free (gpg->auto_key_locate); free (gpg->trust_model); gpgme_data_release (gpg->override_session_key); gpgme_data_release (gpg->diagnostics); free (gpg); } static gpgme_error_t gpg_new (void **engine, const char *file_name, const char *home_dir, const char *version) { engine_gpg_t gpg; gpgme_error_t rc = 0; char *dft_display = NULL; char dft_ttyname[64]; char *dft_ttytype = NULL; char *env_tty = NULL; gpg = calloc (1, sizeof *gpg); if (!gpg) return gpg_error_from_syserror (); if (file_name) { gpg->file_name = strdup (file_name); if (!gpg->file_name) { rc = gpg_error_from_syserror (); goto leave; } } if (version) { gpg->version = strdup (version); if (!gpg->version) { rc = gpg_error_from_syserror (); goto leave; } } gpg->argtail = &gpg->arglist; gpg->status.fd[0] = -1; gpg->status.fd[1] = -1; gpg->colon.fd[0] = -1; gpg->colon.fd[1] = -1; gpg->cmd.fd = -1; gpg->cmd.idx = -1; /* Allocate the read buffer for the status pipe. */ gpg->status.bufsize = 1024; gpg->status.readpos = 0; gpg->status.buffer = malloc (gpg->status.bufsize); if (!gpg->status.buffer) { rc = gpg_error_from_syserror (); goto leave; } /* In any case we need a status pipe - create it right here and don't handle it with our generic gpgme_data_t mechanism. */ if (_gpgme_io_pipe (gpg->status.fd, 1) == -1) { rc = gpg_error_from_syserror (); goto leave; } - if (_gpgme_io_set_close_notify (gpg->status.fd[0], - close_notify_handler, gpg) - || _gpgme_io_set_close_notify (gpg->status.fd[1], - close_notify_handler, gpg)) + if (_gpgme_fdtable_add_close_notify (gpg->status.fd[0], + close_notify_handler, gpg) + || _gpgme_fdtable_add_close_notify (gpg->status.fd[1], + close_notify_handler, gpg)) { rc = gpg_error (GPG_ERR_GENERAL); goto leave; } gpg->status.eof = 0; if (home_dir) { rc = add_arg (gpg, "--homedir"); if (!rc) rc = add_arg (gpg, home_dir); if (rc) goto leave; } rc = add_arg (gpg, "--status-fd"); if (rc) goto leave; { char buf[25]; _gpgme_io_fd2str (buf, sizeof (buf), gpg->status.fd[1]); rc = add_arg_with_locp (gpg, buf, &gpg->status.arg_loc); if (rc) goto leave; } rc = add_arg (gpg, "--no-tty"); if (!rc) rc = add_arg (gpg, "--charset"); if (!rc) rc = add_arg (gpg, "utf8"); if (!rc) rc = add_arg (gpg, "--enable-progress-filter"); if (!rc && have_gpg_version (gpg, "2.1.11")) rc = add_arg (gpg, "--exit-on-status-write-error"); if (rc) goto leave; rc = _gpgme_getenv ("DISPLAY", &dft_display); if (rc) goto leave; if (dft_display) { rc = add_arg (gpg, "--display"); if (!rc) rc = add_arg (gpg, dft_display); free (dft_display); if (rc) goto leave; } rc = _gpgme_getenv ("GPG_TTY", &env_tty); if (isatty (1) || env_tty || rc) { int err = 0; if (rc) goto leave; else if (env_tty) { snprintf (dft_ttyname, sizeof (dft_ttyname), "%s", env_tty); free (env_tty); } else err = 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 (!err) { if (*dft_ttyname) { rc = add_arg (gpg, "--ttyname"); if (!rc) rc = add_arg (gpg, dft_ttyname); } else rc = 0; if (!rc) { rc = _gpgme_getenv ("TERM", &dft_ttytype); if (rc) goto leave; if (dft_ttytype) { rc = add_arg (gpg, "--ttytype"); if (!rc) rc = add_arg (gpg, dft_ttytype); } free (dft_ttytype); } if (rc) goto leave; } } rc = gpgme_data_new (&gpg->diagnostics); if (rc) goto leave; rc = add_arg (gpg, "--logger-fd"); if (rc) goto leave; rc = add_data (gpg, gpg->diagnostics, -2, 1); leave: if (rc) gpg_release (gpg); else *engine = gpg; return rc; } /* Copy flags from CTX into the engine object. */ static void gpg_set_engine_flags (void *engine, const gpgme_ctx_t ctx) { engine_gpg_t gpg = engine; if (ctx->request_origin && have_gpg_version (gpg, "2.2.6")) { if (strlen (ctx->request_origin) + 1 > sizeof gpg->request_origin) strcpy (gpg->request_origin, "xxx"); /* Too long - force error */ else strcpy (gpg->request_origin, ctx->request_origin); } else *gpg->request_origin = 0; if (ctx->auto_key_locate && have_gpg_version (gpg, "2.1.18")) { if (gpg->auto_key_locate) free (gpg->auto_key_locate); gpg->auto_key_locate = _gpgme_strconcat ("--auto-key-locate=", ctx->auto_key_locate, NULL); } if (ctx->trust_model && strlen (ctx->trust_model)) { if (gpg->trust_model) free (gpg->trust_model); gpg->trust_model = _gpgme_strconcat ("--trust-model=", ctx->trust_model, NULL); } gpg->flags.no_symkey_cache = (ctx->no_symkey_cache && have_gpg_version (gpg, "2.2.7")); gpg->flags.offline = (ctx->offline && have_gpg_version (gpg, "2.1.23")); gpg->flags.ignore_mdc_error = !!ctx->ignore_mdc_error; } static gpgme_error_t gpg_set_locale (void *engine, int category, const char *value) { engine_gpg_t gpg = engine; if (0) ; #ifdef LC_CTYPE else if (category == LC_CTYPE) { if (gpg->lc_ctype) { free (gpg->lc_ctype); gpg->lc_ctype = NULL; } if (value) { gpg->lc_ctype = strdup (value); if (!gpg->lc_ctype) return gpg_error_from_syserror (); } } #endif #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { if (gpg->lc_messages) { free (gpg->lc_messages); gpg->lc_messages = NULL; } if (value) { gpg->lc_messages = strdup (value); if (!gpg->lc_messages) return gpg_error_from_syserror (); } } #endif /* LC_MESSAGES */ else return gpg_error (GPG_ERR_INV_VALUE); return 0; } /* This sets a status callback for monitoring status lines before they * are passed to a caller set handler. */ static void gpg_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value) { engine_gpg_t gpg = engine; gpg->status.mon_cb = cb; gpg->status.mon_cb_value = cb_value; } /* Note, that the status_handler is allowed to modify the args value. */ static void gpg_set_status_handler (void *engine, engine_status_handler_t fnc, void *fnc_value) { engine_gpg_t gpg = engine; gpg->status.fnc = fnc; gpg->status.fnc_value = fnc_value; } /* Kludge to process --with-colon output. */ static gpgme_error_t gpg_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc, void *fnc_value) { engine_gpg_t gpg = engine; gpg->colon.bufsize = 1024; gpg->colon.readpos = 0; gpg->colon.buffer = malloc (gpg->colon.bufsize); if (!gpg->colon.buffer) return gpg_error_from_syserror (); if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1) { int saved_err = gpg_error_from_syserror (); free (gpg->colon.buffer); gpg->colon.buffer = NULL; return saved_err; } - if (_gpgme_io_set_close_notify (gpg->colon.fd[0], close_notify_handler, gpg) - || _gpgme_io_set_close_notify (gpg->colon.fd[1], - close_notify_handler, gpg)) + if (_gpgme_fdtable_add_close_notify (gpg->colon.fd[0], + close_notify_handler, gpg) + || _gpgme_fdtable_add_close_notify (gpg->colon.fd[1], + close_notify_handler, gpg)) return gpg_error (GPG_ERR_GENERAL); gpg->colon.eof = 0; gpg->colon.fnc = fnc; gpg->colon.fnc_value = fnc_value; return 0; } static gpgme_error_t command_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_gpg_t gpg = (engine_gpg_t) data->handler_value; gpgme_error_t err; int processed = 0; assert (gpg->cmd.used); assert (gpg->cmd.code); assert (gpg->cmd.fnc); err = gpg->cmd.fnc (gpg->cmd.fnc_value, gpg->cmd.code, gpg->cmd.keyword, fd, &processed); gpg->cmd.code = 0; /* And sleep again until read_status will wake us up again. */ /* XXX We must check if there are any more fds active after removing this one. */ (*gpg->io_cbs.remove) (gpg->fd_data_map[gpg->cmd.idx].tag); gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd; gpg->fd_data_map[gpg->cmd.idx].fd = -1; if (err) return err; /* We always need to send at least a newline character. */ if (!processed) _gpgme_io_write (fd, "\n", 1); return 0; } /* The FNC will be called to get a value for one of the commands with * a key KEY. If the code passed to FNC is 0, the function may * release resources associated with the returned value from another * call. To match such a second call to a first call, the returned * value from the first call is passed as keyword. */ static gpgme_error_t gpg_set_command_handler (void *engine, engine_command_handler_t fnc, void *fnc_value) { engine_gpg_t gpg = engine; gpgme_error_t rc; rc = add_arg (gpg, "--command-fd"); if (rc) return rc; /* This is a hack. We don't have a real data object. The only thing that matters is that we use something unique, so we use the address of the cmd structure in the gpg object. */ rc = add_data (gpg, (void *) &gpg->cmd, -2, 0); if (rc) return rc; gpg->cmd.fnc = fnc; gpg->cmd.cb_data = (void *) &gpg->cmd; gpg->cmd.fnc_value = fnc_value; gpg->cmd.used = 1; return 0; } static gpgme_error_t build_argv (engine_gpg_t gpg, const char *pgmname) { gpgme_error_t err; struct arg_and_data_s *a; struct fd_data_map_s *fd_data_map; size_t datac=0, argc=0; char **argv; int need_special = 0; int use_agent = 0; char *p; if (_gpgme_in_gpg_one_mode ()) { /* In GnuPG-1 mode we don't want to use the agent with a malformed environment variable. This is only a very basic test but sufficient to make our life in the regression tests easier. With GnuPG-2 the agent is anyway required and on modern installations GPG_AGENT_INFO is optional. */ err = _gpgme_getenv ("GPG_AGENT_INFO", &p); if (err) return err; use_agent = (p && strchr (p, ':')); if (p) free (p); } if (gpg->argv) { free_argv (gpg->argv); gpg->argv = NULL; } if (gpg->fd_data_map) { free_fd_data_map (gpg->fd_data_map); gpg->fd_data_map = NULL; } argc++; /* For argv[0]. */ for (a = gpg->arglist; a; a = a->next) { argc++; if (a->data) { /*fprintf (stderr, "build_argv: data\n" );*/ datac++; if (a->dup_to == -1 && !a->print_fd) need_special = 1; } else { /* fprintf (stderr, "build_argv: arg=`%s'\n", a->arg );*/ } } if (need_special) argc++; if (use_agent) argc++; if (gpg->pinentry_mode) argc++; if (!gpg->cmd.used) argc++; /* --batch */ argc += 4; /* --no-sk-comments, --request-origin, --no-symkey-cache */ /* --disable-dirmngr */ argv = calloc (argc + 1, sizeof *argv); if (!argv) return gpg_error_from_syserror (); fd_data_map = calloc (datac + 1, sizeof *fd_data_map); if (!fd_data_map) { int saved_err = gpg_error_from_syserror (); free_argv (argv); return saved_err; } argc = datac = 0; argv[argc] = strdup (_gpgme_get_basename (pgmname)); /* argv[0] */ if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; if (need_special) { argv[argc] = strdup ("--enable-special-filenames"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (use_agent) { argv[argc] = strdup ("--use-agent"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (*gpg->request_origin) { argv[argc] = _gpgme_strconcat ("--request-origin=", gpg->request_origin, NULL); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->auto_key_locate) { argv[argc] = strdup (gpg->auto_key_locate); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->trust_model) { argv[argc] = strdup (gpg->trust_model); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->flags.no_symkey_cache) { argv[argc] = strdup ("--no-symkey-cache"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->flags.ignore_mdc_error) { argv[argc] = strdup ("--ignore-mdc-error"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->flags.offline) { argv[argc] = strdup ("--disable-dirmngr"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } if (gpg->pinentry_mode && have_gpg_version (gpg, "2.1.0")) { const char *s = NULL; switch (gpg->pinentry_mode) { case GPGME_PINENTRY_MODE_DEFAULT: break; case GPGME_PINENTRY_MODE_ASK: s = "--pinentry-mode=ask"; break; case GPGME_PINENTRY_MODE_CANCEL: s = "--pinentry-mode=cancel"; break; case GPGME_PINENTRY_MODE_ERROR: s = "--pinentry-mode=error"; break; case GPGME_PINENTRY_MODE_LOOPBACK:s = "--pinentry-mode=loopback"; break; } if (s) { argv[argc] = strdup (s); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } } if (!gpg->cmd.used) { argv[argc] = strdup ("--batch"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } argv[argc] = strdup ("--no-sk-comments"); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; for (a = gpg->arglist; a; a = a->next) { if (a->arg_locp) *(a->arg_locp) = argc; if (a->data) { /* Create a pipe to pass it down to gpg. */ fd_data_map[datac].inbound = a->inbound; /* Create a pipe. */ { int fds[2]; if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0) == -1) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } - if (_gpgme_io_set_close_notify (fds[0], - close_notify_handler, gpg) - || _gpgme_io_set_close_notify (fds[1], - close_notify_handler, - gpg)) + if (_gpgme_fdtable_add_close_notify (fds[0], + close_notify_handler, gpg) + || _gpgme_fdtable_add_close_notify (fds[1], + close_notify_handler, + gpg)) { /* We leak fd_data_map and the fds. This is not easy to avoid and given that we reach this here only after a malloc failure for a small object, it is probably better not to do anything. */ return gpg_error (GPG_ERR_GENERAL); } /* If the data_type is FD, we have to do a dup2 here. */ if (fd_data_map[datac].inbound) { fd_data_map[datac].fd = fds[0]; fd_data_map[datac].peer_fd = fds[1]; } else { fd_data_map[datac].fd = fds[1]; fd_data_map[datac].peer_fd = fds[0]; } } /* Hack to get hands on the fd later. */ if (gpg->cmd.used) { if (gpg->cmd.cb_data == a->data) { assert (gpg->cmd.idx == -1); gpg->cmd.idx = datac; } } fd_data_map[datac].data = a->data; fd_data_map[datac].dup_to = a->dup_to; if (a->dup_to == -1) { char *ptr; int buflen = 25; argv[argc] = malloc (buflen); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } ptr = argv[argc]; if (!a->print_fd) { *(ptr++) = '-'; *(ptr++) = '&'; buflen -= 2; } _gpgme_io_fd2str (ptr, buflen, fd_data_map[datac].peer_fd); fd_data_map[datac].arg_loc = argc; argc++; } datac++; } else { argv[argc] = strdup (a->arg); if (!argv[argc]) { int saved_err = gpg_error_from_syserror (); free (fd_data_map); free_argv (argv); return saved_err; } argc++; } } gpg->argv = argv; gpg->fd_data_map = fd_data_map; return 0; } static gpgme_error_t add_io_cb (engine_gpg_t gpg, int fd, int dir, gpgme_io_cb_t handler, void *data, void **tag) { gpgme_error_t err; err = (*gpg->io_cbs.add) (gpg->io_cbs.add_priv, fd, dir, handler, data, tag); if (err) return err; if (!dir) /* FIXME Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (fd); return err; } /* Handle the status output of GnuPG. This function does read entire lines and passes them as C strings to the callback function (we can use C Strings because the status output is always UTF-8 encoded). Of course we have to buffer the lines to cope with long lines e.g. with a large user ID. Note: We can optimize this to only cope with status line code we know about and skip all other stuff without buffering (i.e. without extending the buffer). */ static gpgme_error_t read_status (engine_gpg_t gpg) { char *p; int nread; size_t bufsize = gpg->status.bufsize; char *buffer = gpg->status.buffer; size_t readpos = gpg->status.readpos; gpgme_error_t err; assert (buffer); if (bufsize - readpos < 256) { /* Need more room for the read. */ bufsize += 1024; buffer = realloc (buffer, bufsize); if (!buffer) return gpg_error_from_syserror (); } nread = _gpgme_io_read (gpg->status.fd[0], buffer + readpos, bufsize-readpos); if (nread == -1) return gpg_error_from_syserror (); if (!nread) { err = 0; gpg->status.eof = 1; if (gpg->status.mon_cb) err = gpg->status.mon_cb (gpg->status.mon_cb_value, "", ""); if (gpg->status.fnc) { char emptystring[1] = {0}; err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, emptystring); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } return err; } while (nread > 0) { for (p = buffer + readpos; nread; nread--, p++) { if (*p == '\n') { /* (we require that the last line is terminated by a LF) */ if (p > buffer && p[-1] == '\r') p[-1] = 0; *p = 0; if (!strncmp (buffer, "[GNUPG:] ", 9) && buffer[9] >= 'A' && buffer[9] <= 'Z') { char *rest; gpgme_status_code_t r; rest = strchr (buffer + 9, ' '); if (!rest) rest = p; /* Set to an empty string. */ else *rest++ = 0; r = _gpgme_parse_status (buffer + 9); if (gpg->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 = gpg->status.mon_cb (gpg->status.mon_cb_value, buffer + 9, rest); if (err) return err; } if (r >= 0) { if (gpg->cmd.used && (r == GPGME_STATUS_GET_BOOL || r == GPGME_STATUS_GET_LINE || r == GPGME_STATUS_GET_HIDDEN)) { gpg->cmd.code = r; if (gpg->cmd.keyword) free (gpg->cmd.keyword); gpg->cmd.keyword = strdup (rest); if (!gpg->cmd.keyword) return gpg_error_from_syserror (); /* This should be the last thing we have received and the next thing will be that the command handler does its action. */ if (nread > 1) TRACE (DEBUG_CTX, "gpgme:read_status", 0, "error: unexpected data"); add_io_cb (gpg, gpg->cmd.fd, 0, command_handler, gpg, &gpg->fd_data_map[gpg->cmd.idx].tag); gpg->fd_data_map[gpg->cmd.idx].fd = gpg->cmd.fd; gpg->cmd.fd = -1; } else if (gpg->status.fnc) { err = gpg->status.fnc (gpg->status.fnc_value, r, rest); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ if (err) return err; } } } /* To reuse the buffer for the next line we have to shift the remaining data to the buffer start and restart the loop Hmmm: We can optimize this function by looking forward in the buffer to see whether a second complete line is available and in this case avoid the memmove for this line. */ nread--; p++; if (nread) memmove (buffer, p, nread); readpos = 0; break; /* the for loop */ } else readpos++; } } /* Update the gpg object. */ gpg->status.bufsize = bufsize; gpg->status.buffer = buffer; gpg->status.readpos = readpos; return 0; } static gpgme_error_t status_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_gpg_t gpg = (engine_gpg_t) data->handler_value; int err; assert (fd == gpg->status.fd[0]); err = read_status (gpg); if (err) return err; if (gpg->status.eof) _gpgme_io_close (fd); return 0; } static gpgme_error_t read_colon_line (engine_gpg_t gpg) { char *p; int nread; size_t bufsize = gpg->colon.bufsize; char *buffer = gpg->colon.buffer; size_t readpos = gpg->colon.readpos; assert (buffer); if (bufsize - readpos < 256) { /* Need more room for the read. */ bufsize += 1024; buffer = realloc (buffer, bufsize); if (!buffer) return gpg_error_from_syserror (); } nread = _gpgme_io_read (gpg->colon.fd[0], buffer+readpos, bufsize-readpos); if (nread == -1) return gpg_error_from_syserror (); if (!nread) { gpg->colon.eof = 1; assert (gpg->colon.fnc); gpg->colon.fnc (gpg->colon.fnc_value, NULL); return 0; } while (nread > 0) { for (p = buffer + readpos; nread; nread--, p++) { if ( *p == '\n' ) { /* (we require that the last line is terminated by a LF) and we skip empty lines. Note: we use UTF8 encoding and escaping of special characters. We require at least one colon to cope with some other printed information. */ *p = 0; if (*buffer && strchr (buffer, ':')) { char *line = NULL; if (gpg->colon.preprocess_fnc) { gpgme_error_t err; err = gpg->colon.preprocess_fnc (buffer, &line); if (err) return err; } assert (gpg->colon.fnc); if (line) { char *linep = line; char *endp; do { endp = strchr (linep, '\n'); if (endp) *endp++ = 0; gpg->colon.fnc (gpg->colon.fnc_value, linep); linep = endp; } while (linep && *linep); gpgrt_free (line); } else gpg->colon.fnc (gpg->colon.fnc_value, buffer); } /* To reuse the buffer for the next line we have to shift the remaining data to the buffer start and restart the loop Hmmm: We can optimize this function by looking forward in the buffer to see whether a second complete line is available and in this case avoid the memmove for this line. */ nread--; p++; if (nread) memmove (buffer, p, nread); readpos = 0; break; /* The for loop. */ } else readpos++; } } /* Update the gpg object. */ gpg->colon.bufsize = bufsize; gpg->colon.buffer = buffer; gpg->colon.readpos = readpos; return 0; } /* This colonline handler thing is not the clean way to do it. It might be better to enhance the gpgme_data_t object to act as a wrapper for a callback. Same goes for the status thing. For now we use this thing here because it is easier to implement. */ static gpgme_error_t colon_line_handler (void *opaque, int fd) { struct io_cb_data *data = (struct io_cb_data *) opaque; engine_gpg_t gpg = (engine_gpg_t) data->handler_value; gpgme_error_t rc = 0; assert (fd == gpg->colon.fd[0]); rc = read_colon_line (gpg); if (rc) return rc; if (gpg->colon.eof) _gpgme_io_close (fd); return 0; } static gpgme_error_t start (engine_gpg_t gpg) { gpgme_error_t rc; int i, n; int status; struct spawn_fd_item_s *fd_list; pid_t pid; const char *pgmname; if (!gpg) return gpg_error (GPG_ERR_INV_VALUE); if (!gpg->file_name && !_gpgme_get_default_gpg_name ()) return trace_gpg_error (GPG_ERR_INV_ENGINE); if (gpg->lc_ctype) { rc = add_arg_ext (gpg, gpg->lc_ctype, 1); if (!rc) rc = add_arg_ext (gpg, "--lc-ctype", 1); if (rc) return rc; } if (gpg->lc_messages) { rc = add_arg_ext (gpg, gpg->lc_messages, 1); if (!rc) rc = add_arg_ext (gpg, "--lc-messages", 1); if (rc) return rc; } pgmname = gpg->file_name ? gpg->file_name : _gpgme_get_default_gpg_name (); rc = build_argv (gpg, pgmname); if (rc) return rc; /* status_fd, colon_fd and end of list. */ n = 3; for (i = 0; gpg->fd_data_map[i].data; i++) n++; fd_list = calloc (n, sizeof *fd_list); if (! fd_list) return gpg_error_from_syserror (); /* Build the fd list for the child. */ n = 0; fd_list[n].fd = gpg->status.fd[1]; fd_list[n].dup_to = -1; fd_list[n].arg_loc = gpg->status.arg_loc; n++; if (gpg->colon.fnc) { fd_list[n].fd = gpg->colon.fd[1]; fd_list[n].dup_to = 1; n++; } for (i = 0; gpg->fd_data_map[i].data; i++) { fd_list[n].fd = gpg->fd_data_map[i].peer_fd; fd_list[n].dup_to = gpg->fd_data_map[i].dup_to; fd_list[n].arg_loc = gpg->fd_data_map[i].arg_loc; n++; } fd_list[n].fd = -1; fd_list[n].dup_to = -1; status = _gpgme_io_spawn (pgmname, gpg->argv, (IOSPAWN_FLAG_DETACHED |IOSPAWN_FLAG_ALLOW_SET_FG), fd_list, NULL, NULL, &pid); { int saved_err = gpg_error_from_syserror (); free (fd_list); if (status == -1) return saved_err; } /*_gpgme_register_term_handler ( closure, closure_value, pid );*/ rc = add_io_cb (gpg, gpg->status.fd[0], 1, status_handler, gpg, &gpg->status.tag); if (rc) /* FIXME: kill the child */ return rc; if (gpg->colon.fnc) { assert (gpg->colon.fd[0] != -1); rc = add_io_cb (gpg, gpg->colon.fd[0], 1, colon_line_handler, gpg, &gpg->colon.tag); if (rc) /* FIXME: kill the child */ return rc; } for (i = 0; gpg->fd_data_map[i].data; i++) { if (gpg->cmd.used && i == gpg->cmd.idx) { /* Park the cmd fd. */ gpg->cmd.fd = gpg->fd_data_map[i].fd; gpg->fd_data_map[i].fd = -1; } else { rc = add_io_cb (gpg, gpg->fd_data_map[i].fd, gpg->fd_data_map[i].inbound, gpg->fd_data_map[i].inbound ? _gpgme_data_inbound_handler : _gpgme_data_outbound_handler, gpg->fd_data_map[i].data, &gpg->fd_data_map[i].tag); if (rc) /* FIXME: kill the child */ return rc; } } gpg_io_event (gpg, GPGME_EVENT_START, NULL); /* fixme: check what data we can release here */ return 0; } /* Add the --input-size-hint option if requested. */ static gpgme_error_t add_input_size_hint (engine_gpg_t gpg, gpgme_data_t data) { gpgme_error_t err; gpgme_off_t value = _gpgme_data_get_size_hint (data); char numbuf[50]; /* Large enough for even 2^128 in base-10. */ char *p; if (!value || !have_gpg_version (gpg, "2.1.15")) return 0; err = add_arg (gpg, "--input-size-hint"); if (!err) { p = numbuf + sizeof numbuf; *--p = 0; do { *--p = '0' + (value % 10); value /= 10; } while (value); err = add_arg (gpg, p); } return err; } static gpgme_error_t gpg_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_gpg_t gpg = engine; gpgme_error_t err; err = add_arg (gpg, "--decrypt"); if (!err && (flags & GPGME_DECRYPT_UNWRAP)) { if (!have_gpg_version (gpg, "2.1.12")) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = add_arg (gpg, "--unwrap"); } if (!err && export_session_key) err = add_arg (gpg, "--show-session-key"); if (!err && auto_key_retrieve) err = add_arg (gpg, "--auto-key-retrieve"); if (!err && override_session_key && *override_session_key) { if (have_gpg_version (gpg, "2.1.16")) { gpgme_data_release (gpg->override_session_key); TRACE (DEBUG_ENGINE, "override", gpg, "seskey='%s' len=%zu\n", override_session_key, strlen (override_session_key)); err = gpgme_data_new_from_mem (&gpg->override_session_key, override_session_key, strlen (override_session_key), 1); if (!err) { /* We add --no-keyring because a keyring is not required * when we are overriding the session key. It would * work without that option but --no-keyring avoids that * gpg return a failure due to a missing key log_error() * diagnostic. --no-keyring is supported since 2.1.14. */ err = add_arg (gpg, "--no-keyring"); if (!err) err = add_arg (gpg, "--override-session-key-fd"); if (!err) err = add_data (gpg, gpg->override_session_key, -2, 0); } } else { /* Using that option may leak the session key via ps(1). */ err = add_arg (gpg, "--override-session-key"); if (!err) err = add_arg (gpg, override_session_key); } } /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--output"); if (!err) err = add_arg (gpg, "-"); if (!err) err = add_data (gpg, plain, 1, 1); if (!err) err = add_input_size_hint (gpg, ciph); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, ciph, -1, 0); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_delete (void *engine, gpgme_key_t key, unsigned int flags) { engine_gpg_t gpg = engine; gpgme_error_t err = 0; int allow_secret = flags & GPGME_DELETE_ALLOW_SECRET; int force = flags & GPGME_DELETE_FORCE; if (force) err = add_arg (gpg, "--yes"); if (!err) err = add_arg (gpg, allow_secret ? "--delete-secret-and-public-key" : "--delete-key"); if (!err) err = add_arg (gpg, "--"); if (!err) { if (!key->subkeys || !key->subkeys->fpr) return gpg_error (GPG_ERR_INV_VALUE); else err = add_arg (gpg, key->subkeys->fpr); } if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_passwd (void *engine, gpgme_key_t key, unsigned int flags) { engine_gpg_t gpg = engine; gpgme_error_t err; (void)flags; if (!key || !key->subkeys || !key->subkeys->fpr) return gpg_error (GPG_ERR_INV_CERT_OBJ); err = add_arg (gpg, "--passwd"); if (!err) err = add_arg (gpg, key->subkeys->fpr); if (!err) err = start (gpg); return err; } static gpgme_error_t append_args_from_signers (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */) { gpgme_error_t err = 0; int i; gpgme_key_t key; for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++) { const char *s = key->subkeys ? key->subkeys->keyid : NULL; if (s) { if (!err) err = add_arg (gpg, "-u"); if (!err) err = add_arg (gpg, s); } gpgme_key_unref (key); if (err) break; } return err; } static gpgme_error_t append_args_from_sender (engine_gpg_t gpg, gpgme_ctx_t ctx) { gpgme_error_t err; if (ctx->sender && have_gpg_version (gpg, "2.1.15")) { err = add_arg (gpg, "--sender"); if (!err) err = add_arg (gpg, ctx->sender); } else err = 0; return err; } static gpgme_error_t append_args_from_sig_notations (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */) { gpgme_error_t err = 0; gpgme_sig_notation_t notation; notation = gpgme_sig_notation_get (ctx); while (!err && notation) { if (notation->name && !(notation->flags & GPGME_SIG_NOTATION_HUMAN_READABLE)) err = gpg_error (GPG_ERR_INV_VALUE); else if (notation->name) { char *arg; /* Maximum space needed is one byte for the "critical" flag, the name, one byte for '=', the value, and a terminating '\0'. */ arg = malloc (1 + notation->name_len + 1 + notation->value_len + 1); if (!arg) err = gpg_error_from_syserror (); if (!err) { char *argp = arg; if (notation->critical) *(argp++) = '!'; memcpy (argp, notation->name, notation->name_len); argp += notation->name_len; *(argp++) = '='; /* We know that notation->name is '\0' terminated. */ strcpy (argp, notation->value); } if (!err) err = add_arg (gpg, "--sig-notation"); if (!err) err = add_arg (gpg, arg); if (arg) free (arg); } else { /* This is a policy URL. */ char *value; if (notation->critical) { value = malloc (1 + notation->value_len + 1); if (!value) err = gpg_error_from_syserror (); else { value[0] = '!'; /* We know that notation->value is '\0' terminated. */ strcpy (&value[1], notation->value); } } else value = notation->value; if (!err) err = add_arg (gpg, "--sig-policy-url"); if (!err) err = add_arg (gpg, value); if (value != notation->value) free (value); } notation = notation->next; } return err; } static gpgme_error_t gpg_edit (void *engine, int type, gpgme_key_t key, gpgme_data_t out, gpgme_ctx_t ctx /* FIXME */) { engine_gpg_t gpg = engine; gpgme_error_t err; err = add_arg (gpg, "--with-colons"); if (!err) err = append_args_from_signers (gpg, ctx); if (!err) err = add_arg (gpg, type == 0 ? "--edit-key" : "--card-edit"); if (!err) err = add_data (gpg, out, 1, 1); if (!err) err = add_arg (gpg, "--"); if (!err && type == 0) { const char *s = key->subkeys ? key->subkeys->fpr : NULL; if (!s) err = gpg_error (GPG_ERR_INV_VALUE); else err = add_arg (gpg, s); } if (!err) err = start (gpg); return err; } /* Add a single argument from a key to an -r option. */ static gpg_error_t add_arg_recipient (engine_gpg_t gpg, gpgme_encrypt_flags_t flags, gpgme_key_t key) { gpg_error_t err; if ((flags & GPGME_ENCRYPT_WANT_ADDRESS)) { /* We have no way to figure out which mail address was * requested. FIXME: It would be possible to figure this out by * consulting the SENDER property of the context. */ err = gpg_error (GPG_ERR_INV_USER_ID); } else err = add_arg (gpg, key->subkeys->fpr); return err; } /* Add a single argument from a USERID string to an -r option. */ static gpg_error_t add_arg_recipient_string (engine_gpg_t gpg, gpgme_encrypt_flags_t flags, const char *userid, int useridlen) { gpg_error_t err; if ((flags & GPGME_ENCRYPT_WANT_ADDRESS)) { char *tmpstr, *mbox; tmpstr = malloc (useridlen + 1); if (!tmpstr) err = gpg_error_from_syserror (); else { memcpy (tmpstr, userid, useridlen); tmpstr[useridlen] = 0; mbox = _gpgme_mailbox_from_userid (tmpstr); if (!mbox) { err = gpg_error_from_syserror (); if (gpg_err_code (err) == GPG_ERR_EINVAL) err = gpg_error (GPG_ERR_INV_USER_ID); } else err = add_arg (gpg, mbox); free (mbox); free (tmpstr); } } else err = add_arg_len (gpg, NULL, userid, useridlen); return err; } static gpgme_error_t append_args_from_recipients (engine_gpg_t gpg, gpgme_encrypt_flags_t flags, gpgme_key_t recp[]) { gpgme_error_t err = 0; int i = 0; while (recp[i]) { if (!recp[i]->subkeys || !recp[i]->subkeys->fpr) err = gpg_error (GPG_ERR_INV_VALUE); if (!err) err = add_arg (gpg, "-r"); if (!err) err = add_arg_recipient (gpg, flags, recp[i]); if (err) break; i++; } return err; } /* Take recipients from the LF delimited STRING and add -r args. */ static gpg_error_t append_args_from_recipients_string (engine_gpg_t gpg, gpgme_encrypt_flags_t flags, const char *string) { gpg_error_t err = 0; gpgme_encrypt_flags_t orig_flags = flags; int any = 0; int ignore = 0; int hidden = 0; int file = 0; const char *s; int n; do { /* Skip leading white space */ while (*string == ' ' || *string == '\t') string++; if (!*string) break; /* Look for the LF. */ 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 == 8 && !memcmp (string, "--hidden", 8)) hidden = 1; else if (!ignore && n == 11 && !memcmp (string, "--no-hidden", 11)) hidden = 0; else if (!ignore && n == 6 && !memcmp (string, "--file", 6)) { file = 1; /* Because the key is used as is we need to ignore this flag: */ flags &= ~GPGME_ENCRYPT_WANT_ADDRESS; } else if (!ignore && n == 9 && !memcmp (string, "--no-file", 9)) { file = 0; flags = orig_flags; } else if (!ignore && n > 2 && !memcmp (string, "--", 2)) err = gpg_error (GPG_ERR_UNKNOWN_OPTION); else if (n) /* Not empty - use it. */ { err = add_arg (gpg, file? (hidden? "-F":"-f") : (hidden? "-R":"-r")); if (!err) err = add_arg_recipient_string (gpg, flags, string, n); if (!err) any = 1; } string += n + !!s; } while (!err); if (!err && !any) err = gpg_error (GPG_ERR_MISSING_KEY); return err; } static gpgme_error_t gpg_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_gpg_t gpg = engine; gpgme_error_t err = 0; if (recp || recpstring) err = add_arg (gpg, "--encrypt"); if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || (!recp && !recpstring))) err = add_arg (gpg, "--symmetric"); if (!err && use_armor) err = add_arg (gpg, "--armor"); if (!err && (flags & GPGME_ENCRYPT_WRAP)) { /* gpg is current not able to detect already compressed * packets. Thus when using * gpg --unwrap -d | gpg --no-literal -e * the encryption would add an additional compression layer. * We better suppress that. */ flags |= GPGME_ENCRYPT_NO_COMPRESS; err = add_arg (gpg, "--no-literal"); } if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS)) err = add_arg (gpg, "--compress-algo=none"); if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS)) err = add_arg (gpg, "--throw-keyids"); if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME && have_gpg_version (gpg, "2.1.14")) err = add_arg (gpg, "--mimemode"); if (recp || recpstring) { /* If we know that all recipients are valid (full or ultimate trust) we can suppress further checks. */ if (!err && (flags & GPGME_ENCRYPT_ALWAYS_TRUST)) err = add_arg (gpg, "--always-trust"); if (!err && (flags & GPGME_ENCRYPT_NO_ENCRYPT_TO)) err = add_arg (gpg, "--no-encrypt-to"); if (!err && !recp && recpstring) err = append_args_from_recipients_string (gpg, flags, recpstring); else if (!err) err = append_args_from_recipients (gpg, flags, recp); } /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--output"); if (!err) err = add_arg (gpg, "-"); if (!err) err = add_data (gpg, ciph, 1, 1); if (gpgme_data_get_file_name (plain)) { if (!err) err = add_arg (gpg, "--set-filename"); if (!err) err = add_arg (gpg, gpgme_data_get_file_name (plain)); } if (!err) err = add_input_size_hint (gpg, plain); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, plain, -1, 0); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_encrypt_sign (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, gpgme_ctx_t ctx /* FIXME */) { engine_gpg_t gpg = engine; gpgme_error_t err = 0; if (recp || recpstring) err = add_arg (gpg, "--encrypt"); if (!err && ((flags & GPGME_ENCRYPT_SYMMETRIC) || (!recp && !recpstring))) err = add_arg (gpg, "--symmetric"); if (!err) err = add_arg (gpg, "--sign"); if (!err && use_armor) err = add_arg (gpg, "--armor"); if (!err && (flags & GPGME_ENCRYPT_NO_COMPRESS)) err = add_arg (gpg, "--compress-algo=none"); if (!err && (flags & GPGME_ENCRYPT_THROW_KEYIDS)) err = add_arg (gpg, "--throw-keyids"); if (gpgme_data_get_encoding (plain) == GPGME_DATA_ENCODING_MIME && have_gpg_version (gpg, "2.1.14")) err = add_arg (gpg, "--mimemode"); if (recp || recpstring) { /* If we know that all recipients are valid (full or ultimate trust) we can suppress further checks. */ if (!err && (flags & GPGME_ENCRYPT_ALWAYS_TRUST)) err = add_arg (gpg, "--always-trust"); if (!err && (flags & GPGME_ENCRYPT_NO_ENCRYPT_TO)) err = add_arg (gpg, "--no-encrypt-to"); if (!err && !recp && recpstring) err = append_args_from_recipients_string (gpg, flags, recpstring); else if (!err) err = append_args_from_recipients (gpg, flags, recp); } if (!err) err = append_args_from_signers (gpg, ctx); if (!err) err = append_args_from_sender (gpg, ctx); if (!err) err = append_args_from_sig_notations (gpg, ctx); /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--output"); if (!err) err = add_arg (gpg, "-"); if (!err) err = add_data (gpg, ciph, 1, 1); if (gpgme_data_get_file_name (plain)) { if (!err) err = add_arg (gpg, "--set-filename"); if (!err) err = add_arg (gpg, gpgme_data_get_file_name (plain)); } if (!err) err = add_input_size_hint (gpg, plain); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, plain, -1, 0); if (!err) err = start (gpg); return err; } static gpgme_error_t export_common (engine_gpg_t gpg, gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { gpgme_error_t err = 0; if ((mode & ~(GPGME_EXPORT_MODE_EXTERN |GPGME_EXPORT_MODE_MINIMAL |GPGME_EXPORT_MODE_SECRET))) return gpg_error (GPG_ERR_NOT_SUPPORTED); if ((mode & GPGME_EXPORT_MODE_MINIMAL)) { if ((mode & GPGME_EXPORT_MODE_NOUID)) err = add_arg (gpg, "--export-options=export-minimal,export-drop-uids"); else err = add_arg (gpg, "--export-options=export-minimal"); } else if ((mode & GPGME_EXPORT_MODE_NOUID)) err = add_arg (gpg, "--export-options=export-drop-uids"); if (err) ; else if ((mode & GPGME_EXPORT_MODE_EXTERN)) { err = add_arg (gpg, "--send-keys"); if (!err && (mode & GPGME_EXPORT_MODE_NOUID)) err = add_arg (gpg, "--keyserver-options=export-drop-uids"); } else { if ((mode & GPGME_EXPORT_MODE_SECRET)) err = add_arg (gpg, "--export-secret-keys"); else err = add_arg (gpg, "--export"); if (!err && use_armor) err = add_arg (gpg, "--armor"); if (!err) err = add_data (gpg, keydata, 1, 1); } if (!err) err = add_arg (gpg, "--"); return err; } static gpgme_error_t gpg_export (void *engine, const char *pattern, gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { engine_gpg_t gpg = engine; gpgme_error_t err; err = export_common (gpg, mode, keydata, use_armor); if (!err && pattern && *pattern) err = add_arg (gpg, pattern); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_export_ext (void *engine, const char *pattern[], gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { engine_gpg_t gpg = engine; gpgme_error_t err; err = export_common (gpg, mode, keydata, use_armor); if (pattern) { while (!err && *pattern && **pattern) err = add_arg (gpg, *(pattern++)); } if (!err) err = start (gpg); return err; } /* Helper to add algo, usage, and expire to the list of args. */ static gpgme_error_t gpg_add_algo_usage_expire (engine_gpg_t gpg, const char *algo, unsigned long expires, unsigned int flags) { gpg_error_t err; /* This condition is only required to allow the use of gpg < 2.1.16 */ if (algo || (flags & (GPGME_CREATE_SIGN | GPGME_CREATE_ENCR | GPGME_CREATE_CERT | GPGME_CREATE_AUTH | GPGME_CREATE_NOEXPIRE)) || expires) { err = add_arg (gpg, algo? algo : "default"); if (!err) { char tmpbuf[5*4+1]; snprintf (tmpbuf, sizeof tmpbuf, "%s%s%s%s", (flags & GPGME_CREATE_SIGN)? " sign":"", (flags & GPGME_CREATE_ENCR)? " encr":"", (flags & GPGME_CREATE_CERT)? " cert":"", (flags & GPGME_CREATE_AUTH)? " auth":""); err = add_arg (gpg, *tmpbuf? tmpbuf : "default"); } if (!err) { if ((flags & GPGME_CREATE_NOEXPIRE)) err = add_arg (gpg, "never"); else if (expires == 0) err = add_arg (gpg, "-"); else { char tmpbuf[8+20]; snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expires); err = add_arg (gpg, tmpbuf); } } } else err = 0; return err; } static gpgme_error_t gpg_createkey_from_param (engine_gpg_t gpg, gpgme_data_t help_data, unsigned int extraflags) { gpgme_error_t err; err = add_arg (gpg, "--gen-key"); if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) err = add_arg (gpg, "--armor"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, help_data, -1, 0); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_createkey (engine_gpg_t gpg, const char *userid, const char *algo, unsigned long expires, unsigned int flags, unsigned int extraflags) { gpgme_error_t err; err = add_arg (gpg, "--quick-gen-key"); if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) err = add_arg (gpg, "--armor"); if (!err && (flags & GPGME_CREATE_NOPASSWD)) { err = add_arg (gpg, "--passphrase"); if (!err) err = add_arg (gpg, ""); if (!err) err = add_arg (gpg, "--batch"); } if (!err && (flags & GPGME_CREATE_FORCE)) err = add_arg (gpg, "--yes"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, userid); if (!err) err = gpg_add_algo_usage_expire (gpg, algo, expires, flags); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_addkey (engine_gpg_t gpg, const char *algo, unsigned long expires, gpgme_key_t key, unsigned int flags, unsigned int extraflags) { gpgme_error_t err; if (!key || !key->fpr) return gpg_error (GPG_ERR_INV_ARG); err = add_arg (gpg, "--quick-addkey"); if (!err && (extraflags & GENKEY_EXTRAFLAG_ARMOR)) err = add_arg (gpg, "--armor"); if (!err && (flags & GPGME_CREATE_NOPASSWD)) { err = add_arg (gpg, "--passphrase"); if (!err) err = add_arg (gpg, ""); if (!err) err = add_arg (gpg, "--batch"); } if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, key->fpr); if (!err) err = gpg_add_algo_usage_expire (gpg, algo, expires, flags); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_adduid (engine_gpg_t gpg, gpgme_key_t key, const char *userid, unsigned int extraflags) { gpgme_error_t err; if (!key || !key->fpr || !userid) return gpg_error (GPG_ERR_INV_ARG); if ((extraflags & GENKEY_EXTRAFLAG_SETPRIMARY)) { if (!have_gpg_version (gpg, "2.1.20")) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else err = add_arg (gpg, "--quick-set-primary-uid"); } else if ((extraflags & GENKEY_EXTRAFLAG_REVOKE)) err = add_arg (gpg, "--quick-revuid"); else err = add_arg (gpg, "--quick-adduid"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, key->fpr); if (!err) err = add_arg (gpg, userid); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_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_gpg_t gpg = engine; gpgme_error_t err; (void)reserved; if (!gpg) return gpg_error (GPG_ERR_INV_VALUE); /* If HELP_DATA is given the use of the old interface * (gpgme_op_genkey) has been requested. The other modes are: * * USERID && !KEY - Create a new keyblock. * !USERID && KEY - Add a new subkey to KEY (gpg >= 2.1.14) * USERID && KEY && !ALGO - Add a new user id to KEY (gpg >= 2.1.14). * or set a flag on a user id. */ if (help_data) { /* We need a special mechanism to get the fd of a pipe here, so that we can use this for the %pubring and %secring parameters. We don't have this yet, so we implement only the adding to the standard keyrings. */ if (pubkey || seckey) err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); else err = gpg_createkey_from_param (gpg, help_data, extraflags); } else if (!have_gpg_version (gpg, "2.1.13")) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else if (userid && !key) err = gpg_createkey (gpg, userid, algo, expires, flags, extraflags); else if (!userid && key) err = gpg_addkey (gpg, algo, expires, key, flags, extraflags); else if (userid && key && !algo) err = gpg_adduid (gpg, key, userid, extraflags); else err = gpg_error (GPG_ERR_INV_VALUE); return err; } /* Return the next DELIM delimited string from DATA as a C-string. The caller needs to provide the address of a pointer variable which he has to set to NULL before the first call. After the last call to this function, this function needs to be called once more with DATA set to NULL so that the function can release its internal state. After that the pointer variable is free for use again. Note that we use a delimiter and thus a trailing delimiter is not required. DELIM may not be changed after the first call. */ static const char * string_from_data (gpgme_data_t data, int delim, void **helpptr, gpgme_error_t *r_err) { #define MYBUFLEN 2000 /* Fixme: We don't support URLs longer than that. */ struct { int eof_seen; int nbytes; /* Length of the last returned string including the delimiter. */ int buflen; /* Valid length of BUF. */ char buf[MYBUFLEN+1]; /* Buffer with one byte extra space. */ } *self; char *p; int nread; *r_err = 0; if (!data) { if (*helpptr) { free (*helpptr); *helpptr = NULL; } return NULL; } if (*helpptr) self = *helpptr; else { self = malloc (sizeof *self); if (!self) { *r_err = gpg_error_from_syserror (); return NULL; } *helpptr = self; self->eof_seen = 0; self->nbytes = 0; self->buflen = 0; } if (self->eof_seen) return NULL; assert (self->nbytes <= self->buflen); memmove (self->buf, self->buf + self->nbytes, self->buflen - self->nbytes); self->buflen -= self->nbytes; self->nbytes = 0; do { /* Fixme: This is fairly infective scanning because we may scan the buffer several times. */ p = memchr (self->buf, delim, self->buflen); if (p) { *p = 0; self->nbytes = p - self->buf + 1; return self->buf; } if ( !(MYBUFLEN - self->buflen) ) { /* Not enough space - URL too long. */ *r_err = gpg_error (GPG_ERR_TOO_LARGE); return NULL; } nread = gpgme_data_read (data, self->buf + self->buflen, MYBUFLEN - self->buflen); if (nread < 0) { *r_err = gpg_error_from_syserror (); return NULL; } self->buflen += nread; } while (nread); /* EOF reached. If we have anything in the buffer, append a Nul and return it. */ self->eof_seen = 1; if (self->buflen) { self->buf[self->buflen] = 0; /* (we allocated one extra byte) */ return self->buf; } return NULL; #undef MYBUFLEN } static gpgme_error_t gpg_import (void *engine, gpgme_data_t keydata, gpgme_key_t *keyarray) { engine_gpg_t gpg = engine; gpgme_error_t err; int idx; gpgme_data_encoding_t dataenc; if (keydata && keyarray) return gpg_error (GPG_ERR_INV_VALUE); /* Only one is allowed. */ dataenc = gpgme_data_get_encoding (keydata); if (keyarray) { err = add_arg (gpg, "--recv-keys"); if (!err) err = add_arg (gpg, "--"); for (idx=0; !err && keyarray[idx]; idx++) { if (keyarray[idx]->protocol != GPGME_PROTOCOL_OpenPGP) ; else if (!keyarray[idx]->subkeys) ; else if (keyarray[idx]->subkeys->fpr && *keyarray[idx]->subkeys->fpr) err = add_arg (gpg, keyarray[idx]->subkeys->fpr); else if (*keyarray[idx]->subkeys->keyid) err = add_arg (gpg, keyarray[idx]->subkeys->keyid); } } else if (dataenc == GPGME_DATA_ENCODING_URL || dataenc == GPGME_DATA_ENCODING_URL0) { void *helpptr; const char *string; gpgme_error_t xerr; int delim = (dataenc == GPGME_DATA_ENCODING_URL)? '\n': 0; /* FIXME: --fetch-keys is probably not correct because it can't grok all kinds of URLs. On Unix it should just work but on Windows we will build the command line and that may fail for some embedded control characters. It is anyway limited to the maximum size of the command line. We need another command which can take its input from a file. Maybe we should use an option to gpg to modify such commands (ala --multifile). */ err = add_arg (gpg, "--fetch-keys"); if (!err) err = add_arg (gpg, "--"); helpptr = NULL; while (!err && (string = string_from_data (keydata, delim, &helpptr, &xerr))) err = add_arg (gpg, string); if (!err) err = xerr; string_from_data (NULL, delim, &helpptr, &xerr); } else if (dataenc == GPGME_DATA_ENCODING_URLESC) { /* Already escaped URLs are not yet supported. */ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); } else { err = add_arg (gpg, "--import"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, keydata, -1, 0); } if (!err) err = start (gpg); return err; } /* The output for external keylistings in GnuPG is different from all the other key listings. We catch this here with a special preprocessor that reformats the colon handler lines. */ static gpgme_error_t gpg_keylist_preprocess (char *line, char **r_line) { enum { RT_NONE, RT_INFO, RT_PUB, RT_UID } rectype = RT_NONE; #define NR_FIELDS 16 char *field[NR_FIELDS]; int fields = 0; size_t n; *r_line = NULL; while (line && fields < NR_FIELDS) { field[fields++] = line; line = strchr (line, ':'); if (line) *(line++) = '\0'; } if (!strcmp (field[0], "info")) rectype = RT_INFO; else if (!strcmp (field[0], "pub")) rectype = RT_PUB; else if (!strcmp (field[0], "uid")) rectype = RT_UID; else rectype = RT_NONE; switch (rectype) { case RT_INFO: /* FIXME: Eventually, check the version number at least. */ return 0; case RT_PUB: if (fields < 7) return 0; /* The format is: pub:::::: as defined in 5.2. Machine Readable Indexes of the OpenPGP HTTP Keyserver Protocol (draft). Modern versions of the SKS keyserver return the fingerprint instead of the keyid. We detect this here and use the v4 fingerprint format to convert it to a key id. We want: pub:o::::::::::::: */ n = strlen (field[1]); if (n > 16) { if (gpgrt_asprintf (r_line, "pub:o%s:%s:%s:%s:%s:%s::::::::\n" "fpr:::::::::%s:", field[6], field[3], field[2], field[1] + n - 16, field[4], field[5], field[1]) < 0) return gpg_error_from_syserror (); } else { if (gpgrt_asprintf (r_line, "pub:o%s:%s:%s:%s:%s:%s::::::::", field[6], field[3], field[2], field[1], field[4], field[5]) < 0) return gpg_error_from_syserror (); } return 0; case RT_UID: /* The format is: uid:::: as defined in 5.2. Machine Readable Indexes of the OpenPGP HTTP Keyserver Protocol (draft). For an ldap keyserver the format is: uid: We want: uid:o::::::::: */ { /* The user ID is percent escaped, but we want c-coded. Because we have to replace each '%HL' by '\xHL', we need at most 4/3 th the number of bytes. But because we also need to escape the backslashes we allocate twice as much. */ char *uid = malloc (2 * strlen (field[1]) + 1); char *src; char *dst; if (! uid) return gpg_error_from_syserror (); src = field[1]; dst = uid; while (*src) { if (*src == '%') { *(dst++) = '\\'; *(dst++) = 'x'; src++; /* Copy the next two bytes unconditionally. */ if (*src) *(dst++) = *(src++); if (*src) *(dst++) = *(src++); } else if (*src == '\\') { *dst++ = '\\'; *dst++ = '\\'; src++; } else *(dst++) = *(src++); } *dst = '\0'; if (fields < 4) { if (gpgrt_asprintf (r_line, "uid:o::::::::%s:", uid) < 0) return gpg_error_from_syserror (); } else { if (gpgrt_asprintf (r_line, "uid:o%s::::%s:%s:::%s:", field[4], field[2], field[3], uid) < 0) return gpg_error_from_syserror (); } } return 0; case RT_NONE: /* Unknown record. */ break; } return 0; } static gpg_error_t gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, gpgme_keylist_mode_t mode) { gpg_error_t err; err = add_arg (gpg, "--with-colons"); /* Since gpg 2.1.15 fingerprints are always printed, thus there is * no more need to explicitly request them. */ if (!have_gpg_version (gpg, "2.1.15")) { if (!err) err = add_arg (gpg, "--fixed-list-mode"); if (!err) err = add_arg (gpg, "--with-fingerprint"); if (!err) err = add_arg (gpg, "--with-fingerprint"); } if (!err && (mode & GPGME_KEYLIST_MODE_WITH_TOFU) && have_gpg_version (gpg, "2.1.16")) err = add_arg (gpg, "--with-tofu-info"); if (!err && (mode & GPGME_KEYLIST_MODE_WITH_SECRET)) err = add_arg (gpg, "--with-secret"); if (!err && (mode & GPGME_KEYLIST_MODE_SIGS) && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) { err = add_arg (gpg, "--list-options"); if (!err) err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); } if (!err) { if ( (mode & GPGME_KEYLIST_MODE_EXTERN) ) { if (secret_only) err = gpg_error (GPG_ERR_NOT_SUPPORTED); else if ( (mode & GPGME_KEYLIST_MODE_LOCAL)) { /* The local+extern mode is special. It works only with gpg >= 2.0.10. FIXME: We should check that we have such a version to that we can return a proper error code. The problem is that we don't know the context here and thus can't access the cached version number for the engine info structure. */ err = add_arg (gpg, "--locate-keys"); if ((mode & GPGME_KEYLIST_MODE_SIGS)) err = add_arg (gpg, "--with-sig-check"); } else { err = add_arg (gpg, "--search-keys"); gpg->colon.preprocess_fnc = gpg_keylist_preprocess; } } else { err = add_arg (gpg, secret_only ? "--list-secret-keys" : ((mode & GPGME_KEYLIST_MODE_SIGS) ? "--check-sigs" : "--list-keys")); } } if (!err) err = add_arg (gpg, "--"); return err; } static gpgme_error_t gpg_keylist (void *engine, const char *pattern, int secret_only, gpgme_keylist_mode_t mode, int engine_flags) { engine_gpg_t gpg = engine; gpgme_error_t err; (void)engine_flags; err = gpg_keylist_build_options (gpg, secret_only, mode); if (!err && pattern && *pattern) err = add_arg (gpg, pattern); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_keylist_ext (void *engine, const char *pattern[], int secret_only, int reserved, gpgme_keylist_mode_t mode, int engine_flags) { engine_gpg_t gpg = engine; gpgme_error_t err; (void)engine_flags; if (reserved) return gpg_error (GPG_ERR_INV_VALUE); err = gpg_keylist_build_options (gpg, secret_only, mode); if (pattern) { while (!err && *pattern && **pattern) err = add_arg (gpg, *(pattern++)); } if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_keylist_data (void *engine, gpgme_data_t data) { engine_gpg_t gpg = engine; gpgme_error_t err; if (!have_gpg_version (gpg, "2.1.14")) return gpg_error (GPG_ERR_NOT_SUPPORTED); err = add_arg (gpg, "--with-colons"); if (!err) err = add_arg (gpg, "--with-fingerprint"); if (!err) err = add_arg (gpg, "--import-options"); if (!err) err = add_arg (gpg, "import-show"); if (!err) err = add_arg (gpg, "--dry-run"); if (!err) err = add_arg (gpg, "--import"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, data, -1, 0); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_keysign (void *engine, gpgme_key_t key, const char *userid, unsigned long expire, unsigned int flags, gpgme_ctx_t ctx) { engine_gpg_t gpg = engine; gpgme_error_t err; const char *s; if (!key || !key->fpr) return gpg_error (GPG_ERR_INV_ARG); if (!have_gpg_version (gpg, "2.1.12")) return gpg_error (GPG_ERR_NOT_SUPPORTED); if ((flags & GPGME_KEYSIGN_LOCAL)) err = add_arg (gpg, "--quick-lsign-key"); else err = add_arg (gpg, "--quick-sign-key"); if (!err) err = append_args_from_signers (gpg, ctx); /* If an expiration time has been given use that. If none has been * given the default from gpg.conf is used. To make sure not to set * an expiration time at all the flag GPGME_KEYSIGN_NOEXPIRE can be * used. */ if (!err && (expire || (flags & GPGME_KEYSIGN_NOEXPIRE))) { char tmpbuf[8+20]; if ((flags & GPGME_KEYSIGN_NOEXPIRE)) expire = 0; snprintf (tmpbuf, sizeof tmpbuf, "seconds=%lu", expire); err = add_arg (gpg, "--default-cert-expire"); if (!err) err = add_arg (gpg, tmpbuf); } if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, key->fpr); if (!err && userid) { if ((flags & GPGME_KEYSIGN_LFSEP)) { for (; !err && (s = strchr (userid, '\n')); userid = s + 1) if ((s - userid)) err = add_arg_len (gpg, "=", userid, s - userid); if (!err && *userid) err = add_arg_pfx (gpg, "=", userid); } else err = add_arg_pfx (gpg, "=", userid); } if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_tofu_policy (void *engine, gpgme_key_t key, gpgme_tofu_policy_t policy) { engine_gpg_t gpg = engine; gpgme_error_t err; const char *policystr = NULL; if (!key || !key->fpr) return gpg_error (GPG_ERR_INV_ARG); switch (policy) { case GPGME_TOFU_POLICY_NONE: break; case GPGME_TOFU_POLICY_AUTO: policystr = "auto"; break; case GPGME_TOFU_POLICY_GOOD: policystr = "good"; break; case GPGME_TOFU_POLICY_BAD: policystr = "bad"; break; case GPGME_TOFU_POLICY_ASK: policystr = "ask"; break; case GPGME_TOFU_POLICY_UNKNOWN: policystr = "unknown"; break; } if (!policystr) return gpg_error (GPG_ERR_INV_VALUE); if (!have_gpg_version (gpg, "2.1.10")) return gpg_error (GPG_ERR_NOT_SUPPORTED); err = add_arg (gpg, "--tofu-policy"); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, policystr); if (!err) err = add_arg (gpg, key->fpr); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_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_gpg_t gpg = engine; gpgme_error_t err; (void)include_certs; if (mode == GPGME_SIG_MODE_CLEAR) err = add_arg (gpg, "--clearsign"); else { err = add_arg (gpg, "--sign"); if (!err && mode == GPGME_SIG_MODE_DETACH) err = add_arg (gpg, "--detach"); if (!err && use_armor) err = add_arg (gpg, "--armor"); if (!err) { if (gpgme_data_get_encoding (in) == GPGME_DATA_ENCODING_MIME && have_gpg_version (gpg, "2.1.14")) err = add_arg (gpg, "--mimemode"); else if (use_textmode) err = add_arg (gpg, "--textmode"); } } if (!err) err = append_args_from_signers (gpg, ctx); if (!err) err = append_args_from_sender (gpg, ctx); if (!err) err = append_args_from_sig_notations (gpg, ctx); if (gpgme_data_get_file_name (in)) { if (!err) err = add_arg (gpg, "--set-filename"); if (!err) err = add_arg (gpg, gpgme_data_get_file_name (in)); } /* Tell the gpg object about the data. */ if (!err) err = add_input_size_hint (gpg, in); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, in, -1, 0); if (!err) err = add_data (gpg, out, 1, 1); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_trustlist (void *engine, const char *pattern) { engine_gpg_t gpg = engine; gpgme_error_t err; err = add_arg (gpg, "--with-colons"); if (!err) err = add_arg (gpg, "--list-trust-path"); /* Tell the gpg object about the data. */ if (!err) err = add_arg (gpg, "--"); if (!err) err = add_arg (gpg, pattern); if (!err) err = start (gpg); return err; } static gpgme_error_t gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_ctx_t ctx) { engine_gpg_t gpg = engine; gpgme_error_t err; err = append_args_from_sender (gpg, ctx); if (!err && ctx->auto_key_retrieve) err = add_arg (gpg, "--auto-key-retrieve"); if (err) ; else if (plaintext) { /* Normal or cleartext signature. */ err = add_arg (gpg, "--output"); if (!err) err = add_arg (gpg, "-"); if (!err) err = add_input_size_hint (gpg, sig); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, sig, -1, 0); if (!err) err = add_data (gpg, plaintext, 1, 1); } else { err = add_arg (gpg, "--verify"); if (!err) err = add_input_size_hint (gpg, signed_text); if (!err) err = add_arg (gpg, "--"); if (!err) err = add_data (gpg, sig, -1, 0); if (!err && signed_text) err = add_data (gpg, signed_text, -1, 0); } if (!err) err = start (gpg); return err; } static void gpg_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_gpg_t gpg = engine; gpg->io_cbs = *io_cbs; } static gpgme_error_t gpg_set_pinentry_mode (void *engine, gpgme_pinentry_mode_t mode) { engine_gpg_t gpg = engine; gpg->pinentry_mode = mode; return 0; } static gpgme_error_t gpg_getauditlog (void *engine, gpgme_data_t output, unsigned int flags) { engine_gpg_t gpg = engine; #define MYBUFLEN 4096 char buf[MYBUFLEN]; int nread; int any_written = 0; if (!(flags & GPGME_AUDITLOG_DIAG)) { return gpg_error (GPG_ERR_NOT_IMPLEMENTED); } if (!gpg || !output) { return gpg_error (GPG_ERR_INV_VALUE); } if (!gpg->diagnostics) { return gpg_error (GPG_ERR_GENERAL); } gpgme_data_rewind (gpg->diagnostics); while ((nread = gpgme_data_read (gpg->diagnostics, buf, MYBUFLEN)) > 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; #undef MYBUFLEN } struct engine_ops _gpgme_engine_ops_gpg = { /* Static functions. */ _gpgme_get_default_gpg_name, NULL, gpg_get_version, gpg_get_req_version, gpg_new, /* Member functions. */ gpg_release, NULL, /* reset */ gpg_set_status_cb, gpg_set_status_handler, gpg_set_command_handler, gpg_set_colon_line_handler, gpg_set_locale, NULL, /* set_protocol */ gpg_set_engine_flags, /* set_engine_flags */ gpg_decrypt, gpg_delete, gpg_edit, gpg_encrypt, gpg_encrypt_sign, gpg_export, gpg_export_ext, gpg_genkey, gpg_import, gpg_keylist, gpg_keylist_ext, gpg_keylist_data, gpg_keysign, gpg_tofu_policy, /* tofu_policy */ gpg_sign, gpg_trustlist, gpg_verify, gpg_getauditlog, NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ gpg_set_io_cbs, gpg_io_event, gpg_cancel, NULL, /* cancel_op */ gpg_passwd, gpg_set_pinentry_mode, NULL /* opspawn */ }; diff --git a/src/engine-gpgsm.c b/src/engine-gpgsm.c index ae5d8ef1..295703a7 100644 --- a/src/engine-gpgsm.c +++ b/src/engine-gpgsm.c @@ -1,2342 +1,2351 @@ /* 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 void +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_io_set_close_notify (gpgsm->input_cb.fd, - close_notify_handler, gpgsm) - || _gpgme_io_set_close_notify (gpgsm->output_cb.fd, - close_notify_handler, gpgsm) - || _gpgme_io_set_close_notify (gpgsm->message_cb.fd, - close_notify_handler, gpgsm))) + && (_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_io_set_close_notify (gpgsm->diag_cb.fd, - close_notify_handler, gpgsm)) + 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_io_set_close_notify (iocb_data->fd, - close_notify_handler, gpgsm)) + 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, 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, 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, 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, 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, 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, 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) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-gpgsm:add_io_cb", gpgsm, "fd=%d, dir %d", iocbd->fd, iocbd->dir); 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_io_set_close_notify (gpgsm->status_cb.fd, - close_notify_handler, gpgsm)) + 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); if (!err && gpgsm->input_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->input_cb, _gpgme_data_outbound_handler); if (!err && gpgsm->output_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->output_cb, _gpgme_data_inbound_handler); if (!err && gpgsm->message_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->message_cb, _gpgme_data_outbound_handler); if (!err && gpgsm->diag_cb.fd != -1) err = add_io_cb (gpgsm, &gpgsm->diag_cb, _gpgme_data_inbound_handler); 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++; 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; 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; if (!gpgsm) return gpg_error (GPG_ERR_INV_VALUE); if (!pattern) pattern = ""; cmd = malloc (7 + 9 + 9 + strlen (pattern) + 1); if (!cmd) return gpg_error_from_syserror (); strcpy (cmd, "EXPORT "); if ((mode & GPGME_EXPORT_MODE_SECRET)) { strcat (cmd, "--secret "); if ((mode & GPGME_EXPORT_MODE_RAW)) strcat (cmd, "--raw "); else if ((mode & GPGME_EXPORT_MODE_PKCS12)) strcat (cmd, "--pkcs12 "); } strcat (cmd, pattern); 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); 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); /* Length is "LISTSECRETKEYS " + p + '\0'. */ line = malloc (15 + strlen (pattern) + 1); if (!line) return gpg_error_from_syserror (); if (secret_only) { strcpy (line, "LISTSECRETKEYS "); strcpy (&line[15], pattern); } else { strcpy (line, "LISTKEYS "); strcpy (&line[9], pattern); } 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); 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/engine-spawn.c b/src/engine-spawn.c index 296d7f25..39428f29 100644 --- a/src/engine-spawn.c +++ b/src/engine-spawn.c @@ -1,484 +1,487 @@ /* engine-spawn.c - Run an arbitrary program * Copyright (C) 2014 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_LOCALE_H #include #endif #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "context.h" /*temp hack until we have GpmeData methods to do I/O */ #include "priv-io.h" #include "sema.h" #include "debug.h" #include "engine-backend.h" /* This type is used to build a list of data sources/sinks. */ struct datalist_s { struct datalist_s *next; gpgme_data_t data; /* The data object. */ int inbound; /* True if this is used for reading from the peer. */ int dup_to; /* The fd used by the peer. */ }; struct fd_data_map_s { gpgme_data_t data; int inbound; /* True if this is used for reading from the peer. */ int dup_to; /* Dup the fd to that one. */ int fd; /* The fd to use. */ int peer_fd; /* The other side of the pipe. */ void *tag; /* Tag used by the I/O callback. */ }; struct engine_spawn { struct datalist_s *arglist; struct datalist_s **argtail; struct fd_data_map_s *fd_data_map; struct gpgme_io_cbs io_cbs; }; typedef struct engine_spawn *engine_spawn_t; static void engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data); static gpgme_error_t engspawn_cancel (void *engine); -static void +static gpg_error_t close_notify_handler (int fd, void *opaque) { engine_spawn_t esp = opaque; int i; assert (fd != -1); if (esp->fd_data_map) { for (i = 0; esp->fd_data_map[i].data; i++) { if (esp->fd_data_map[i].fd == fd) { if (esp->fd_data_map[i].tag) (*esp->io_cbs.remove) (esp->fd_data_map[i].tag); esp->fd_data_map[i].fd = -1; break; } if (esp->fd_data_map[i].peer_fd == fd) { esp->fd_data_map[i].peer_fd = -1; break; } } } + return 0; } static gpgme_error_t add_data (engine_spawn_t esp, gpgme_data_t data, int dup_to, int inbound) { struct datalist_s *a; assert (esp); assert (data); a = malloc (sizeof *a); if (!a) return gpg_error_from_syserror (); a->next = NULL; a->data = data; a->inbound = inbound; a->dup_to = dup_to; *esp->argtail = a; esp->argtail = &a->next; return 0; } static void free_fd_data_map (struct fd_data_map_s *fd_data_map) { int i; if (!fd_data_map) return; for (i = 0; fd_data_map[i].data; i++) { if (fd_data_map[i].fd != -1) _gpgme_io_close (fd_data_map[i].fd); if (fd_data_map[i].peer_fd != -1) _gpgme_io_close (fd_data_map[i].peer_fd); /* Don't release data because this is only a reference. */ } free (fd_data_map); } static gpgme_error_t build_fd_data_map (engine_spawn_t esp) { struct datalist_s *a; size_t datac; int fds[2]; for (datac = 0, a = esp->arglist; a; a = a->next) if (a->data) datac++; free_fd_data_map (esp->fd_data_map); esp->fd_data_map = calloc (datac + 1, sizeof *esp->fd_data_map); if (!esp->fd_data_map) return gpg_error_from_syserror (); for (datac = 0, a = esp->arglist; a; a = a->next) { assert (a->data); if (_gpgme_io_pipe (fds, a->inbound ? 1 : 0) == -1) { free (esp->fd_data_map); esp->fd_data_map = NULL; return gpg_error_from_syserror (); } - if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, esp) - || _gpgme_io_set_close_notify (fds[1], close_notify_handler, esp)) + if (_gpgme_fdtable_add_close_notify (fds[0], + close_notify_handler, esp) + || _gpgme_fdtable_add_close_notify (fds[1], + close_notify_handler, esp)) { /* FIXME: Need error cleanup. */ return gpg_error (GPG_ERR_GENERAL); } esp->fd_data_map[datac].inbound = a->inbound; if (a->inbound) { esp->fd_data_map[datac].fd = fds[0]; esp->fd_data_map[datac].peer_fd = fds[1]; } else { esp->fd_data_map[datac].fd = fds[1]; esp->fd_data_map[datac].peer_fd = fds[0]; } esp->fd_data_map[datac].data = a->data; esp->fd_data_map[datac].dup_to = a->dup_to; datac++; } return 0; } static gpgme_error_t add_io_cb (engine_spawn_t esp, int fd, int dir, gpgme_io_cb_t handler, void *data, void **tag) { gpgme_error_t err; err = (*esp->io_cbs.add) (esp->io_cbs.add_priv, fd, dir, handler, data, tag); if (err) return err; if (!dir) /* Fixme: Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (fd); return err; } static gpgme_error_t engspawn_start (engine_spawn_t esp, const char *file, const char *argv[], unsigned int flags) { gpgme_error_t err; int i, n; int status; struct spawn_fd_item_s *fd_list; pid_t pid; unsigned int spflags; const char *save_argv0 = NULL; if (!esp || !file || !argv || !argv[0]) return gpg_error (GPG_ERR_INV_VALUE); spflags = 0; if ((flags & GPGME_SPAWN_DETACHED)) spflags |= IOSPAWN_FLAG_DETACHED; if ((flags & GPGME_SPAWN_ALLOW_SET_FG)) spflags |= IOSPAWN_FLAG_ALLOW_SET_FG; if ((flags & GPGME_SPAWN_SHOW_WINDOW)) spflags |= IOSPAWN_FLAG_SHOW_WINDOW; err = build_fd_data_map (esp); if (err) return err; n = 0; for (i = 0; esp->fd_data_map[i].data; i++) n++; fd_list = calloc (n+1, sizeof *fd_list); if (!fd_list) return gpg_error_from_syserror (); /* Build the fd list for the child. */ n = 0; for (i = 0; esp->fd_data_map[i].data; i++) { fd_list[n].fd = esp->fd_data_map[i].peer_fd; fd_list[n].dup_to = esp->fd_data_map[i].dup_to; n++; } fd_list[n].fd = -1; fd_list[n].dup_to = -1; if (argv[0] && !*argv[0]) { save_argv0 = argv[0]; argv[0] = _gpgme_get_basename (file); } status = _gpgme_io_spawn (file, (char * const *)argv, spflags, fd_list, NULL, NULL, &pid); if (save_argv0) argv[0] = save_argv0; free (fd_list); if (status == -1) return gpg_error_from_syserror (); for (i = 0; esp->fd_data_map[i].data; i++) { err = add_io_cb (esp, esp->fd_data_map[i].fd, esp->fd_data_map[i].inbound, esp->fd_data_map[i].inbound ? _gpgme_data_inbound_handler : _gpgme_data_outbound_handler, esp->fd_data_map[i].data, &esp->fd_data_map[i].tag); if (err) return err; /* FIXME: kill the child */ } engspawn_io_event (esp, GPGME_EVENT_START, NULL); return 0; } /* Public functions */ static const char * engspawn_get_file_name (void) { return "/nonexistent"; } static char * engspawn_get_version (const char *file_name) { (void)file_name; return NULL; } static const char * engspawn_get_req_version (void) { return NULL; } static gpgme_error_t engspawn_new (void **engine, const char *file_name, const char *home_dir, const char *version) { engine_spawn_t esp; (void)file_name; (void)home_dir; (void)version; esp = calloc (1, sizeof *esp); if (!esp) return gpg_error_from_syserror (); esp->argtail = &esp->arglist; *engine = esp; return 0; } static void engspawn_release (void *engine) { engine_spawn_t esp = engine; if (!esp) return; engspawn_cancel (engine); while (esp->arglist) { struct datalist_s *next = esp->arglist->next; free (esp->arglist); esp->arglist = next; } free (esp); } static void engspawn_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_spawn_t esp = engine; esp->io_cbs = *io_cbs; } static void engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_spawn_t esp = engine; TRACE (DEBUG_ENGINE, "gpgme:engspawn_io_event", esp, "event %p, type %d, type_data %p", esp->io_cbs.event, type, type_data); if (esp->io_cbs.event) (*esp->io_cbs.event) (esp->io_cbs.event_priv, type, type_data); } static gpgme_error_t engspawn_cancel (void *engine) { engine_spawn_t esp = engine; if (!esp) return gpg_error (GPG_ERR_INV_VALUE); if (esp->fd_data_map) { free_fd_data_map (esp->fd_data_map); esp->fd_data_map = NULL; } return 0; } static gpgme_error_t engspawn_op_spawn (void *engine, const char *file, const char *argv[], gpgme_data_t datain, gpgme_data_t dataout, gpgme_data_t dataerr, unsigned int flags) { engine_spawn_t esp = engine; gpgme_error_t err = 0; if (datain) err = add_data (esp, datain, 0, 0); if (!err && dataout) err = add_data (esp, dataout, 1, 1); if (!err && dataerr) err = add_data (esp, dataerr, 2, 1); if (!err) err = engspawn_start (esp, file, argv, flags); return err; } struct engine_ops _gpgme_engine_ops_spawn = { /* Static functions. */ engspawn_get_file_name, NULL, /* get_home_dir */ engspawn_get_version, engspawn_get_req_version, engspawn_new, /* Member functions. */ engspawn_release, NULL, /* reset */ NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ NULL, /* set_locale */ NULL, /* set_protocol */ NULL, /* set_engine_flags */ NULL, /* decrypt */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ engspawn_set_io_cbs, engspawn_io_event, /* io_event */ engspawn_cancel, /* cancel */ NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ engspawn_op_spawn /* opspawn */ }; diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index cb8e155d..e7896bbe 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -1,1455 +1,1457 @@ /* engine-uiserver.c - Uiserver engine. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2009 g10 Code GmbH * * This file is part of GPGME. * * GPGME is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * GPGME is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see . * SPDX-License-Identifier: LGPL-2.1-or-later */ /* Peculiar: Use special keys from email address for recipient and signer (==sender). Use no data objects with encryption for prep_encrypt. */ #if HAVE_CONFIG_H #include #endif #include #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #ifdef HAVE_UNISTD_H # include #endif #include #include /* FIXME */ #include #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_uiserver { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; gpgme_protocol_t protocol; 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; 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. */ struct gpgme_io_cbs io_cbs; }; typedef struct engine_uiserver *engine_uiserver_t; static void uiserver_io_event (void *engine, gpgme_event_io_t type, void *type_data); static char * uiserver_get_version (const char *file_name) { (void)file_name; return NULL; } static const char * uiserver_get_req_version (void) { return NULL; } -static void +static gpg_error_t close_notify_handler (int fd, void *opaque) { engine_uiserver_t uiserver = opaque; assert (fd != -1); if (uiserver->status_cb.fd == fd) { if (uiserver->status_cb.tag) (*uiserver->io_cbs.remove) (uiserver->status_cb.tag); uiserver->status_cb.fd = -1; uiserver->status_cb.tag = NULL; } else if (uiserver->input_cb.fd == fd) { if (uiserver->input_cb.tag) (*uiserver->io_cbs.remove) (uiserver->input_cb.tag); uiserver->input_cb.fd = -1; uiserver->input_cb.tag = NULL; if (uiserver->input_helper_data) { gpgme_data_release (uiserver->input_helper_data); uiserver->input_helper_data = NULL; } if (uiserver->input_helper_memory) { free (uiserver->input_helper_memory); uiserver->input_helper_memory = NULL; } } else if (uiserver->output_cb.fd == fd) { if (uiserver->output_cb.tag) (*uiserver->io_cbs.remove) (uiserver->output_cb.tag); uiserver->output_cb.fd = -1; uiserver->output_cb.tag = NULL; } else if (uiserver->message_cb.fd == fd) { if (uiserver->message_cb.tag) (*uiserver->io_cbs.remove) (uiserver->message_cb.tag); uiserver->message_cb.fd = -1; uiserver->message_cb.tag = NULL; } + + return 0; } /* This is the default inquiry callback. We use it to handle the Pinentry notifications. */ static gpgme_error_t default_inq_cb (engine_uiserver_t uiserver, const char *line) { (void)uiserver; 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 uiserver_cancel (void *engine) { engine_uiserver_t uiserver = engine; if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->status_cb.fd != -1) _gpgme_io_close (uiserver->status_cb.fd); if (uiserver->input_cb.fd != -1) _gpgme_io_close (uiserver->input_cb.fd); if (uiserver->output_cb.fd != -1) _gpgme_io_close (uiserver->output_cb.fd); if (uiserver->message_cb.fd != -1) _gpgme_io_close (uiserver->message_cb.fd); if (uiserver->assuan_ctx) { assuan_release (uiserver->assuan_ctx); uiserver->assuan_ctx = NULL; } return 0; } static void uiserver_release (void *engine) { engine_uiserver_t uiserver = engine; if (!uiserver) return; uiserver_cancel (engine); free (uiserver->colon.attic.line); free (uiserver); } static gpgme_error_t uiserver_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_uiserver_t uiserver; char *dft_display = NULL; char dft_ttyname[64]; char *env_tty = NULL; char *dft_ttytype = NULL; char *optstr; (void)home_dir; (void)version; /* Not yet used. */ uiserver = calloc (1, sizeof *uiserver); if (!uiserver) return gpg_error_from_syserror (); uiserver->protocol = GPGME_PROTOCOL_DEFAULT; uiserver->status_cb.fd = -1; uiserver->status_cb.dir = 1; uiserver->status_cb.tag = 0; uiserver->status_cb.data = uiserver; uiserver->input_cb.fd = -1; uiserver->input_cb.dir = 0; uiserver->input_cb.tag = 0; uiserver->input_cb.server_fd = -1; *uiserver->input_cb.server_fd_str = 0; uiserver->output_cb.fd = -1; uiserver->output_cb.dir = 1; uiserver->output_cb.tag = 0; uiserver->output_cb.server_fd = -1; *uiserver->output_cb.server_fd_str = 0; uiserver->message_cb.fd = -1; uiserver->message_cb.dir = 0; uiserver->message_cb.tag = 0; uiserver->message_cb.server_fd = -1; *uiserver->message_cb.server_fd_str = 0; uiserver->status.fnc = 0; uiserver->colon.fnc = 0; uiserver->colon.attic.line = 0; uiserver->colon.attic.linesize = 0; uiserver->colon.attic.linelen = 0; uiserver->colon.any = 0; uiserver->inline_data = NULL; uiserver->io_cbs.add = NULL; uiserver->io_cbs.add_priv = NULL; uiserver->io_cbs.remove = NULL; uiserver->io_cbs.event = NULL; uiserver->io_cbs.event_priv = NULL; err = assuan_new_ext (&uiserver->assuan_ctx, GPG_ERR_SOURCE_GPGME, &_gpgme_assuan_malloc_hooks, _gpgme_assuan_log_cb, NULL); if (err) goto leave; assuan_ctx_set_system_hooks (uiserver->assuan_ctx, &_gpgme_assuan_system_hooks); err = assuan_socket_connect (uiserver->assuan_ctx, file_name ? file_name : _gpgme_get_default_uisrv_socket (), 0, ASSUAN_SOCKET_SERVER_FDPASSING); 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) { err = gpg_error_from_syserror (); free (dft_display); goto leave; } free (dft_display); err = assuan_transact (uiserver->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 (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (gpgrt_asprintf (&optstr, "OPTION ttytype=%s", dft_ttytype)< 0) { err = gpg_error_from_syserror (); free (dft_ttytype); goto leave; } free (dft_ttytype); err = assuan_transact (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); if (err) goto leave; } } } #ifdef HAVE_W32_SYSTEM /* Under Windows we need to use AllowSetForegroundWindow. Tell uiserver to tell us when it needs it. */ if (!err) { err = assuan_transact (uiserver->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 uiserver. */ } #endif /*HAVE_W32_SYSTEM*/ leave: if (err) uiserver_release (uiserver); else *engine = uiserver; return err; } static gpgme_error_t uiserver_set_locale (void *engine, int category, const char *value) { engine_uiserver_t uiserver = 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. UISERVER needs support for this. */ if (category == LC_CTYPE) { catstr = "lc-ctype"; if (!value && uiserver->lc_ctype_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) uiserver->lc_ctype_set = 1; } #ifdef LC_MESSAGES else if (category == LC_MESSAGES) { catstr = "lc-messages"; if (!value && uiserver->lc_messages_set) return gpg_error (GPG_ERR_INV_VALUE); if (value) uiserver->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 (uiserver->assuan_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); gpgrt_free (optstr); } return err; } static gpgme_error_t uiserver_set_protocol (void *engine, gpgme_protocol_t protocol) { engine_uiserver_t uiserver = engine; if (protocol != GPGME_PROTOCOL_OpenPGP && protocol != GPGME_PROTOCOL_CMS && protocol != GPGME_PROTOCOL_DEFAULT) return gpg_error (GPG_ERR_INV_VALUE); uiserver->protocol = protocol; return 0; } static gpgme_error_t uiserver_assuan_simple_command (engine_uiserver_t uiserver, const char *cmd, engine_status_handler_t status_fnc, void *status_fnc_value) { assuan_context_t ctx = uiserver->assuan_ctx; gpg_error_t err; char *line; size_t linelen; err = assuan_write_line (ctx, cmd); if (err) return err; do { err = assuan_read_line (ctx, &line, &linelen); if (err) return err; if (*line == '#' || !linelen) continue; if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) return 0; else if (linelen >= 4 && line[0] == 'E' && line[1] == 'R' && line[2] == 'R' && line[3] == ' ') err = atoi (&line[4]); 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 (uiserver->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 = uiserver->status.mon_cb (uiserver->status.mon_cb_value, line + 2, rest); } if (err) ; else if (r >= 0 && status_fnc) err = status_fnc (status_fnc_value, r, rest); else err = gpg_error (GPG_ERR_GENERAL); } else err = gpg_error (GPG_ERR_GENERAL); } while (!err); return err; } typedef enum { INPUT_FD, OUTPUT_FD, MESSAGE_FD } fd_type_t; #define COMMANDLINELEN 40 static gpgme_error_t uiserver_set_fd (engine_uiserver_t uiserver, fd_type_t fd_type, const char *opt) { gpg_error_t err = 0; char line[COMMANDLINELEN]; const char *which; iocb_data_t *iocb_data; int dir; switch (fd_type) { case INPUT_FD: which = "INPUT"; iocb_data = &uiserver->input_cb; break; case OUTPUT_FD: which = "OUTPUT"; iocb_data = &uiserver->output_cb; break; case MESSAGE_FD: which = "MESSAGE"; iocb_data = &uiserver->message_cb; break; default: return gpg_error (GPG_ERR_INV_VALUE); } dir = iocb_data->dir; /* We try to short-cut the communication by giving UISERVER 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, 0) < 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_io_set_close_notify (iocb_data->fd, - close_notify_handler, uiserver)) + if (_gpgme_fdtable_add_close_notify (iocb_data->fd, + close_notify_handler, uiserver)) { err = gpg_error (GPG_ERR_GENERAL); goto leave_set_fd; } } err = assuan_sendfd (uiserver->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); err = uiserver_assuan_simple_command (uiserver, line, NULL, NULL); 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; } } 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_uiserver_t uiserver = (engine_uiserver_t) data->handler_value; gpgme_error_t err = 0; char *line; size_t linelen; do { err = assuan_read_line (uiserver->assuan_ctx, &line, &linelen); if (err) { /* Try our best to terminate the connection friendly. */ /* assuan_write_line (uiserver->assuan_ctx, "BYE"); */ TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: 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", uiserver, "fd 0x%x: ERR line - mapped to: %s", fd, err ? gpg_strerror (err) : "ok"); /* Try our best to terminate the connection friendly. */ /* assuan_write_line (uiserver->assuan_ctx, "BYE"); */ } else if (linelen >= 2 && line[0] == 'O' && line[1] == 'K' && (line[2] == '\0' || line[2] == ' ')) { if (uiserver->status.fnc) { char emptystring[1] = {0}; err = uiserver->status.fnc (uiserver->status.fnc_value, GPGME_STATUS_EOF, emptystring); if (gpg_err_code (err) == GPG_ERR_FALSE) err = 0; /* Drop special error code. */ } if (!err && uiserver->colon.fnc && uiserver->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. */ uiserver->colon.any = 0; err = uiserver->colon.fnc (uiserver->colon.fnc_value, NULL); } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: OK line - final status: %s", fd, err ? gpg_strerror (err) : "ok"); _gpgme_io_close (uiserver->status_cb.fd); return err; } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && uiserver->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 = &uiserver->colon.attic.line; int *alinelen = &uiserver->colon.attic.linelen; if (uiserver->colon.attic.linesize < *alinelen + linelen + 1) { char *newline = realloc (*aline, *alinelen + linelen + 1); if (!newline) err = gpg_error_from_syserror (); else { *aline = newline; uiserver->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. */ uiserver->colon.any = 1; if (*alinelen > 1 && *(dst - 1) == '\r') dst--; *dst = '\0'; /* FIXME How should we handle the return code? */ err = uiserver->colon.fnc (uiserver->colon.fnc_value, *aline); if (!err) { dst = *aline; *alinelen = 0; } } else dst++; } } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: D line; final status: %s", fd, err? gpg_strerror (err):"ok"); } else if (linelen > 2 && line[0] == 'D' && line[1] == ' ' && uiserver->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 (uiserver->inline_data, src, linelen); if (!nwritten || (nwritten < 0 && errno != EINTR) || nwritten > linelen) { err = gpg_error_from_syserror (); break; } src += nwritten; linelen -= nwritten; } TRACE (DEBUG_CTX, "gpgme:status_handler", uiserver, "fd 0x%x: 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 (r >= 0) { if (uiserver->status.fnc) { err = uiserver->status.fnc (uiserver->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", uiserver, "fd 0x%x: 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 (uiserver, keyword); assuan_write_line (uiserver->assuan_ctx, "END"); } } while (!err && assuan_pending_line (uiserver->assuan_ctx)); return err; } static gpgme_error_t add_io_cb (engine_uiserver_t uiserver, iocb_data_t *iocbd, gpgme_io_cb_t handler) { gpgme_error_t err; TRACE_BEG (DEBUG_ENGINE, "engine-uiserver:add_io_cb", uiserver, "fd=%d, dir %d", iocbd->fd, iocbd->dir); err = (*uiserver->io_cbs.add) (uiserver->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_uiserver_t uiserver, const char *command) { gpgme_error_t err; int fdlist[5]; int nfds; /* 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 (uiserver->assuan_ctx, 0 /* read fds */, fdlist, DIM (fdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* 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. */ uiserver->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (uiserver->status_cb.fd < 0) return gpg_error_from_syserror (); - if (_gpgme_io_set_close_notify (uiserver->status_cb.fd, - close_notify_handler, uiserver)) + if (_gpgme_fdtable_add_close_notify (uiserver->status_cb.fd, + close_notify_handler, uiserver)) { _gpgme_io_close (uiserver->status_cb.fd); uiserver->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (uiserver, &uiserver->status_cb, status_handler); if (!err && uiserver->input_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->input_cb, _gpgme_data_outbound_handler); if (!err && uiserver->output_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->output_cb, _gpgme_data_inbound_handler); if (!err && uiserver->message_cb.fd != -1) err = add_io_cb (uiserver, &uiserver->message_cb, _gpgme_data_outbound_handler); if (!err) err = assuan_write_line (uiserver->assuan_ctx, command); if (!err) uiserver_io_event (uiserver, GPGME_EVENT_START, NULL); return err; } static gpgme_error_t uiserver_reset (void *engine) { engine_uiserver_t uiserver = engine; /* We must send a reset because we need to reset the list of signers. Note that RESET does not reset OPTION commands. */ return uiserver_assuan_simple_command (uiserver, "RESET", NULL, NULL); } static gpgme_error_t uiserver_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_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; int verify = !!(flags & GPGME_DECRYPT_VERIFY); (void)override_session_key; /* Fixme: We need to see now to add this * to the UI server protocol */ (void)auto_key_retrieve; /* Not yet supported. */ if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "DECRYPT%s%s%s", protocol, verify ? "" : " --no-verify", export_session_key ? " --export-session-key" : "") < 0) return gpg_error_from_syserror (); uiserver->input_cb.data = ciph; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->output_cb.data = plain; err = uiserver_set_fd (uiserver, OUTPUT_FD, 0); if (err) { gpgrt_free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->inline_data = NULL; err = start (engine, cmd); gpgrt_free (cmd); return err; } static gpgme_error_t set_recipients (engine_uiserver_t uiserver, gpgme_key_t recp[]) { gpgme_error_t err = 0; char *line; int linelen; int invalid_recipients = 0; int i; 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 *uid; int newlen; /* We use only the first user ID of the key. */ if (!recp[i]->uids || !(uid=recp[i]->uids->uid) || !*uid) { invalid_recipients++; continue; } newlen = 11 + strlen (uid); 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; } /* FIXME: need to do proper escaping */ strcpy (&line[10], uid); err = uiserver_assuan_simple_command (uiserver, line, uiserver->status.fnc, uiserver->status.fnc_value); /* FIXME: This might 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_uiserver_t uiserver, const char *string) { gpg_error_t err = 0; char *line = NULL; int no_pubkey = 0; const char *s; int n; for (;;) { 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--; gpgrt_free (line); if (gpgrt_asprintf (&line, "RECIPIENT %.*s", n, string) < 0) { err = gpg_error_from_syserror (); break; } string += n + !!s; err = uiserver_assuan_simple_command (uiserver, line, uiserver->status.fnc, uiserver->status.fnc_value); /* Fixme: Improve error reporting. */ if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY) no_pubkey++; else if (err) break; } gpgrt_free (line); return err? err : no_pubkey? gpg_error (GPG_ERR_NO_PUBKEY) : 0; } static gpgme_error_t uiserver_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_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (flags & GPGME_ENCRYPT_PREPARE) { if (!recp || plain || ciph) return gpg_error (GPG_ERR_INV_VALUE); if (gpgrt_asprintf (&cmd, "PREP_ENCRYPT%s%s", protocol, (flags & GPGME_ENCRYPT_EXPECT_SIGN) ? " --expect-sign" : "") < 0) return gpg_error_from_syserror (); } else { if (!plain || !ciph) return gpg_error (GPG_ERR_INV_VALUE); if (gpgrt_asprintf (&cmd, "ENCRYPT%s", protocol) < 0) return gpg_error_from_syserror (); } if (plain) { uiserver->input_cb.data = plain; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } } if (ciph) { uiserver->output_cb.data = ciph; err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (uiserver->output_cb.data)); if (err) { gpgrt_free (cmd); return err; } } uiserver->inline_data = NULL; if (recp || recpstring) { if (recp) err = set_recipients (uiserver, recp); else err = set_recipients_from_string (uiserver, recpstring); if (err) { gpgrt_free (cmd); return err; } } err = start (uiserver, cmd); gpgrt_free (cmd); return err; } static gpgme_error_t uiserver_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_uiserver_t uiserver = engine; gpgme_error_t err = 0; const char *protocol; char *cmd; gpgme_key_t key; (void)use_textmode; (void)include_certs; if (!uiserver || !in || !out) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "SIGN%s%s", protocol, (mode == GPGME_SIG_MODE_DETACH) ? " --detached" : "") < 0) return gpg_error_from_syserror (); key = gpgme_signers_enum (ctx, 0); if (key) { const char *s = NULL; if (key && key->uids) s = key->uids->email; if (s && strlen (s) < 80) { char buf[100]; strcpy (stpcpy (buf, "SENDER --info "), s); err = uiserver_assuan_simple_command (uiserver, buf, uiserver->status.fnc, uiserver->status.fnc_value); } else err = gpg_error (GPG_ERR_INV_VALUE); gpgme_key_unref (key); if (err) { gpgrt_free (cmd); return err; } } uiserver->input_cb.data = in; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } uiserver->output_cb.data = out; err = uiserver_set_fd (uiserver, OUTPUT_FD, use_armor ? "--armor" : map_data_enc (uiserver->output_cb.data)); if (err) { gpgrt_free (cmd); return err; } uiserver->inline_data = NULL; err = start (uiserver, cmd); gpgrt_free (cmd); return err; } /* FIXME: Missing a way to specify --silent. */ static gpgme_error_t uiserver_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext, gpgme_ctx_t ctx) { engine_uiserver_t uiserver = engine; gpgme_error_t err; const char *protocol; char *cmd; (void)ctx; /* FIXME: We should to add a --sender option to the * UISever protocol. */ if (!uiserver) return gpg_error (GPG_ERR_INV_VALUE); if (uiserver->protocol == GPGME_PROTOCOL_DEFAULT) protocol = ""; else if (uiserver->protocol == GPGME_PROTOCOL_OpenPGP) protocol = " --protocol=OpenPGP"; else if (uiserver->protocol == GPGME_PROTOCOL_CMS) protocol = " --protocol=CMS"; else return gpgme_error (GPG_ERR_UNSUPPORTED_PROTOCOL); if (gpgrt_asprintf (&cmd, "VERIFY%s", protocol) < 0) return gpg_error_from_syserror (); uiserver->input_cb.data = sig; err = uiserver_set_fd (uiserver, INPUT_FD, map_data_enc (uiserver->input_cb.data)); if (err) { gpgrt_free (cmd); return err; } if (plaintext) { /* Normal or cleartext signature. */ uiserver->output_cb.data = plaintext; err = uiserver_set_fd (uiserver, OUTPUT_FD, 0); } else { /* Detached signature. */ uiserver->message_cb.data = signed_text; err = uiserver_set_fd (uiserver, MESSAGE_FD, 0); } uiserver->inline_data = NULL; if (!err) err = start (uiserver, cmd); gpgrt_free (cmd); return err; } /* This sets a status callback for monitoring status lines before they * are passed to a caller set handler. */ static void uiserver_set_status_cb (void *engine, gpgme_status_cb_t cb, void *cb_value) { engine_uiserver_t uiserver = engine; uiserver->status.mon_cb = cb; uiserver->status.mon_cb_value = cb_value; } static void uiserver_set_status_handler (void *engine, engine_status_handler_t fnc, void *fnc_value) { engine_uiserver_t uiserver = engine; uiserver->status.fnc = fnc; uiserver->status.fnc_value = fnc_value; } static gpgme_error_t uiserver_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc, void *fnc_value) { engine_uiserver_t uiserver = engine; uiserver->colon.fnc = fnc; uiserver->colon.fnc_value = fnc_value; uiserver->colon.any = 0; return 0; } static void uiserver_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_uiserver_t uiserver = engine; uiserver->io_cbs = *io_cbs; } static void uiserver_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_uiserver_t uiserver = engine; TRACE (DEBUG_ENGINE, "gpgme:uiserver_io_event", uiserver, "event %p, type %d, type_data %p", uiserver->io_cbs.event, type, type_data); if (uiserver->io_cbs.event) (*uiserver->io_cbs.event) (uiserver->io_cbs.event_priv, type, type_data); } struct engine_ops _gpgme_engine_ops_uiserver = { /* Static functions. */ _gpgme_get_default_uisrv_socket, NULL, uiserver_get_version, uiserver_get_req_version, uiserver_new, /* Member functions. */ uiserver_release, uiserver_reset, uiserver_set_status_cb, uiserver_set_status_handler, NULL, /* set_command_handler */ uiserver_set_colon_line_handler, uiserver_set_locale, uiserver_set_protocol, NULL, /* set_engine_flags */ uiserver_decrypt, NULL, /* delete */ NULL, /* edit */ uiserver_encrypt, NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* keylist_data */ NULL, /* keysign */ NULL, /* tofu_policy */ uiserver_sign, NULL, /* trustlist */ uiserver_verify, NULL, /* getauditlog */ NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ NULL, /* conf_dir */ NULL, /* query_swdb */ uiserver_set_io_cbs, uiserver_io_event, uiserver_cancel, NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/fdtable.c b/src/fdtable.c new file mode 100644 index 00000000..2e682dea --- /dev/null +++ b/src/fdtable.c @@ -0,0 +1,205 @@ +/* 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. */ + + /* 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].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); +} + + +/* 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)); + } + + 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; + + UNLOCK (fdtable_lock); + + err = handler? handler (fd, handlervalue) : 0; + + return TRACE_ERR (err); +} diff --git a/src/fdtable.h b/src/fdtable.h new file mode 100644 index 00000000..6038b249 --- /dev/null +++ b/src/fdtable.h @@ -0,0 +1,43 @@ +/* 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 + +/* 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); + +/* Remove FD from the table. This also runs the close handlers. */ +gpg_error_t _gpgme_fdtable_remove (int fd); + + +#endif /*GPGME_FDTABLE_H*/ diff --git a/src/posix-io.c b/src/posix-io.c index e712ef28..b754ac34 100644 --- a/src/posix-io.c +++ b/src/posix-io.c @@ -1,927 +1,872 @@ /* 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); } - -/* The table to hold notification handlers. We use a linear search - and extend the table as needed. */ -struct notify_table_item_s -{ - int fd; /* -1 indicates an unused entry. */ - _gpgme_close_notify_handler_t handler; - void *value; -}; -typedef struct notify_table_item_s *notify_table_item_t; - -static notify_table_item_t notify_table; -static size_t notify_table_size; -DEFINE_STATIC_LOCK (notify_table_lock); - - 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 err; + int i; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", NULL, "inherit_idx=%i (GPGME uses it for %s)", inherit_idx, inherit_idx ? "reading" : "writing"); - err = pipe (filedes); - if (err < 0) - return TRACE_SYSRES (err); + res = pipe (filedes); + if (res < 0) + return TRACE_SYSRES (res); /* FIXME: Should get the old flags first. */ - err = fcntl (filedes[1 - inherit_idx], F_SETFD, FD_CLOEXEC); + res = fcntl (filedes[1 - inherit_idx], F_SETFD, FD_CLOEXEC); saved_errno = errno; - if (err < 0) + if (res < 0) { close (filedes[0]); close (filedes[1]); } errno = saved_errno; - if (err) - return TRACE_SYSRES (err); + 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; - _gpgme_close_notify_handler_t handler = NULL; - void *handler_value; - int idx; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", NULL, "fd=%d", fd); - if (fd == -1) - { - errno = EINVAL; - return TRACE_SYSRES (-1); - } + return TRACE_SYSRES (0); /* Igore invalid FDs. */ - /* First call the notify handler. */ - LOCK (notify_table_lock); - for (idx=0; idx < notify_table_size; idx++) + /* 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) { - if (notify_table[idx].fd == fd) - { - handler = notify_table[idx].handler; - handler_value = notify_table[idx].value; - notify_table[idx].handler = NULL; - notify_table[idx].value = NULL; - notify_table[idx].fd = -1; /* Mark slot as free. */ - break; - } - } - UNLOCK (notify_table_lock); - if (handler) - { - TRACE_LOG ("invoking close handler %p/%p", handler, handler_value); - handler (fd, handler_value); + 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_close_notify (int fd, _gpgme_close_notify_handler_t handler, - void *value) -{ - int res = 0; - int idx; - - TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", NULL, - "fd=%d close_handler=%p/%p", fd, handler, value); - - assert (fd != -1); - - LOCK (notify_table_lock); - for (idx=0; idx < notify_table_size; idx++) - if (notify_table[idx].fd == -1) - break; - if (idx == notify_table_size) - { - /* We need to increase the size of the table. The approach we - take is straightforward to minimize the risk of bugs. */ - notify_table_item_t newtbl; - size_t newsize = notify_table_size + 64; - - newtbl = calloc (newsize, sizeof *newtbl); - if (!newtbl) - { - res = -1; - goto leave; - } - for (idx=0; idx < notify_table_size; idx++) - newtbl[idx] = notify_table[idx]; - for (; idx < newsize; idx++) - { - newtbl[idx].fd = -1; - newtbl[idx].handler = NULL; - newtbl[idx].value = NULL; - } - free (notify_table); - notify_table = newtbl; - idx = notify_table_size; - notify_table_size = newsize; - } - notify_table[idx].fd = fd; - notify_table[idx].handler = handler; - notify_table[idx].value = value; - - leave: - UNLOCK (notify_table_lock); - - 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. */ int _gpgme_io_select (struct io_select_fd_s *fds, size_t 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); 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, "_gpgme_io_dup", NULL, "fd=%d -> fd=%d", fd, new_fd); + 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 d21f1b34..f40cdffc 100644 --- a/src/priv-io.h +++ b/src/priv-io.h @@ -1,117 +1,114 @@ /* 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 { int fd; int for_read; int for_write; int signaled; void *opaque; }; /* 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); -typedef void (*_gpgme_close_notify_handler_t) (int,void*); -int _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, - void *value); 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); /* 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/w32-io.c b/src/w32-io.c index c5c21f59..ea9622e9 100644 --- a/src/w32-io.c +++ b/src/w32-io.c @@ -1,1984 +1,1984 @@ /* w32-io.c - W32 API I/O functions. * Copyright (C) 2000 Werner Koch (dd9jn) * Copyright (C) 2001-2004, 2007, 2010, 2018 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+ */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #ifdef HAVE_SYS_TIME_H # include #endif #ifdef HAVE_SYS_TYPES_H # include #endif #include #include "util.h" #include "sema.h" #include "priv-io.h" #include "debug.h" #include "sys-util.h" /* The number of entries in our file table. We may eventually use a * lower value and dynamically resize the table. */ #define MAX_SLAFD 512 #define handle_to_fd(a) ((int)(a)) #define READBUF_SIZE 4096 #define WRITEBUF_SIZE 4096 #define PIPEBUF_SIZE 4096 /* An object to store handles or sockets. */ struct hddesc_s { HANDLE hd; SOCKET sock; int refcount; }; typedef struct hddesc_s *hddesc_t; /* The context used by a reader thread. */ struct reader_context_s { hddesc_t hdd; HANDLE thread_hd; int refcount; /* Bumped if the FD has been duped and thus we have * another FD referencing this context. */ DECLARE_LOCK (mutex); int stop_me; int eof; int eof_shortcut; int error; int error_code; /* This is manually reset. */ HANDLE have_data_ev; /* This is automatically reset. */ HANDLE have_space_ev; /* This is manually reset but actually only triggered once. */ HANDLE close_ev; size_t readpos, writepos; char buffer[READBUF_SIZE]; }; /* The context used by a writer thread. */ struct writer_context_s { hddesc_t hdd; HANDLE thread_hd; int refcount; DECLARE_LOCK (mutex); int stop_me; int error; int error_code; /* This is manually reset. */ HANDLE have_data; HANDLE is_empty; HANDLE close_ev; size_t nbytes; char buffer[WRITEBUF_SIZE]; }; /* An object to keep track of HANDLEs and sockets and map them to an * integer similar to what we use in Unix. Note that despite this * integer is often named "fd", it is not a file descriptor but really * only an index into this table. Never ever pass such an fd to any * other function except for those implemented here. */ static struct { int used; /* The handle descriptor. */ hddesc_t hdd; /* DUP_FROM is just a debug helper to show from which fd this fd was * dup-ed. */ int dup_from; /* Two flags to indicate whether a reader or writer (or both) are * needed. This is so that we can delay the actual thread creation * until they are needed. */ unsigned int want_reader:1; unsigned int want_writer:1; /* The context of an associated reader object or NULL. */ struct reader_context_s *reader; /* The context of an associated writer object or NULL. */ struct writer_context_s *writer; - /* A notification handler. Noet that we current support only one + /* A notification handler. Note that we current support only one * callback per fd. */ struct { _gpgme_close_notify_handler_t handler; void *value; } notify; } fd_table[MAX_SLAFD]; static size_t fd_table_size = MAX_SLAFD; DEFINE_STATIC_LOCK (fd_table_lock); /* We use a single global lock for all hddesc_t objects. */ DEFINE_STATIC_LOCK (hddesc_lock); /* Wrapper around CloseHandle to print an error. */ #define close_handle(hd) _close_handle ((hd), __LINE__); static void _close_handle (HANDLE hd, int line) { if (!CloseHandle (hd)) { TRACE (DEBUG_INIT, "w32-io", hd, "CloseHandle failed at line %d: ec=%d", line, (int) GetLastError ()); } } /* Wrapper around WaitForSingleObject to print an error. */ #define wait_for_single_object(hd,msec) \ _wait_for_single_object ((hd), (msec), __LINE__) static DWORD _wait_for_single_object (HANDLE hd, DWORD msec, int line) { DWORD res; res = WaitForSingleObject (hd, msec); if (res == WAIT_FAILED) { TRACE (DEBUG_INIT, "w32-io", hd, "WFSO failed at line %d: ec=%d", line, (int) GetLastError ()); } return res; } /* Create a new handle descriptor object. */ static hddesc_t new_hddesc (void) { hddesc_t hdd; hdd = malloc (sizeof *hdd); if (!hdd) return NULL; hdd->hd = INVALID_HANDLE_VALUE; hdd->sock = INVALID_SOCKET; hdd->refcount = 0; return hdd; } static hddesc_t ref_hddesc (hddesc_t hdd) { LOCK (hddesc_lock); hdd->refcount++; UNLOCK (hddesc_lock); return hdd; } /* Release a handle descriptor object and close its handle or socket * if needed. */ static void release_hddesc (hddesc_t hdd) { if (!hdd) return; LOCK (hddesc_lock); hdd->refcount--; if (hdd->refcount < 1) { /* Holds a valid handle or was never initialized (in which case * REFCOUNT would be -1 here). */ TRACE_BEG (DEBUG_SYSIO, "gpgme:release_hddesc", hdd, "hd=%p, sock=%d, refcount=%d", hdd->hd, hdd->sock, hdd->refcount); if (hdd->hd != INVALID_HANDLE_VALUE) close_handle (hdd->hd); if (hdd->sock != INVALID_SOCKET) { TRACE_LOG ("closing socket %d", hdd->sock); if (closesocket (hdd->sock)) { TRACE_LOG ("closesocket failed: ec=%d", (int)WSAGetLastError ()); } } free (hdd); TRACE_SUC (""); } UNLOCK (hddesc_lock); } /* Returns our FD or -1 on resource limit. The returned integer * references a new object which has not been initialized but can be * release with release_fd. */ static int new_fd (void) { int idx; LOCK (fd_table_lock); for (idx = 0; idx < fd_table_size; idx++) if (! fd_table[idx].used) break; if (idx == fd_table_size) { gpg_err_set_errno (EIO); idx = -1; } else { fd_table[idx].used = 1; fd_table[idx].hdd = NULL; fd_table[idx].dup_from = -1; fd_table[idx].want_reader = 0; fd_table[idx].want_writer = 0; fd_table[idx].reader = NULL; fd_table[idx].writer = NULL; fd_table[idx].notify.handler = NULL; fd_table[idx].notify.value = NULL; } UNLOCK (fd_table_lock); return idx; } /* Releases our FD but it this is just this entry. No close operation * is involved here; it must be done prior to calling this * function. */ static void release_fd (int fd) { if (fd < 0 || fd >= fd_table_size) return; LOCK (fd_table_lock); if (fd_table[fd].used) { release_hddesc (fd_table[fd].hdd); fd_table[fd].used = 0; fd_table[fd].hdd = NULL; fd_table[fd].dup_from = -1; fd_table[fd].want_reader = 0; fd_table[fd].want_writer = 0; fd_table[fd].reader = NULL; fd_table[fd].writer = NULL; fd_table[fd].notify.handler = NULL; fd_table[fd].notify.value = NULL; } UNLOCK (fd_table_lock); } static int get_desired_thread_priority (void) { int value; if (!_gpgme_get_conf_int ("IOThreadPriority", &value)) { value = THREAD_PRIORITY_HIGHEST; TRACE (DEBUG_SYSIO, "gpgme:get_desired_thread_priority", 0, "%d (default)", value); } else { TRACE (DEBUG_SYSIO, "gpgme:get_desired_thread_priority", 0, "%d (configured)", value); } return value; } /* The reader thread. Created on the fly by gpgme_io_read and * destroyed by destroy_reader. Note that this functions works with a * copy of the value of the HANDLE variable frm the FS_TABLE. */ static DWORD CALLBACK reader (void *arg) { struct reader_context_s *ctx = arg; int nbytes; DWORD nread; int sock; TRACE_BEG (DEBUG_SYSIO, "gpgme:reader", ctx->hdd, "hd=%p, sock=%d, thread=%p, refcount=%d", ctx->hdd->hd, ctx->hdd->sock, ctx->thread_hd, ctx->refcount); if (ctx->hdd->hd != INVALID_HANDLE_VALUE) sock = 0; else sock = 1; for (;;) { LOCK (ctx->mutex); /* Leave a 1 byte gap so that we can see whether it is empty or full. */ if ((ctx->writepos + 1) % READBUF_SIZE == ctx->readpos) { /* Wait for space. */ if (!ResetEvent (ctx->have_space_ev)) { TRACE_LOG ("ResetEvent failed: ec=%d", (int) GetLastError ()); } UNLOCK (ctx->mutex); TRACE_LOG ("waiting for space (refcnt=%d)", ctx->refcount); wait_for_single_object (ctx->have_space_ev, INFINITE); TRACE_LOG ("got space"); LOCK (ctx->mutex); } if (ctx->stop_me) { UNLOCK (ctx->mutex); break; } nbytes = (ctx->readpos + READBUF_SIZE - ctx->writepos - 1) % READBUF_SIZE; if (nbytes > READBUF_SIZE - ctx->writepos) nbytes = READBUF_SIZE - ctx->writepos; UNLOCK (ctx->mutex); TRACE_LOG ("%s %d bytes", sock? "receiving":"reading", nbytes); if (sock) { int n; n = recv (ctx->hdd->sock, ctx->buffer + ctx->writepos, nbytes, 0); if (n < 0) { ctx->error_code = (int) WSAGetLastError (); if (ctx->error_code == ERROR_BROKEN_PIPE) { ctx->eof = 1; TRACE_LOG ("got EOF (broken connection)"); } else { /* Check whether the shutdown triggered the error - no need to to print a warning in this case. */ if ( ctx->error_code == WSAECONNABORTED || ctx->error_code == WSAECONNRESET) { LOCK (ctx->mutex); if (ctx->stop_me) { UNLOCK (ctx->mutex); TRACE_LOG ("got shutdown"); break; } UNLOCK (ctx->mutex); } ctx->error = 1; TRACE_LOG ("recv error: ec=%d", ctx->error_code); } break; } nread = n; } else { if (!ReadFile (ctx->hdd->hd, ctx->buffer + ctx->writepos, nbytes, &nread, NULL)) { ctx->error_code = (int) GetLastError (); if (ctx->error_code == ERROR_BROKEN_PIPE) { ctx->eof = 1; TRACE_LOG ("got EOF (broken pipe)"); } else if (ctx->error_code == ERROR_OPERATION_ABORTED) { ctx->eof = 1; TRACE_LOG ("got EOF (closed by us)"); } else { ctx->error = 1; TRACE_LOG ("read error: ec=%d", ctx->error_code); } break; } } LOCK (ctx->mutex); if (ctx->stop_me) { UNLOCK (ctx->mutex); break; } if (!nread) { ctx->eof = 1; TRACE_LOG ("got eof"); UNLOCK (ctx->mutex); break; } TRACE_LOG ("got %lu bytes (refcnt=%d)", nread, ctx->refcount); ctx->writepos = (ctx->writepos + nread) % READBUF_SIZE; if (!SetEvent (ctx->have_data_ev)) { TRACE_LOG ("SetEvent (%p) failed: ec=%d", ctx->have_data_ev, (int) GetLastError ()); } UNLOCK (ctx->mutex); } /* Indicate that we have an error or EOF. */ if (!SetEvent (ctx->have_data_ev)) { TRACE_LOG ("SetEvent (%p) failed: ec=%d", ctx->have_data_ev, (int) GetLastError ()); } TRACE_LOG ("waiting for close"); wait_for_single_object (ctx->close_ev, INFINITE); release_hddesc (ctx->hdd); close_handle (ctx->close_ev); close_handle (ctx->have_data_ev); close_handle (ctx->have_space_ev); close_handle (ctx->thread_hd); DESTROY_LOCK (ctx->mutex); free (ctx); TRACE_SUC (""); return 0; } /* Create a new reader thread and return its context object. The * input is the handle descriptor HDD. This function may not call any * fd based functions because the caller already holds a lock on the * fd_table. */ static struct reader_context_s * create_reader (hddesc_t hdd) { struct reader_context_s *ctx; SECURITY_ATTRIBUTES sec_attr; DWORD tid; TRACE_BEG (DEBUG_SYSIO, "gpgme:create_reader", hdd, "handle=%p sock=%d refhdd=%d", hdd->hd, hdd->sock, hdd->refcount); memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; ctx = calloc (1, sizeof *ctx); if (!ctx) { TRACE_SYSERR (errno); return NULL; } ctx->hdd = ref_hddesc (hdd); ctx->refcount = 1; ctx->have_data_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL); if (ctx->have_data_ev) ctx->have_space_ev = CreateEvent (&sec_attr, FALSE, TRUE, NULL); if (ctx->have_space_ev) ctx->close_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL); if (!ctx->have_data_ev || !ctx->have_space_ev || !ctx->close_ev) { TRACE_LOG ("CreateEvent failed: ec=%d", (int) GetLastError ()); if (ctx->have_data_ev) close_handle (ctx->have_data_ev); if (ctx->have_space_ev) close_handle (ctx->have_space_ev); if (ctx->close_ev) close_handle (ctx->close_ev); release_hddesc (ctx->hdd); free (ctx); TRACE_SYSERR (EIO); return NULL; } INIT_LOCK (ctx->mutex); ctx->thread_hd = CreateThread (&sec_attr, 0, reader, ctx, 0, &tid); if (!ctx->thread_hd) { TRACE_LOG ("CreateThread failed: ec=%d", (int) GetLastError ()); DESTROY_LOCK (ctx->mutex); if (ctx->have_data_ev) close_handle (ctx->have_data_ev); if (ctx->have_space_ev) close_handle (ctx->have_space_ev); if (ctx->close_ev) close_handle (ctx->close_ev); release_hddesc (ctx->hdd); free (ctx); TRACE_SYSERR (EIO); return NULL; } else { /* We set the priority of the thread higher because we know that it only runs for a short time. This greatly helps to increase the performance of the I/O. */ SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ()); } TRACE_SUC (""); return ctx; } /* Prepare destruction of the reader thread for CTX. Returns 0 if a call to this function is sufficient and destroy_reader_finish shall not be called. */ static void destroy_reader (struct reader_context_s *ctx) { LOCK (ctx->mutex); ctx->refcount--; if (ctx->refcount != 0) { TRACE (DEBUG_SYSIO, "gpgme:destroy_reader", ctx, "hdd=%p refcount now %d", ctx->hdd, ctx->refcount); UNLOCK (ctx->mutex); return; } ctx->stop_me = 1; if (ctx->have_space_ev) SetEvent (ctx->have_space_ev); TRACE (DEBUG_SYSIO, "gpgme:destroy_reader", ctx, "hdd=%p close triggered", ctx->hdd); UNLOCK (ctx->mutex); /* The reader thread is usually blocking in recv or ReadFile. If the peer does not send an EOF or breaks the pipe the WFSO might get stuck waiting for the termination of the reader thread. This happens quite often with sockets, thus we definitely need to get out of the recv. A shutdown does this nicely. For handles (i.e. pipes) it would also be nice to cancel the operation, but such a feature is only available since Vista. Thus we need to dlopen that syscall. */ assert (ctx->hdd); if (ctx->hdd && ctx->hdd->hd != INVALID_HANDLE_VALUE) { _gpgme_w32_cancel_synchronous_io (ctx->thread_hd); } else if (ctx->hdd && ctx->hdd->sock != INVALID_SOCKET) { if (shutdown (ctx->hdd->sock, 2)) TRACE (DEBUG_SYSIO, "gpgme:destroy_reader", ctx, "shutdown socket %d failed: ec=%d", ctx->hdd->sock, (int) WSAGetLastError ()); } /* After setting this event CTX is void. */ SetEvent (ctx->close_ev); } /* Find a reader context or create a new one. Note that the reader * context will last until a _gpgme_io_close. NULL is returned for a * bad FD or for other errors. */ static struct reader_context_s * find_reader (int fd) { struct reader_context_s *rd = NULL; TRACE_BEG (DEBUG_SYSIO, "gpgme:find_reader", fd, ""); LOCK (fd_table_lock); if (fd < 0 || fd >= fd_table_size || !fd_table[fd].used) { UNLOCK (fd_table_lock); gpg_err_set_errno (EBADF); TRACE_SUC ("EBADF"); return NULL; } rd = fd_table[fd].reader; if (rd) { UNLOCK (fd_table_lock); TRACE_SUC ("rd=%p", rd); return rd; /* Return already initialized reader thread object. */ } /* Create a new reader thread. */ TRACE_LOG ("fd=%d -> hdd=%p dupfrom=%d creating reader", fd, fd_table[fd].hdd, fd_table[fd].dup_from); rd = create_reader (fd_table[fd].hdd); if (!rd) gpg_err_set_errno (EIO); else fd_table[fd].reader = rd; UNLOCK (fd_table_lock); TRACE_SUC ("rd=%p (new)", rd); return rd; } int _gpgme_io_read (int fd, void *buffer, size_t count) { int nread; struct reader_context_s *ctx; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_read", fd, "buffer=%p, count=%u", buffer, count); ctx = find_reader (fd); if (!ctx) return TRACE_SYSRES (-1); if (ctx->eof_shortcut) return TRACE_SYSRES (0); LOCK (ctx->mutex); if (ctx->readpos == ctx->writepos && !ctx->error) { /* No data available. */ UNLOCK (ctx->mutex); TRACE_LOG ("waiting for data from thread %p", ctx->thread_hd); wait_for_single_object (ctx->have_data_ev, INFINITE); TRACE_LOG ("data from thread %p available", ctx->thread_hd); LOCK (ctx->mutex); } if (ctx->readpos == ctx->writepos || ctx->error) { UNLOCK (ctx->mutex); ctx->eof_shortcut = 1; if (ctx->eof) return TRACE_SYSRES (0); if (!ctx->error) { TRACE_LOG ("EOF but ctx->eof flag not set"); return 0; } gpg_err_set_errno (ctx->error_code); return TRACE_SYSRES (-1); } nread = ctx->readpos < ctx->writepos ? ctx->writepos - ctx->readpos : READBUF_SIZE - ctx->readpos; if (nread > count) nread = count; memcpy (buffer, ctx->buffer + ctx->readpos, nread); ctx->readpos = (ctx->readpos + nread) % READBUF_SIZE; if (ctx->readpos == ctx->writepos && !ctx->eof) { if (!ResetEvent (ctx->have_data_ev)) { TRACE_LOG ("ResetEvent failed: ec=%d", (int) GetLastError ()); UNLOCK (ctx->mutex); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } } if (!SetEvent (ctx->have_space_ev)) { TRACE_LOG ("SetEvent (%p) failed: ec=%d", ctx->have_space_ev, (int) GetLastError ()); UNLOCK (ctx->mutex); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } UNLOCK (ctx->mutex); TRACE_LOGBUFX (buffer, nread); return TRACE_SYSRES (nread); } /* The writer does use a simple buffering strategy so that we are informed about write errors as soon as possible (i. e. with the the next call to the write function. */ static DWORD CALLBACK writer (void *arg) { struct writer_context_s *ctx = arg; DWORD nwritten; int sock; TRACE_BEG (DEBUG_SYSIO, "gpgme:writer", ctx->hdd, "hd=%p, sock=%d, thread=%p, refcount=%d", ctx->hdd->hd, ctx->hdd->sock, ctx->thread_hd, ctx->refcount); if (ctx->hdd->hd != INVALID_HANDLE_VALUE) sock = 0; else sock = 1; for (;;) { LOCK (ctx->mutex); if (ctx->stop_me && !ctx->nbytes) { UNLOCK (ctx->mutex); break; } if (!ctx->nbytes) { if (!SetEvent (ctx->is_empty)) TRACE_LOG ("SetEvent failed: ec=%d", (int) GetLastError ()); if (!ResetEvent (ctx->have_data)) TRACE_LOG ("ResetEvent failed: ec=%d", (int) GetLastError ()); UNLOCK (ctx->mutex); TRACE_LOG ("idle"); wait_for_single_object (ctx->have_data, INFINITE); TRACE_LOG ("got data to send"); LOCK (ctx->mutex); } if (ctx->stop_me && !ctx->nbytes) { UNLOCK (ctx->mutex); break; } UNLOCK (ctx->mutex); TRACE_LOG ("%s %d bytes", sock?"sending":"writing", ctx->nbytes); /* Note that CTX->nbytes is not zero at this point, because _gpgme_io_write always writes at least 1 byte before waking us up, unless CTX->stop_me is true, which we catch above. */ if (sock) { /* We need to try send first because a socket handle can't be used with WriteFile. */ int n; n = send (ctx->hdd->sock, ctx->buffer, ctx->nbytes, 0); if (n < 0) { ctx->error_code = (int) WSAGetLastError (); ctx->error = 1; TRACE_LOG ("send error: ec=%d", ctx->error_code); break; } nwritten = n; } else { if (!WriteFile (ctx->hdd->hd, ctx->buffer, ctx->nbytes, &nwritten, NULL)) { if (GetLastError () == ERROR_BUSY) { /* Probably stop_me is set now. */ TRACE_LOG ("pipe busy (unblocked?)"); continue; } ctx->error_code = (int) GetLastError (); ctx->error = 1; TRACE_LOG ("write error: ec=%d", ctx->error_code); break; } } TRACE_LOG ("wrote %d bytes", (int) nwritten); LOCK (ctx->mutex); ctx->nbytes -= nwritten; UNLOCK (ctx->mutex); } /* Indicate that we have an error. */ if (!SetEvent (ctx->is_empty)) TRACE_LOG ("SetEvent failed: ec=%d", (int) GetLastError ()); TRACE_LOG ("waiting for close"); wait_for_single_object (ctx->close_ev, INFINITE); if (ctx->nbytes) TRACE_LOG ("still %d bytes in buffer at close time", ctx->nbytes); release_hddesc (ctx->hdd); close_handle (ctx->close_ev); close_handle (ctx->have_data); close_handle (ctx->is_empty); close_handle (ctx->thread_hd); DESTROY_LOCK (ctx->mutex); free (ctx); TRACE_SUC (""); return 0; } static struct writer_context_s * create_writer (hddesc_t hdd) { struct writer_context_s *ctx; SECURITY_ATTRIBUTES sec_attr; DWORD tid; TRACE_BEG (DEBUG_SYSIO, "gpgme:create_writer", hdd, "handle=%p sock=%d refhdd=%d", hdd->hd, hdd->sock, hdd->refcount); memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; ctx = calloc (1, sizeof *ctx); if (!ctx) { TRACE_SYSERR (errno); return NULL; } ctx->hdd = ref_hddesc (hdd); ctx->refcount = 1; ctx->have_data = CreateEvent (&sec_attr, TRUE, FALSE, NULL); if (ctx->have_data) ctx->is_empty = CreateEvent (&sec_attr, TRUE, TRUE, NULL); if (ctx->is_empty) ctx->close_ev = CreateEvent (&sec_attr, TRUE, FALSE, NULL); if (!ctx->have_data || !ctx->is_empty || !ctx->close_ev) { TRACE_LOG ("CreateEvent failed: ec=%d", (int) GetLastError ()); if (ctx->have_data) close_handle (ctx->have_data); if (ctx->is_empty) close_handle (ctx->is_empty); if (ctx->close_ev) close_handle (ctx->close_ev); release_hddesc (ctx->hdd); free (ctx); TRACE_SYSERR (EIO); return NULL; } INIT_LOCK (ctx->mutex); ctx->thread_hd = CreateThread (&sec_attr, 0, writer, ctx, 0, &tid ); if (!ctx->thread_hd) { TRACE_LOG ("CreateThread failed: ec=%d", (int) GetLastError ()); DESTROY_LOCK (ctx->mutex); if (ctx->have_data) close_handle (ctx->have_data); if (ctx->is_empty) close_handle (ctx->is_empty); if (ctx->close_ev) close_handle (ctx->close_ev); release_hddesc (ctx->hdd); free (ctx); TRACE_SYSERR (EIO); return NULL; } else { /* We set the priority of the thread higher because we know that it only runs for a short time. This greatly helps to increase the performance of the I/O. */ SetThreadPriority (ctx->thread_hd, get_desired_thread_priority ()); } TRACE_SUC (""); return ctx; } static void destroy_writer (struct writer_context_s *ctx) { LOCK (ctx->mutex); ctx->refcount--; if (ctx->refcount != 0) { TRACE (DEBUG_SYSIO, "gpgme:destroy_writer", ctx, "hdd=%p refcount now %d", ctx->hdd, ctx->refcount); UNLOCK (ctx->mutex); return; } ctx->stop_me = 1; if (ctx->have_data) SetEvent (ctx->have_data); TRACE (DEBUG_SYSIO, "gpgme:destroy_writer", ctx, "hdd=%p close triggered", ctx->hdd); UNLOCK (ctx->mutex); /* Give the writer a chance to flush the buffer. */ wait_for_single_object (ctx->is_empty, INFINITE); /* After setting this event CTX is void. */ SetEvent (ctx->close_ev); } /* Find a writer context or create a new one. Note that the writer * context will last until a _gpgme_io_close. NULL is returned for a * bad FD or for other errors. */ static struct writer_context_s * find_writer (int fd) { struct writer_context_s *wt = NULL; TRACE_BEG (DEBUG_SYSIO, "gpgme:find_writer", fd, ""); LOCK (fd_table_lock); if (fd < 0 || fd >= fd_table_size || !fd_table[fd].used) { UNLOCK (fd_table_lock); gpg_err_set_errno (EBADF); TRACE_SUC ("EBADF"); return NULL; } wt = fd_table[fd].writer; if (wt) { UNLOCK (fd_table_lock); TRACE_SUC ("wt=%p", wt); return wt; /* Return already initialized writer thread object. */ } /* Create a new writer thread. */ TRACE_LOG ("fd=%d -> handle=%p socket=%d dupfrom=%d creating writer", fd, fd_table[fd].hdd->hd, fd_table[fd].hdd->sock, fd_table[fd].dup_from); wt = create_writer (fd_table[fd].hdd); if (!wt) gpg_err_set_errno (EIO); else fd_table[fd].writer = wt; UNLOCK (fd_table_lock); TRACE_SUC ("wt=%p (new)", wt); return wt; } int _gpgme_io_write (int fd, const void *buffer, size_t count) { struct writer_context_s *ctx; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_write", fd, "buffer=%p, count=%u", buffer, count); TRACE_LOGBUFX (buffer, count); if (count == 0) return TRACE_SYSRES (0); ctx = find_writer (fd); if (!ctx) return TRACE_SYSRES (-1); LOCK (ctx->mutex); if (!ctx->error && ctx->nbytes) { /* Bytes are pending for send. */ /* Reset the is_empty event. Better safe than sorry. */ if (!ResetEvent (ctx->is_empty)) { TRACE_LOG ("ResetEvent failed: ec=%d", (int) GetLastError ()); UNLOCK (ctx->mutex); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } UNLOCK (ctx->mutex); TRACE_LOG ("waiting for empty buffer in thread %p", ctx->thread_hd); wait_for_single_object (ctx->is_empty, INFINITE); TRACE_LOG ("thread %p buffer is empty", ctx->thread_hd); LOCK (ctx->mutex); } if (ctx->error) { UNLOCK (ctx->mutex); if (ctx->error_code == ERROR_NO_DATA) gpg_err_set_errno (EPIPE); else gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } /* If no error occurred, the number of bytes in the buffer must be zero. */ assert (!ctx->nbytes); if (count > WRITEBUF_SIZE) count = WRITEBUF_SIZE; memcpy (ctx->buffer, buffer, count); ctx->nbytes = count; /* We have to reset the is_empty event early, because it is also * used by the select() implementation to probe the channel. */ if (!ResetEvent (ctx->is_empty)) { TRACE_LOG ("ResetEvent failed: ec=%d", (int) GetLastError ()); UNLOCK (ctx->mutex); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } if (!SetEvent (ctx->have_data)) { TRACE_LOG ("SetEvent failed: ec=%d", (int) GetLastError ()); UNLOCK (ctx->mutex); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } UNLOCK (ctx->mutex); return TRACE_SYSRES ((int) count); } int _gpgme_io_pipe (int filedes[2], int inherit_idx) { int rfd; int wfd; HANDLE rh; HANDLE wh; hddesc_t rhdesc; hddesc_t whdesc; SECURITY_ATTRIBUTES sec_attr; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_pipe", filedes, "inherit_idx=%i (GPGME uses it for %s)", inherit_idx, inherit_idx ? "reading" : "writing"); /* Get a new empty file descriptor. */ rfd = new_fd (); if (rfd == -1) return TRACE_SYSRES (-1); wfd = new_fd (); if (wfd == -1) { release_fd (rfd); return TRACE_SYSRES (-1); } rhdesc = new_hddesc (); if (!rhdesc) { release_fd (rfd); release_fd (wfd); return TRACE_SYSRES (-1); } whdesc = new_hddesc (); if (!whdesc) { release_fd (rfd); release_fd (wfd); release_hddesc (rhdesc); return TRACE_SYSRES (-1); } /* Create a pipe. */ memset (&sec_attr, 0, sizeof (sec_attr)); sec_attr.nLength = sizeof (sec_attr); sec_attr.bInheritHandle = FALSE; if (!CreatePipe (&rh, &wh, &sec_attr, PIPEBUF_SIZE)) { TRACE_LOG ("CreatePipe failed: ec=%d", (int) GetLastError ()); release_fd (rfd); release_fd (wfd); release_hddesc (rhdesc); release_hddesc (whdesc); gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } /* Make one end inheritable. */ if (inherit_idx == 0) { HANDLE hd; if (!DuplicateHandle (GetCurrentProcess(), rh, GetCurrentProcess(), &hd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { TRACE_LOG ("DuplicateHandle failed: ec=%d", (int) GetLastError ()); release_fd (rfd); release_fd (wfd); close_handle (rh); close_handle (wh); release_hddesc (rhdesc); release_hddesc (whdesc); gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } close_handle (rh); rh = hd; } else if (inherit_idx == 1) { HANDLE hd; if (!DuplicateHandle( GetCurrentProcess(), wh, GetCurrentProcess(), &hd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { TRACE_LOG ("DuplicateHandle failed: ec=%d", (int) GetLastError ()); release_fd (rfd); release_fd (wfd); close_handle (rh); close_handle (wh); release_hddesc (rhdesc); release_hddesc (whdesc); gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } close_handle (wh); wh = hd; } /* Put the HANDLEs of the new pipe into the file descriptor table. * Note that we don't need to lock the table because we have just * acquired these two fresh fds and they are not known by any other * thread. */ fd_table[rfd].want_reader = 1; ref_hddesc (rhdesc)->hd = rh; fd_table[rfd].hdd = rhdesc; fd_table[wfd].want_writer = 1; ref_hddesc (whdesc)->hd = wh; fd_table[wfd].hdd = whdesc; filedes[0] = rfd; filedes[1] = wfd; TRACE_SUC ("read=0x%x (hdd=%p,hd=%p), write=0x%x (hdd=%p,hd=%p)", rfd, fd_table[rfd].hdd, fd_table[rfd].hdd->hd, wfd, fd_table[wfd].hdd, fd_table[wfd].hdd->hd); return 0; } /* Close out File descriptor FD. */ int _gpgme_io_close (int fd) { _gpgme_close_notify_handler_t handler = NULL; void *value = NULL; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_close", fd, ""); if (fd < 0) { gpg_err_set_errno (EBADF); return TRACE_SYSRES (-1); } LOCK (fd_table_lock); /* Check the size in the locked state because we may eventually add * code to change that size. */ if (fd >= fd_table_size || !fd_table[fd].used) { UNLOCK (fd_table_lock); gpg_err_set_errno (EBADF); return TRACE_SYSRES (-1); } TRACE_LOG ("hdd=%p dupfrom=%d", fd_table[fd].hdd, fd_table[fd].dup_from); if (fd_table[fd].reader) { TRACE_LOG ("destroying reader %p", fd_table[fd].reader); destroy_reader (fd_table[fd].reader); fd_table[fd].reader = NULL; } if (fd_table[fd].writer) { TRACE_LOG ("destroying writer %p", fd_table[fd].writer); destroy_writer (fd_table[fd].writer); fd_table[fd].writer = NULL; } /* The handler may not use any fd function because the table is * locked. Can we avoid this? */ handler = fd_table[fd].notify.handler; value = fd_table[fd].notify.value; /* Release our reference to the handle descriptor. Note that if no * reader or writer threads were used this release will also take * care that the handle descriptor is closed * (i.e. CloseHandle(hdd->hd) is called). */ release_hddesc (fd_table[fd].hdd); fd_table[fd].hdd = NULL; UNLOCK (fd_table_lock); /* Run the notification callback. */ if (handler) handler (fd, value); release_fd (fd); /* FIXME: We should have a release_fd_locked () */ return TRACE_SYSRES (0); } /* Set a close notification callback which is called right after FD * has been closed but before its slot (ie. the FD number) is being * released. The HANDLER may thus use the provided value of the FD * but it may not pass it to any I/O functions. Note: Only the last * handler set for an FD is used. */ int _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler, void *value) { TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_set_close_notify", fd, "close_handler=%p/%p", handler, value); LOCK (fd_table_lock); if (fd < 0 || fd >= fd_table_size || !fd_table[fd].used) { UNLOCK (fd_table_lock); gpg_err_set_errno (EBADF); return TRACE_SYSRES (-1);; } fd_table[fd].notify.handler = handler; fd_table[fd].notify.value = value; UNLOCK (fd_table_lock); return TRACE_SYSRES (0); } int _gpgme_io_set_nonblocking (int fd) { TRACE (DEBUG_SYSIO, "_gpgme_io_set_nonblocking", fd, ""); return 0; } static char * build_commandline (char **argv) { int i; int n = 0; char *buf; char *p; /* We have to quote some things because under Windows the program parses the commandline and does some unquoting. We enclose the whole argument in double-quotes, and escape literal double-quotes as well as backslashes with a backslash. We end up with a trailing space at the end of the line, but that is harmless. */ for (i = 0; argv[i]; i++) { p = argv[i]; /* The leading double-quote. */ n++; while (*p) { /* An extra one for each literal that must be escaped. */ if (*p == '\\' || *p == '"') n++; n++; p++; } /* The trailing double-quote and the delimiter. */ n += 2; } /* And a trailing zero. */ n++; buf = p = malloc (n); if (!buf) return NULL; for (i = 0; argv[i]; i++) { char *argvp = argv[i]; *(p++) = '"'; while (*argvp) { if (*argvp == '\\' || *argvp == '"') *(p++) = '\\'; *(p++) = *(argvp++); } *(p++) = '"'; *(p++) = ' '; } *(p++) = 0; return buf; } 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) { PROCESS_INFORMATION pi = { NULL, /* returns process handle */ 0, /* returns primary thread handle */ 0, /* returns pid */ 0 /* returns tid */ }; int i; SECURITY_ATTRIBUTES sec_attr; STARTUPINFOA si; int cr_flags = CREATE_DEFAULT_ERROR_MODE; char **args; char *arg_string; /* FIXME. */ int debug_me = 0; int tmp_fd; char *tmp_name; const char *spawnhelper; static int spawn_warning_shown = 0; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_spawn", path, "path=%s", path); (void)atfork; (void)atforkvalue; i = 0; while (argv[i]) { TRACE_LOG ("argv[%2i] = %s", i, argv[i]); i++; } /* We do not inherit any handles by default, and just insert those handles we want the child to have afterwards. But some handle values occur on the command line, and we need to move stdin/out/err to the right location. So we use a wrapper program which gets the information from a temporary file. */ if (_gpgme_mkstemp (&tmp_fd, &tmp_name) < 0) { TRACE_LOG ("_gpgme_mkstemp failed: %s", strerror (errno)); return TRACE_SYSRES (-1); } TRACE_LOG ("tmp_name = %s", tmp_name); args = calloc (2 + i + 1, sizeof (*args)); args[0] = (char *) _gpgme_get_w32spawn_path (); args[1] = tmp_name; args[2] = (char *)path; memcpy (&args[3], &argv[1], i * sizeof (*args)); memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; arg_string = build_commandline (args); free (args); if (!arg_string) { close (tmp_fd); DeleteFileA (tmp_name); free (tmp_name); return TRACE_SYSRES (-1); } memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = debug_me ? SW_SHOW : SW_HIDE; si.hStdInput = INVALID_HANDLE_VALUE; si.hStdOutput = INVALID_HANDLE_VALUE; si.hStdError = INVALID_HANDLE_VALUE; cr_flags |= CREATE_SUSPENDED; if ((flags & IOSPAWN_FLAG_DETACHED)) cr_flags |= DETACHED_PROCESS; cr_flags |= GetPriorityClass (GetCurrentProcess ()); spawnhelper = _gpgme_get_w32spawn_path (); if (!spawnhelper) { /* This is a common mistake for new users of gpgme not to include gpgme-w32spawn.exe with their binary. So we want to make this transparent to developers. If users have somehow messed up their installation this should also be properly communicated as otherwise calls to gnupg will result in unsupported protocol errors that do not explain a lot. */ if (!spawn_warning_shown) { char *msg; gpgrt_asprintf (&msg, "gpgme-w32spawn.exe was not found in the " "detected installation directory of GpgME" "\n\t\"%s\"\n\n" "Crypto operations will not work.\n\n" "If you see this it indicates a problem " "with your installation.\n" "Please report the problem to your " "distributor of GpgME.\n\n" "Developer's Note: The install dir can be " "manually set with: gpgme_set_global_flag", _gpgme_get_inst_dir ()); MessageBoxA (NULL, msg, "GpgME not installed correctly", MB_OK); gpgrt_free (msg); spawn_warning_shown = 1; } gpg_err_set_errno (EIO); close (tmp_fd); DeleteFileA (tmp_name); free (tmp_name); return TRACE_SYSRES (-1); } if (!_gpgme_create_process_utf8 (spawnhelper, arg_string, &sec_attr, /* process security attributes */ &sec_attr, /* thread security attributes */ FALSE, /* inherit handles */ cr_flags, /* creation flags */ NULL, /* environment */ NULL, /* use current drive/directory */ &si, /* startup information */ &pi)) /* returns process information */ { int lasterr = (int)GetLastError (); TRACE_LOG ("CreateProcess failed: ec=%d", lasterr); free (arg_string); close (tmp_fd); DeleteFileA (tmp_name); free (tmp_name); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } if (flags & IOSPAWN_FLAG_ALLOW_SET_FG) _gpgme_allow_set_foreground_window ((pid_t)pi.dwProcessId); /* Insert the inherited handles. */ LOCK (fd_table_lock); for (i = 0; fd_list[i].fd != -1; i++) { int fd = fd_list[i].fd; HANDLE ohd = INVALID_HANDLE_VALUE; HANDLE hd = INVALID_HANDLE_VALUE; /* Make it inheritable for the wrapper process. */ if (fd >= 0 && fd < fd_table_size && fd_table[fd].used && fd_table[fd].hdd) ohd = fd_table[fd].hdd->hd; if (!DuplicateHandle (GetCurrentProcess(), ohd, pi.hProcess, &hd, 0, TRUE, DUPLICATE_SAME_ACCESS)) { TRACE_LOG ("DuplicateHandle failed: ec=%d", (int) GetLastError ()); TerminateProcess (pi.hProcess, 0); /* Just in case TerminateProcess didn't work, let the process fail on its own. */ ResumeThread (pi.hThread); close_handle (pi.hThread); close_handle (pi.hProcess); close (tmp_fd); DeleteFileA (tmp_name); free (tmp_name); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); UNLOCK (fd_table_lock); return TRACE_SYSRES (-1); } /* Return the child name of this handle. */ fd_list[i].peer_name = handle_to_fd (hd); } /* Write the handle translation information to the temporary file. */ { /* Hold roughly MAX_TRANS quadruplets of 64 bit numbers in hex notation: "0xFEDCBA9876543210" with an extra white space after every quadruplet. 10*(19*4 + 1) - 1 = 769. This plans ahead for a time when a HANDLE is 64 bit. */ #define BUFFER_MAX 810 char line[BUFFER_MAX + 1]; int res; int written; size_t len; if (flags) snprintf (line, BUFFER_MAX, "~%i \n", flags); else strcpy (line, "\n"); for (i = 0; fd_list[i].fd != -1; i++) { /* Strip the newline. */ len = strlen (line) - 1; /* Format is: Local name, stdin/stdout/stderr, peer name, argv idx. */ snprintf (&line[len], BUFFER_MAX - len, "0x%x %d 0x%x %d \n", fd_list[i].fd, fd_list[i].dup_to, fd_list[i].peer_name, fd_list[i].arg_loc); /* Rather safe than sorry. */ line[BUFFER_MAX - 1] = '\n'; line[BUFFER_MAX] = '\0'; } len = strlen (line); written = 0; do { res = write (tmp_fd, &line[written], len - written); if (res > 0) written += res; } while (res > 0 || (res < 0 && errno == EAGAIN)); } close (tmp_fd); /* The temporary file is deleted by the gpgme-w32spawn process (hopefully). */ free (tmp_name); free (arg_string); UNLOCK (fd_table_lock); TRACE_LOG ("CreateProcess ready: hProcess=%p, hThread=%p, " "dwProcessID=%d, dwThreadId=%d", pi.hProcess, pi.hThread, (int) pi.dwProcessId, (int) pi.dwThreadId); if (r_pid) *r_pid = (pid_t)pi.dwProcessId; if (ResumeThread (pi.hThread) == (DWORD)(-1)) TRACE_LOG ("ResumeThread failed: ec=%d", (int) GetLastError ()); close_handle (pi.hThread); TRACE_LOG ("process=%p", pi.hProcess); /* We don't need to wait for the process. */ close_handle (pi.hProcess); if (! (flags & IOSPAWN_FLAG_NOCLOSE)) { for (i = 0; fd_list[i].fd != -1; i++) _gpgme_io_close (fd_list[i].fd); } for (i = 0; fd_list[i].fd != -1; i++) if (fd_list[i].dup_to == -1) TRACE_LOG ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].peer_name); else TRACE_LOG ("fd[%i] = 0x%x -> 0x%x (std%s)", i, fd_list[i].fd, fd_list[i].peer_name, (fd_list[i].dup_to == 0) ? "in" : ((fd_list[i].dup_to == 1) ? "out" : "err")); return TRACE_SYSRES (0); } /* 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) { HANDLE waitbuf[MAXIMUM_WAIT_OBJECTS]; int waitidx[MAXIMUM_WAIT_OBJECTS]; int code; int nwait; int i; int any; int count; void *dbg_help; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_select", fds, "nfds=%u, nonblock=%u", nfds, nonblock); #if 0 restart: #endif TRACE_SEQ (dbg_help, "select on [ "); any = 0; nwait = 0; count = 0; for (i=0; i < nfds; i++) { if (fds[i].fd == -1) continue; fds[i].signaled = 0; if (fds[i].for_read || fds[i].for_write) { if (fds[i].for_read) { /* FIXME: A find_reader_locked() along with separate * lock calls might be a better appaoched here. */ struct reader_context_s *ctx = find_reader (fds[i].fd); if (!ctx) TRACE_LOG ("error: no reader for FD 0x%x (ignored)", fds[i].fd); else { if (nwait >= DIM (waitbuf)) { TRACE_END (dbg_help, "oops ]"); TRACE_LOG ("Too many objects for WFMO!"); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } waitidx[nwait] = i; waitbuf[nwait++] = ctx->have_data_ev; } TRACE_ADD1 (dbg_help, "r0x%x ", fds[i].fd); any = 1; } else if (fds[i].for_write) { struct writer_context_s *ctx = find_writer (fds[i].fd); if (!ctx) TRACE_LOG ("error: no writer for FD 0x%x (ignored)", fds[i].fd); else { if (nwait >= DIM (waitbuf)) { TRACE_END (dbg_help, "oops ]"); TRACE_LOG ("Too many objects for WFMO!"); /* FIXME: Should translate the error code. */ gpg_err_set_errno (EIO); return TRACE_SYSRES (-1); } waitidx[nwait] = i; waitbuf[nwait++] = ctx->is_empty; } TRACE_ADD1 (dbg_help, "w0x%x ", fds[i].fd); any = 1; } } } TRACE_END (dbg_help, "]"); if (!any) return TRACE_SYSRES (0); code = WaitForMultipleObjects (nwait, waitbuf, 0, nonblock ? 0 : 1000); if (code < WAIT_OBJECT_0 + nwait) { /* The WFMO is a really silly function: It does return either the index of the signaled object or if 2 objects have been signalled at the same time, the index of the object with the lowest object is returned - so and how do we find out how many objects have been signaled?. The only solution I can imagine is to test each object starting with the returned index individually - how dull. */ any = 0; for (i = code - WAIT_OBJECT_0; i < nwait; i++) { if (wait_for_single_object (waitbuf[i], 0) == WAIT_OBJECT_0) { assert (waitidx[i] >=0 && waitidx[i] < nfds); fds[waitidx[i]].signaled = 1; any = 1; count++; } } if (!any) { TRACE_LOG ("no signaled objects found after WFMO"); count = -1; } } else if (code == WAIT_TIMEOUT) TRACE_LOG ("WFMO timed out"); else if (code == WAIT_FAILED) { int le = (int) GetLastError (); #if 0 if (le == ERROR_INVALID_HANDLE) { int k; int j = handle_to_fd (waitbuf[i]); TRACE_LOG ("WFMO invalid handle %d removed", j); for (k = 0 ; k < nfds; k++) { if (fds[k].fd == j) { fds[k].for_read = fds[k].for_write = 0; goto restart; } } TRACE_LOG (" oops, or not???"); } #endif TRACE_LOG ("WFMO failed: %d", le); count = -1; } else { TRACE_LOG ("WFMO returned %d", code); count = -1; } if (count > 0) { TRACE_SEQ (dbg_help, "select OK [ "); for (i = 0; i < nfds; i++) { if (fds[i].fd == -1) continue; if ((fds[i].for_read || fds[i].for_write) && fds[i].signaled) TRACE_ADD2 (dbg_help, "%c0x%x ", fds[i].for_read ? 'r' : 'w', fds[i].fd); } TRACE_END (dbg_help, "]"); } if (count < 0) { /* FIXME: Should determine a proper error code. */ gpg_err_set_errno (EIO); } return TRACE_SYSRES (count); } void _gpgme_io_subsystem_init (void) { /* Nothing to do. */ } /* Write the printable version of FD to BUFFER which has an allocated * length of BUFLEN. The printable version is the representation on * the command line that the child process expects. Note that this * works closely together with the gpgme-32spawn wrapper process which * translates these command line args to the real handles. */ int _gpgme_io_fd2str (char *buffer, int buflen, int fd) { return snprintf (buffer, buflen, "%d", fd); } int _gpgme_io_dup (int fd) { int newfd; struct reader_context_s *rd_ctx; struct writer_context_s *wt_ctx; int want_reader, want_writer; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_dup", fd, ""); LOCK (fd_table_lock); if (fd < 0 || fd >= fd_table_size || !fd_table[fd].used) { UNLOCK (fd_table_lock); gpg_err_set_errno (EBADF); return TRACE_SYSRES (-1); } newfd = new_fd(); if (newfd == -1) { UNLOCK (fd_table_lock); gpg_err_set_errno (EMFILE); return TRACE_SYSRES (-1); } fd_table[newfd].hdd = ref_hddesc (fd_table[fd].hdd); fd_table[newfd].dup_from = fd; want_reader = fd_table[fd].want_reader; want_writer = fd_table[fd].want_writer; UNLOCK (fd_table_lock); rd_ctx = want_reader? find_reader (fd) : NULL; if (rd_ctx) { /* NEWFD initializes a freshly allocated slot and does not need * to be locked. */ LOCK (rd_ctx->mutex); rd_ctx->refcount++; UNLOCK (rd_ctx->mutex); fd_table[newfd].reader = rd_ctx; } wt_ctx = want_writer? find_writer (fd) : NULL; if (wt_ctx) { LOCK (wt_ctx->mutex); wt_ctx->refcount++; UNLOCK (wt_ctx->mutex); fd_table[newfd].writer = wt_ctx; } return TRACE_SYSRES (newfd); } /* The following interface is only useful for GPGME Glib and Qt. */ /* Compatibility interface, obsolete. */ void * gpgme_get_giochannel (int fd) { (void)fd; return NULL; } /* Look up the giochannel or qiodevice for file descriptor FD. */ void * gpgme_get_fdptr (int fd) { (void)fd; return NULL; } static int wsa2errno (int err) { switch (err) { case WSAENOTSOCK: return EINVAL; case WSAEWOULDBLOCK: return EAGAIN; case ERROR_BROKEN_PIPE: return EPIPE; case WSANOTINITIALISED: return ENOSYS; default: return EIO; } } int _gpgme_io_socket (int domain, int type, int proto) { int res; int fd; hddesc_t hdd; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_socket", domain, "type=%i, protp=%i", type, proto); fd = new_fd(); if (fd == -1) return TRACE_SYSRES (-1); hdd = new_hddesc (); if (!hdd) { UNLOCK (fd_table_lock); release_fd (fd); gpg_err_set_errno (ENOMEM); return TRACE_SYSRES (-1); } res = socket (domain, type, proto); if (res == INVALID_SOCKET) { release_fd (fd); gpg_err_set_errno (wsa2errno (WSAGetLastError ())); return TRACE_SYSRES (-1); } ref_hddesc (hdd)->sock = res; fd_table[fd].hdd = hdd; fd_table[fd].want_reader = 1; fd_table[fd].want_writer = 1; TRACE_SUC ("hdd=%p, socket=0x%x (0x%x)", hdd, fd, hdd->sock); return fd; } int _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen) { int res; int sock; TRACE_BEG (DEBUG_SYSIO, "_gpgme_io_connect", fd, "addr=%p, addrlen=%i", addr, addrlen); LOCK (fd_table_lock); if (fd < 0 || fd >= fd_table_size || !fd_table[fd].used || !fd_table[fd].hdd) { gpg_err_set_errno (EBADF); UNLOCK (fd_table_lock); return TRACE_SYSRES (-1); } sock = fd_table[fd].hdd->sock; UNLOCK (fd_table_lock); res = connect (sock, addr, addrlen); if (res) { gpg_err_set_errno (wsa2errno (WSAGetLastError ())); return TRACE_SYSRES (-1); } TRACE_SUC (""); return 0; }