gpgme/gpgme/rungpg.c
Marcus Brinkmann 679d5c2b49 2001-11-22 Marcus Brinkmann <marcus@g10code.de>
* rungpg.c (gpg_inbound_handler, write_mem_data, write_cb_data,
	gpg_outbound_handler): Moved to ...
	* data.c (_gpgme_data_inbound_handler, write_mem_data,
	write_cb_data, _gpgme_data_outbound_handler): ... here.  Make the
	_gpgme_* ones non-static.
	* data.c: Include io.h.

	* ops.h (_gpgme_data_inbound_handler): New prototype.
	(_gpgme_data_outbound_handler): Likewise.
	(_gpgme_gpg_spawn): Use these new functions.

	* engine-gpgsm.h (_gpgme_gpgsm_op_decrypt, _gpgme_gpgsm_op_delete,
	_gpgme_gpgsm_op_encrypt, _gpgme_gpgsm_op_export,
	_gpgme_gpgsm_op_genkey, _gpgme_gpgsm_op_import,
	_gpgme_gpgsm_op_keylist, _gpgme_gpgsm_op_sign,
	_gpgme_gpgsm_op_trustlist, _gpgme_gpgsm_op_verify,
	_gpgme_gpgsm_start, _gpgme_gpgsm_set_status_handler): New prototype.
	Include <rungpg.h> for status handler function.

	* engine-gpgsm.c (struct gpgsm_object_s): New members input_fd,
	input_data, output_fd, output_data, message_fd, message_data, command
	and status.
	(_gpgme_gpgsm_new): Open input, output and message pipes before
	connecting to the client.  Close server's ends afterwards.
	(_gpgme_gpgsm_release): Close open file descriptors.  Remove
	server process from wait queue.
	(_gpgme_gpgsm_op_verify, _gpgme_gpgsm_start,
	_gpgme_gpgsm_set_status_handler, gpgms_status_handler): New function.

	* engine.c (_gpgme_engine_start): Implement for GPGME_PROTOCOL_CMS.
	(_gpgme_engine_set_status_handler): Likewise.
	(_gpgme_engine_op_verify): Likewise.
2001-11-22 03:08:58 +00:00

1575 lines
42 KiB
C

/* rungpg.c
* Copyright (C) 2000 Werner Koch (dd9jn)
* Copyright (C) 2001 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 this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#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 "rungpg.h"
#include "context.h" /*temp hack until we have GpmeData methods to do I/O */
#include "io.h"
#include "sema.h"
#include "status-table.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;
GpgmeData data; /* If this is not NULL .. */
int dup_to;
int print_fd; /* print the fd number and not the special form of it */
char arg[1]; /* .. this is used */
};
struct fd_data_map_s {
GpgmeData 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 */
};
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;
GpgStatusHandler fnc;
void *fnc_value;
} status;
/* This is a kludge - see the comment at gpg_colon_line_handler */
struct {
int fd[2];
size_t bufsize;
char *buffer;
size_t readpos;
int eof;
GpgColonLineHandler fnc; /* this indicate use of this structrue */
void *fnc_value;
int simple;
} colon;
char **argv;
struct fd_data_map_s *fd_data_map;
int pid; /* we can't use pid_t because we don't use it in Windoze */
int running;
/* stuff needed for pipemode */
struct {
int used;
int active;
GpgmeData sig;
GpgmeData text;
int stream_started;
} pm;
/* stuff needed for interactive (command) mode */
struct {
int used;
int fd;
GpgmeData cb_data; /* hack to get init the above fd later */
GpgStatusCode code; /* last code */
char *keyword; /* what has been requested (malloced) */
GpgCommandHandler fnc;
void *fnc_value;
} cmd;
};
struct reap_s {
struct reap_s *next;
int pid;
time_t entered;
int term_send;
};
static struct reap_s *reap_list;
DEFINE_STATIC_LOCK (reap_list_lock);
static void free_argv ( char **argv );
static void free_fd_data_map ( struct fd_data_map_s *fd_data_map );
static int gpg_status_handler ( void *opaque, int pid, int fd );
static GpgmeError read_status ( GpgObject gpg );
static int gpg_colon_line_handler ( void *opaque, int pid, int fd );
static GpgmeError read_colon_line ( GpgObject gpg );
static int pipemode_cb ( void *opaque,
char *buffer, size_t length, size_t *nread );
static int command_cb ( void *opaque,
char *buffer, size_t length, size_t *nread );
static void
close_notify_handler ( int fd, void *opaque )
{
GpgObject gpg = opaque;
assert (fd != -1);
if (gpg->status.fd[0] == fd )
gpg->status.fd[0] = -1;
else if (gpg->status.fd[1] == fd )
gpg->status.fd[1] = -1;
else if (gpg->colon.fd[0] == fd )
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 ) {
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;
}
}
}
}
const char *
_gpgme_gpg_get_version (void)
{
static const char *gpg_version;
/* FIXME: Locking. */
if (!gpg_version)
gpg_version = _gpgme_get_program_version (_gpgme_get_gpg_path ());
return gpg_version;
}
GpgmeError
_gpgme_gpg_check_version (void)
{
return _gpgme_compare_versions (_gpgme_gpg_get_version (),
NEED_GPG_VERSION)
? 0 : mk_error (Invalid_Engine);
}
GpgmeError
_gpgme_gpg_new ( GpgObject *r_gpg )
{
GpgObject gpg;
int rc = 0;
gpg = xtrycalloc ( 1, sizeof *gpg );
if ( !gpg ) {
rc = mk_error (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->pid = -1;
/* allocate the read buffer for the status pipe */
gpg->status.bufsize = 1024;
gpg->status.readpos = 0;
gpg->status.buffer = xtrymalloc (gpg->status.bufsize);
if (!gpg->status.buffer) {
rc = mk_error (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 GpgmeData mechanism */
if (_gpgme_io_pipe (gpg->status.fd, 1) == -1) {
rc = mk_error (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 = mk_error (General_Error);
goto leave;
}
gpg->status.eof = 0;
_gpgme_gpg_add_arg ( gpg, "--status-fd" );
{
char buf[25];
sprintf ( buf, "%d", gpg->status.fd[1]);
_gpgme_gpg_add_arg ( gpg, buf );
}
_gpgme_gpg_add_arg ( gpg, "--no-tty" );
leave:
if (rc) {
_gpgme_gpg_release (gpg);
*r_gpg = NULL;
}
else
*r_gpg = gpg;
return rc;
}
void
_gpgme_gpg_release (GpgObject gpg)
{
if (!gpg)
return;
while (gpg->arglist)
{
struct arg_and_data_s *next = gpg->arglist->next;
xfree (gpg->arglist);
gpg->arglist = next;
}
xfree (gpg->status.buffer);
xfree (gpg->colon.buffer);
if (gpg->argv)
free_argv (gpg->argv);
xfree (gpg->cmd.cb_data);
xfree (gpg->cmd.keyword);
if (gpg->pid != -1)
_gpgme_remove_proc_from_wait_queue (gpg->pid);
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]);
free_fd_data_map (gpg->fd_data_map);
if (gpg->running)
{
int pid = gpg->pid;
struct reap_s *r;
/* Reuse the memory, so that we don't need to allocate another
memory block and to handle errors. */
assert (sizeof *r < sizeof *gpg);
r = (void*)gpg;
memset (r, 0, sizeof *r);
r->pid = pid;
r->entered = time (NULL);
LOCK(reap_list_lock);
r->next = reap_list;
reap_list = r;
UNLOCK(reap_list_lock);
}
else
xfree (gpg);
}
static void
do_reaping (void)
{
struct reap_s *r, *rlast;
static time_t last_check;
time_t cur_time = time (NULL);
/* a race does not matter here */
if (!last_check)
last_check = time(NULL);
if (last_check >= cur_time)
return; /* we check only every second */
/* fixme: it would be nice if to have a TRYLOCK here */
LOCK (reap_list_lock);
for (r=reap_list,rlast=NULL; r ; rlast=r, r=r?r->next:NULL) {
int dummy1, dummy2;
if ( _gpgme_io_waitpid (r->pid, 0, &dummy1, &dummy2) ) {
/* process has terminated - remove it from the queue */
void *p = r;
if (!rlast) {
reap_list = r->next;
r = reap_list;
}
else {
rlast->next = r->next;
r = rlast;
}
xfree (p);
}
else if ( !r->term_send ) {
if( r->entered+1 >= cur_time ) {
_gpgme_io_kill ( r->pid, 0);
r->term_send = 1;
r->entered = cur_time;
}
}
else {
/* give it 5 second before we are going to send the killer */
if ( r->entered+5 >= cur_time ) {
_gpgme_io_kill (r->pid, 1);
r->entered = cur_time; /* just in case we have to repat it */
}
}
}
UNLOCK (reap_list_lock);
}
void
_gpgme_gpg_housecleaning ()
{
do_reaping ();
}
void
_gpgme_gpg_enable_pipemode ( GpgObject gpg )
{
gpg->pm.used = 1;
assert ( !gpg->pm.sig );
assert ( !gpg->pm.text );
}
GpgmeError
_gpgme_gpg_add_arg ( GpgObject gpg, const char *arg )
{
struct arg_and_data_s *a;
assert (gpg);
assert (arg);
if (gpg->pm.active)
return 0;
a = xtrymalloc ( sizeof *a + strlen (arg) );
if ( !a ) {
gpg->arg_error = 1;
return mk_error(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;
}
GpgmeError
_gpgme_gpg_add_data ( GpgObject gpg, GpgmeData data, int dup_to )
{
struct arg_and_data_s *a;
assert (gpg);
assert (data);
if (gpg->pm.active)
return 0;
a = xtrymalloc ( sizeof *a - 1 );
if ( !a ) {
gpg->arg_error = 1;
return mk_error(Out_Of_Core);
}
a->next = NULL;
a->data = data;
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;
}
GpgmeError
_gpgme_gpg_add_pm_data ( GpgObject gpg, GpgmeData data, int what )
{
GpgmeError rc=0;
assert ( gpg->pm.used );
if ( !what ) {
/* the signature */
assert ( !gpg->pm.sig );
gpg->pm.sig = data;
}
else if (what == 1) {
/* the signed data */
assert ( !gpg->pm.text );
gpg->pm.text = data;
}
else {
assert (0);
}
if ( gpg->pm.sig && gpg->pm.text ) {
if ( !gpg->pm.active ) {
/* create the callback handler and connect it to stdin */
GpgmeData tmp;
rc = gpgme_data_new_with_read_cb ( &tmp, pipemode_cb, gpg );
if (!rc )
rc = _gpgme_gpg_add_data (gpg, tmp, 0);
}
if ( !rc ) {
/* here we can reset the handler stuff */
gpg->pm.stream_started = 0;
}
}
return rc;
}
/*
* Note, that the status_handler is allowed to modifiy the args value
*/
void
_gpgme_gpg_set_status_handler ( GpgObject gpg,
GpgStatusHandler fnc, void *fnc_value )
{
assert (gpg);
if (gpg->pm.active)
return;
gpg->status.fnc = fnc;
gpg->status.fnc_value = fnc_value;
}
/* Kludge to process --with-colon output */
GpgmeError
_gpgme_gpg_set_colon_line_handler ( GpgObject gpg,
GpgColonLineHandler fnc, void *fnc_value )
{
assert (gpg);
if (gpg->pm.active)
return 0;
gpg->colon.bufsize = 1024;
gpg->colon.readpos = 0;
gpg->colon.buffer = xtrymalloc (gpg->colon.bufsize);
if (!gpg->colon.buffer) {
return mk_error (Out_Of_Core);
}
if (_gpgme_io_pipe (gpg->colon.fd, 1) == -1) {
xfree (gpg->colon.buffer); gpg->colon.buffer = NULL;
return mk_error (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 mk_error (General_Error);
}
gpg->colon.eof = 0;
gpg->colon.fnc = fnc;
gpg->colon.fnc_value = fnc_value;
gpg->colon.simple = 0;
return 0;
}
GpgmeError
_gpgme_gpg_set_simple_line_handler ( GpgObject gpg,
GpgColonLineHandler fnc,
void *fnc_value )
{
GpgmeError err;
err = _gpgme_gpg_set_colon_line_handler (gpg, fnc, fnc_value);
if (!err)
gpg->colon.simple = 1;
return err;
}
/*
* 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.
*/
GpgmeError
_gpgme_gpg_set_command_handler ( GpgObject gpg,
GpgCommandHandler fnc, void *fnc_value )
{
GpgmeData tmp;
GpgmeError err;
assert (gpg);
if (gpg->pm.active)
return 0;
err = gpgme_data_new_with_read_cb ( &tmp, command_cb, gpg );
if (err)
return err;
_gpgme_gpg_add_arg ( gpg, "--command-fd" );
_gpgme_gpg_add_data (gpg, tmp, -2);
gpg->cmd.cb_data = tmp;
gpg->cmd.fnc = fnc;
gpg->cmd.fnc_value = fnc_value;
gpg->cmd.used = 1;
return 0;
}
static void
free_argv ( char **argv )
{
int i;
for (i=0; argv[i]; i++ )
xfree (argv[i]);
xfree (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 */
}
xfree (fd_data_map);
}
static GpgmeError
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 = !!getenv ("GPG_AGENT_INFO");
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++;
argc += 2; /* --comment */
argv = xtrycalloc ( argc+1, sizeof *argv );
if (!argv)
return mk_error (Out_Of_Core);
fd_data_map = xtrycalloc ( datac+1, sizeof *fd_data_map );
if (!fd_data_map) {
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc = datac = 0;
argv[argc] = xtrystrdup ( "gpg" ); /* argv[0] */
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
if ( need_special ) {
argv[argc] = xtrystrdup ( "--enable-special-filenames" );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
}
if ( use_agent ) {
argv[argc] = xtrystrdup ( "--use-agent" );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
}
if ( !gpg->cmd.used ) {
argv[argc] = xtrystrdup ( "--batch" );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
}
argv[argc] = xtrystrdup ( "--comment" );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
argv[argc] = xtrystrdup ( "" );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
for ( a=gpg->arglist; a; a = a->next ) {
if ( a->data ) {
switch ( _gpgme_data_get_mode (a->data) ) {
case GPGME_DATA_MODE_NONE:
case GPGME_DATA_MODE_INOUT:
xfree (fd_data_map);
free_argv (argv);
return mk_error (Invalid_Mode);
case GPGME_DATA_MODE_IN:
/* create a pipe to read from gpg */
fd_data_map[datac].inbound = 1;
break;
case GPGME_DATA_MODE_OUT:
/* create a pipe to pass it down to gpg */
fd_data_map[datac].inbound = 0;
break;
}
switch ( gpgme_data_get_type (a->data) ) {
case GPGME_DATA_TYPE_NONE:
if ( fd_data_map[datac].inbound )
break; /* allowed */
xfree (fd_data_map);
free_argv (argv);
return mk_error (Invalid_Type);
case GPGME_DATA_TYPE_MEM:
case GPGME_DATA_TYPE_CB:
break;
case GPGME_DATA_TYPE_FD:
case GPGME_DATA_TYPE_FILE:
xfree (fd_data_map);
free_argv (argv);
return mk_error (Not_Implemented);
}
/* create a pipe */
{
int fds[2];
if (_gpgme_io_pipe (fds, fd_data_map[datac].inbound?1:0 )
== -1) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (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 mk_error (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 && gpg->cmd.cb_data == a->data ) {
assert (gpg->cmd.fd == -1);
gpg->cmd.fd = fd_data_map[datac].fd;
}
fd_data_map[datac].data = a->data;
fd_data_map[datac].dup_to = a->dup_to;
if ( a->dup_to == -1 ) {
argv[argc] = xtrymalloc ( 25 );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
sprintf ( argv[argc],
a->print_fd? "%d" : "-&%d",
fd_data_map[datac].peer_fd );
argc++;
}
datac++;
}
else {
argv[argc] = xtrystrdup ( a->arg );
if (!argv[argc]) {
xfree (fd_data_map);
free_argv (argv);
return mk_error (Out_Of_Core);
}
argc++;
}
}
gpg->argv = argv;
gpg->fd_data_map = fd_data_map;
return 0;
}
GpgmeError
_gpgme_gpg_spawn( GpgObject gpg, void *opaque )
{
int rc;
int i, n;
int pid;
struct spawn_fd_item_s *fd_child_list, *fd_parent_list;
if (!gpg)
return mk_error (Invalid_Value);
if (! _gpgme_get_gpg_path ())
return mk_error (Invalid_Engine);
/* Kludge, so that we don't need to check the return code of
* all the gpgme_gpg_add_arg(). we bail out here instead */
if ( gpg->arg_error )
return mk_error (Out_Of_Core);
if (gpg->pm.active)
return 0;
rc = build_argv ( gpg );
if ( rc )
return rc;
n = 4; /* status fd, 2*colon_fd and end of list */
for (i=0; gpg->fd_data_map[i].data; i++ )
n += 2;
fd_child_list = xtrycalloc ( n+n, sizeof *fd_child_list );
if (!fd_child_list)
return mk_error (Out_Of_Core);
fd_parent_list = fd_child_list + n;
/* build the fd list for the child */
n=0;
fd_child_list[n].fd = gpg->status.fd[0];
fd_child_list[n].dup_to = -1;
n++;
if ( gpg->colon.fnc ) {
fd_child_list[n].fd = gpg->colon.fd[0];
fd_child_list[n].dup_to = -1;
n++;
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++ ) {
fd_child_list[n].fd = gpg->fd_data_map[i].fd;
fd_child_list[n].dup_to = -1;
n++;
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++;
gpg->status.fd[1] = -1;
}
if ( gpg->colon.fd[1] != -1 ) {
fd_parent_list[n].fd = gpg->colon.fd[1];
fd_parent_list[n].dup_to = -1;
n++;
gpg->colon.fd[1] = -1;
}
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++;
gpg->fd_data_map[i].peer_fd = -1;
}
fd_parent_list[n].fd = -1;
fd_parent_list[n].dup_to = -1;
pid = _gpgme_io_spawn (_gpgme_get_gpg_path (),
gpg->argv, fd_child_list, fd_parent_list);
xfree (fd_child_list);
if (pid == -1) {
return mk_error (Exec_Error);
}
gpg->pid = pid;
if (gpg->pm.used)
gpg->pm.active = 1;
/*_gpgme_register_term_handler ( closure, closure_value, pid );*/
if ( _gpgme_register_pipe_handler ( opaque, gpg_status_handler,
gpg, pid, gpg->status.fd[0], 1 ) ) {
/* FIXME: kill the child */
return mk_error (General_Error);
}
if ( gpg->colon.fnc ) {
assert ( gpg->colon.fd[0] != -1 );
if ( _gpgme_register_pipe_handler ( opaque, gpg_colon_line_handler,
gpg, pid, gpg->colon.fd[0], 1 ) ) {
/* FIXME: kill the child */
return mk_error (General_Error);
}
}
for (i=0; gpg->fd_data_map[i].data; i++ ) {
/* Due to problems with select and write we set outbound pipes
* to non-blocking */
if (!gpg->fd_data_map[i].inbound) {
_gpgme_io_set_nonblocking (gpg->fd_data_map[i].fd);
}
if ( _gpgme_register_pipe_handler (
opaque,
gpg->fd_data_map[i].inbound?
_gpgme_data_inbound_handler:_gpgme_data_outbound_handler,
gpg->fd_data_map[i].data,
pid, gpg->fd_data_map[i].fd,
gpg->fd_data_map[i].inbound )
) {
/* FIXME: kill the child */
return mk_error (General_Error);
}
}
if ( gpg->cmd.used )
_gpgme_freeze_fd ( gpg->cmd.fd );
/* fixme: check what data we can release here */
gpg->running = 1;
return 0;
}
static int
gpg_status_handler ( void *opaque, int pid, int fd )
{
GpgObject gpg = opaque;
int rc = 0;
assert ( fd == gpg->status.fd[0] );
rc = read_status ( gpg );
if ( rc ) {
DEBUG1 ("gpg_handler: read_status problem %d\n - stop", rc);
return 1;
}
return gpg->status.eof;
}
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 GpgmeError
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 = xtryrealloc (buffer, bufsize);
if ( !buffer )
return mk_error (Out_Of_Core);
}
nread = _gpgme_io_read ( gpg->status.fd[0],
buffer+readpos, bufsize-readpos );
if (nread == -1)
return mk_error(Read_Error);
if (!nread) {
gpg->status.eof = 1;
if (gpg->status.fnc)
gpg->status.fnc ( gpg->status.fnc_value, STATUS_EOF, "" );
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 as one extra element) */
r = bsearch ( &t, status_table, DIM(status_table)-1,
sizeof t, status_cmp );
if ( r ) {
if ( gpg->cmd.used
&& ( r->code == STATUS_GET_BOOL
|| r->code == STATUS_GET_LINE
|| r->code == STATUS_GET_HIDDEN )) {
gpg->cmd.code = r->code;
xfree (gpg->cmd.keyword);
gpg->cmd.keyword = xtrystrdup (rest);
if ( !gpg->cmd.keyword )
return mk_error (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");
_gpgme_thaw_fd (gpg->cmd.fd);
}
else if ( gpg->status.fnc ) {
gpg->status.fnc ( gpg->status.fnc_value,
r->code, rest);
}
if ( r->code == STATUS_END_STREAM ) {
if ( gpg->cmd.used )
_gpgme_freeze_fd ( gpg->cmd.fd );
}
}
}
/* 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;
}
/*
* This colonline handler thing is not the clean way to do it.
* It might be better to enhance the GpgmeData object to act as
* a wrapper for a callback. Same goes for the status thing.
* For now we use this thing here becuase it is easier to implement.
*/
static int
gpg_colon_line_handler ( void *opaque, int pid, int fd )
{
GpgObject gpg = opaque;
GpgmeError rc = 0;
assert ( fd == gpg->colon.fd[0] );
rc = read_colon_line ( gpg );
if ( rc ) {
DEBUG1 ("gpg_colon_line_handler: "
"read problem %d\n - stop", rc);
return 1;
}
return gpg->colon.eof;
}
static GpgmeError
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 = xtryrealloc (buffer, bufsize);
if ( !buffer )
return mk_error (Out_Of_Core);
}
nread = _gpgme_io_read ( gpg->colon.fd[0],
buffer+readpos, bufsize-readpos );
if (nread == -1)
return mk_error(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 ( gpg->colon.simple
|| (*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;
}
static GpgmeError
pipemode_copy (char *buffer, size_t length, size_t *nread, GpgmeData data )
{
GpgmeError err;
size_t nbytes;
char tmp[1000], *s, *d;
/* we can optimize this whole thing but for now we just
* return after each escape character */
if (length > 990)
length = 990;
err = gpgme_data_read ( data, tmp, length, &nbytes );
if (err)
return err;
for (s=tmp, d=buffer; nbytes; s++, nbytes--) {
*d++ = *s;
if (*s == '@' ) {
*d++ = '@';
break;
}
}
*nread = d - buffer;
return 0;
}
static int
pipemode_cb ( void *opaque, char *buffer, size_t length, size_t *nread )
{
GpgObject gpg = opaque;
GpgmeError err;
if ( !buffer || !length || !nread )
return 0; /* those values are reserved for extensions */
*nread =0;
if ( !gpg->pm.stream_started ) {
assert (length > 4 );
strcpy (buffer, "@<@B" );
*nread = 4;
gpg->pm.stream_started = 1;
}
else if ( gpg->pm.sig ) {
err = pipemode_copy ( buffer, length, nread, gpg->pm.sig );
if ( err == GPGME_EOF ) {
gpg->pm.sig = NULL;
assert (length > 4 );
strcpy (buffer, "@t" );
*nread = 2;
}
else if (err) {
DEBUG1 ("pipemode_cb: copy sig failed: %s\n",
gpgme_strerror (err) );
return -1;
}
}
else if ( gpg->pm.text ) {
err = pipemode_copy ( buffer, length, nread, gpg->pm.text );
if ( err == GPGME_EOF ) {
gpg->pm.text = NULL;
assert (length > 4 );
strcpy (buffer, "@.@>" );
*nread = 4;
}
else if (err) {
DEBUG1 ("pipemode_cb: copy data failed: %s\n",
gpgme_strerror (err) );
return -1;
}
}
else {
return 0; /* eof */
}
return 0;
}
/*
* Here we handle --command-fd. This works closely together with
* the status handler.
*/
static int
command_cb ( void *opaque, char *buffer, size_t length, size_t *nread )
{
GpgObject gpg = opaque;
const char *value;
int value_len;
DEBUG0 ("command_cb: enter\n");
assert (gpg->cmd.used);
if ( !buffer || !length || !nread )
return 0; /* those values are reserved for extensions */
*nread =0;
if ( !gpg->cmd.code ) {
DEBUG0 ("command_cb: no code\n");
return -1;
}
if ( !gpg->cmd.fnc ) {
DEBUG0 ("command_cb: no user cb\n");
return -1;
}
value = gpg->cmd.fnc ( gpg->cmd.fnc_value,
gpg->cmd.code, gpg->cmd.keyword );
if ( !value ) {
DEBUG0 ("command_cb: no data from user cb\n");
gpg->cmd.fnc ( gpg->cmd.fnc_value, 0, value);
return -1;
}
value_len = strlen (value);
if ( value_len+1 > length ) {
DEBUG0 ("command_cb: too much data from user cb\n");
gpg->cmd.fnc ( gpg->cmd.fnc_value, 0, value);
return -1;
}
memcpy ( buffer, value, value_len );
if ( !value_len || (value_len && value[value_len-1] != '\n') )
buffer[value_len++] = '\n';
*nread = value_len;
gpg->cmd.fnc ( gpg->cmd.fnc_value, 0, value);
gpg->cmd.code = 0;
/* and sleep again until read_status will wake us up again */
_gpgme_freeze_fd ( gpg->cmd.fd );
return 0;
}
GpgmeError
_gpgme_gpg_op_decrypt (GpgObject gpg, GpgmeData ciph, GpgmeData plain)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--decrypt");
/* Tell the gpg object about the data. */
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--output");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "-");
if (!err)
err = _gpgme_gpg_add_data (gpg, plain, 1);
if (!err)
err = _gpgme_gpg_add_data (gpg, ciph, 0);
return err;
}
GpgmeError
_gpgme_gpg_op_delete (GpgObject gpg, GpgmeKey key, int allow_secret)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, allow_secret
? "--delete-secret-and-public-key"
: "--delete-key");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err)
{
const char *s = gpgme_key_get_string_attr (key, GPGME_ATTR_FPR, NULL, 0);
if (!s)
err = mk_error (Invalid_Key);
else
err = _gpgme_gpg_add_arg (gpg, s);
}
return err;
}
static GpgmeError
_gpgme_append_gpg_args_from_recipients (GpgObject gpg,
const GpgmeRecipients rset)
{
GpgmeError err = 0;
struct user_id_s *r;
assert (rset);
for (r = rset->list; r; r = r->next)
{
err = _gpgme_gpg_add_arg (gpg, "-r");
if (!err)
_gpgme_gpg_add_arg (gpg, r->name);
if (err)
break;
}
return err;
}
GpgmeError
_gpgme_gpg_op_encrypt (GpgObject gpg, GpgmeRecipients recp,
GpgmeData plain, GpgmeData ciph, int use_armor)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--encrypt");
if (!err && use_armor)
err = _gpgme_gpg_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 = _gpgme_gpg_add_arg (gpg, "--always-trust");
if (!err)
err = _gpgme_append_gpg_args_from_recipients (gpg, recp);
/* Tell the gpg object about the data. */
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--output");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "-");
if (!err)
err = _gpgme_gpg_add_data (gpg, ciph, 1);
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err)
err = _gpgme_gpg_add_data (gpg, plain, 0);
return err;
}
GpgmeError
_gpgme_gpg_op_export (GpgObject gpg, GpgmeRecipients recp,
GpgmeData keydata, int use_armor)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--export");
if (!err && use_armor)
err = _gpgme_gpg_add_arg (gpg, "--armor");
if (!err)
err = _gpgme_gpg_add_data (gpg, keydata, 1);
if (!err)
err = _gpgme_gpg_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 = _gpgme_gpg_add_arg (gpg, s);
if (!err)
err = gpgme_recipients_enum_close (recp, &ec);
}
return err;
}
GpgmeError
_gpgme_gpg_op_genkey (GpgObject gpg, GpgmeData help_data, int use_armor)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--gen-key");
if (!err && use_armor)
err = _gpgme_gpg_add_arg (gpg, "--armor");
if (!err)
err = _gpgme_gpg_add_data (gpg, help_data, 0);
return err;
}
GpgmeError
_gpgme_gpg_op_import (GpgObject gpg, GpgmeData keydata)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--import");
if (!err)
err = _gpgme_gpg_add_data (gpg, keydata, 0);
return err;
}
GpgmeError
_gpgme_gpg_op_keylist (GpgObject gpg, const char *pattern, int secret_only,
int keylist_mode)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--with-colons");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--fixed-list-mode");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--with-fingerprint");
if (!err && keylist_mode == 1)
err = _gpgme_gpg_add_arg (gpg, "--no-expensive-trust-checks");
if (!err)
err = _gpgme_gpg_add_arg (gpg, secret_only ? "--list-secret-keys"
: "--list-keys");
/* Tell the gpg object about the data */
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err && pattern && *pattern)
err = _gpgme_gpg_add_arg (gpg, pattern);
return err;
}
GpgmeError
_gpgme_gpg_op_sign (GpgObject gpg, GpgmeData in, GpgmeData out,
GpgmeSigMode mode, int use_armor,
int use_textmode, GpgmeCtx ctx /* FIXME */)
{
GpgmeError err;
GpgmeKey key;
int i;
if (mode == GPGME_SIG_MODE_CLEAR)
err = _gpgme_gpg_add_arg (gpg, "--clearsign");
else
{
err = _gpgme_gpg_add_arg (gpg, "--sign");
if (!err && mode == GPGME_SIG_MODE_DETACH)
err = _gpgme_gpg_add_arg (gpg, "--detach");
if (!err && use_armor)
err = _gpgme_gpg_add_arg (gpg, "--armor");
if (!err && use_textmode)
_gpgme_gpg_add_arg (gpg, "--textmode");
}
for (i = 0; (key = gpgme_signers_enum (ctx, i)); i++)
{
const char *s = gpgme_key_get_string_attr (key, GPGME_ATTR_KEYID,
NULL, 0);
if (s)
{
if (!err)
err = _gpgme_gpg_add_arg (gpg, "-u");
if (!err)
err = _gpgme_gpg_add_arg (gpg, s);
}
gpgme_key_unref (key);
if (err) break;
}
/* Tell the gpg object about the data. */
if (!err)
err = _gpgme_gpg_add_data (gpg, in, 0);
if (!err)
err = _gpgme_gpg_add_data (gpg, out, 1);
return err;
}
GpgmeError
_gpgme_gpg_op_trustlist (GpgObject gpg, const char *pattern)
{
GpgmeError err;
err = _gpgme_gpg_add_arg (gpg, "--with-colons");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--list-trust-path");
/* Tell the gpg object about the data */
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err)
err = _gpgme_gpg_add_arg (gpg, pattern);
return err;
}
GpgmeError
_gpgme_gpg_op_verify (GpgObject gpg, GpgmeData sig, GpgmeData text)
{
GpgmeError err = 0;
if (gpg->pm.used)
{
err = _gpgme_gpg_add_arg (gpg, gpg->pm.used ? "--pipemode" : "--verify");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err)
err = _gpgme_gpg_add_pm_data (gpg, sig, 0);
if (!err)
err = _gpgme_gpg_add_pm_data (gpg, text, 1);
}
else
{
err = _gpgme_gpg_add_arg (gpg, "--verify");
if (!err)
err = _gpgme_gpg_add_arg (gpg, "--");
if (!err)
err = _gpgme_gpg_add_data (gpg, sig, -1);
if (text)
{
if (!err)
err = _gpgme_gpg_add_arg (gpg, "-");
if (!err)
err = _gpgme_gpg_add_data (gpg, text, 0);
}
}
return err;
}