gpgme/gpgme/rungpg.c
2000-11-20 20:17:36 +00:00

943 lines
26 KiB
C

/* 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 <fcntl.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;
/* 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;
} colon;
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 void free_fd_data_map ( struct fd_data_map_s *fd_data_map );
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 int gpg_status_handler ( void *opaque, pid_t pid, int fd );
static GpgmeError read_status ( GpgObject gpg );
static int gpg_colon_line_handler ( void *opaque, pid_t pid, int fd );
static GpgmeError read_colon_line ( GpgObject gpg );
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;
/* 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" );
{
char buf[25];
sprintf ( buf, "%d", gpg->status.fd[1]);
_gpgme_gpg_add_arg ( gpg, buf );
}
_gpgme_gpg_add_arg ( gpg, "--batch" );
_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;
xfree (gpg->status.buffer);
xfree (gpg->colon.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]);
if (gpg->colon.fd[0] != -1 )
close (gpg->colon.fd[0]);
if (gpg->colon.fd[1] != -1 )
close (gpg->colon.fd[1]);
free_fd_data_map (gpg->fd_data_map);
kill_gpg (gpg); /* fixme: should be done asyncronously */
xfree (gpg);
}
static void
kill_gpg ( GpgObject gpg )
{
#if 0
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;
}
#endif
}
static int
set_nonblocking ( int fd )
{
int flags;
flags = fcntl (fd, F_GETFL, 0);
if (flags == -1)
return -1;
flags |= O_NONBLOCK;
return fcntl (fd, F_SETFL, flags);
}
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;
}
/* Kludge to process --with-colon output */
GpgmeError
_gpgme_gpg_set_colon_line_handler ( GpgObject gpg,
GpgColonLineHandler fnc, void *fnc_value )
{
assert (gpg);
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 (pipe (gpg->colon.fd) == -1) {
xfree (gpg->colon.buffer); gpg->colon.buffer = NULL;
return mk_error (Pipe_Error);
}
gpg->colon.eof = 0;
gpg->colon.fnc = fnc;
gpg->colon.fnc_value = fnc_value;
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;
for (i=0; fd_data_map[i].data; i++ ) {
if ( fd_data_map[i].fd != -1 )
close (fd_data_map[i].fd);
if ( fd_data_map[i].peer_fd != -1 )
close (fd_data_map[i].peer_fd);
/* don't realease 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 )
need_special = 1;
}
else {
/* fprintf (stderr, "build_argv: arg=`%s'\n", a->arg );*/
}
}
if ( need_special )
argc++;
if (use_agent)
argc++;
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++;
}
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:
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;
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], "-&%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;
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 */
int duped_stdin = 0;
int duped_stderr = 0;
close (gpg->status.fd[0]);
if (gpg->colon.fnc) {
/* dup it to stdout */
if ( dup2 ( gpg->colon.fd[1], 1 ) == -1 ) {
fprintf (stderr,"dup2(colon, 1) failed: %s\n",
strerror (errno) );
_exit (8);
}
close (gpg->colon.fd[0]);
close (gpg->colon.fd[1]);
}
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);
}
if ( gpg->fd_data_map[i].dup_to == 0 )
duped_stdin=1;
if ( gpg->fd_data_map[i].dup_to == 2 )
duped_stderr=1;
close ( gpg->fd_data_map[i].peer_fd );
}
}
if( !duped_stdin || !duped_stderr ) {
int fd = open ( "/dev/null", O_RDONLY );
if ( fd == -1 ) {
fprintf (stderr,"can't open `/dev/null': %s\n",
strerror (errno) );
_exit (8);
}
/* Make sure that gpg has a connected stdin */
if ( !duped_stdin ) {
if ( dup2 ( fd, 0 ) == -1 ) {
fprintf (stderr,"dup2(/dev/null, 0) failed: %s\n",
strerror (errno) );
_exit (8);
}
}
/* We normally don't want all the normal output */
if ( !duped_stderr ) {
if (!getenv ("GPGME_DEBUG") ) {
if ( dup2 ( fd, 2 ) == -1 ) {
fprintf (stderr,"dup2(dev/null, 2) failed: %s\n",
strerror (errno) );
_exit (8);
}
}
}
close (fd);
}
execv ( GPG_PATH, gpg->argv );
fprintf (stderr,"exec of gpg failed\n");
_exit (8);
}
/* parent */
gpg->pid = pid;
/*_gpgme_register_term_handler ( closure, closure_value, pid );*/
if ( gpg->status.fd[1] != -1 ) {
close (gpg->status.fd[1]);
gpg->status.fd[1] = -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);
}
if ( gpg->colon.fd[1] != -1 ) {
close (gpg->colon.fd[1]);
gpg->colon.fd[1] = -1;
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++ ) {
close (gpg->fd_data_map[i].peer_fd);
gpg->fd_data_map[i].peer_fd = -1;
/* Due to problems with select and write we set outbound pipes
* to non-blocking */
if (!gpg->fd_data_map[i].inbound) {
set_nonblocking (gpg->fd_data_map[i].fd);
}
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;
GpgmeError err;
int nread;
char buf[200];
assert ( _gpgme_data_get_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 */
/* We could improve this with a GpgmeData function which takes
* the read function or provides a memory area for writing to it.
*/
err = _gpgme_data_append ( dh, buf, nread );
if ( err ) {
fprintf (stderr, "_gpgme_append_data failed: %s\n",
gpgme_strerror(err));
/* Fixme: we should close the pipe or read it to /dev/null in
* this case. Returnin EOF is not sufficient */
return 1;
}
return 0;
}
static int
write_mem_data ( GpgmeData dh, int fd )
{
size_t nbytes;
int nwritten;
nbytes = dh->len - dh->readpos;
if ( !nbytes ) {
fprintf (stderr, "write_mem_data(%d): closing\n", fd );
close (fd);
return 1;
}
/* FIXME: Arggg, the pipe blocks on large write request, although
* select told us that it is okay to write - need to figure out
* why this happens? Stevens says nothing about this problem (or
* is it my Linux kernel 2.4.0test1)
* To avoid that we have set the pipe to nonblocking.
*/
do {
fprintf (stderr, "write_mem_data(%d): about to write %d bytes len=%d rpos=%d\n",
fd, (int)nbytes, (int)dh->len, dh->readpos );
nwritten = write ( fd, dh->data+dh->readpos, nbytes );
fprintf (stderr, "write_mem_data(%d): wrote %d bytes\n", fd, nwritten );
} while ( nwritten == -1 && errno == EINTR );
if (nwritten == -1 && errno == EAGAIN )
return 0;
if ( nwritten < 1 ) {
fprintf (stderr, "write_mem_data(%d): write failed (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_data_get_mode (dh) == GPGME_DATA_MODE_OUT );
switch ( gpgme_data_get_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 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);
}
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;
}
/*
* 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, pid_t pid, int fd )
{
GpgObject gpg = opaque;
GpgmeError rc = 0;
assert ( fd == gpg->colon.fd[0] );
rc = read_colon_line ( gpg );
if ( rc ) {
fprintf (stderr, "gpg_colon_line_handler: "
"read problem %d\n - stop", rc);
return 1;
}
return gpg->status.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);
}
do {
nread = read ( gpg->colon.fd[0], buffer+readpos, bufsize-readpos );
} while (nread == -1 && errno == EINTR);
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 ( *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;
}