summaryrefslogtreecommitdiffstats
path: root/libkdenetwork/libgpgme-copy/gpgme/rungpg.c
diff options
context:
space:
mode:
Diffstat (limited to 'libkdenetwork/libgpgme-copy/gpgme/rungpg.c')
-rw-r--r--libkdenetwork/libgpgme-copy/gpgme/rungpg.c2111
1 files changed, 2111 insertions, 0 deletions
diff --git a/libkdenetwork/libgpgme-copy/gpgme/rungpg.c b/libkdenetwork/libgpgme-copy/gpgme/rungpg.c
new file mode 100644
index 000000000..58ab39d8b
--- /dev/null
+++ b/libkdenetwork/libgpgme-copy/gpgme/rungpg.c
@@ -0,0 +1,2111 @@
+/* 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., 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. */
+ 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
+ };