diff options
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 */ +  }; | 
