diff --git a/src/engine-assuan.c b/src/engine-assuan.c index 681be62c..c4a84a39 100644 --- a/src/engine-assuan.c +++ b/src/engine-assuan.c @@ -1,791 +1,791 @@ /* 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 <http://www.gnu.org/licenses/>. */ /* Note: This engine requires a modern Assuan server which uses gpg-error codes. In particular there is no backward compatible mapping of old Assuan error codes implemented. */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <string.h> #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #include <assert.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include <errno.h> #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "priv-io.h" #include "sema.h" #include "assuan.h" #include "debug.h" #include "engine-backend.h" typedef struct { int fd; /* FD we talk about. */ int server_fd;/* Server FD for this connection. */ int dir; /* Inbound/Outbound, maybe given implicit? */ void *data; /* Handler-specific data. */ void *tag; /* ID from the user for gpgme_remove_io_callback. */ } iocb_data_t; /* Engine instance data. */ struct engine_llass { assuan_context_t assuan_ctx; int lc_ctype_set; int lc_messages_set; iocb_data_t status_cb; struct gpgme_io_cbs io_cbs; /* Hack for old opassuan.c interface, see there the result struct. */ gpg_error_t last_op_err; /* User provided callbacks. */ struct { gpgme_assuan_data_cb_t data_cb; void *data_cb_value; gpgme_assuan_inquire_cb_t inq_cb; void *inq_cb_value; gpgme_assuan_status_cb_t status_cb; void *status_cb_value; } user; /* Option flags. */ struct { int gpg_agent:1; /* Assume this is a gpg-agent connection. */ } opt; }; 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) { - return strdup ("1.0.0"); + return NULL; } static const char * llass_get_req_version (void) { - return "1.0.0"; + return NULL; } static void close_notify_handler (int fd, void *opaque) { engine_llass_t llass = opaque; assert (fd != -1); if (llass->status_cb.fd == fd) { if (llass->status_cb.tag) llass->io_cbs.remove (llass->status_cb.tag); llass->status_cb.fd = -1; llass->status_cb.tag = NULL; } } static gpgme_error_t llass_cancel (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); if (llass->assuan_ctx) { assuan_release (llass->assuan_ctx); llass->assuan_ctx = NULL; } return 0; } static gpgme_error_t llass_cancel_op (void *engine) { engine_llass_t llass = engine; if (!llass) return gpg_error (GPG_ERR_INV_VALUE); if (llass->status_cb.fd != -1) _gpgme_io_close (llass->status_cb.fd); return 0; } static void llass_release (void *engine) { engine_llass_t llass = engine; if (!llass) return; llass_cancel (engine); free (llass); } /* Create a new instance. If HOME_DIR is NULL standard options for use with gpg-agent are issued. */ static gpgme_error_t llass_new (void **engine, const char *file_name, const char *home_dir, const char *version) { gpgme_error_t err = 0; engine_llass_t llass; char *optstr; (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 (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); free (optstr); if (err) goto leave; } } if (llass->opt.gpg_agent && isatty (1)) { int rc; char dft_ttyname[64]; char *dft_ttytype = NULL; 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 (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); free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (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); 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; } 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; 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 (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); 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) { TRACE1 (DEBUG_CTX, "gpgme:llass_status_handler", llass, "fd 0x%x: EAGAIN reading assuan line (ignored)", fd); err = 0; continue; } TRACE2 (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); TRACE2 (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); TRACE2 (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); TRACE3 (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); TRACE2 (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] == ' ')) { TRACE1 (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_BEG2 (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; /* We need to know the fd used by assuan for reads. We do this by using the assumption that the first returned fd from assuan_get_active_fds() is always this one. */ nfds = assuan_get_active_fds (llass->assuan_ctx, 0 /* read fds */, afdlist, DIM (afdlist)); if (nfds < 1) return gpg_error (GPG_ERR_GENERAL); /* FIXME */ /* For now... */ for (i = 0; i < nfds; i++) fdlist[i] = (int) afdlist[i]; /* We "duplicate" the file descriptor, so we can close it here (we can't close fdlist[0], as that is closed by libassuan, and closing it here might cause libassuan to close some unrelated FD later). Alternatively, we could special case status_fd and register/unregister it manually as needed, but this increases code duplication and is more complicated as we can not use the close notifications etc. A third alternative would be to let Assuan know that we closed the FD, but that complicates the Assuan interface. */ llass->status_cb.fd = _gpgme_io_dup (fdlist[0]); if (llass->status_cb.fd < 0) return gpg_error_from_syserror (); if (_gpgme_io_set_close_notify (llass->status_cb.fd, close_notify_handler, llass)) { _gpgme_io_close (llass->status_cb.fd); llass->status_cb.fd = -1; return gpg_error (GPG_ERR_GENERAL); } err = add_io_cb (llass, &llass->status_cb, llass_status_handler); if (!err) err = assuan_write_line (llass->assuan_ctx, command); /* FIXME: If *command == '#' no answer is expected. */ if (!err) llass_io_event (llass, GPGME_EVENT_START, NULL); return err; } static gpgme_error_t llass_transact (void *engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { engine_llass_t llass = engine; gpgme_error_t err; if (!llass || !command || !*command) return gpg_error (GPG_ERR_INV_VALUE); llass->user.data_cb = data_cb; llass->user.data_cb_value = data_cb_value; llass->user.inq_cb = inq_cb; llass->user.inq_cb_value = inq_cb_value; llass->user.status_cb = status_cb; llass->user.status_cb_value = status_cb_value; err = start (llass, command); return err; } static void llass_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) { engine_llass_t llass = engine; llass->io_cbs = *io_cbs; } static void llass_io_event (void *engine, gpgme_event_io_t type, void *type_data) { engine_llass_t llass = engine; TRACE3 (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 */ NULL, /* decrypt */ NULL, /* decrypt_verify */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ llass_transact, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ 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-spawn.c b/src/engine-spawn.c index c01b50e2..e2ee8ba6 100644 --- a/src/engine-spawn.c +++ b/src/engine-spawn.c @@ -1,477 +1,477 @@ /* 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 <http://www.gnu.org/licenses/>. */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <errno.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #ifdef HAVE_LOCALE_H #include <locale.h> #endif #include "gpgme.h" #include "util.h" #include "ops.h" #include "wait.h" #include "context.h" /*temp hack until we have GpmeData methods to do I/O */ #include "priv-io.h" #include "sema.h" #include "debug.h" #include "engine-backend.h" /* This type is used to build a list of data sources/sinks. */ struct datalist_s { struct datalist_s *next; gpgme_data_t data; /* The data object. */ int inbound; /* True if this is used for reading from the peer. */ int dup_to; /* The fd used by the peer. */ }; struct fd_data_map_s { gpgme_data_t data; int inbound; /* True if this is used for reading from the peer. */ int dup_to; /* Dup the fd to that one. */ int fd; /* The fd to use. */ int peer_fd; /* The other side of the pipe. */ void *tag; /* Tag used by the I/O callback. */ }; struct engine_spawn { struct datalist_s *arglist; struct datalist_s **argtail; struct fd_data_map_s *fd_data_map; struct gpgme_io_cbs io_cbs; }; typedef struct engine_spawn *engine_spawn_t; static void engspawn_io_event (void *engine, gpgme_event_io_t type, void *type_data); static gpgme_error_t engspawn_cancel (void *engine); static void close_notify_handler (int fd, void *opaque) { engine_spawn_t esp = opaque; int i; assert (fd != -1); if (esp->fd_data_map) { for (i = 0; esp->fd_data_map[i].data; i++) { if (esp->fd_data_map[i].fd == fd) { if (esp->fd_data_map[i].tag) (*esp->io_cbs.remove) (esp->fd_data_map[i].tag); esp->fd_data_map[i].fd = -1; break; } if (esp->fd_data_map[i].peer_fd == fd) { esp->fd_data_map[i].peer_fd = -1; break; } } } } static gpgme_error_t add_data (engine_spawn_t esp, gpgme_data_t data, int dup_to, int inbound) { struct datalist_s *a; assert (esp); assert (data); a = malloc (sizeof *a); if (!a) return gpg_error_from_syserror (); a->next = NULL; a->data = data; a->inbound = inbound; a->dup_to = dup_to; *esp->argtail = a; esp->argtail = &a->next; return 0; } static void free_fd_data_map (struct fd_data_map_s *fd_data_map) { int i; if (!fd_data_map) return; for (i = 0; fd_data_map[i].data; i++) { if (fd_data_map[i].fd != -1) _gpgme_io_close (fd_data_map[i].fd); if (fd_data_map[i].peer_fd != -1) _gpgme_io_close (fd_data_map[i].peer_fd); /* Don't release data because this is only a reference. */ } free (fd_data_map); } static gpgme_error_t build_fd_data_map (engine_spawn_t esp) { struct datalist_s *a; size_t datac; int fds[2]; for (datac = 0, a = esp->arglist; a; a = a->next) if (a->data) datac++; free_fd_data_map (esp->fd_data_map); esp->fd_data_map = calloc (datac + 1, sizeof *esp->fd_data_map); if (!esp->fd_data_map) return gpg_error_from_syserror (); for (datac = 0, a = esp->arglist; a; a = a->next) { assert (a->data); if (_gpgme_io_pipe (fds, a->inbound ? 1 : 0) == -1) { free (esp->fd_data_map); esp->fd_data_map = NULL; return gpg_error_from_syserror (); } if (_gpgme_io_set_close_notify (fds[0], close_notify_handler, esp) || _gpgme_io_set_close_notify (fds[1], close_notify_handler, esp)) { /* FIXME: Need error cleanup. */ return gpg_error (GPG_ERR_GENERAL); } esp->fd_data_map[datac].inbound = a->inbound; if (a->inbound) { esp->fd_data_map[datac].fd = fds[0]; esp->fd_data_map[datac].peer_fd = fds[1]; } else { esp->fd_data_map[datac].fd = fds[1]; esp->fd_data_map[datac].peer_fd = fds[0]; } esp->fd_data_map[datac].data = a->data; esp->fd_data_map[datac].dup_to = a->dup_to; datac++; } return 0; } static gpgme_error_t add_io_cb (engine_spawn_t esp, int fd, int dir, gpgme_io_cb_t handler, void *data, void **tag) { gpgme_error_t err; err = (*esp->io_cbs.add) (esp->io_cbs.add_priv, fd, dir, handler, data, tag); if (err) return err; if (!dir) /* Fixme: Kludge around poll() problem. */ err = _gpgme_io_set_nonblocking (fd); return err; } static gpgme_error_t engspawn_start (engine_spawn_t esp, const char *file, const char *argv[], unsigned int flags) { gpgme_error_t err; int i, n; int status; struct spawn_fd_item_s *fd_list; pid_t pid; unsigned int spflags; 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; 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 strdup ("1.0.0"); + return NULL; } static const char * engspawn_get_req_version (void) { - return "1.0.0"; + 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; TRACE3 (DEBUG_ENGINE, "gpgme:engspawn_io_event", esp, "event %p, type %d, type_data %p", esp->io_cbs.event, type, type_data); if (esp->io_cbs.event) (*esp->io_cbs.event) (esp->io_cbs.event_priv, type, type_data); } static gpgme_error_t engspawn_cancel (void *engine) { engine_spawn_t esp = engine; if (!esp) return gpg_error (GPG_ERR_INV_VALUE); if (esp->fd_data_map) { free_fd_data_map (esp->fd_data_map); esp->fd_data_map = NULL; } return 0; } static gpgme_error_t engspawn_op_spawn (void *engine, const char *file, const char *argv[], gpgme_data_t datain, gpgme_data_t dataout, gpgme_data_t dataerr, unsigned int flags) { engine_spawn_t esp = engine; gpgme_error_t err = 0; if (datain) err = add_data (esp, datain, 0, 0); if (!err && dataout) err = add_data (esp, dataout, 1, 1); if (!err && dataerr) err = add_data (esp, dataerr, 2, 1); if (!err) err = engspawn_start (esp, file, argv, flags); return err; } struct engine_ops _gpgme_engine_ops_spawn = { /* Static functions. */ engspawn_get_file_name, NULL, /* get_home_dir */ engspawn_get_version, engspawn_get_req_version, engspawn_new, /* Member functions. */ engspawn_release, NULL, /* reset */ NULL, /* set_status_cb */ NULL, /* set_status_handler */ NULL, /* set_command_handler */ NULL, /* set_colon_line_handler */ NULL, /* set_locale */ NULL, /* set_protocol */ NULL, /* decrypt */ NULL, /* decrypt_verify */ NULL, /* delete */ NULL, /* edit */ NULL, /* encrypt */ NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ NULL, /* sign */ NULL, /* trustlist */ NULL, /* verify */ NULL, /* getauditlog */ NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ engspawn_set_io_cbs, engspawn_io_event, /* io_event */ engspawn_cancel, /* cancel */ NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ engspawn_op_spawn /* opspawn */ }; diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c index 1869ff3f..de12f2b1 100644 --- a/src/engine-uiserver.c +++ b/src/engine-uiserver.c @@ -1,1371 +1,1372 @@ /* 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* 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 <config.h> #endif #include <stdlib.h> #include <string.h> #ifdef HAVE_SYS_TYPES_H # include <sys/types.h> #endif #include <assert.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include <locale.h> #include <fcntl.h> /* FIXME */ #include <errno.h> #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) { - return strdup ("1.0.0"); + (void)file_name; + return NULL; } static const char * uiserver_get_req_version (void) { - return "1.0.0"; + return NULL; } static void 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; } } /* 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) { 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 *dft_ttytype = NULL; char *optstr; (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 (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); free (optstr); if (err) goto leave; } if (isatty (1)) { int rc; 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 (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); free (optstr); if (err) goto leave; err = _gpgme_getenv ("TERM", &dft_ttytype); if (err) goto leave; if (dft_ttytype) { if (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); 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; 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 (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); 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, 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]; 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)) { 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"); */ TRACE3 (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); TRACE2 (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) err = uiserver->status.fnc (uiserver->status.fnc_value, GPGME_STATUS_EOF, ""); 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); } TRACE2 (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++; } } TRACE2 (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; } TRACE2 (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); } else fprintf (stderr, "[UNKNOWN STATUS]%s %s", line + 2, rest); TRACE3 (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_BEG2 (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)) { _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, int verify, gpgme_data_t ciph, gpgme_data_t plain) { 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 (asprintf (&cmd, "DECRYPT%s%s", protocol, verify ? "" : " --no-verify") < 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) { free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->output_cb.data = plain; err = uiserver_set_fd (uiserver, OUTPUT_FD, 0); if (err) { free (cmd); return gpg_error (GPG_ERR_GENERAL); /* FIXME */ } uiserver->inline_data = NULL; err = start (engine, cmd); free (cmd); return err; } static gpgme_error_t uiserver_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) { return _uiserver_decrypt (engine, 0, ciph, plain); } static gpgme_error_t uiserver_decrypt_verify (void *engine, gpgme_data_t ciph, gpgme_data_t plain) { return _uiserver_decrypt (engine, 1, ciph, plain); } 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); } static gpgme_error_t uiserver_encrypt (void *engine, gpgme_key_t recp[], 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 (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 (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) { 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) { free (cmd); return err; } } uiserver->inline_data = NULL; if (recp) { err = set_recipients (uiserver, recp); if (err) { free (cmd); return err; } } err = start (uiserver, cmd); 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; 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 (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) { 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) { 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) { free (cmd); return err; } uiserver->inline_data = NULL; err = start (uiserver, cmd); 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) { 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 (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) { 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); 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; TRACE3 (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, uiserver_decrypt, uiserver_decrypt_verify, NULL, /* delete */ NULL, /* edit */ uiserver_encrypt, NULL, /* encrypt_sign */ NULL, /* export */ NULL, /* export_ext */ NULL, /* genkey */ NULL, /* import */ NULL, /* keylist */ NULL, /* keylist_ext */ uiserver_sign, NULL, /* trustlist */ uiserver_verify, NULL, /* getauditlog */ NULL, /* opassuan_transact */ NULL, /* conf_load */ NULL, /* conf_save */ uiserver_set_io_cbs, uiserver_io_event, uiserver_cancel, NULL, /* cancel_op */ NULL, /* passwd */ NULL, /* set_pinentry_mode */ NULL /* opspawn */ }; diff --git a/src/engine.c b/src/engine.c index 4e59adad..a7c016f0 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,976 +1,1003 @@ /* engine.c - GPGME engine support. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2006, 2009, 2010 g10 Code GmbH This file is part of GPGME. GPGME is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. GPGME is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <string.h> #include <errno.h> #include <assert.h> #include "gpgme.h" #include "util.h" #include "sema.h" #include "ops.h" #include "debug.h" #include "engine.h" #include "engine-backend.h" struct engine { struct engine_ops *ops; void *engine; }; static struct engine_ops *engine_ops[] = { &_gpgme_engine_ops_gpg, /* OpenPGP. */ &_gpgme_engine_ops_gpgsm, /* CMS. */ &_gpgme_engine_ops_gpgconf, /* gpg-conf. */ &_gpgme_engine_ops_assuan, /* Low-Level Assuan. */ &_gpgme_engine_ops_g13, /* Crypto VFS. */ #ifdef ENABLE_UISERVER &_gpgme_engine_ops_uiserver, /* UI-Server. */ #else NULL, #endif &_gpgme_engine_ops_spawn }; /* The engine info. */ static gpgme_engine_info_t engine_info; DEFINE_STATIC_LOCK (engine_info_lock); /* Get the file name of the engine for PROTOCOL. */ static const char * engine_get_file_name (gpgme_protocol_t proto) { if (proto > DIM (engine_ops)) return NULL; if (engine_ops[proto] && engine_ops[proto]->get_file_name) return (*engine_ops[proto]->get_file_name) (); else return NULL; } /* Get the standard home dir of the engine for PROTOCOL. */ static const char * engine_get_home_dir (gpgme_protocol_t proto) { if (proto > DIM (engine_ops)) return NULL; if (engine_ops[proto] && engine_ops[proto]->get_home_dir) return (*engine_ops[proto]->get_home_dir) (); else return NULL; } /* Get a malloced string containing the version number of the engine - for PROTOCOL. */ + * for PROTOCOL. If this function returns NULL for a valid protocol, + * it should be assumed that the engine is a pseudo engine. */ static char * engine_get_version (gpgme_protocol_t proto, const char *file_name) { if (proto > DIM (engine_ops)) return NULL; if (engine_ops[proto] && engine_ops[proto]->get_version) return (*engine_ops[proto]->get_version) (file_name); else return NULL; } -/* Get the required version number of the engine for PROTOCOL. */ +/* Get the required version number of the engine for PROTOCOL. This + * may be NULL. */ static const char * engine_get_req_version (gpgme_protocol_t proto) { if (proto > DIM (engine_ops)) return NULL; if (engine_ops[proto] && engine_ops[proto]->get_req_version) return (*engine_ops[proto]->get_req_version) (); else return NULL; } /* Verify the version requirement for the engine for PROTOCOL. */ gpgme_error_t gpgme_engine_check_version (gpgme_protocol_t proto) { gpgme_error_t err; gpgme_engine_info_t info; int result; LOCK (engine_info_lock); info = engine_info; if (!info) { /* Make sure it is initialized. */ UNLOCK (engine_info_lock); err = gpgme_get_engine_info (&info); if (err) return err; LOCK (engine_info_lock); } while (info && info->protocol != proto) info = info->next; if (!info) result = 0; else result = _gpgme_compare_versions (info->version, info->req_version); UNLOCK (engine_info_lock); return result ? 0 : trace_gpg_error (GPG_ERR_INV_ENGINE); } /* Release the engine info INFO. */ void _gpgme_engine_info_release (gpgme_engine_info_t info) { while (info) { gpgme_engine_info_t next_info = info->next; - assert (info->file_name); - free (info->file_name); + if (info->file_name) + free (info->file_name); if (info->home_dir) free (info->home_dir); if (info->version) free (info->version); free (info); info = next_info; } } /* Get the information about the configured and installed engines. A pointer to the first engine in the statically allocated linked list is returned in *INFO. If an error occurs, it is returned. The returned data is valid until the next gpgme_set_engine_info. */ gpgme_error_t gpgme_get_engine_info (gpgme_engine_info_t *info) { gpgme_error_t err; LOCK (engine_info_lock); if (!engine_info) { gpgme_engine_info_t *lastp = &engine_info; gpgme_protocol_t proto_list[] = { GPGME_PROTOCOL_OpenPGP, GPGME_PROTOCOL_CMS, GPGME_PROTOCOL_GPGCONF, GPGME_PROTOCOL_ASSUAN, GPGME_PROTOCOL_G13, GPGME_PROTOCOL_UISERVER, GPGME_PROTOCOL_SPAWN }; unsigned int proto; err = 0; for (proto = 0; proto < DIM (proto_list); proto++) { const char *ofile_name = engine_get_file_name (proto_list[proto]); const char *ohome_dir = engine_get_home_dir (proto_list[proto]); + char *version = engine_get_version (proto_list[proto], NULL); char *file_name; char *home_dir; if (!ofile_name) continue; file_name = strdup (ofile_name); if (!file_name) err = gpg_error_from_syserror (); if (ohome_dir) { home_dir = strdup (ohome_dir); if (!home_dir && !err) err = gpg_error_from_syserror (); } else home_dir = NULL; - *lastp = malloc (sizeof (*engine_info)); + *lastp = calloc (1, sizeof (*engine_info)); if (!*lastp && !err) err = gpg_error_from_syserror (); + /* Now set the dummy version for pseudo engines. */ + if (!err && !version) + { + version = strdup ("1.0.0"); + if (!version) + err = gpg_error_from_syserror (); + } + if (err) { _gpgme_engine_info_release (engine_info); engine_info = NULL; if (file_name) free (file_name); if (home_dir) free (home_dir); + if (version) + free (version); UNLOCK (engine_info_lock); return err; } (*lastp)->protocol = proto_list[proto]; (*lastp)->file_name = file_name; (*lastp)->home_dir = home_dir; - (*lastp)->version = engine_get_version (proto_list[proto], NULL); + (*lastp)->version = version; (*lastp)->req_version = engine_get_req_version (proto_list[proto]); + if (!(*lastp)->req_version) + (*lastp)->req_version = "1.0.0"; /* Dummy for pseudo engines. */ (*lastp)->next = NULL; lastp = &(*lastp)->next; } } *info = engine_info; UNLOCK (engine_info_lock); return 0; } /* Get a deep copy of the engine info and return it in INFO. */ gpgme_error_t _gpgme_engine_info_copy (gpgme_engine_info_t *r_info) { gpgme_error_t err = 0; gpgme_engine_info_t info; gpgme_engine_info_t new_info; gpgme_engine_info_t *lastp; LOCK (engine_info_lock); info = engine_info; if (!info) { /* Make sure it is initialized. */ UNLOCK (engine_info_lock); err = gpgme_get_engine_info (&info); if (err) return err; LOCK (engine_info_lock); } new_info = NULL; lastp = &new_info; while (info) { char *file_name; char *home_dir; char *version; assert (info->file_name); file_name = strdup (info->file_name); if (!file_name) err = gpg_error_from_syserror (); if (info->home_dir) { home_dir = strdup (info->home_dir); if (!home_dir && !err) err = gpg_error_from_syserror (); } else home_dir = NULL; if (info->version) { version = strdup (info->version); if (!version && !err) err = gpg_error_from_syserror (); } else version = NULL; *lastp = malloc (sizeof (*engine_info)); if (!*lastp && !err) err = gpg_error_from_syserror (); if (err) { _gpgme_engine_info_release (new_info); if (file_name) free (file_name); if (home_dir) free (home_dir); if (version) free (version); UNLOCK (engine_info_lock); return err; } (*lastp)->protocol = info->protocol; (*lastp)->file_name = file_name; (*lastp)->home_dir = home_dir; (*lastp)->version = version; (*lastp)->req_version = info->req_version; (*lastp)->next = NULL; lastp = &(*lastp)->next; info = info->next; } *r_info = new_info; UNLOCK (engine_info_lock); return 0; } /* Set the engine info for the info list INFO, protocol PROTO, to the file name FILE_NAME and the home directory HOME_DIR. */ gpgme_error_t _gpgme_set_engine_info (gpgme_engine_info_t info, gpgme_protocol_t proto, const char *file_name, const char *home_dir) { char *new_file_name; char *new_home_dir; + char *new_version; /* FIXME: Use some PROTO_MAX definition. */ if (proto > DIM (engine_ops)) return gpg_error (GPG_ERR_INV_VALUE); while (info && info->protocol != proto) info = info->next; if (!info) return trace_gpg_error (GPG_ERR_INV_ENGINE); /* Prepare new members. */ if (file_name) new_file_name = strdup (file_name); else { const char *ofile_name = engine_get_file_name (proto); assert (ofile_name); new_file_name = strdup (ofile_name); } if (!new_file_name) return gpg_error_from_syserror (); if (home_dir) { new_home_dir = strdup (home_dir); if (!new_home_dir) { free (new_file_name); return gpg_error_from_syserror (); } } else { const char *ohome_dir = engine_get_home_dir (proto); if (ohome_dir) { new_home_dir = strdup (ohome_dir); if (!new_home_dir) { free (new_file_name); return gpg_error_from_syserror (); } } else new_home_dir = NULL; } + new_version = engine_get_version (proto, new_file_name); + if (!new_version) + { + new_version = strdup ("1.0.0"); /* Fake one for dummy entries. */ + if (!new_version) + { + free (new_file_name); + free (new_home_dir); + } + } + /* Remove the old members. */ assert (info->file_name); free (info->file_name); if (info->home_dir) free (info->home_dir); if (info->version) free (info->version); /* Install the new members. */ info->file_name = new_file_name; info->home_dir = new_home_dir; - info->version = engine_get_version (proto, new_file_name); + info->version = new_version; return 0; } /* Set the default engine info for the protocol PROTO to the file name FILE_NAME and the home directory HOME_DIR. */ gpgme_error_t gpgme_set_engine_info (gpgme_protocol_t proto, const char *file_name, const char *home_dir) { gpgme_error_t err; gpgme_engine_info_t info; LOCK (engine_info_lock); info = engine_info; if (!info) { /* Make sure it is initialized. */ UNLOCK (engine_info_lock); err = gpgme_get_engine_info (&info); if (err) return err; LOCK (engine_info_lock); } err = _gpgme_set_engine_info (info, proto, file_name, home_dir); UNLOCK (engine_info_lock); return err; } gpgme_error_t _gpgme_engine_new (gpgme_engine_info_t info, engine_t *r_engine) { engine_t engine; if (!info->file_name || !info->version) return trace_gpg_error (GPG_ERR_INV_ENGINE); engine = calloc (1, sizeof *engine); if (!engine) return gpg_error_from_syserror (); engine->ops = engine_ops[info->protocol]; if (engine->ops->new) { gpgme_error_t err; err = (*engine->ops->new) (&engine->engine, info->file_name, info->home_dir, info->version); if (err) { free (engine); return err; } } else engine->engine = NULL; *r_engine = engine; return 0; } gpgme_error_t _gpgme_engine_reset (engine_t engine) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->reset) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->reset) (engine->engine); } void _gpgme_engine_release (engine_t engine) { if (!engine) return; if (engine->ops->release) (*engine->ops->release) (engine->engine); free (engine); } /* Set a status callback which is used to monitor the status values * before they are passed to a handler set with * _gpgme_engine_set_status_handler. */ void _gpgme_engine_set_status_cb (engine_t engine, gpgme_status_cb_t cb, void *cb_value) { if (!engine) return; if (engine->ops->set_status_cb) (*engine->ops->set_status_cb) (engine->engine, cb, cb_value); } void _gpgme_engine_set_status_handler (engine_t engine, engine_status_handler_t fnc, void *fnc_value) { if (!engine) return; if (engine->ops->set_status_handler) (*engine->ops->set_status_handler) (engine->engine, fnc, fnc_value); } gpgme_error_t _gpgme_engine_set_command_handler (engine_t engine, engine_command_handler_t fnc, void *fnc_value, gpgme_data_t linked_data) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->set_command_handler) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->set_command_handler) (engine->engine, fnc, fnc_value, linked_data); } gpgme_error_t _gpgme_engine_set_colon_line_handler (engine_t engine, engine_colon_line_handler_t fnc, void *fnc_value) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->set_colon_line_handler) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->set_colon_line_handler) (engine->engine, fnc, fnc_value); } gpgme_error_t _gpgme_engine_set_locale (engine_t engine, int category, const char *value) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->set_locale) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->set_locale) (engine->engine, category, value); } gpgme_error_t _gpgme_engine_set_protocol (engine_t engine, gpgme_protocol_t protocol) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->set_protocol) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->set_protocol) (engine->engine, protocol); } gpgme_error_t _gpgme_engine_op_decrypt (engine_t engine, gpgme_data_t ciph, gpgme_data_t plain) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->decrypt) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->decrypt) (engine->engine, ciph, plain); } gpgme_error_t _gpgme_engine_op_decrypt_verify (engine_t engine, gpgme_data_t ciph, gpgme_data_t plain) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->decrypt_verify) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->decrypt_verify) (engine->engine, ciph, plain); } gpgme_error_t _gpgme_engine_op_delete (engine_t engine, gpgme_key_t key, int allow_secret) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->delete) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->delete) (engine->engine, key, allow_secret); } gpgme_error_t _gpgme_engine_op_edit (engine_t engine, int type, gpgme_key_t key, gpgme_data_t out, gpgme_ctx_t ctx /* FIXME */) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->edit) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->edit) (engine->engine, type, key, out, ctx); } gpgme_error_t _gpgme_engine_op_encrypt (engine_t engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t ciph, int use_armor) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->encrypt) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->encrypt) (engine->engine, recp, flags, plain, ciph, use_armor); } gpgme_error_t _gpgme_engine_op_encrypt_sign (engine_t engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, gpgme_data_t plain, gpgme_data_t ciph, int use_armor, gpgme_ctx_t ctx /* FIXME */) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->encrypt_sign) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->encrypt_sign) (engine->engine, recp, flags, plain, ciph, use_armor, ctx); } gpgme_error_t _gpgme_engine_op_export (engine_t engine, const char *pattern, gpgme_export_mode_t mode, gpgme_data_t keydata, int use_armor) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->export) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->export) (engine->engine, pattern, mode, keydata, use_armor); } gpgme_error_t _gpgme_engine_op_export_ext (engine_t engine, const char *pattern[], unsigned int reserved, gpgme_data_t keydata, int use_armor) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->export_ext) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->export_ext) (engine->engine, pattern, reserved, keydata, use_armor); } gpgme_error_t _gpgme_engine_op_genkey (engine_t engine, gpgme_data_t help_data, int use_armor, gpgme_data_t pubkey, gpgme_data_t seckey) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->genkey) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->genkey) (engine->engine, help_data, use_armor, pubkey, seckey); } gpgme_error_t _gpgme_engine_op_import (engine_t engine, gpgme_data_t keydata, gpgme_key_t *keyarray) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->import) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->import) (engine->engine, keydata, keyarray); } gpgme_error_t _gpgme_engine_op_keylist (engine_t engine, const char *pattern, int secret_only, gpgme_keylist_mode_t mode, int engine_flags) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->keylist) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->keylist) (engine->engine, pattern, secret_only, mode, engine_flags); } gpgme_error_t _gpgme_engine_op_keylist_ext (engine_t engine, const char *pattern[], int secret_only, int reserved, gpgme_keylist_mode_t mode, int engine_flags) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->keylist_ext) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->keylist_ext) (engine->engine, pattern, secret_only, reserved, mode, engine_flags); } gpgme_error_t _gpgme_engine_op_sign (engine_t engine, gpgme_data_t in, gpgme_data_t out, gpgme_sig_mode_t mode, int use_armor, int use_textmode, int include_certs, gpgme_ctx_t ctx /* FIXME */) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->sign) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->sign) (engine->engine, in, out, mode, use_armor, use_textmode, include_certs, ctx); } gpgme_error_t _gpgme_engine_op_trustlist (engine_t engine, const char *pattern) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->trustlist) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->trustlist) (engine->engine, pattern); } gpgme_error_t _gpgme_engine_op_verify (engine_t engine, gpgme_data_t sig, gpgme_data_t signed_text, gpgme_data_t plaintext) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->verify) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->verify) (engine->engine, sig, signed_text, plaintext); } gpgme_error_t _gpgme_engine_op_getauditlog (engine_t engine, gpgme_data_t output, unsigned int flags) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->getauditlog) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->getauditlog) (engine->engine, output, flags); } gpgme_error_t _gpgme_engine_op_assuan_transact (engine_t engine, const char *command, gpgme_assuan_data_cb_t data_cb, void *data_cb_value, gpgme_assuan_inquire_cb_t inq_cb, void *inq_cb_value, gpgme_assuan_status_cb_t status_cb, void *status_cb_value) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->opassuan_transact) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->opassuan_transact) (engine->engine, command, data_cb, data_cb_value, inq_cb, inq_cb_value, status_cb, status_cb_value); } gpgme_error_t _gpgme_engine_op_conf_load (engine_t engine, gpgme_conf_comp_t *conf_p) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->conf_load) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->conf_load) (engine->engine, conf_p); } gpgme_error_t _gpgme_engine_op_conf_save (engine_t engine, gpgme_conf_comp_t conf) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->conf_save) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->conf_save) (engine->engine, conf); } void _gpgme_engine_set_io_cbs (engine_t engine, gpgme_io_cbs_t io_cbs) { if (!engine) return; (*engine->ops->set_io_cbs) (engine->engine, io_cbs); } void _gpgme_engine_io_event (engine_t engine, gpgme_event_io_t type, void *type_data) { if (!engine) return; (*engine->ops->io_event) (engine->engine, type, type_data); } /* Cancel the session and the pending operation if any. */ gpgme_error_t _gpgme_engine_cancel (engine_t engine) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->cancel) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->cancel) (engine->engine); } /* Cancel the pending operation, but not the complete session. */ gpgme_error_t _gpgme_engine_cancel_op (engine_t engine) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->cancel_op) return 0; return (*engine->ops->cancel_op) (engine->engine); } /* Change the passphrase for KEY. */ gpgme_error_t _gpgme_engine_op_passwd (engine_t engine, gpgme_key_t key, unsigned int flags) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->passwd) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->passwd) (engine->engine, key, flags); } /* Set the pinentry mode for ENGINE to MODE. */ gpgme_error_t _gpgme_engine_set_pinentry_mode (engine_t engine, gpgme_pinentry_mode_t mode) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->set_pinentry_mode) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->set_pinentry_mode) (engine->engine, mode); } gpgme_error_t _gpgme_engine_op_spawn (engine_t engine, const char *file, const char *argv[], gpgme_data_t datain, gpgme_data_t dataout, gpgme_data_t dataerr, unsigned int flags) { if (!engine) return gpg_error (GPG_ERR_INV_VALUE); if (!engine->ops->opspawn) return gpg_error (GPG_ERR_NOT_IMPLEMENTED); return (*engine->ops->opspawn) (engine->engine, file, argv, datain, dataout, dataerr, flags); } diff --git a/src/version.c b/src/version.c index 15e5aeec..e2f1c353 100644 --- a/src/version.c +++ b/src/version.c @@ -1,371 +1,371 @@ /* version.c - Version check routines. Copyright (C) 2000 Werner Koch (dd9jn) Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007, 2008 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. */ #if HAVE_CONFIG_H #include <config.h> #endif #include <stdlib.h> #include <string.h> #include <limits.h> #include <ctype.h> #ifdef HAVE_W32_SYSTEM #include <winsock2.h> #endif #include "gpgme.h" #include "priv-io.h" #include "debug.h" #include "context.h" /* For _gpgme_sema_subsystem_init and _gpgme_status_init. */ #include "sema.h" #include "util.h" #ifdef HAVE_ASSUAN_H #include "assuan.h" #endif #ifdef HAVE_W32_SYSTEM #include "windows.h" #endif /* We implement this function, so we have to disable the overriding macro. */ #undef gpgme_check_version /* Bootstrap the subsystems needed for concurrent operation. This must be done once at startup. We can not guarantee this using a lock, though, because the semaphore subsystem needs to be initialized itself before it can be used. So we expect that the user performs the necessary synchronization. */ static void do_subsystem_inits (void) { static int done = 0; if (done) return; #ifdef HAVE_W32_SYSTEM /* We need to make sure that the sockets are initialized. */ { WSADATA wsadat; WSAStartup (0x202, &wsadat); } #endif _gpgme_sema_subsystem_init (); _gpgme_debug_subsystem_init (); _gpgme_io_subsystem_init (); _gpgme_status_init (); done = 1; } /* Put vesion information into the binary. */ static const char * cright_blurb (void) { static const char blurb[] = "\n\n" "This is GPGME " PACKAGE_VERSION " - The GnuPG Made Easy library\n" CRIGHTBLURB "\n" "(" BUILD_REVISION " " BUILD_TIMESTAMP ")\n" "\n\n"; return blurb; } /* Read the next number in the version string STR and return it in *NUMBER. Return a pointer to the tail of STR after parsing, or *NULL if the version string was invalid. */ static const char * parse_version_number (const char *str, int *number) { #define MAXVAL ((INT_MAX - 10) / 10) int val = 0; /* Leading zeros are not allowed. */ if (*str == '0' && isdigit(str[1])) return NULL; while (isdigit (*str) && val <= MAXVAL) { val *= 10; val += *(str++) - '0'; } *number = val; return val > MAXVAL ? NULL : str; } /* Parse the version string STR in the format MAJOR.MINOR.MICRO (for example, 9.3.2) and return the components in MAJOR, MINOR and MICRO as integers. The function returns the tail of the string that - follows the version number. This might be te empty string if there + follows the version number. This might be the empty string if there is nothing following the version number, or a patchlevel. The function returns NULL if the version string is not valid. */ static const char * parse_version_string (const char *str, int *major, int *minor, int *micro) { str = parse_version_number (str, major); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, minor); if (!str || *str != '.') return NULL; str++; str = parse_version_number (str, micro); if (!str) return NULL; /* A patchlevel might follow. */ return str; } /* Return true if MY_VERSION is at least REQ_VERSION, and false otherwise. */ int _gpgme_compare_versions (const char *my_version, const char *rq_version) { int my_major, my_minor, my_micro; int rq_major, rq_minor, rq_micro; const char *my_plvl, *rq_plvl; if (!rq_version) return 1; if (!my_version) return 0; my_plvl = parse_version_string (my_version, &my_major, &my_minor, &my_micro); if (!my_plvl) return 0; rq_plvl = parse_version_string (rq_version, &rq_major, &rq_minor, &rq_micro); if (!rq_plvl) return 0; if (my_major > rq_major || (my_major == rq_major && my_minor > rq_minor) || (my_major == rq_major && my_minor == rq_minor && my_micro > rq_micro) || (my_major == rq_major && my_minor == rq_minor && my_micro == rq_micro && strcmp (my_plvl, rq_plvl) >= 0)) return 1; return 0; } /* Check that the the version of the library is at minimum the requested one and return the version string; return NULL if the condition is not met. If a NULL is passed to this function, no check is done and the version string is simply returned. This function must be run once at startup, as it also initializes some subsystems. Its invocation must be synchronized against calling any of the other functions in a multi-threaded environments. */ const char * gpgme_check_version (const char *req_version) { char *result; do_subsystem_inits (); /* Catch-22: We need to get at least the debug subsystem ready before using the trace facility. If we won't the trace would automagically initialize the debug system with out the locks being initialized and missing the assuan log level setting. */ TRACE2 (DEBUG_INIT, "gpgme_check_version", 0, "req_version=%s, VERSION=%s", req_version? req_version:"(null)", VERSION); result = _gpgme_compare_versions (VERSION, req_version) ? VERSION : NULL; if (result != NULL) _gpgme_selftest = 0; return result; } /* Check the version and also at runtime if the struct layout of the library matches the one of the user. This is particular useful for Windows targets (-mms-bitfields). */ const char * gpgme_check_version_internal (const char *req_version, size_t offset_sig_validity) { const char *result; if (req_version && req_version[0] == 1 && req_version[1] == 1) return cright_blurb (); result = gpgme_check_version (req_version); if (result == NULL) return result; /* Catch-22, see above. */ TRACE2 (DEBUG_INIT, "gpgme_check_version_internal", 0, "req_version=%s, offset_sig_validity=%i", req_version ? req_version : "(null)", offset_sig_validity); if (offset_sig_validity != offsetof (struct _gpgme_signature, validity)) { TRACE1 (DEBUG_INIT, "gpgme_check_version_internal", 0, "offset_sig_validity mismatch: expected %i", offsetof (struct _gpgme_signature, validity)); _gpgme_selftest = GPG_ERR_SELFTEST_FAILED; } return result; } #define LINELENGTH 80 /* Extract the version string of a program from STRING. The version number is expected to be in GNU style format: foo 1.2.3 foo (bar system) 1.2.3 foo 1.2.3 cruft foo (bar system) 1.2.3 cruft. Spaces and tabs are skipped and used as delimiters, a term in (nested) parenthesis before the version string is skipped, the version string may consist of any non-space and non-tab characters but needs to bstart with a digit. */ static const char * extract_version_string (const char *string, size_t *r_len) { const char *s; int count, len; for (s=string; *s; s++) if (*s == ' ' || *s == '\t') break; while (*s == ' ' || *s == '\t') s++; if (*s == '(') { for (count=1, s++; count && *s; s++) if (*s == '(') count++; else if (*s == ')') count--; } /* For robustness we look for a digit. */ while ( *s && !(*s >= '0' && *s <= '9') ) s++; if (*s >= '0' && *s <= '9') { for (len=0; s[len]; len++) if (s[len] == ' ' || s[len] == '\t') break; } else len = 0; *r_len = len; return s; } /* Retrieve the version number from the --version output of the program FILE_NAME. */ char * _gpgme_get_program_version (const char *const file_name) { char line[LINELENGTH] = ""; int linelen = 0; char *mark = NULL; int rp[2]; int nread; char *argv[] = {NULL /* file_name */, "--version", 0}; struct spawn_fd_item_s cfd[] = { {-1, 1 /* STDOUT_FILENO */, -1, 0}, {-1, -1} }; int status; if (!file_name) return NULL; argv[0] = (char *) file_name; if (_gpgme_io_pipe (rp, 1) < 0) return NULL; cfd[0].fd = rp[1]; status = _gpgme_io_spawn (file_name, argv, IOSPAWN_FLAG_DETACHED, cfd, NULL, NULL, NULL); if (status < 0) { _gpgme_io_close (rp[0]); _gpgme_io_close (rp[1]); return NULL; } do { nread = _gpgme_io_read (rp[0], &line[linelen], LINELENGTH - linelen - 1); if (nread > 0) { line[linelen + nread] = '\0'; mark = strchr (&line[linelen], '\n'); if (mark) { if (mark > &line[0] && *mark == '\r') mark--; *mark = '\0'; break; } linelen += nread; } } while (nread > 0 && linelen < LINELENGTH - 1); _gpgme_io_close (rp[0]); if (mark) { size_t len; const char *s; s = extract_version_string (line, &len); if (!len) return NULL; mark = malloc (len + 1); if (!mark) return NULL; memcpy (mark, s, len); mark[len] = 0; return mark; } return NULL; }