diff options
author | Marcus Brinkmann <[email protected]> | 2009-11-10 09:07:19 +0000 |
---|---|---|
committer | Marcus Brinkmann <[email protected]> | 2009-11-10 09:07:19 +0000 |
commit | 96cf17b15995c9b925f80a64dbd42c1305ca9895 (patch) | |
tree | 364f15711af329ff9b3b207245516e1320fb3ba0 /src/engine-uiserver.c | |
parent | Fix last change. (diff) | |
download | gpgme-96cf17b15995c9b925f80a64dbd42c1305ca9895.tar.gz gpgme-96cf17b15995c9b925f80a64dbd42c1305ca9895.zip |
2009-11-10 Marcus Brinkmann <[email protected]>
* configure.ac: Activate UIServer if FD passing is enabled and
Assuan is available.
m4/
2009-11-10 Marcus Brinkmann <[email protected]>
* libassuan.m4: Fix LIBASSUAN_VERSION.
src/
2009-11-10 Marcus Brinkmann <[email protected]>
* Makefile.am (uiserver_components): New variable.
(main_sources): Add it.
* ops.h, key.c (_gpgme_key_append_name): Take CONVERT argument,
implement it. Adjust callers.
(gpgme_key_from_uid): New function.
* gpgme.h.in (gpgme_protocol_t): Add GPGME_PROTOCOL_DEFAULT.
(gpgme_encrypt_flags_t): Add GPGME_ENCRYPT_PREPARE,
GPGME_ENCRYPT_EXPECT_SIGN.
(gpgme_set_sub_protocol, gpgme_key_from_uid): New functions.
* libgpgme.vers, gpgme.def: Add new functions.
* gpgme.c (gpgme_set_protocol): Add UIServer protocol.
(gpgme_set_sub_protocol): New function.
(gpgme_get_protocol_name): Add UIServer and default protocol.
* assuan-support.c: Return correct error values, implement
socketpair for POSIX.
* priv-io.h, posix-io.c, w32-io.c, w32-glib-io.c,
w32-qt-io.cpp (_gpgme_io_spawn): Add ATFORK and ATFORKVALUE
arguments. Implement it for POSIX. Adjust all callers.
* engine.h, engine-backend.h (_gpgme_engine_set_protocol)
(_gpgme_engine_op_decrypt_verify): New prototypes. Adjust all
users.
* engine.c (engine_ops, gpgme_get_engine_info): Add UIServer
engine.
(_gpgme_engine_set_protocol, _gpgme_engine_op_decrypt_verify): New
function.
* decrypt-verify.c (decrypt_verify_start): Call
_gpgme_engine_op_decrypt_verify.
* util.h, posix-util.c,
w32-util.c (_gpgme_get_uiserver_socket_path): New function.
* engine-gpgsm.c (gpgsm_set_fd): Fix _gpgme_io_pipe invocation.
* gpgme-tool.c: Some support for UIServer protocol.
* engine-uiserver.c: New file.
Diffstat (limited to '')
-rw-r--r-- | src/engine-uiserver.c | 1361 |
1 files changed, 1361 insertions, 0 deletions
diff --git a/src/engine-uiserver.c b/src/engine-uiserver.c new file mode 100644 index 00000000..a867832a --- /dev/null +++ b/src/engine-uiserver.c @@ -0,0 +1,1361 @@ +/* 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> +#include <sys/types.h> +#include <assert.h> +#include <unistd.h> +#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 "status-table.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; + } 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"); +} + + +static const char * +uiserver_get_req_version (void) +{ + return "1.0"; +} + + +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) +{ + gpgme_error_t err = 0; + engine_uiserver_t uiserver; + char *dft_display = NULL; + char dft_ttyname[64]; + char *dft_ttytype = NULL; + char *optstr; + + 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_uiserver_socket_path (), + 0, 0); + 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) + { + free (dft_display); + err = gpg_error_from_errno (errno); + 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)); + if (rc) + { + err = gpg_error_from_errno (rc); + goto leave; + } + else + { + if (asprintf (&optstr, "OPTION ttyname=%s", dft_ttyname) < 0) + { + err = gpg_error_from_errno (errno); + 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) + { + free (dft_ttytype); + err = gpg_error_from_errno (errno); + 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_errno (errno); + 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; +} + + +/* Forward declaration. */ +static gpgme_status_code_t parse_status (const char *name); + +static gpgme_error_t +uiserver_assuan_simple_command (assuan_context_t ctx, char *cmd, + engine_status_handler_t status_fnc, + void *status_fnc_value) +{ + 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 = parse_status (line + 2); + + 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_errno (errno); + + 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->assuan_ctx, 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 int +status_cmp (const void *ap, const void *bp) +{ + const struct status_table_s *a = ap; + const struct status_table_s *b = bp; + + return strcmp (a->name, b->name); +} + + +static gpgme_status_code_t +parse_status (const char *name) +{ + struct status_table_s t, *r; + t.name = name; + r = bsearch (&t, status_table, DIM(status_table) - 1, + sizeof t, status_cmp); + return r ? r->code : -1; +} + + +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_errno (errno); + else + { + *aline = newline; + uiserver->colon.attic.linesize += 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; + 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_errno (errno); + 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 = 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->assuan_ctx, "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_errno (errno); + + 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; + assuan_context_t ctx = uiserver->assuan_ctx; + char *line; + int linelen; + int invalid_recipients = 0; + int i = 0; + + linelen = 10 + 40 + 1; /* "RECIPIENT " + guess + '\0'. */ + line = malloc (10 + 40 + 1); + if (!line) + return gpg_error_from_errno (errno); + strcpy (line, "RECIPIENT "); + while (!err && recp[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_errno = errno; + free (line); + return gpg_error_from_errno (saved_errno); + } + line = newline; + linelen = newlen; + } + strcpy (&line[10], fpr); + + err = uiserver_assuan_simple_command (ctx, line, uiserver->status.fnc, + uiserver->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; + } + i++; + } + 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_errno (errno); + } + else + { + if (!plain || !ciph) + return gpg_error (GPG_ERR_INV_VALUE); + + if (asprintf (&cmd, "ENCRYPT%s", protocol) < 0) + return gpg_error_from_errno (errno); + } + + 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; + + 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_errno (errno); + + { + gpgme_key_t key = gpgme_signers_enum (ctx, 0); + 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->assuan_ctx, 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_errno (errno); + + 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; +} + + +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_uiserver_socket_path, + NULL, + uiserver_get_version, + uiserver_get_req_version, + uiserver_new, + + /* Member functions. */ + uiserver_release, + uiserver_reset, + 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 */ + }; |