/* rungpg.c - Gpg Engine.
   Copyright (C) 2000 Werner Koch (dd9jn)
   Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301, 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.  */
  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 outher side of the pipe */
  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];  
    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];  
    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;

  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_ext (engine_gpg_t gpg, const char *arg, int front)
{
  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;
  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 (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;
  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->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;
    }
  if (gpg->cmd.fd != -1)
    _gpgme_io_close (gpg->cmd.fd);

  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 (gpg, buf);
    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 (dft_display)
    {
      rc = add_arg (gpg, "--display");
      if (!rc)
	rc = add_arg (gpg, dft_display);

      free (dft_display);
    }
  if (rc)
    goto leave;

  if (isatty (1))
    {
      if (ttyname_r (1, dft_ttyname, sizeof (dft_ttyname)))
	rc = gpg_error_from_errno (errno);
      else
	{
	  rc = add_arg (gpg, "--ttyname");
	  if (!rc)
	    rc = add_arg (gpg, dft_ttyname);
	  if (!rc)
	    {
	      rc = _gpgme_getenv ("TERM", &dft_ttytype);
	      if (!rc)
		goto leave;

	      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);
  if (err)
    return err;

  /* We always need to send at least a newline character.  */
  if (!processed)
    _gpgme_io_write (fd, "\n", 1);

  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;

  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->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);
	      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)
			    DEBUG0 ("ERROR, unexpected data in read_status");

			  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.frozen = 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_child_list, *fd_parent_list;

  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;

  n = 3; /* status_fd, colon_fd and end of list */
  for (i = 0; gpg->fd_data_map[i].data; i++) 
    n++;
  fd_child_list = calloc (n + n, sizeof *fd_child_list);
  if (!fd_child_list)
    return gpg_error_from_errno (errno);
  fd_parent_list = fd_child_list + n;

  /* build the fd list for the child */
  n = 0;
  if (gpg->colon.fnc)
    {
      fd_child_list[n].fd = gpg->colon.fd[1]; 
      fd_child_list[n].dup_to = 1; /* dup to stdout */
      n++;
    }
  for (i = 0; gpg->fd_data_map[i].data; i++)
    {
      if (gpg->fd_data_map[i].dup_to != -1)
	{
	  fd_child_list[n].fd = gpg->fd_data_map[i].peer_fd;
	  fd_child_list[n].dup_to = gpg->fd_data_map[i].dup_to;
	  n++;
        }
    }
  fd_child_list[n].fd = -1;
  fd_child_list[n].dup_to = -1;

  /* Build the fd list for the parent.  */
  n = 0;
  if (gpg->status.fd[1] != -1)
    {
      fd_parent_list[n].fd = gpg->status.fd[1];
      fd_parent_list[n].dup_to = -1;
      n++;
    }
  if (gpg->colon.fd[1] != -1)
    {
      fd_parent_list[n].fd = gpg->colon.fd[1];
      fd_parent_list[n].dup_to = -1;
      n++;
    }
  for (i = 0; gpg->fd_data_map[i].data; i++)
    {
      fd_parent_list[n].fd = gpg->fd_data_map[i].peer_fd;
      fd_parent_list[n].dup_to = -1;
      n++;
    }        
  fd_parent_list[n].fd = -1;
  fd_parent_list[n].dup_to = -1;

  status = _gpgme_io_spawn (gpg->file_name ? gpg->file_name :
			    _gpgme_get_gpg_path (),
			    gpg->argv, fd_child_list, fd_parent_list);
  saved_errno = errno;
  free (fd_child_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;
	}
    }

  (*gpg->io_cbs.event) (gpg->io_cbs.event_priv, 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_data (gpg, ciph, 0, 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, 0, 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, 0, 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_data (gpg, help_data, 0, 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_data (gpg, keydata, 0, 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>:::<uid>:
      */

      if (asprintf (r_line, "uid:o%s::::%s:%s:::%s:",
		    field[4], field[2], field[3], field[1]) < 0)
	return gpg_error_from_errno (errno);
      return 0;

    case RT_NONE:
      /* Unknown record.  */
      break;
    }
  return 0;

}


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;

  if (mode & GPGME_KEYLIST_MODE_EXTERN)
    {
      if ((mode & GPGME_KEYLIST_MODE_LOCAL)
	  || secret_only)
	return gpg_error (GPG_ERR_NOT_SUPPORTED);
    }
  
  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)
	{
	  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"));
	}
    }

  /* Tell the gpg object about the data.  */
  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_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 = 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)
    err = add_arg (gpg, secret_only ? "--list-secret-keys"
		   : ((mode & GPGME_KEYLIST_MODE_SIGS)
		      ? "--check-sigs" : "--list-keys"));
  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_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_data (gpg, in, 0, 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, 0, 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 (signed_text)
	{
	  if (!err)
	    err = add_arg (gpg, "-");
	  if (!err)
	    err = add_data (gpg, signed_text, 0, 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,
    gpg_set_io_cbs,
    gpg_io_event,
    gpg_cancel
  };
