gpgme/gpgme/rungpg.c
Marcus Brinkmann 03bcb7f4c1 doc/
2003-05-27  Marcus Brinkmann  <marcus@g10code.de>

	* gpgme.texi (Passphrase Callback): Document new prototype.

gpgme/
2003-05-26  Marcus Brinkmann  <marcus@g10code.de>

	* engine.h (EngineCommandHandler): Change last argument to int fd.
	* gpgme.h (gpgme_passphrase_cb_t): Rewritten to take parts of the
	description and fd.
	(gpgme_edit_cb_t): Change last argument to int fd.
	* ops.h (_gpgme_passphrase_command_handler_internal): New prototype.
	* passphrase.c: Include <assert.h>.
	(op_data_t): Rename userid_hint to uid_hint, remove last_pw_handle.
	(release_op_data): Check values before calling free.
	(_gpgme_passphrase_status_handler): Likewise.
	(_gpgme_passphrase_command_handler_internal): New function.
	(_gpgme_passphrase_command_handler): Rewritten.
	* edit.c (edit_status_handler): Pass -1 as fd argument.
	(command_handler): Update prototype.  New variable processed.  Use
	it to store return value of
	_gpgme_passphrase_command_handler_internal which is now used
	instead _gpgme_passphrase_command_handler.  Use it also to check
	if we should call the user's edit function.  Pass fd to user's
	edit function.
	* rungpg.c (struct gpg_object_s): Change type of cmd.cb_data to
	void *.
	(gpg_release): Check value before calling free.  Do not release
	cmd.cb_data.
	(command_cb): Function removed.
	(command_handler): New function.  Thus we don't use a data object
	for command handler stuff anymore, but handle it directly.  This
	allows proper error reporting (cancel of passphrase requests, for
	example).  Also all callbacks work via direct writes to the file
	descriptor (so that passphrases are not kept in insecure memory).
	(gpg_set_command_handler): Rewritten to use even more ugly hacks.
	(read_status): Check cmd.keyword before calling free.  Install
	command_handler as the I/O callback handler with GPG as private
	data.

tests/
2003-05-27  Marcus Brinkmann  <marcus@g10code.de>

	* (t-decrypt-verify.c, t-decrypt.c, t-edit.c, t-encrypt-sign.c,
	t-encrypt-sym.c, t-sign.c, t-signers.c): Include <unistd.h>.
	(passphrase_cb): Rewritten.
	* t-edit.c (edit_fnc): Rewritten.
2003-05-27 01:31:06 +00:00

1635 lines
36 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* rungpg.c - Gpg Engine.
Copyright (C) 2000 Werner Koch (dd9jn)
Copyright (C) 2001, 2002, 2003 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 General Public License as published by
the Free Software Foundation; either version 2 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GPGME; 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 <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <fcntl.h>
#include "unistd.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 "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;
};
struct gpg_object_s
{
struct arg_and_data_s *arglist;
struct arg_and_data_s **argtail;
int arg_error;
struct
{
int fd[2];
size_t bufsize;
char *buffer;
size_t readpos;
int eof;
EngineStatusHandler 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;
EngineColonLineHandler fnc; /* this indicate use of this structrue */
void *fnc_value;
void *tag;
} 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) */
EngineCommandHandler 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 gpg_object_s *GpgObject;
static void
gpg_io_event (void *engine, gpgme_event_io_t type, void *type_data)
{
GpgObject 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)
{
GpgObject 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;
}
}
}
}
static gpgme_error_t
add_arg (GpgObject gpg, const char *arg)
{
struct arg_and_data_s *a;
assert (gpg);
assert (arg);
a = malloc (sizeof *a + strlen (arg));
if (!a)
{
gpg->arg_error = 1;
return GPGME_Out_Of_Core;
}
a->next = NULL;
a->data = NULL;
a->dup_to = -1;
strcpy (a->arg, arg);
*gpg->argtail = a;
gpg->argtail = &a->next;
return 0;
}
static gpgme_error_t
add_data (GpgObject 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)
{
gpg->arg_error = 1;
return GPGME_Out_Of_Core;
}
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 const char *
gpg_get_version (void)
{
static const char *gpg_version;
DEFINE_STATIC_LOCK (gpg_version_lock);
LOCK (gpg_version_lock);
if (!gpg_version)
gpg_version = _gpgme_get_program_version (_gpgme_get_gpg_path ());
UNLOCK (gpg_version_lock);
return gpg_version;
}
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 void
gpg_release (void *engine)
{
GpgObject gpg = engine;
if (!gpg)
return;
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);
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);
if (gpg->cmd.fd != -1)
_gpgme_io_close (gpg->cmd.fd);
free (gpg);
}
static gpgme_error_t
gpg_new (void **engine)
{
GpgObject gpg;
int rc = 0;
gpg = calloc (1, sizeof *gpg);
if (!gpg)
{
rc = GPGME_Out_Of_Core;
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 = GPGME_Out_Of_Core;
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 = GPGME_Pipe_Error;
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 = GPGME_General_Error;
goto leave;
}
gpg->status.eof = 0;
add_arg (gpg, "--status-fd");
{
char buf[25];
sprintf (buf, "%d", gpg->status.fd[1]);
add_arg (gpg, buf);
}
add_arg (gpg, "--no-tty");
add_arg (gpg, "--charset");
add_arg (gpg, "utf8");
add_arg (gpg, "--enable-progress-filter");
leave:
if (rc)
gpg_release (gpg);
else
*engine = gpg;
return rc;
}
/* Note, that the status_handler is allowed to modifiy the args
value. */
static void
gpg_set_status_handler (void *engine, EngineStatusHandler fnc, void *fnc_value)
{
GpgObject 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, EngineColonLineHandler fnc,
void *fnc_value)
{
GpgObject gpg = engine;
gpg->colon.bufsize = 1024;
gpg->colon.readpos = 0;
gpg->colon.buffer = malloc (gpg->colon.bufsize);
if (!gpg->colon.buffer)
return GPGME_Out_Of_Core;
if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1)
{
free (gpg->colon.buffer);
gpg->colon.buffer = NULL;
return GPGME_Pipe_Error;
}
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 GPGME_General_Error;
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;
GpgObject gpg = (GpgObject) opaque;
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);
if (err)
return err;
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, EngineCommandHandler fnc,
void *fnc_value, gpgme_data_t linked_data)
{
GpgObject gpg = engine;
add_arg (gpg, "--command-fd");
/* 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. */
add_data (gpg, (void *) &gpg->cmd, -2, 0);
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 (GpgObject gpg)
{
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. */
p = getenv ("GPG_AGENT_INFO");
use_agent = (p && strchr (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 += 2; /* --comment */
argv = calloc (argc + 1, sizeof *argv);
if (!argv)
return GPGME_Out_Of_Core;
fd_data_map = calloc (datac + 1, sizeof *fd_data_map);
if (!fd_data_map)
{
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc = datac = 0;
argv[argc] = strdup ("gpg"); /* argv[0] */
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
if (need_special)
{
argv[argc] = strdup ("--enable-special-filenames");
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
}
if (use_agent)
{
argv[argc] = strdup ("--use-agent");
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
}
if (!gpg->cmd.used)
{
argv[argc] = strdup ("--batch");
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
}
argv[argc] = strdup ("--comment");
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
argv[argc] = strdup ("");
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
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)
{
free (fd_data_map);
free_argv (argv);
return GPGME_Pipe_Error;
}
if (_gpgme_io_set_close_notify (fds[0],
close_notify_handler, gpg)
|| _gpgme_io_set_close_notify (fds[1],
close_notify_handler,
gpg))
{
return GPGME_General_Error;
}
/* 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)
{
argv[argc] = malloc (25);
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
sprintf (argv[argc],
a->print_fd ? "%d" : "-&%d",
fd_data_map[datac].peer_fd);
argc++;
}
datac++;
}
else
{
argv[argc] = strdup (a->arg);
if (!argv[argc])
{
free (fd_data_map);
free_argv (argv);
return GPGME_Out_Of_Core;
}
argc++;
}
}
gpg->argv = argv;
gpg->fd_data_map = fd_data_map;
return 0;
}
static gpgme_error_t
add_io_cb (GpgObject 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 (GpgObject 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 GPGME_Out_Of_Core;
}
nread = _gpgme_io_read (gpg->status.fd[0],
buffer + readpos, bufsize-readpos);
if (nread == -1)
return GPGME_Read_Error;
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) */
*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 GPGME_Out_Of_Core;
/* 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");
/* 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);
}
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)
{
/* 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)
{
GpgObject 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 (GpgObject 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 GPGME_Out_Of_Core;
}
nread = _gpgme_io_read (gpg->colon.fd[0], buffer+readpos, bufsize-readpos);
if (nread == -1)
return GPGME_Read_Error;
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, ':'))
{
assert (gpg->colon.fnc);
gpg->colon.fnc (gpg->colon.fnc_value, buffer);
}
/* 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)
{
GpgObject 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 (GpgObject gpg)
{
gpgme_error_t rc;
int i, n;
int status;
struct spawn_fd_item_s *fd_child_list, *fd_parent_list;
if (!gpg)
return GPGME_Invalid_Value;
if (! _gpgme_get_gpg_path ())
return GPGME_Invalid_Engine;
/* Kludge, so that we don't need to check the return code of all the
add_arg (). We bail out here instead. */
if (gpg->arg_error)
return GPGME_Out_Of_Core;
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 GPGME_Out_Of_Core;
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 (_gpgme_get_gpg_path (),
gpg->argv, fd_child_list, fd_parent_list);
free (fd_child_list);
if (status == -1)
return GPGME_Exec_Error;
/*_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)
{
GpgObject 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)
{
GpgObject 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)
err = GPGME_Invalid_Key;
else
err = add_arg (gpg, key->subkeys->fpr);
}
if (!err)
start (gpg);
return err;
}
static gpgme_error_t
append_args_from_signers (GpgObject 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
gpg_edit (void *engine, gpgme_key_t key, gpgme_data_t out,
gpgme_ctx_t ctx /* FIXME */)
{
GpgObject 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, "--edit-key");
if (!err)
err = add_data (gpg, out, 1, 1);
if (!err)
err = add_arg (gpg, "--");
if (!err)
{
const char *s = key->subkeys ? key->subkeys->fpr : NULL;
if (!s)
err = GPGME_Invalid_Key;
else
err = add_arg (gpg, s);
}
if (!err)
err = start (gpg);
return err;
}
static gpgme_error_t
append_args_from_recipients (GpgObject gpg, const gpgme_recipients_t rset)
{
gpgme_error_t err = 0;
gpgme_user_id_t uid;
assert (rset);
for (uid = rset->list; uid; uid = uid->next)
{
err = add_arg (gpg, "-r");
if (!err)
err = add_arg (gpg, uid->uid);
if (err)
break;
}
return err;
}
static gpgme_error_t
gpg_encrypt (void *engine, gpgme_recipients_t recp, gpgme_data_t plain,
gpgme_data_t ciph, int use_armor)
{
GpgObject 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 && _gpgme_recipients_all_valid (recp))
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 (!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_recipients_t recp, gpgme_data_t plain,
gpgme_data_t ciph, int use_armor,
gpgme_ctx_t ctx /* FIXME */)
{
GpgObject 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 && _gpgme_recipients_all_valid (recp))
err = add_arg (gpg, "--always-trust");
if (!err)
err = append_args_from_recipients (gpg, recp);
if (!err)
err = append_args_from_signers (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 (!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, gpgme_recipients_t recp, gpgme_data_t keydata,
int use_armor)
{
GpgObject gpg = engine;
gpgme_error_t err;
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)
{
void *ec;
const char *s;
err = gpgme_recipients_enum_open (recp, &ec);
while (!err && (s = gpgme_recipients_enum_read (recp, &ec)))
err = add_arg (gpg, s);
if (!err)
err = gpgme_recipients_enum_close (recp, &ec);
}
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)
{
GpgObject gpg = engine;
gpgme_error_t err;
if (!gpg)
return GPGME_Invalid_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 err = GPGME_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)
{
GpgObject 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;
}
static gpgme_error_t
gpg_keylist (void *engine, const char *pattern, int secret_only,
int keylist_mode)
{
GpgObject gpg = engine;
gpgme_error_t err;
err = add_arg (gpg, "--with-colons");
if (!err)
err = add_arg (gpg, "--fixed-list-mode");
if (!err)
err = add_arg (gpg, "--with-fingerprint");
if (!err)
err = add_arg (gpg, "--with-fingerprint");
if (!err)
err = add_arg (gpg, secret_only ? "--list-secret-keys"
: ((keylist_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, int keylist_mode)
{
GpgObject gpg = engine;
gpgme_error_t err;
if (reserved)
return GPGME_Invalid_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)
err = add_arg (gpg, secret_only ? "--list-secret-keys"
: ((keylist_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)
{
while (*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 */)
{
GpgObject 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)
add_arg (gpg, "--textmode");
}
if (!err)
err = append_args_from_signers (gpg, ctx);
/* 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)
{
GpgObject 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)
{
GpgObject 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)
{
GpgObject 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,
gpg_set_status_handler,
gpg_set_command_handler,
gpg_set_colon_line_handler,
gpg_decrypt,
gpg_delete,
gpg_edit,
gpg_encrypt,
gpg_encrypt_sign,
gpg_export,
gpg_genkey,
gpg_import,
gpg_keylist,
gpg_keylist_ext,
gpg_sign,
gpg_trustlist,
gpg_verify,
gpg_set_io_cbs,
gpg_io_event
};