diff options
| author | Marcus Brinkmann <[email protected]> | 2008-11-03 17:24:09 +0000 | 
|---|---|---|
| committer | Marcus Brinkmann <[email protected]> | 2008-11-03 17:24:09 +0000 | 
| commit | 66d0fa1973e5e1a1bff619de8b595673d1b76cc5 (patch) | |
| tree | 4b1f8e470fa455cbe3d9b5c4ab6fb4fa77f20ba3 /src/rungpg.c | |
| parent | assuan/ (diff) | |
| download | gpgme-66d0fa1973e5e1a1bff619de8b595673d1b76cc5.tar.gz gpgme-66d0fa1973e5e1a1bff619de8b595673d1b76cc5.zip | |
008-11-03  Marcus Brinkmann  <[email protected]>
        * configure.ac: Replace gpgme paths with src.
        * gpgme: Move to ...
        * src: ... this new directory.
assuan/
2008-11-03  Marcus Brinkmann  <[email protected]>
	* Makefile.am (INCLUDES): Replace gpgme path with src.
tests/
2008-11-03  Marcus Brinkmann  <[email protected]>
        * gpgsm/Makefile.am (INCLUDES, LDADD): Replace gpgme path with src.
        * gpg/Makefile.am (INCLUDES, LDADD, t_thread1_LDADD): Likewise.
	* Makefile.am (LDADD): Likewise.
Diffstat (limited to 'src/rungpg.c')
| -rw-r--r-- | src/rungpg.c | 2192 | 
1 files changed, 2192 insertions, 0 deletions
| diff --git a/src/rungpg.c b/src/rungpg.c new file mode 100644 index 00000000..fc0da117 --- /dev/null +++ b/src/rungpg.c @@ -0,0 +1,2192 @@ +/* rungpg.c - Gpg Engine. +   Copyright (C) 2000 Werner Koch (dd9jn) +   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007 g10 Code GmbH +  +   This file is part of GPGME. +  +   GPGME is free software; you can redistribute it and/or modify it +   under the terms of the GNU Lesser General Public License as +   published by the Free Software Foundation; either version 2.1 of +   the License, or (at your option) any later version. +    +   GPGME is distributed in the hope that it will be useful, but +   WITHOUT ANY WARRANTY; without even the implied warranty of +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU +   Lesser General Public License for more details. +    +   You should have received a copy of the GNU Lesser General Public +   License along with this program; if not, 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <unistd.h> +#include <locale.h> + +#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 "status-table.h" +#include "engine-backend.h" + + +/* This type is used to build a list of gpg arguments and data +   sources/sinks.  */ +struct arg_and_data_s +{ +  struct arg_and_data_s *next; +  gpgme_data_t data;  /* If this is not NULL, use arg below.  */ +  int inbound;     /* True if this is used for reading from gpg.  */ +  int dup_to; +  int print_fd;    /* Print the fd number and not the special form of it.  */ +  int *arg_locp;   /* Write back the argv idx of this argument when +		      building command line to this location.  */ +  char arg[1];     /* Used if data above is not used.  */ +}; + + +struct fd_data_map_s +{ +  gpgme_data_t data; +  int inbound;  /* true if this is used for reading from gpg */ +  int dup_to; +  int fd;       /* the fd to use */ +  int peer_fd;  /* the other side of the pipe */ +  int arg_loc;  /* The index into the argv for translation purposes.  */ +  void *tag; +}; + + +typedef gpgme_error_t (*colon_preprocessor_t) (char *line, char **rline); + +struct engine_gpg +{ +  char *file_name; + +  char *lc_messages; +  char *lc_ctype; + +  struct arg_and_data_s *arglist; +  struct arg_and_data_s **argtail; + +  struct +  { +    int fd[2];   +    int arg_loc; +    size_t bufsize; +    char *buffer; +    size_t readpos; +    int eof; +    engine_status_handler_t fnc; +    void *fnc_value; +    void *tag; +  } status; + +  /* This is a kludge - see the comment at colon_line_handler.  */ +  struct +  { +    int fd[2];   +    int arg_loc; +    size_t bufsize; +    char *buffer; +    size_t readpos; +    int eof; +    engine_colon_line_handler_t fnc;  /* this indicate use of this structrue */ +    void *fnc_value; +    void *tag; +    colon_preprocessor_t preprocess_fnc; +  } colon; + +  char **argv;   +  struct fd_data_map_s *fd_data_map; + +  /* stuff needed for interactive (command) mode */ +  struct +  { +    int used; +    int fd; +    void *cb_data; +    int idx;		/* Index in fd_data_map */ +    gpgme_status_code_t code;  /* last code */ +    char *keyword;       /* what has been requested (malloced) */ +    engine_command_handler_t fnc;  +    void *fnc_value; +    /* The kludges never end.  This is used to couple command handlers +       with output data in edit key mode.  */ +    gpgme_data_t linked_data; +    int linked_idx; +  } cmd; + +  struct gpgme_io_cbs io_cbs; +}; + +typedef struct engine_gpg *engine_gpg_t; + + +static void +gpg_io_event (void *engine, gpgme_event_io_t type, void *type_data) +{ +  engine_gpg_t gpg = engine; + +  TRACE3 (DEBUG_ENGINE, "gpgme:gpg_io_event", gpg, +          "event %p, type %d, type_data %p", +          gpg->io_cbs.event, type, type_data); +  if (gpg->io_cbs.event) +    (*gpg->io_cbs.event) (gpg->io_cbs.event_priv, type, type_data); +} + + +static void +close_notify_handler (int fd, void *opaque) +{ +  engine_gpg_t gpg = opaque; +  assert (fd != -1); + +  if (gpg->status.fd[0] == fd) +    { +      if (gpg->status.tag) +	(*gpg->io_cbs.remove) (gpg->status.tag); +      gpg->status.fd[0] = -1; +    } +  else if (gpg->status.fd[1] == fd) +    gpg->status.fd[1] = -1; +  else if (gpg->colon.fd[0] == fd) +    { +      if (gpg->colon.tag) +	(*gpg->io_cbs.remove) (gpg->colon.tag); +      gpg->colon.fd[0] = -1; +    } +  else if (gpg->colon.fd[1] == fd) +    gpg->colon.fd[1] = -1; +  else if (gpg->fd_data_map) +    { +      int i; + +      for (i = 0; gpg->fd_data_map[i].data; i++) +	{ +	  if (gpg->fd_data_map[i].fd == fd) +	    { +	      if (gpg->fd_data_map[i].tag) +		(*gpg->io_cbs.remove) (gpg->fd_data_map[i].tag); +	      gpg->fd_data_map[i].fd = -1; +	      break; +            } +	  if (gpg->fd_data_map[i].peer_fd == fd) +	    { +	      gpg->fd_data_map[i].peer_fd = -1; +	      break; +            } +        } +    } +} + +/* If FRONT is true, push at the front of the list.  Use this for +   options added late in the process.  */ +static gpgme_error_t +_add_arg (engine_gpg_t gpg, const char *arg, int front, int *arg_locp) +{ +  struct arg_and_data_s *a; + +  assert (gpg); +  assert (arg); + +  a = malloc (sizeof *a + strlen (arg)); +  if (!a) +    return gpg_error_from_errno (errno); + +  a->data = NULL; +  a->dup_to = -1; +  a->arg_locp = arg_locp; + +  strcpy (a->arg, arg); +  if (front) +    { +      a->next = gpg->arglist; +      if (!gpg->arglist) +	{ +	  /* If this is the first argument, we need to update the tail +	     pointer.  */ +	  gpg->argtail = &a->next; +	} +      gpg->arglist = a; +    } +  else +    { +      a->next = NULL; +      *gpg->argtail = a; +      gpg->argtail = &a->next; +    } + +  return 0; +} + +static gpgme_error_t +add_arg_ext (engine_gpg_t gpg, const char *arg, int front) +{ +  return _add_arg (gpg, arg, front, NULL); +} + + +static gpgme_error_t +add_arg_with_locp (engine_gpg_t gpg, const char *arg, int *locp) +{ +  return _add_arg (gpg, arg, 0, locp); +} + + +static gpgme_error_t +add_arg (engine_gpg_t gpg, const char *arg) +{ +  return add_arg_ext (gpg, arg, 0); +} + + +static gpgme_error_t +add_data (engine_gpg_t gpg, gpgme_data_t data, int dup_to, int inbound) +{ +  struct arg_and_data_s *a; + +  assert (gpg); +  assert (data); + +  a = malloc (sizeof *a - 1); +  if (!a) +    return gpg_error_from_errno (errno); +  a->next = NULL; +  a->data = data; +  a->inbound = inbound; +  a->arg_locp = NULL; + +  if (dup_to == -2) +    { +      a->print_fd = 1; +      a->dup_to = -1; +    } +  else +    { +      a->print_fd = 0; +      a->dup_to = dup_to; +    } +  *gpg->argtail = a; +  gpg->argtail = &a->next; +  return 0; +} + + +static char * +gpg_get_version (const char *file_name) +{ +  return _gpgme_get_program_version (file_name ? file_name +				     : _gpgme_get_gpg_path ()); +} + + +static const char * +gpg_get_req_version (void) +{ +  return NEED_GPG_VERSION; +} + + +static void +free_argv (char **argv) +{ +  int i; + +  for (i = 0; argv[i]; i++) +    free (argv[i]); +  free (argv); +} + + +static void +free_fd_data_map (struct fd_data_map_s *fd_data_map) +{ +  int i; + +  if (!fd_data_map) +    return; + +  for (i = 0; fd_data_map[i].data; i++) +    { +      if (fd_data_map[i].fd != -1) +	_gpgme_io_close (fd_data_map[i].fd); +      if (fd_data_map[i].peer_fd != -1) +	_gpgme_io_close (fd_data_map[i].peer_fd); +      /* Don't release data because this is only a reference.  */ +    } +  free (fd_data_map); +} + + +static gpgme_error_t +gpg_cancel (void *engine) +{ +  engine_gpg_t gpg = engine; + +  if (!gpg) +    return gpg_error (GPG_ERR_INV_VALUE); + +  /* If gpg may be waiting for a cmd, close the cmd fd first.  On +     Windows, close operations block on the reader/writer thread.  */ +  if (gpg->cmd.used) +    { +      if (gpg->cmd.fd != -1) +	_gpgme_io_close (gpg->cmd.fd); +      else if (gpg->fd_data_map +	       && gpg->fd_data_map[gpg->cmd.idx].fd != -1) +	_gpgme_io_close (gpg->fd_data_map[gpg->cmd.idx].fd); +    } + +  if (gpg->status.fd[0] != -1) +    _gpgme_io_close (gpg->status.fd[0]); +  if (gpg->status.fd[1] != -1) +    _gpgme_io_close (gpg->status.fd[1]); +  if (gpg->colon.fd[0] != -1) +    _gpgme_io_close (gpg->colon.fd[0]); +  if (gpg->colon.fd[1] != -1) +    _gpgme_io_close (gpg->colon.fd[1]); +  if (gpg->fd_data_map) +    { +      free_fd_data_map (gpg->fd_data_map); +      gpg->fd_data_map = NULL; +    } + +  return 0; +} + +static void +gpg_release (void *engine) +{ +  engine_gpg_t gpg = engine; + +  if (!gpg) +    return; + +  gpg_cancel (engine); + +  if (gpg->file_name) +    free (gpg->file_name); + +  if (gpg->lc_messages) +    free (gpg->lc_messages); +  if (gpg->lc_ctype) +    free (gpg->lc_ctype); + +  while (gpg->arglist) +    { +      struct arg_and_data_s *next = gpg->arglist->next; + +      if (gpg->arglist) +	free (gpg->arglist); +      gpg->arglist = next; +    } + +  if (gpg->status.buffer) +    free (gpg->status.buffer); +  if (gpg->colon.buffer) +    free (gpg->colon.buffer); +  if (gpg->argv) +    free_argv (gpg->argv); +  if (gpg->cmd.keyword) +    free (gpg->cmd.keyword); + +  free (gpg); +} + + +static gpgme_error_t +gpg_new (void **engine, const char *file_name, const char *home_dir) +{ +  engine_gpg_t gpg; +  gpgme_error_t rc = 0; +  char *dft_display = NULL; +  char dft_ttyname[64]; +  char *dft_ttytype = NULL; + +  gpg = calloc (1, sizeof *gpg); +  if (!gpg) +    return gpg_error_from_errno (errno); + +  if (file_name) +    { +      gpg->file_name = strdup (file_name); +      if (!gpg->file_name) +	{ +	  rc = gpg_error_from_errno (errno); +	  goto leave; +	} +    } + +  gpg->argtail = &gpg->arglist; +  gpg->status.fd[0] = -1; +  gpg->status.fd[1] = -1; +  gpg->colon.fd[0] = -1; +  gpg->colon.fd[1] = -1; +  gpg->cmd.fd = -1; +  gpg->cmd.idx = -1; +  gpg->cmd.linked_data = NULL; +  gpg->cmd.linked_idx = -1; + +  /* Allocate the read buffer for the status pipe.  */ +  gpg->status.bufsize = 1024; +  gpg->status.readpos = 0; +  gpg->status.buffer = malloc (gpg->status.bufsize); +  if (!gpg->status.buffer) +    { +      rc = gpg_error_from_errno (errno); +      goto leave; +    } +  /* In any case we need a status pipe - create it right here and +     don't handle it with our generic gpgme_data_t mechanism.  */ +  if (_gpgme_io_pipe (gpg->status.fd, 1) == -1) +    { +      rc = gpg_error_from_errno (errno); +      goto leave; +    } +  if (_gpgme_io_set_close_notify (gpg->status.fd[0], +				  close_notify_handler, gpg) +      || _gpgme_io_set_close_notify (gpg->status.fd[1], +				     close_notify_handler, gpg)) +    { +      rc = gpg_error (GPG_ERR_GENERAL); +      goto leave; +    } +  gpg->status.eof = 0; + +  if (home_dir) +    { +      rc = add_arg (gpg, "--homedir"); +      if (!rc) +	rc = add_arg (gpg, home_dir); +      if (rc) +	goto leave; +    } + +  rc = add_arg (gpg, "--status-fd"); +  if (rc) +    goto leave; + +  { +    char buf[25]; +    _gpgme_io_fd2str (buf, sizeof (buf), gpg->status.fd[1]); +    rc = add_arg_with_locp (gpg, buf, &gpg->status.arg_loc); +    if (rc) +      goto leave; +  } + +  rc = add_arg (gpg, "--no-tty"); +  if (!rc) +    rc = add_arg (gpg, "--charset"); +  if (!rc) +    rc = add_arg (gpg, "utf8"); +  if (!rc) +    rc = add_arg (gpg, "--enable-progress-filter"); +  if (rc) +    goto leave; + +  rc = _gpgme_getenv ("DISPLAY", &dft_display); +  if (rc) +    goto leave; +  if (dft_display) +    { +      rc = add_arg (gpg, "--display"); +      if (!rc) +	rc = add_arg (gpg, dft_display); + +      free (dft_display); +    } + +  if (isatty (1)) +    { +      int err; + +      err = ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)); +      if (err) +	rc = gpg_error_from_errno (err); +      else +	{ +          if (*dft_ttyname) +            { +              rc = add_arg (gpg, "--ttyname"); +              if (!rc) +                rc = add_arg (gpg, dft_ttyname); +            } +          else +            rc = 0; +          if (!rc) +	    { +	      rc = _gpgme_getenv ("TERM", &dft_ttytype); +	      if (rc) +		goto leave; +               +              if (dft_ttytype) +                { +                  rc = add_arg (gpg, "--ttytype"); +                  if (!rc) +                    rc = add_arg (gpg, dft_ttytype); +                } + +	      free (dft_ttytype); +	    } +	} +      if (rc) +	goto leave; +    } + + leave: +  if (rc) +    gpg_release (gpg); +  else +    *engine = gpg; +  return rc; +} + + +static gpgme_error_t +gpg_set_locale (void *engine, int category, const char *value) +{ +  engine_gpg_t gpg = engine; + +  if (category == LC_CTYPE) +    { +      if (gpg->lc_ctype) +        { +          free (gpg->lc_ctype); +          gpg->lc_ctype = NULL; +        } +      if (value) +	{ +	  gpg->lc_ctype = strdup (value); +	  if (!gpg->lc_ctype) +	    return gpg_error_from_syserror (); +	} +    } +#ifdef LC_MESSAGES +  else if (category == LC_MESSAGES) +    { +      if (gpg->lc_messages) +        { +          free (gpg->lc_messages); +          gpg->lc_messages = NULL; +        } +      if (value) +	{ +	  gpg->lc_messages = strdup (value); +	  if (!gpg->lc_messages) +	    return gpg_error_from_syserror (); +	} +    } +#endif /* LC_MESSAGES */ +  else +    return gpg_error (GPG_ERR_INV_VALUE); + +  return 0; +} + + +/* Note, that the status_handler is allowed to modifiy the args +   value.  */ +static void +gpg_set_status_handler (void *engine, engine_status_handler_t fnc, +			void *fnc_value) +{ +  engine_gpg_t gpg = engine; + +  gpg->status.fnc = fnc; +  gpg->status.fnc_value = fnc_value; +} + +/* Kludge to process --with-colon output.  */ +static gpgme_error_t +gpg_set_colon_line_handler (void *engine, engine_colon_line_handler_t fnc, +			    void *fnc_value) +{ +  engine_gpg_t gpg = engine; + +  gpg->colon.bufsize = 1024; +  gpg->colon.readpos = 0; +  gpg->colon.buffer = malloc (gpg->colon.bufsize); +  if (!gpg->colon.buffer) +    return gpg_error_from_errno (errno); + +  if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1)  +    { +      int saved_errno = errno; +      free (gpg->colon.buffer); +      gpg->colon.buffer = NULL; +      return gpg_error_from_errno (saved_errno); +    } +  if (_gpgme_io_set_close_notify (gpg->colon.fd[0], close_notify_handler, gpg) +      || _gpgme_io_set_close_notify (gpg->colon.fd[1], +				     close_notify_handler, gpg)) +    return gpg_error (GPG_ERR_GENERAL); +  gpg->colon.eof = 0; +  gpg->colon.fnc = fnc; +  gpg->colon.fnc_value = fnc_value; +  return 0; +} + + +static gpgme_error_t +command_handler (void *opaque, int fd) +{ +  gpgme_error_t err; +  engine_gpg_t gpg = (engine_gpg_t) opaque; +  int processed = 0; + +  assert (gpg->cmd.used); +  assert (gpg->cmd.code); +  assert (gpg->cmd.fnc); + +  err = gpg->cmd.fnc (gpg->cmd.fnc_value, gpg->cmd.code, gpg->cmd.keyword, fd, +		      &processed); + +  gpg->cmd.code = 0; +  /* And sleep again until read_status will wake us up again.  */ +  /* XXX We must check if there are any more fds active after removing +     this one.  */ +  (*gpg->io_cbs.remove) (gpg->fd_data_map[gpg->cmd.idx].tag); +  gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd; +  gpg->fd_data_map[gpg->cmd.idx].fd = -1; + +  if (err) +    return err; + +  /* We always need to send at least a newline character.  */ +  if (!processed) +    _gpgme_io_write (fd, "\n", 1); + +  return 0; +} + + + +/* The Fnc will be called to get a value for one of the commands with +   a key KEY.  If the Code pssed to FNC is 0, the function may release +   resources associated with the returned value from another call.  To +   match such a second call to a first call, the returned value from +   the first call is passed as keyword.  */ +static gpgme_error_t +gpg_set_command_handler (void *engine, engine_command_handler_t fnc, +			 void *fnc_value, gpgme_data_t linked_data) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t rc; + +  rc = add_arg (gpg, "--command-fd"); +  if (rc) +    return rc; + +  /* This is a hack.  We don't have a real data object.  The only +     thing that matters is that we use something unique, so we use the +     address of the cmd structure in the gpg object.  */ +  rc = add_data (gpg, (void *) &gpg->cmd, -2, 0); +  if (rc) +    return rc; + +  gpg->cmd.fnc = fnc; +  gpg->cmd.cb_data = (void *) &gpg->cmd; +  gpg->cmd.fnc_value = fnc_value; +  gpg->cmd.linked_data = linked_data; +  gpg->cmd.used = 1; +  return 0; +} + + +static gpgme_error_t +build_argv (engine_gpg_t gpg) +{ +  gpgme_error_t err; +  struct arg_and_data_s *a; +  struct fd_data_map_s *fd_data_map; +  size_t datac=0, argc=0;   +  char **argv; +  int need_special = 0; +  int use_agent = 0; +  char *p; + +  /* We don't want to use the agent with a malformed environment +     variable.  This is only a very basic test but sufficient to make +     our life in the regression tests easier. */ +  err = _gpgme_getenv ("GPG_AGENT_INFO", &p); +  if (err) +    return err; +  use_agent = (p && strchr (p, ':')); +  if (p) +    free (p); + +  if (gpg->argv) +    { +      free_argv (gpg->argv); +      gpg->argv = NULL; +    } +  if (gpg->fd_data_map) +    { +      free_fd_data_map (gpg->fd_data_map); +      gpg->fd_data_map = NULL; +    } + +  argc++;	/* For argv[0].  */ +  for (a = gpg->arglist; a; a = a->next) +    { +      argc++; +      if (a->data) +	{ +	  /*fprintf (stderr, "build_argv: data\n" );*/ +	  datac++; +	  if (a->dup_to == -1 && !a->print_fd) +	    need_special = 1; +        } +      else +	{ +	  /*   fprintf (stderr, "build_argv: arg=`%s'\n", a->arg );*/ +        } +    } +  if (need_special) +    argc++; +  if (use_agent) +    argc++; +  if (!gpg->cmd.used) +    argc++;	/* --batch */ +  argc += 1;	/* --no-sk-comment */ + +  argv = calloc (argc + 1, sizeof *argv); +  if (!argv) +    return gpg_error_from_errno (errno); +  fd_data_map = calloc (datac + 1, sizeof *fd_data_map); +  if (!fd_data_map) +    { +      int saved_errno = errno; +      free_argv (argv); +      return gpg_error_from_errno (saved_errno); +    } + +  argc = datac = 0; +  argv[argc] = strdup ("gpg"); /* argv[0] */ +  if (!argv[argc]) +    { +      int saved_errno = errno; +      free (fd_data_map); +      free_argv (argv); +      return gpg_error_from_errno (saved_errno); +    } +  argc++; +  if (need_special) +    { +      argv[argc] = strdup ("--enable-special-filenames"); +      if (!argv[argc]) +	{ +	  int saved_errno = errno; +	  free (fd_data_map); +	  free_argv (argv); +	  return gpg_error_from_errno (saved_errno); +        } +      argc++; +    } +  if (use_agent) +    { +      argv[argc] = strdup ("--use-agent"); +      if (!argv[argc]) +	{ +	  int saved_errno = errno; +	  free (fd_data_map); +	  free_argv (argv); +	  return gpg_error_from_errno (saved_errno); +        } +      argc++; +    } +  if (!gpg->cmd.used) +    { +      argv[argc] = strdup ("--batch"); +      if (!argv[argc]) +	{ +	  int saved_errno = errno; +	  free (fd_data_map); +	  free_argv (argv); +	  return gpg_error_from_errno (saved_errno); +        } +      argc++; +    } +  argv[argc] = strdup ("--no-sk-comment"); +  if (!argv[argc]) +    { +      int saved_errno = errno; +      free (fd_data_map); +      free_argv (argv); +      return gpg_error_from_errno (saved_errno); +    } +  argc++; +  for (a = gpg->arglist; a; a = a->next) +    { +      if (a->arg_locp) +	*(a->arg_locp) = argc; + +      if (a->data) +	{ +	  /* Create a pipe to pass it down to gpg.  */ +	  fd_data_map[datac].inbound = a->inbound; + +	  /* Create a pipe.  */ +	  {    +	    int fds[2]; +	     +	    if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound ? 1 : 0) +		== -1) +	      { +		int saved_errno = errno; +		free (fd_data_map); +		free_argv (argv); +		return gpg_error (saved_errno); +	      } +	    if (_gpgme_io_set_close_notify (fds[0], +					    close_notify_handler, gpg) +		|| _gpgme_io_set_close_notify (fds[1], +					       close_notify_handler, +					       gpg)) +	      { +		return gpg_error (GPG_ERR_GENERAL); +	      } +	    /* If the data_type is FD, we have to do a dup2 here.  */ +	    if (fd_data_map[datac].inbound) +	      { +		fd_data_map[datac].fd       = fds[0]; +		fd_data_map[datac].peer_fd  = fds[1]; +	      } +	    else +	      { +		fd_data_map[datac].fd       = fds[1]; +		fd_data_map[datac].peer_fd  = fds[0]; +	      } +	  } + +	  /* Hack to get hands on the fd later.  */ +	  if (gpg->cmd.used) +	    { +	      if (gpg->cmd.cb_data == a->data) +		{ +		  assert (gpg->cmd.idx == -1); +		  gpg->cmd.idx = datac; +		} +	      else if (gpg->cmd.linked_data == a->data) +		{ +		  assert (gpg->cmd.linked_idx == -1); +		  gpg->cmd.linked_idx = datac; +		} +	    } + +	  fd_data_map[datac].data = a->data; +	  fd_data_map[datac].dup_to = a->dup_to; + +	  if (a->dup_to == -1) +	    { +	      char *ptr; +	      int buflen = 25; + +	      argv[argc] = malloc (buflen); +	      if (!argv[argc]) +		{ +		  int saved_errno = errno; +		  free (fd_data_map); +		  free_argv (argv); +		  return gpg_error_from_errno (saved_errno); +                } + +	      ptr = argv[argc]; +	      if (!a->print_fd) +		{ +		  *(ptr++) = '-'; +		  *(ptr++) = '&'; +		  buflen -= 2; +		} + +	      _gpgme_io_fd2str (ptr, buflen, fd_data_map[datac].peer_fd); +	      fd_data_map[datac].arg_loc = argc; +	      argc++; +            } +	  datac++; +        } +      else +	{ +	  argv[argc] = strdup (a->arg); +	  if (!argv[argc]) +	    { +	      int saved_errno = errno; +	      free (fd_data_map); +	      free_argv (argv); +	      return gpg_error_from_errno (saved_errno); +            } +            argc++; +        } +    } + +  gpg->argv = argv; +  gpg->fd_data_map = fd_data_map; +  return 0; +} + + +static gpgme_error_t +add_io_cb (engine_gpg_t gpg, int fd, int dir, gpgme_io_cb_t handler, void *data, +	   void **tag) +{ +  gpgme_error_t err; + +  err = (*gpg->io_cbs.add) (gpg->io_cbs.add_priv, fd, dir, handler, data, tag); +  if (err) +    return err; +  if (!dir) +    /* FIXME Kludge around poll() problem.  */ +    err = _gpgme_io_set_nonblocking (fd); +  return err; +} + + +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); +} + + +/* Handle the status output of GnuPG.  This function does read entire +   lines and passes them as C strings to the callback function (we can +   use C Strings because the status output is always UTF-8 encoded). +   Of course we have to buffer the lines to cope with long lines +   e.g. with a large user ID.  Note: We can optimize this to only cope +   with status line code we know about and skip all other stuff +   without buffering (i.e. without extending the buffer).  */ +static gpgme_error_t +read_status (engine_gpg_t gpg) +{ +  char *p; +  int nread; +  size_t bufsize = gpg->status.bufsize;  +  char *buffer = gpg->status.buffer; +  size_t readpos = gpg->status.readpos;  + +  assert (buffer); +  if (bufsize - readpos < 256) +    {  +      /* Need more room for the read.  */ +      bufsize += 1024; +      buffer = realloc (buffer, bufsize); +      if (!buffer) +	return gpg_error_from_errno (errno); +    } + +  nread = _gpgme_io_read (gpg->status.fd[0], +			  buffer + readpos, bufsize-readpos); +  if (nread == -1) +    return gpg_error_from_errno (errno); + +  if (!nread) +    { +      gpg->status.eof = 1; +      if (gpg->status.fnc) +	{ +	  gpgme_error_t err; +	  err = gpg->status.fnc (gpg->status.fnc_value, GPGME_STATUS_EOF, ""); +	  if (err) +	    return err; +	} +      return 0; +    } + +  while (nread > 0) +    { +      for (p = buffer + readpos; nread; nread--, p++) +	{ +	  if (*p == '\n') +	    { +	      /* (we require that the last line is terminated by a LF) */ +	      if (p > buffer && p[-1] == '\r') +		p[-1] = 0; +	      *p = 0; +	      if (!strncmp (buffer, "[GNUPG:] ", 9) +		  && buffer[9] >= 'A' && buffer[9] <= 'Z') +		{ +		  struct status_table_s t, *r; +		  char *rest; + +		  rest = strchr (buffer + 9, ' '); +		  if (!rest) +		    rest = p; /* Set to an empty string.  */ +		  else +		    *rest++ = 0; +                     +		  t.name = buffer+9; +		  /* (the status table has one extra element) */ +		  r = bsearch (&t, status_table, DIM(status_table) - 1, +			       sizeof t, status_cmp); +		  if (r) +		    { +		      if (gpg->cmd.used +			  && (r->code == GPGME_STATUS_GET_BOOL +			      || r->code == GPGME_STATUS_GET_LINE +			      || r->code == GPGME_STATUS_GET_HIDDEN)) +			{ +			  gpg->cmd.code = r->code; +			  if (gpg->cmd.keyword) +			    free (gpg->cmd.keyword); +			  gpg->cmd.keyword = strdup (rest); +			  if (!gpg->cmd.keyword) +			    return gpg_error_from_errno (errno); +			  /* This should be the last thing we have +			     received and the next thing will be that +			     the command handler does its action.  */ +			  if (nread > 1) +			    TRACE0 (DEBUG_CTX, "gpgme:read_status", 0, +				    "error: unexpected data"); + +			  add_io_cb (gpg, gpg->cmd.fd, 0, +				     command_handler, gpg, +				     &gpg->fd_data_map[gpg->cmd.idx].tag); +			  gpg->fd_data_map[gpg->cmd.idx].fd = gpg->cmd.fd; +			  gpg->cmd.fd = -1; +                        } +		      else if (gpg->status.fnc) +			{ +			  gpgme_error_t err; +			  err = gpg->status.fnc (gpg->status.fnc_value,  +						 r->code, rest); +			  if (err) +			    return err; +                        } +                     +		      if (r->code == GPGME_STATUS_END_STREAM) +			{ +			  if (gpg->cmd.used) +			    { +			      /* Before we can actually add the +				 command fd, we might have to flush +				 the linked output data pipe.  */ +			      if (gpg->cmd.linked_idx != -1 +				  && gpg->fd_data_map[gpg->cmd.linked_idx].fd +				  != -1) +				{ +				  struct io_select_fd_s fds; +				  fds.fd = +				    gpg->fd_data_map[gpg->cmd.linked_idx].fd; +				  fds.for_read = 1; +				  fds.for_write = 0; +				  fds.opaque = NULL; +				  do +				    { +				      fds.signaled = 0; +				      _gpgme_io_select (&fds, 1, 1); +				      if (fds.signaled) +					_gpgme_data_inbound_handler +					  (gpg->cmd.linked_data, fds.fd); +				    } +				  while (fds.signaled); +				} + +			      /* XXX We must check if there are any +				 more fds active after removing this +				 one.  */ +			      (*gpg->io_cbs.remove) +				(gpg->fd_data_map[gpg->cmd.idx].tag); +			      gpg->cmd.fd = gpg->fd_data_map[gpg->cmd.idx].fd; +			      gpg->fd_data_map[gpg->cmd.idx].fd = -1; +			    } +                        } +                    } +                } +	      /* To reuse the buffer for the next line we have to +		 shift the remaining data to the buffer start and +		 restart the loop Hmmm: We can optimize this function +		 by looking forward in the buffer to see whether a +		 second complete line is available and in this case +		 avoid the memmove for this line.  */ +	      nread--; p++; +	      if (nread) +		memmove (buffer, p, nread); +	      readpos = 0; +	      break; /* the for loop */ +            } +	  else +	    readpos++; +        } +    }  + +  /* Update the gpg object.  */ +  gpg->status.bufsize = bufsize; +  gpg->status.buffer = buffer; +  gpg->status.readpos = readpos; +  return 0; +} + + +static gpgme_error_t +status_handler (void *opaque, int fd) +{ +  engine_gpg_t gpg = opaque; +  int err; + +  assert (fd == gpg->status.fd[0]); +  err = read_status (gpg); +  if (err) +    return err; +  if (gpg->status.eof) +    _gpgme_io_close (fd); +  return 0; +} + + +static gpgme_error_t +read_colon_line (engine_gpg_t gpg) +{ +  char *p; +  int nread; +  size_t bufsize = gpg->colon.bufsize;  +  char *buffer = gpg->colon.buffer; +  size_t readpos = gpg->colon.readpos;  + +  assert (buffer); +  if (bufsize - readpos < 256) +    {  +      /* Need more room for the read.  */ +      bufsize += 1024; +      buffer = realloc (buffer, bufsize); +      if (!buffer)  +	return gpg_error_from_errno (errno); +    } + +  nread = _gpgme_io_read (gpg->colon.fd[0], buffer+readpos, bufsize-readpos); +  if (nread == -1) +    return gpg_error_from_errno (errno); + +  if (!nread) +    { +      gpg->colon.eof = 1; +      assert (gpg->colon.fnc); +      gpg->colon.fnc (gpg->colon.fnc_value, NULL); +      return 0; +    } + +  while (nread > 0) +    { +      for (p = buffer + readpos; nread; nread--, p++) +	{ +	  if ( *p == '\n' ) +	    { +	      /* (we require that the last line is terminated by a LF) +		 and we skip empty lines.  Note: we use UTF8 encoding +		 and escaping of special characters.  We require at +		 least one colon to cope with some other printed +		 information.  */ +	      *p = 0; +	      if (*buffer && strchr (buffer, ':')) +		{ +		  char *line = NULL; + +		  if (gpg->colon.preprocess_fnc) +		    { +		      gpgme_error_t err; + +		      err = gpg->colon.preprocess_fnc (buffer, &line); +		      if (err) +			return err; +		    } + +		  assert (gpg->colon.fnc); +		  gpg->colon.fnc (gpg->colon.fnc_value, line ? line : buffer); +		  if (line) +		    free (line); +		} +             +	      /* To reuse the buffer for the next line we have to +		 shift the remaining data to the buffer start and +		 restart the loop Hmmm: We can optimize this function +		 by looking forward in the buffer to see whether a +		 second complete line is available and in this case +		 avoid the memmove for this line.  */ +	      nread--; p++; +	      if (nread) +		memmove (buffer, p, nread); +	      readpos = 0; +	      break; /* The for loop.  */ +            } +	  else +	    readpos++; +        } +    }  + +  /* Update the gpg object.  */ +  gpg->colon.bufsize = bufsize; +  gpg->colon.buffer  = buffer; +  gpg->colon.readpos = readpos; +  return 0; +} + + +/* This colonline handler thing is not the clean way to do it.  It +   might be better to enhance the gpgme_data_t object to act as a wrapper +   for a callback.  Same goes for the status thing.  For now we use +   this thing here because it is easier to implement.  */ +static gpgme_error_t +colon_line_handler (void *opaque, int fd) +{ +  engine_gpg_t gpg = opaque; +  gpgme_error_t rc = 0; + +  assert (fd == gpg->colon.fd[0]); +  rc = read_colon_line (gpg); +  if (rc) +    return rc; +  if (gpg->colon.eof) +    _gpgme_io_close (fd); +  return 0; +} + + +static gpgme_error_t +start (engine_gpg_t gpg) +{ +  gpgme_error_t rc; +  int saved_errno; +  int i, n; +  int status; +  struct spawn_fd_item_s *fd_list; +  pid_t pid; + +  if (!gpg) +    return gpg_error (GPG_ERR_INV_VALUE); + +  if (!gpg->file_name && !_gpgme_get_gpg_path ()) +    return gpg_error (GPG_ERR_INV_ENGINE); + +  if (gpg->lc_ctype) +    { +      rc = add_arg_ext (gpg, gpg->lc_ctype, 1); +      if (!rc) +	rc = add_arg_ext (gpg, "--lc-ctype", 1); +      if (rc) +	return rc; +    } + +  if (gpg->lc_messages) +    { +      rc = add_arg_ext (gpg, gpg->lc_messages, 1); +      if (!rc) +	rc = add_arg_ext (gpg, "--lc-messages", 1); +      if (rc) +	return rc; +    } + +  rc = build_argv (gpg); +  if (rc) +    return rc; + +  /* status_fd, colon_fd and end of list.  */ +  n = 3; +  for (i = 0; gpg->fd_data_map[i].data; i++)  +    n++; +  fd_list = calloc (n, sizeof *fd_list); +  if (! fd_list) +    return gpg_error_from_errno (errno); + +  /* Build the fd list for the child.  */ +  n = 0; +  fd_list[n].fd = gpg->status.fd[1]; +  fd_list[n].dup_to = -1; +  fd_list[n].arg_loc = gpg->status.arg_loc; +  n++; +  if (gpg->colon.fnc) +    { +      fd_list[n].fd = gpg->colon.fd[1];  +      fd_list[n].dup_to = 1; +      n++; +    } +  for (i = 0; gpg->fd_data_map[i].data; i++) +    { +      fd_list[n].fd = gpg->fd_data_map[i].peer_fd; +      fd_list[n].dup_to = gpg->fd_data_map[i].dup_to; +      fd_list[n].arg_loc = gpg->fd_data_map[i].arg_loc; +      n++; +    } +  fd_list[n].fd = -1; +  fd_list[n].dup_to = -1; + +  status = _gpgme_io_spawn (gpg->file_name ? gpg->file_name : +			    _gpgme_get_gpg_path (), gpg->argv, fd_list, &pid); +  saved_errno = errno; + +  free (fd_list); +  if (status == -1) +    return gpg_error_from_errno (saved_errno); + +  /*_gpgme_register_term_handler ( closure, closure_value, pid );*/ + +  rc = add_io_cb (gpg, gpg->status.fd[0], 1, status_handler, gpg, +		  &gpg->status.tag); +  if (rc) +    /* FIXME: kill the child */ +    return rc; + +  if (gpg->colon.fnc) +    { +      assert (gpg->colon.fd[0] != -1); +      rc = add_io_cb (gpg, gpg->colon.fd[0], 1, colon_line_handler, gpg, +		      &gpg->colon.tag); +      if (rc) +	/* FIXME: kill the child */ +	return rc; +    } + +  for (i = 0; gpg->fd_data_map[i].data; i++) +    { +      if (gpg->cmd.used && i == gpg->cmd.idx) +	{ +	  /* Park the cmd fd.  */ +	  gpg->cmd.fd = gpg->fd_data_map[i].fd; +	  gpg->fd_data_map[i].fd = -1; +	} +      else +	{ +	  rc = add_io_cb (gpg, gpg->fd_data_map[i].fd, +			  gpg->fd_data_map[i].inbound, +			  gpg->fd_data_map[i].inbound +			  ? _gpgme_data_inbound_handler +			  : _gpgme_data_outbound_handler, +			  gpg->fd_data_map[i].data, &gpg->fd_data_map[i].tag); +	   +	  if (rc) +	    /* FIXME: kill the child */ +	    return rc; +	} +    } + +  _gpgme_allow_set_foregound_window (pid); + +  gpg_io_event (gpg, GPGME_EVENT_START, NULL); +   +  /* fixme: check what data we can release here */ +  return 0; +} + + +static gpgme_error_t +gpg_decrypt (void *engine, gpgme_data_t ciph, gpgme_data_t plain) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, "--decrypt"); + +  /* Tell the gpg object about the data.  */ +  if (!err) +    err = add_arg (gpg, "--output"); +  if (!err) +    err = add_arg (gpg, "-"); +  if (!err) +    err = add_data (gpg, plain, 1, 1); +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, ciph, -1, 0); + +  if (!err) +    start (gpg); +  return err; +} + +static gpgme_error_t +gpg_delete (void *engine, gpgme_key_t key, int allow_secret) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, allow_secret ? "--delete-secret-and-public-key" +		 : "--delete-key"); +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    { +      if (!key->subkeys || !key->subkeys->fpr) +	return gpg_error (GPG_ERR_INV_VALUE); +      else +	err = add_arg (gpg, key->subkeys->fpr); +    } + +  if (!err) +    start (gpg); +  return err; +} + + +static gpgme_error_t +append_args_from_signers (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */) +{ +  gpgme_error_t err = 0; +  int i; +  gpgme_key_t key; + +  for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++) +    { +      const char *s = key->subkeys ? key->subkeys->keyid : NULL; +      if (s) +	{ +	  if (!err) +	    err = add_arg (gpg, "-u"); +	  if (!err) +	    err = add_arg (gpg, s); +	} +      gpgme_key_unref (key); +      if (err) break; +    } +  return err; +} + + +static gpgme_error_t +append_args_from_sig_notations (engine_gpg_t gpg, gpgme_ctx_t ctx /* FIXME */) +{ +  gpgme_error_t err = 0; +  gpgme_sig_notation_t notation; + +  notation = gpgme_sig_notation_get (ctx); + +  while (!err && notation) +    { +      if (notation->name +	  && !(notation->flags & GPGME_SIG_NOTATION_HUMAN_READABLE)) +	err = gpg_error (GPG_ERR_INV_VALUE); +      else if (notation->name) +	{ +	  char *arg; + +	  /* Maximum space needed is one byte for the "critical" flag, +	     the name, one byte for '=', the value, and a terminating +	     '\0'.  */ + +	  arg = malloc (1 + notation->name_len + 1 + notation->value_len + 1); +	  if (!arg) +	    err = gpg_error_from_errno (errno); + +	  if (!err) +	    { +	      char *argp = arg; + +	      if (notation->critical) +		*(argp++) = '!'; + +	      memcpy (argp, notation->name, notation->name_len); +	      argp += notation->name_len; + +	      *(argp++) = '='; + +	      /* We know that notation->name is '\0' terminated.  */ +	      strcpy (argp, notation->value); +	    } + +	  if (!err) +	    err = add_arg (gpg, "--sig-notation"); +	  if (!err) +	    err = add_arg (gpg, arg); + +	  if (arg) +	    free (arg); +	} +      else +	{ +	  /* This is a policy URL.  */ + +	  char *value; + +	  if (notation->critical) +	    { +	      value = malloc (1 + notation->value_len + 1); +	      if (!value) +		err = gpg_error_from_errno (errno); +	      else +		{ +		  value[0] = '!'; +		  /* We know that notation->value is '\0' terminated.  */ +		  strcpy (&value[1], notation->value); +		} +	    } +	  else +	    value = notation->value; + +	  if (!err) +	    err = add_arg (gpg, "--sig-policy-url"); +	  if (!err) +	    err = add_arg (gpg, value); + +	  if (value != notation->value) +	    free (value); +      	} + +      notation = notation->next; +    } +  return err; +} + + +static gpgme_error_t +gpg_edit (void *engine, int type, gpgme_key_t key, gpgme_data_t out, +	  gpgme_ctx_t ctx /* FIXME */) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, "--with-colons"); +  if (!err) +    err = append_args_from_signers (gpg, ctx); +  if (!err) +  err = add_arg (gpg, type == 0 ? "--edit-key" : "--card-edit"); +  if (!err) +    err = add_data (gpg, out, 1, 1); +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err && type == 0) +    { +      const char *s = key->subkeys ? key->subkeys->fpr : NULL; +      if (!s) +	err = gpg_error (GPG_ERR_INV_VALUE); +      else +	err = add_arg (gpg, s); +    } +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +append_args_from_recipients (engine_gpg_t gpg, gpgme_key_t recp[]) +{ +  gpgme_error_t err = 0; +  int i = 0; + +  while (recp[i]) +    { +      if (!recp[i]->subkeys || !recp[i]->subkeys->fpr) +	err = gpg_error (GPG_ERR_INV_VALUE); +      if (!err) +	err = add_arg (gpg, "-r"); +      if (!err) +	err = add_arg (gpg, recp[i]->subkeys->fpr); +      if (err) +	break; +      i++; +    }     +  return err; +} + + +static gpgme_error_t +gpg_encrypt (void *engine, gpgme_key_t recp[], gpgme_encrypt_flags_t flags, +	     gpgme_data_t plain, gpgme_data_t ciph, int use_armor) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; +  int symmetric = !recp; + +  err = add_arg (gpg, symmetric ? "--symmetric" : "--encrypt"); + +  if (!err && use_armor) +    err = add_arg (gpg, "--armor"); + +  if (!symmetric) +    { +      /* If we know that all recipients are valid (full or ultimate trust) +	 we can suppress further checks.  */ +      if (!err && !symmetric && (flags & GPGME_ENCRYPT_ALWAYS_TRUST)) +	err = add_arg (gpg, "--always-trust"); + +      if (!err) +	err = append_args_from_recipients (gpg, recp); +    } + +  /* Tell the gpg object about the data.  */ +  if (!err) +    err = add_arg (gpg, "--output"); +  if (!err) +    err = add_arg (gpg, "-"); +  if (!err) +    err = add_data (gpg, ciph, 1, 1); +  if (gpgme_data_get_file_name (plain)) +    { +      if (!err) +	err = add_arg (gpg, "--set-filename"); +      if (!err) +	err = add_arg (gpg, gpgme_data_get_file_name (plain)); +    } +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, plain, -1, 0); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_encrypt_sign (void *engine, gpgme_key_t recp[], +		  gpgme_encrypt_flags_t flags, gpgme_data_t plain, +		  gpgme_data_t ciph, int use_armor, +		  gpgme_ctx_t ctx /* FIXME */) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, "--encrypt"); +  if (!err) +    err = add_arg (gpg, "--sign"); +  if (!err && use_armor) +    err = add_arg (gpg, "--armor"); + +  /* If we know that all recipients are valid (full or ultimate trust) +     we can suppress further checks.  */ +  if (!err && (flags & GPGME_ENCRYPT_ALWAYS_TRUST)) +    err = add_arg (gpg, "--always-trust"); + +  if (!err) +    err = append_args_from_recipients (gpg, recp); + +  if (!err) +    err = append_args_from_signers (gpg, ctx); +  if (!err) +    err = append_args_from_sig_notations (gpg, ctx); + +  /* Tell the gpg object about the data.  */ +  if (!err) +    err = add_arg (gpg, "--output"); +  if (!err) +    err = add_arg (gpg, "-"); +  if (!err) +    err = add_data (gpg, ciph, 1, 1); +  if (gpgme_data_get_file_name (plain)) +    { +      if (!err) +	err = add_arg (gpg, "--set-filename"); +      if (!err) +	err = add_arg (gpg, gpgme_data_get_file_name (plain)); +    } +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, plain, -1, 0); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_export (void *engine, const char *pattern, unsigned int reserved, +	    gpgme_data_t keydata, int use_armor) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  if (reserved) +    return gpg_error (GPG_ERR_INV_VALUE); + +  err = add_arg (gpg, "--export"); +  if (!err && use_armor) +    err = add_arg (gpg, "--armor"); +  if (!err) +    err = add_data (gpg, keydata, 1, 1); +  if (!err) +    err = add_arg (gpg, "--"); + +  if (!err && pattern && *pattern) +    err = add_arg (gpg, pattern); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_export_ext (void *engine, const char *pattern[], unsigned int reserved, +		gpgme_data_t keydata, int use_armor) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  if (reserved) +    return gpg_error (GPG_ERR_INV_VALUE); + +  err = add_arg (gpg, "--export"); +  if (!err && use_armor) +    err = add_arg (gpg, "--armor"); +  if (!err) +    err = add_data (gpg, keydata, 1, 1); +  if (!err) +    err = add_arg (gpg, "--"); + +  if (pattern) +    { +      while (!err && *pattern && **pattern) +	err = add_arg (gpg, *(pattern++)); +    } + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_genkey (void *engine, gpgme_data_t help_data, int use_armor, +	    gpgme_data_t pubkey, gpgme_data_t seckey) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  if (!gpg) +    return gpg_error (GPG_ERR_INV_VALUE); + +  /* We need a special mechanism to get the fd of a pipe here, so that +     we can use this for the %pubring and %secring parameters.  We +     don't have this yet, so we implement only the adding to the +     standard keyrings.  */ +  if (pubkey || seckey) +    return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + +  err = add_arg (gpg, "--gen-key"); +  if (!err && use_armor) +    err = add_arg (gpg, "--armor"); +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, help_data, -1, 0); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_import (void *engine, gpgme_data_t keydata) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, "--import"); +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, keydata, -1, 0); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +/* The output for external keylistings in GnuPG is different from all +   the other key listings.  We catch this here with a special +   preprocessor that reformats the colon handler lines.  */ +static gpgme_error_t +gpg_keylist_preprocess (char *line, char **r_line) +{ +  enum +    { +      RT_NONE, RT_INFO, RT_PUB, RT_UID +    } +  rectype = RT_NONE; +#define NR_FIELDS 16 +  char *field[NR_FIELDS]; +  int fields = 0; + +  *r_line = NULL; + +  while (line && fields < NR_FIELDS) +    { +      field[fields++] = line; +      line = strchr (line, ':'); +      if (line) +	*(line++) = '\0'; +    } + +  if (!strcmp (field[0], "info")) +    rectype = RT_INFO; +  else if (!strcmp (field[0], "pub")) +    rectype = RT_PUB; +  else if (!strcmp (field[0], "uid")) +    rectype = RT_UID; +  else  +    rectype = RT_NONE; + +  switch (rectype) +    { +    case RT_INFO: +      /* FIXME: Eventually, check the version number at least.  */ +      return 0; + +    case RT_PUB: +      if (fields < 7) +	return 0; + +      /* The format is: + +	 pub:<keyid>:<algo>:<keylen>:<creationdate>:<expirationdate>:<flags> + +	 as defined in 5.2. Machine Readable Indexes of the OpenPGP +	 HTTP Keyserver Protocol (draft).  + +	 We want: +	 pub:o<flags>:<keylen>:<algo>:<keyid>:<creatdate>:<expdate>:::::::: +      */ + +      if (asprintf (r_line, "pub:o%s:%s:%s:%s:%s:%s::::::::", +		    field[6], field[3], field[2], field[1], +		    field[4], field[5]) < 0) +	return gpg_error_from_errno (errno); +      return 0; + +    case RT_UID: +      /* The format is: + +         uid:<escaped uid string>:<creationdate>:<expirationdate>:<flags> + +	 as defined in 5.2. Machine Readable Indexes of the OpenPGP +	 HTTP Keyserver Protocol (draft).  + +	 We want: +	 uid:o<flags>::::<creatdate>:<expdate>:::<c-coded uid>: +      */ + +      { +	/* The user ID is percent escaped, but we want c-coded. +	   Because we have to replace each '%HL' by '\xHL', we need at +	   most 4/3 th the number of bytes.  But because we also need +	   to escape the backslashes we allocate twice as much.  */ +	char *uid = malloc (2 * strlen (field[1]) + 1); +	char *src; +	char *dst; + +	if (! uid) +	  return gpg_error_from_errno (errno); +	src = field[1]; +	dst = uid; +	while (*src) +	  { +	    if (*src == '%') +	      { +		*(dst++) = '\\'; +		*(dst++) = 'x'; +		src++; +		/* Copy the next two bytes unconditionally.  */ +		if (*src) +		  *(dst++) = *(src++); +		if (*src) +		  *(dst++) = *(src++); +	      } +	    else if (*src == '\\') +              { +                *dst++ = '\\'; +                *dst++ = '\\'; +              } +	    else +	      *(dst++) = *(src++); +	  } +	*dst = '\0'; + +	if (asprintf (r_line, "uid:o%s::::%s:%s:::%s:", +		      field[4], field[2], field[3], uid) < 0) +	  return gpg_error_from_errno (errno); +      } +      return 0; + +    case RT_NONE: +      /* Unknown record.  */ +      break; +    } +  return 0; + +} + + +static gpg_error_t +gpg_keylist_build_options (engine_gpg_t gpg, int secret_only, +                           gpgme_keylist_mode_t mode) +{ +  gpg_error_t err; + +  err = add_arg (gpg, "--with-colons"); +  if (!err) +    err = add_arg (gpg, "--fixed-list-mode"); +  if (!err) +    err = add_arg (gpg, "--with-fingerprint"); +  if (!err) +    err = add_arg (gpg, "--with-fingerprint"); +  if (!err +      && (mode & GPGME_KEYLIST_MODE_SIGS) +      && (mode & GPGME_KEYLIST_MODE_SIG_NOTATIONS)) +    { +      err = add_arg (gpg, "--list-options"); +      if (!err) +	err = add_arg (gpg, "show-sig-subpackets=\"20,26\""); +    } +  if (!err) +    { +      if ( (mode & GPGME_KEYLIST_MODE_EXTERN) ) +	{ +          if (secret_only) +            err = gpg_error (GPG_ERR_NOT_SUPPORTED); +          else if ( (mode & GPGME_KEYLIST_MODE_LOCAL)) +            { +              /* The local+extern mode is special.  It works only with +                 gpg >= 2.0.10.  FIXME: We should check that we have +                 such a version to that we can return a proper error +                 code.  The problem is that we don't know the context +                 here and thus can't accesses the cached version +                 number for the engine info structure.  */ +              err = add_arg (gpg, "--locate-keys"); +              if ((mode & GPGME_KEYLIST_MODE_SIGS)) +                err = add_arg (gpg, "--with-sig-check"); +            } +          else +            { +              err = add_arg (gpg, "--search-keys"); +              gpg->colon.preprocess_fnc = gpg_keylist_preprocess; +            } +	} +      else +        { +          err = add_arg (gpg, secret_only ? "--list-secret-keys" +                         : ((mode & GPGME_KEYLIST_MODE_SIGS) +                            ? "--check-sigs" : "--list-keys")); +        } +    } +  if (!err) +    err = add_arg (gpg, "--"); +   +  return err; +} +                            + +static gpgme_error_t +gpg_keylist (void *engine, const char *pattern, int secret_only, +	     gpgme_keylist_mode_t mode) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = gpg_keylist_build_options (gpg, secret_only, mode); + +  if (!err && pattern && *pattern) +    err = add_arg (gpg, pattern); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_keylist_ext (void *engine, const char *pattern[], int secret_only, +		 int reserved, gpgme_keylist_mode_t mode) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  if (reserved) +    return gpg_error (GPG_ERR_INV_VALUE); + +  err = gpg_keylist_build_options (gpg, secret_only, mode); + +  if (pattern) +    { +      while (!err && *pattern && **pattern) +	err = add_arg (gpg, *(pattern++)); +    } + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_sign (void *engine, gpgme_data_t in, gpgme_data_t out, +	  gpgme_sig_mode_t mode, int use_armor, int use_textmode, +	  int include_certs, gpgme_ctx_t ctx /* FIXME */) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  if (mode == GPGME_SIG_MODE_CLEAR) +    err = add_arg (gpg, "--clearsign"); +  else +    { +      err = add_arg (gpg, "--sign"); +      if (!err && mode == GPGME_SIG_MODE_DETACH) +	err = add_arg (gpg, "--detach"); +      if (!err && use_armor) +	err = add_arg (gpg, "--armor"); +      if (!err && use_textmode) +	err = add_arg (gpg, "--textmode"); +    } + +  if (!err) +    err = append_args_from_signers (gpg, ctx); +  if (!err) +    err = append_args_from_sig_notations (gpg, ctx); + +  if (gpgme_data_get_file_name (in)) +    { +      if (!err) +	err = add_arg (gpg, "--set-filename"); +      if (!err) +	err = add_arg (gpg, gpgme_data_get_file_name (in)); +    } + +  /* Tell the gpg object about the data.  */ +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_data (gpg, in, -1, 0); +  if (!err) +    err = add_data (gpg, out, 1, 1); + +  if (!err) +    start (gpg); + +  return err; +} + +static gpgme_error_t +gpg_trustlist (void *engine, const char *pattern) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err; + +  err = add_arg (gpg, "--with-colons"); +  if (!err) +    err = add_arg (gpg, "--list-trust-path"); +   +  /* Tell the gpg object about the data.  */ +  if (!err) +    err = add_arg (gpg, "--"); +  if (!err) +    err = add_arg (gpg, pattern); + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static gpgme_error_t +gpg_verify (void *engine, gpgme_data_t sig, gpgme_data_t signed_text, +	    gpgme_data_t plaintext) +{ +  engine_gpg_t gpg = engine; +  gpgme_error_t err = 0; + +  if (plaintext) +    { +      /* Normal or cleartext signature.  */ + +      err = add_arg (gpg, "--output"); +      if (!err) +	err = add_arg (gpg, "-"); +      if (!err) +	err = add_arg (gpg, "--"); +      if (!err) +	err = add_data (gpg, sig, -1, 0); +      if (!err) +	err = add_data (gpg, plaintext, 1, 1); +    } +  else +    { +      err = add_arg (gpg, "--verify"); +      if (!err) +	err = add_arg (gpg, "--"); +      if (!err) +	err = add_data (gpg, sig, -1, 0); +      if (!err && signed_text) +	err = add_data (gpg, signed_text, -1, 0); +    } + +  if (!err) +    err = start (gpg); + +  return err; +} + + +static void +gpg_set_io_cbs (void *engine, gpgme_io_cbs_t io_cbs) +{ +  engine_gpg_t gpg = engine; + +  gpg->io_cbs = *io_cbs; +} + + +struct engine_ops _gpgme_engine_ops_gpg = +  { +    /* Static functions.  */ +    _gpgme_get_gpg_path, +    gpg_get_version, +    gpg_get_req_version, +    gpg_new, + +    /* Member functions.  */ +    gpg_release, +    NULL,				/* reset */ +    gpg_set_status_handler, +    gpg_set_command_handler, +    gpg_set_colon_line_handler, +    gpg_set_locale, +    gpg_decrypt, +    gpg_delete, +    gpg_edit, +    gpg_encrypt, +    gpg_encrypt_sign, +    gpg_export, +    gpg_export_ext, +    gpg_genkey, +    gpg_import, +    gpg_keylist, +    gpg_keylist_ext, +    gpg_sign, +    gpg_trustlist, +    gpg_verify, +    NULL,		/* getauditlog */ +    NULL,		/* conf_load */ +    NULL,		/* conf_save */ +    gpg_set_io_cbs, +    gpg_io_event, +    gpg_cancel +  }; | 
