aboutsummaryrefslogtreecommitdiffstats
path: root/gpgme/rungpg.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--gpgme/rungpg.c622
1 files changed, 622 insertions, 0 deletions
diff --git a/gpgme/rungpg.c b/gpgme/rungpg.c
new file mode 100644
index 00000000..6353ad27
--- /dev/null
+++ b/gpgme/rungpg.c
@@ -0,0 +1,622 @@
+/* rungpg.c
+ * Copyright (C) 2000 Werner Koch (dd9jn)
+ *
+ * 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 <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <signal.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 "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;
+ 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;
+
+ char **argv;
+ struct fd_data_map_s *fd_data_map;
+
+ pid_t pid;
+
+ int running;
+ int exit_status;
+ int exit_signal;
+};
+
+static void kill_gpg ( GpgObject gpg );
+static void free_argv ( char **argv );
+static int gpg_status_handler ( void *opaque, pid_t pid, int fd );
+static int gpg_inbound_handler ( void *opaque, pid_t pid, int fd );
+static int gpg_outbound_handler ( void *opaque, pid_t pid, int fd );
+
+static GpgmeError read_status ( GpgObject gpg );
+
+
+
+GpgmeError
+_gpgme_gpg_new_object ( GpgObject *r_gpg )
+{
+ GpgObject gpg;
+ char buf[20];
+ 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;
+
+ /* The name of the beast will be gpg - so put it into argv[0] */
+ _gpgme_gpg_add_arg ( gpg, "gpg" );
+
+ /* 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 (pipe (gpg->status.fd) == -1) {
+ rc = mk_error (Pipe_Error);
+ goto leave;
+ }
+ gpg->status.eof = 0;
+ _gpgme_gpg_add_arg ( gpg, "--status-fd" );
+ sprintf ( buf, "%d", gpg->status.fd[1]);
+ _gpgme_gpg_add_arg ( gpg, buf );
+
+ leave:
+ if (rc) {
+ _gpgme_gpg_release_object (gpg);
+ *r_gpg = NULL;
+ }
+ else
+ *r_gpg = gpg;
+ return rc;
+}
+
+void
+_gpgme_gpg_release_object ( GpgObject gpg )
+{
+ if ( !gpg )
+ return;
+ xfree (gpg->status.buffer);
+ if ( gpg->argv )
+ free_argv (gpg->argv);
+ if (gpg->status.fd[0] != -1 )
+ close (gpg->status.fd[0]);
+ if (gpg->status.fd[1] != -1 )
+ close (gpg->status.fd[1]);
+ /* fixme: walk over the data map and close all fds */
+ xfree (gpg->fd_data_map);
+ kill_gpg (gpg); /* fixme: should be done asyncronously */
+ xfree (gpg);
+}
+
+static void
+kill_gpg ( GpgObject gpg )
+{
+ if ( gpg->running ) {
+ /* still running? Must send a killer */
+ kill ( gpg->pid, SIGTERM);
+ sleep (2);
+ if ( !waitpid (gpg->pid, NULL, WNOHANG) ) {
+ /* pay the murderer better and then forget about it */
+ kill (gpg->pid, SIGKILL);
+ }
+ gpg->running = 0;
+ }
+}
+
+
+GpgmeError
+_gpgme_gpg_add_arg ( GpgObject gpg, const char *arg )
+{
+ struct arg_and_data_s *a;
+
+ assert (gpg);
+ assert (arg);
+ 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);
+ a = xtrymalloc ( sizeof *a - 1 );
+ if ( !a ) {
+ gpg->arg_error = 1;
+ return mk_error(Out_Of_Core);
+ }
+ a->next = NULL;
+ a->data = data;
+ a->dup_to = dup_to;
+ *gpg->argtail = a;
+ gpg->argtail = &a->next;
+ return 0;
+}
+
+/*
+ * 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);
+ gpg->status.fnc = fnc;
+ gpg->status.fnc_value = fnc_value;
+}
+
+static void
+free_argv ( char **argv )
+{
+ int i;
+
+ for (i=0; argv[i]; i++ )
+ xfree (argv[i]);
+ xfree (argv);
+}
+
+
+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;
+
+ if ( gpg->argv ) {
+ free_argv ( gpg->argv );
+ gpg->argv = NULL;
+ }
+ /* fixme: release fd_data_map */
+
+ for ( a=gpg->arglist; a; a = a->next ) {
+ argc++;
+ if (a->data) {
+ fprintf (stderr, "build_argv: data\n" );
+ datac++;
+ }
+ else
+ fprintf (stderr, "build_argv: arg=`%s'\n", a->arg );
+ }
+
+ 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;
+ for ( a=gpg->arglist; a; a = a->next ) {
+ if ( a->data ) {
+ switch ( _gpgme_query_data_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_query_data_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:
+ 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 (pipe (fds) == -1) {
+ xfree (fd_data_map);
+ free_argv (argv);
+ return mk_error (Pipe_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];
+ }
+ }
+ fd_data_map[datac].data = a->data;
+ fd_data_map[datac].dup_to = a->dup_to;
+ 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;
+ pid_t pid;
+
+ if ( !gpg )
+ return mk_error (Invalid_Value);
+
+ /* 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);
+
+ rc = build_argv ( gpg );
+ if ( rc )
+ return rc;
+
+ fflush (stderr);
+ pid = fork ();
+ if (pid == -1) {
+ return mk_error (Exec_Error);
+ }
+
+ if ( !pid ) { /* child */
+ for (i=0; gpg->fd_data_map[i].data; i++ ) {
+ close (gpg->fd_data_map[i].fd);
+ gpg->fd_data_map[i].fd = -1;
+ if ( gpg->fd_data_map[i].dup_to != -1 ) {
+ if ( dup2 (gpg->fd_data_map[i].peer_fd,
+ gpg->fd_data_map[i].dup_to ) == -1 ) {
+ fprintf (stderr, "dup2 failed in child: %s\n",
+ strerror (errno));
+ _exit (8);
+ }
+ close ( gpg->fd_data_map[i].peer_fd );
+ }
+ }
+
+ if ( gpg->status.fd[0] != -1 )
+ close (gpg->status.fd[0]);
+
+ /* FIXME: dup /dev/null to stdin if nothing is connected to stdin */
+ execv ("/usr/local/bin/gpg", gpg->argv );
+ fprintf (stderr,"exec of gpg failed\n"); fflush (stderr);
+ _exit (8);
+ }
+ gpg->pid = pid;
+
+ if ( gpg->status.fd[1] != -1 )
+ close (gpg->status.fd[1]);
+ 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);
+
+ }
+ for (i=0; gpg->fd_data_map[i].data; i++ ) {
+ close (gpg->fd_data_map[i].peer_fd);
+ gpg->fd_data_map[i].peer_fd = -1;
+ if ( _gpgme_register_pipe_handler (
+ opaque,
+ gpg->fd_data_map[i].inbound?
+ gpg_inbound_handler:gpg_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);
+ }
+ }
+
+ /* fixme: check what data we can release here */
+
+ gpg->running = 1;
+ return 0;
+
+}
+
+
+static int
+gpg_inbound_handler ( void *opaque, pid_t pid, int fd )
+{
+ GpgmeData dh = opaque;
+ int nread;
+ char buf[200];
+
+ assert ( _gpgme_query_data_mode (dh) == GPGME_DATA_MODE_IN );
+
+ do {
+ nread = read (fd, buf, 200 );
+ } while ( nread == -1 && errno == EINTR);
+ fprintf(stderr, "inbound on fd %d: nread=%d\n", fd, nread );
+ if ( nread < 0 ) {
+ fprintf (stderr, "read_mem_data: read failed on fd %d (n=%d): %s\n",
+ fd, nread, strerror (errno) );
+ return 1;
+ }
+ else if (!nread)
+ return 1; /* eof */
+
+
+ return 0;
+}
+
+
+static int
+write_mem_data ( GpgmeData dh, int fd )
+{
+ size_t nbytes;
+ int nwritten;
+
+ nbytes = dh->len - dh->readpos;
+ if ( !nbytes ) {
+ close (fd);
+ return 1;
+ }
+
+ do {
+ nwritten = write ( fd, dh->data+dh->readpos, nbytes );
+ } while ( nwritten == -1 && errno == EINTR );
+ if ( nwritten < 1 ) {
+ fprintf (stderr, "write_mem_data: write failed on fd %d (n=%d): %s\n",
+ fd, nwritten, strerror (errno) );
+ close (fd);
+ return 1;
+ }
+
+ dh->readpos += nwritten;
+ return 0;
+}
+
+
+static int
+gpg_outbound_handler ( void *opaque, pid_t pid, int fd )
+{
+ GpgmeData dh = opaque;
+
+ assert ( _gpgme_query_data_mode (dh) == GPGME_DATA_MODE_OUT );
+ switch ( gpgme_query_data_type (dh) ) {
+ case GPGME_DATA_TYPE_MEM:
+ if ( write_mem_data ( dh, fd ) )
+ return 1; /* ready */
+ break;
+ default:
+ assert (0);
+ }
+
+
+ return 0;
+}
+
+
+
+static int
+gpg_status_handler ( void *opaque, pid_t pid, int fd )
+{
+ GpgObject gpg = opaque;
+ int rc = 0;
+
+ assert ( fd == gpg->status.fd[0] );
+ rc = read_status ( gpg );
+ if ( rc ) {
+ fprintf (stderr, "gpg_handler: read_status problem %d\n - stop", rc);
+ return 1;
+ }
+
+ return gpg->status.eof;
+}
+
+
+static int
+status_cmp (const struct status_table_s *a, const struct status_table_s *b)
+{
+ 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);
+ }
+
+
+ do {
+ nread = read ( gpg->status.fd[0], buffer+readpos, bufsize-readpos );
+ } while (nread == -1 && errno == EINTR);
+
+ 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;
+ fprintf (stderr, "read_status: `%s'\n", buffer);
+ if (!strncmp (buffer, "[GNUPG:] ", 9 )
+ && buffer[9] >= 'A' && buffer[9] <= 'Z'
+ && gpg->status.fnc ) {
+ 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 ) {
+ gpg->status.fnc ( gpg->status.fnc_value,
+ r->code, rest);
+ }
+ }
+ /* 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;
+}
+
+