/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors: Jeffrey Stedfast <fejj@ximian.com>
*
* Copyright 2002 Ximian, Inc. (www.ximian.com)
*
* This program 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.
*
* This program 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 Street #330, Boston, MA 02111-1307, USA.
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <termios.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <ctype.h>
#include "camel-gpg-context.h"
#include "camel-stream-fs.h"
#include "camel-operation.h"
static void camel_gpg_context_class_init (CamelGpgContextClass *klass);
static void camel_gpg_context_init (CamelGpgContext *obj);
static void camel_gpg_context_finalise (CamelObject *obj);
static int gpg_sign (CamelCipherContext *ctx, const char *userid, CamelCipherHash hash,
CamelStream *istream, CamelStream *ostream, CamelException *ex);
static int gpg_clearsign (CamelCipherContext *context, const char *userid,
CamelCipherHash hash, CamelStream *istream,
CamelStream *ostream, CamelException *ex);
static CamelCipherValidity *gpg_verify (CamelCipherContext *context, CamelCipherHash hash,
CamelStream *istream, CamelStream *sigstream,
CamelException *ex);
static int gpg_encrypt (CamelCipherContext *context, gboolean sign, const char *userid,
GPtrArray *recipients, CamelStream *istream, CamelStream *ostream,
CamelException *ex);
static int gpg_decrypt (CamelCipherContext *context, CamelStream *istream,
CamelStream *ostream, CamelException *ex);
static const char *gpg_hash_to_id (CamelCipherContext *context, CamelCipherHash hash);
static CamelCipherHash gpg_id_to_hash (CamelCipherContext *context, const char *id);
static CamelCipherContextClass *parent_class = NULL;
CamelType
camel_gpg_context_get_type (void)
{
static CamelType type = CAMEL_INVALID_TYPE;
if (type == CAMEL_INVALID_TYPE) {
type = camel_type_register (camel_cipher_context_get_type (),
"CamelGpgContext",
sizeof (CamelGpgContext),
sizeof (CamelGpgContextClass),
(CamelObjectClassInitFunc) camel_gpg_context_class_init,
NULL,
(CamelObjectInitFunc) camel_gpg_context_init,
(CamelObjectFinalizeFunc) camel_gpg_context_finalise);
}
return type;
}
static void
camel_gpg_context_class_init (CamelGpgContextClass *klass)
{
CamelCipherContextClass *cipher_class = CAMEL_CIPHER_CONTEXT_CLASS (klass);
parent_class = CAMEL_CIPHER_CONTEXT_CLASS (camel_type_get_global_classfuncs (camel_cipher_context_get_type ()));
cipher_class->sign = gpg_sign;
cipher_class->clearsign = gpg_clearsign;
cipher_class->verify = gpg_verify;
cipher_class->encrypt = gpg_encrypt;
cipher_class->decrypt = gpg_decrypt;
cipher_class->hash_to_id = gpg_hash_to_id;
cipher_class->id_to_hash = gpg_id_to_hash;
}
static void
camel_gpg_context_init (CamelGpgContext *context)
{
CamelCipherContext *cipher = (CamelCipherContext *) context;
context->path = NULL;
context->always_trust = FALSE;
cipher->sign_protocol = "application/pgp-signature";
cipher->encrypt_protocol = "application/pgp-encrypted";
}
static void
camel_gpg_context_finalise (CamelObject *object)
{
CamelGpgContext *ctx = (CamelGpgContext *) object;
g_free (ctx->path);
}
static const char *
gpg_hash_to_id (CamelCipherContext *context, CamelCipherHash hash)
{
switch (hash) {
case CAMEL_CIPHER_HASH_MD2:
return "pgp-md2";
case CAMEL_CIPHER_HASH_MD5:
return "pgp-md5";
case CAMEL_CIPHER_HASH_SHA1:
case CAMEL_CIPHER_HASH_DEFAULT:
return "pgp-sha1";
case CAMEL_CIPHER_HASH_RIPEMD160:
return "pgp-ripemd160";
}
return NULL;
}
static CamelCipherHash
gpg_id_to_hash (CamelCipherContext *context, const char *id)
{
if (!strcmp (id, "pgp-md2"))
return CAMEL_CIPHER_HASH_MD2;
else if (!strcmp (id, "pgp-md5"))
return CAMEL_CIPHER_HASH_MD5;
else if (!strcmp (id, "pgp-sha1"))
return CAMEL_CIPHER_HASH_SHA1;
else if (!strcmp (id, "pgp-ripemd160"))
return CAMEL_CIPHER_HASH_RIPEMD160;
return CAMEL_CIPHER_HASH_DEFAULT;
}
enum _GpgCtxMode {
GPG_CTX_MODE_SIGN,
GPG_CTX_MODE_VERIFY,
GPG_CTX_MODE_ENCRYPT,
GPG_CTX_MODE_DECRYPT,
};
struct _GpgCtx {
enum _GpgCtxMode mode;
CamelSession *session;
GHashTable *userid_hint;
gboolean complete;
pid_t pid;
char *path;
char *userid;
char *sigfile;
GPtrArray *recipients;
CamelCipherHash hash;
gboolean always_trust;
gboolean armor;
int stdin;
int stdout;
int stderr;
int status_fd;
int passwd_fd; /* only needed for sign/decrypt */
FILE *status_fp;
gboolean need_passwd;
char *passwd;
CamelStream *istream;
CamelStream *ostream;
GByteArray *diagnostics;
};
static struct _GpgCtx *
gpg_ctx_new (CamelSession *session, const char *path)
{
struct _GpgCtx *gpg;
gpg = g_new (struct _GpgCtx, 1);
gpg->mode = GPG_CTX_MODE_SIGN;
gpg->session = session;
camel_object_ref (CAMEL_OBJECT (session));
gpg->userid_hint = g_hash_table_new (g_str_hash, g_str_equal);
gpg->complete = FALSE;
gpg->pid = (pid_t) -1;
gpg->path = g_strdup (path);
gpg->userid = NULL;
gpg->sigfile = NULL;
gpg->recipients = NULL;
gpg->hash = CAMEL_CIPHER_HASH_DEFAULT;
gpg->always_trust = FALSE;
gpg->armor = FALSE;
gpg->stdin = -1;
gpg->stdout = -1;
gpg->stderr = -1;
gpg->status_fd = -1;
gpg->passwd_fd = -1;
gpg->status_fp = NULL;
gpg->need_passwd = FALSE;
gpg->passwd = NULL;
gpg->istream = NULL;
gpg->ostream = NULL;
gpg->diagnostics = g_byte_array_new ();
return gpg;
}
static void
gpg_ctx_set_mode (struct _GpgCtx *gpg, enum _GpgCtxMode mode)
{
gpg->mode = mode;
gpg->need_passwd = mode == GPG_CTX_MODE_SIGN || mode == GPG_CTX_MODE_DECRYPT;
}
static void
gpg_ctx_set_hash (struct _GpgCtx *gpg, CamelCipherHash hash)
{
gpg->hash = hash;
}
static void
gpg_ctx_set_always_trust (struct _GpgCtx *gpg, gboolean trust)
{
gpg->always_trust = trust;
}
static void
gpg_ctx_set_userid (struct _GpgCtx *gpg, const char *userid)
{
g_free (gpg->userid);
gpg->userid = g_strdup (userid);
}
static void
gpg_ctx_add_recipient (struct _GpgCtx *gpg, const char *keyid)
{
if (gpg->mode != GPG_CTX_MODE_ENCRYPT)
return;
if (!gpg->recipients)
gpg->recipients = g_ptr_array_new ();
g_ptr_array_add (gpg->recipients, g_strdup (keyid));
}
static void
gpg_ctx_set_sigfile (struct _GpgCtx *gpg, const char *sigfile)
{
g_free (gpg->sigfile);
gpg->sigfile = g_strdup (sigfile);
}
static void
gpg_ctx_set_armor (struct _GpgCtx *gpg, gboolean armor)
{
gpg->armor = armor;
}
static void
gpg_ctx_set_istream (struct _GpgCtx *gpg, CamelStream *istream)
{
camel_object_ref (CAMEL_OBJECT (istream));
if (gpg->istream)
camel_object_unref (CAMEL_OBJECT (gpg->istream));
gpg->istream = istream;
}
static void
gpg_ctx_set_ostream (struct _GpgCtx *gpg, CamelStream *ostream)
{
camel_object_ref (CAMEL_OBJECT (ostream));
if (gpg->ostream)
camel_object_unref (CAMEL_OBJECT (gpg->ostream));
gpg->istream = ostream;
}
static char *
gpg_ctx_get_diagnostics (struct _GpgCtx *gpg)
{
return g_strndup (gpg->diagnostics->data, gpg->diagnostics->len);
}
static void
userid_hint_free (gpointer key, gpointer value, gpointer user_data)
{
g_free (key);
g_free (value);
}
static void
gpg_ctx_free (struct _GpgCtx *gpg)
{
int i;
if (gpg->session)
camel_object_unref (CAMEL_OBJECT (gpg->session));
g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL);
g_hash_table_destroy (gpg->userid_hint);
g_free (gpg->path);
g_free (gpg->userid);
g_free (gpg->sigfile);
if (gpg->recipients) {
for (i = 0; i < gpg->recipients->len; i++)
g_free (gpg->recipients->pdata[i]);
g_ptr_array_free (gpg->recipients, TRUE);
}
if (gpg->stdin != -1)
close (gpg->stdin);
if (gpg->stdout != -1)
close (gpg->stdout);
if (gpg->stderr != -1)
close (gpg->stderr);
if (gpg->status_fd != -1 && gpg->status_fp == NULL)
close (gpg->status_fd);
if (gpg->passwd_fd != -1)
close (gpg->passwd_fd);
if (gpg->status_fp != NULL)
fclose (gpg->status_fp);
if (gpg->passwd)
g_free (gpg->passwd);
if (gpg->istream)
camel_object_unref (CAMEL_OBJECT (gpg->istream));
if (gpg->ostream)
camel_object_unref (CAMEL_OBJECT (gpg->ostream));
g_byte_array_free (gpg->diagnostics, TRUE);
g_free (gpg);
}
static const char *
gpg_hash_str (CamelCipherHash hash)
{
switch (hash) {
case CAMEL_CIPHER_HASH_MD2:
return "MD2";
case CAMEL_CIPHER_HASH_MD5:
return "MD5";
case CAMEL_CIPHER_HASH_SHA1:
return "SHA1";
case CAMEL_CIPHER_HASH_RIPEMD160:
return "RIPEMD160";
default:
return NULL;
}
}
static GPtrArray *
gpg_ctx_get_argv (struct _GpgCtx *gpg, int status_fd, char **sfd, int passwd_fd, char **pfd)
{
const char *hash_str;
GPtrArray *argv;
char *buf;
int i;
argv = g_ptr_array_new ();
g_ptr_array_add (argv, "gpg");
g_ptr_array_add (argv, "--verbose");
g_ptr_array_add (argv, "--no-secmem-warning");
g_ptr_array_add (argv, "--no-greeting");
g_ptr_array_add (argv, "--batch");
g_ptr_array_add (argv, "--yes");
*sfd = buf = g_strdup_printf ("%d", status_fd);
g_ptr_array_add (argv, "--status-fd");
g_ptr_array_add (argv, buf);
switch (gpg->mode) {
case GPG_CTX_MODE_SIGN:
g_ptr_array_add (argv, "--sign");
g_ptr_array_add (argv, "-b");
if (gpg->armor)
g_ptr_array_add (argv, "--armor");
hash_str = gpg_hash_str (gpg->hash);
if (hash_str) {
g_ptr_array_add (argv, "--digest-algo");
g_ptr_array_add (argv, (char *) hash_str);
}
if (gpg->userid) {
g_ptr_array_add (argv, "-u");
g_ptr_array_add (argv, (char *) gpg->userid);
}
g_ptr_array_add (argv, "--output");
g_ptr_array_add (argv, "-");
break;
case GPG_CTX_MODE_VERIFY:
if (!camel_session_is_online (gpg->session))
g_ptr_array_add (argv, "--no-auto-key-retrieve");
g_ptr_array_add (argv, "--no-tty");
g_ptr_array_add (argv, "--verify");
if (gpg->sigfile)
g_ptr_array_add (argv, gpg->sigfile);
g_ptr_array_add (argv, "-");
break;
case GPG_CTX_MODE_ENCRYPT:
g_ptr_array_add (argv, "--encrypt");
if (gpg->armor)
g_ptr_array_add (argv, "--armor");
if (gpg->always_trust)
g_ptr_array_add (argv, "--always-trust");
if (gpg->userid) {
g_ptr_array_add (argv, "-u");
g_ptr_array_add (argv, (char *) gpg->userid);
}
if (gpg->recipients) {
for (i = 0; i < gpg->recipients->len; i++) {
g_ptr_array_add (argv, "-r");
g_ptr_array_add (argv, gpg->recipients->pdata[i]);
}
}
g_ptr_array_add (argv, "--output");
g_ptr_array_add (argv, "-");
break;
case GPG_CTX_MODE_DECRYPT:
g_ptr_array_add (argv, "--decrypt");
g_ptr_array_add (argv, "--output");
g_ptr_array_add (argv, "-");
break;
}
if (gpg->need_passwd && passwd_fd != -1) {
*pfd = buf = g_strdup_printf ("%d", passwd_fd);
g_ptr_array_add (argv, "--passphrase-fd");
g_ptr_array_add (argv, buf);
}
g_ptr_array_add (argv, NULL);
return argv;
}
static int
gpg_ctx_op_start (struct _GpgCtx *gpg)
{
char *status_fd = NULL, *passwd_fd = NULL;
int i, maxfd, fds[10];
GPtrArray *argv;
for (i = 0; i < 10; i++)
fds[i] = -1;
maxfd = gpg->need_passwd ? 10 : 8;
for (i = 0; i < maxfd; i += 2) {
if (pipe (fds + i) == -1)
goto exception;
}
argv = gpg_ctx_get_argv (gpg, fds[6], &status_fd, fds[9], &passwd_fd);
if (!(gpg->pid = fork ())) {
/* child process */
if ((dup2 (fds[0], STDIN_FILENO) < 0 ) ||
(dup2 (fds[3], STDOUT_FILENO) < 0 ) ||
(dup2 (fds[5], STDERR_FILENO) < 0 )) {
_exit (255);
}
/* Dissociate from camel's controlling terminal so
* that gpg won't be able to read from it.
*/
setsid ();
maxfd = sysconf (_SC_OPEN_MAX);
if (maxfd > 0) {
/* Loop over all fds. */
for (i = 0; i < maxfd; i++) {
if ((i != STDIN_FILENO) &&
(i != STDOUT_FILENO) &&
(i != STDERR_FILENO) &&
(i != fds[7]) && /* status fd */
(i != fds[8])) /* passwd fd */
close (i);
}
}
/* run gpg */
execvp (gpg->path, (char **) argv->pdata);
_exit (255);
} else if (gpg->pid < 0) {
g_ptr_array_free (argv, TRUE);
g_free (status_fd);
g_free (passwd_fd);
goto exception;
}
g_ptr_array_free (argv, TRUE);
g_free (status_fd);
g_free (passwd_fd);
/* Parent */
close (fds[0]);
gpg->stdin = fds[1];
gpg->stdout = fds[2];
close (fds[3]);
gpg->stderr = fds[4];
close (fds[5]);
gpg->status_fd = fds[6];
close (fds[7]);
if (gpg->need_passwd) {
close (fds[8]);
gpg->passwd_fd = fds[9];
fcntl (gpg->passwd_fd, F_SETFL, O_NONBLOCK);
}
fcntl (gpg->stdin, F_SETFL, O_NONBLOCK);
fcntl (gpg->stdout, F_SETFL, O_NONBLOCK);
fcntl (gpg->stderr, F_SETFL, O_NONBLOCK);
return 0;
exception:
for (i = 0; i < 10; i++) {
if (fds[i] != -1)
close (fds[i]);
}
return -1;
}
static const char *
next_token (const char *in, char **token)
{
const char *start, *inptr = in;
while (*inptr == ' ')
inptr++;
if (*inptr == '\0' || *inptr == '\n') {
if (token)
*token = NULL;
return inptr;
}
start = inptr;
while (*inptr && *inptr != ' ' && *inptr != '\n')
inptr++;
if (token)
*token = g_strndup (start, inptr - start);
return inptr;
}
static int
gpg_ctx_parse_status (struct _GpgCtx *gpg, const char *status, CamelException *ex)
{
if (strncmp (status, "[GNUPG:] ", 9) != 0)
return -1;
status += 9;
if (!strncmp (status, "USERID_HINT ", 12)) {
char *hint, *user;
status += 12;
status = next_token (status, &hint);
if (!hint) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to parse gpg userid hint."));
return -1;
}
if (g_hash_table_lookup (gpg->userid_hint, hint)) {
/* we already have this userid hint... */
g_free (hint);
return 0;
}
user = g_strdup (status);
g_strstrip (user);
g_hash_table_insert (gpg->userid_hint, hint, user);
} else if (!strncmp (status, "NEED_PASSPHRASE ", 16)) {
char *prompt, *userid;
const char *name;
status += 16;
status = next_token (status, &userid);
if (!userid) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to parse gpg passphrase request."));
return -1;
}
name = g_hash_table_lookup (gpg->userid_hint, userid);
if (!name)
name = userid;
prompt = g_strdup_printf (_("You need a passphrase to unlock the key for\n"
"user: \"%s\""), name);
gpg->passwd = camel_session_get_password (gpg->session, prompt, TRUE, NULL, userid, ex);
g_free (prompt);
g_free (userid);
if (gpg->passwd == NULL) {
if (!camel_exception_is_set (ex))
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
return -1;
}
} else if (!strncmp (status, "GOOD_PASSPHRASE ", 16)) {
g_free (gpg->passwd);
gpg->passwd = NULL;
gpg->need_passwd = FALSE;
} else if (!strncmp (status, "BAD_PASSPHRASE ", 15)) {
g_free (gpg->passwd);
gpg->passwd = NULL;
gpg->need_passwd = TRUE;
} else if (!strncmp (status, "UNEXPECTED ", 11)) {
/* this is an error */
return -1;
} else {
/* check to see if we are complete */
switch (gpg->mode) {
case GPG_CTX_MODE_SIGN:
if (!strncmp (status, "SIG_CREATED ", 12))
gpg->complete = TRUE;
break;
case GPG_CTX_MODE_VERIFY:
if (!strncmp (status, "TRUST_", 6))
gpg->complete = TRUE;
break;
case GPG_CTX_MODE_ENCRYPT:
if (!strncmp (status, "END_ENCRYPTION", 14))
gpg->complete = TRUE;
break;
case GPG_CTX_MODE_DECRYPT:
if (!strncmp (status, "END_DECRYPTION", 14))
gpg->complete = TRUE;
break;
}
}
return 0;
}
static int
gpg_ctx_op_step (struct _GpgCtx *gpg, CamelException *ex)
{
struct timeval timeout;
fd_set rdset, wrset;
const char *mode;
int maxfd = 0;
int ret;
do {
FD_ZERO (&rdset);
FD_SET (gpg->stdout, &rdset);
FD_SET (gpg->stderr, &rdset);
FD_SET (gpg->status_fd, &rdset);
maxfd = MAX (gpg->stdout, gpg->stderr);
maxfd = MAX (maxfd, gpg->status_fd);
FD_ZERO (&wrset);
FD_SET (gpg->stdin, &wrset);
maxfd = MAX (maxfd, gpg->stdin);
if (gpg->passwd_fd != -1) {
FD_SET (gpg->passwd_fd, &wrset);
maxfd = MAX (maxfd, gpg->passwd_fd);
}
timeout.tv_sec = 10; /* timeout in seconds */
timeout.tv_usec = 0;
if ((ret = select (maxfd + 1, &rdset, &wrset, NULL, &timeout)) == 0)
return 0;
if (ret < 0) {
if (errno == EINTR)
continue;
return -1;
}
if (FD_ISSET (gpg->status_fd, &rdset)) {
/* read the status message and decide what to do... */
char buffer[4096];
FILE *fp;
if (gpg->status_fp == NULL) {
gpg->status_fp = fdopen (gpg->status_fd, "r");
if (gpg->status_fp == NULL)
goto exception;
}
fp = gpg->status_fp;
fgets (buffer, sizeof (buffer), fp);
return gpg_ctx_parse_status (gpg, buffer, ex);
}
if (FD_ISSET (gpg->stdout, &rdset) && gpg->ostream) {
char buffer[4096];
ssize_t nread;
nread = read (gpg->stdout, buffer, sizeof (buffer));
if (nread > 0)
ret = camel_stream_write (gpg->ostream, buffer, (size_t) nread);
if (ret == -1)
goto exception;
return 0;
}
if (FD_ISSET (gpg->stderr, &rdset)) {
char buffer[4096];
ssize_t nread;
nread = read (gpg->stdout, buffer, sizeof (buffer));
if (nread > 0)
g_byte_array_append (gpg->diagnostics, buffer, nread);
return 0;
}
if (gpg->passwd_fd != -1 && gpg->need_passwd && gpg->passwd && FD_ISSET (gpg->passwd_fd, &wrset)) {
ssize_t w, nwritten = 0;
size_t n;
/* send the passphrase to gpg */
n = strlen (gpg->passwd);
do {
do {
w = write (gpg->passwd_fd, gpg->passwd + nwritten, n - nwritten);
} while (w == -1 && (errno == EINTR || errno == EAGAIN));
if (w > 0)
nwritten += w;
} while (nwritten < n && w != -1);
if (ret == -1)
goto exception;
return 0;
}
if (FD_ISSET (gpg->stdin, &wrset) && gpg->istream && !camel_stream_eos (gpg->istream)) {
CamelStream *stream;
/* write our stream to gpg's stdin */
stream = camel_stream_fs_new_with_fd (gpg->stdin);
ret = camel_stream_write_to_stream (gpg->istream, stream);
if (ret != -1)
ret = camel_stream_flush (stream);
CAMEL_STREAM_FS (stream)->fd = -1;
camel_object_unref (CAMEL_OBJECT (stream));
if (ret == -1)
goto exception;
return 0;
}
} while (1);
return 0;
exception:
switch (gpg->mode) {
case GPG_CTX_MODE_SIGN:
mode = "sign";
break;
case GPG_CTX_MODE_VERIFY:
mode = "verify";
break;
case GPG_CTX_MODE_ENCRYPT:
mode = "encrypt";
break;
case GPG_CTX_MODE_DECRYPT:
mode = "decrypt";
break;
default:
g_assert_not_reached ();
mode = NULL;
break;
}
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to GPG %s message: %s\n"),
mode, g_strerror (errno));
return -1;
}
static gboolean
gpg_ctx_op_complete (struct _GpgCtx *gpg)
{
return gpg->complete;
}
static void
gpg_ctx_op_cancel (struct _GpgCtx *gpg)
{
pid_t retval;
int status;
kill (gpg->pid, SIGTERM);
sleep (1);
retval = waitpid (gpg->pid, &status, WNOHANG);
if (retval == 0) {
/* no more mr nice guy... */
kill (gpg->pid, SIGKILL);
sleep (1);
waitpid (gpg->pid, &status, WNOHANG);
}
}
static int
gpg_ctx_op_wait (struct _GpgCtx *gpg)
{
sigset_t mask, omask;
pid_t retval;
int status;
sigemptyset (&mask);
sigaddset (&mask, SIGALRM);
sigprocmask (SIG_BLOCK, &mask, &omask);
alarm (1);
retval = waitpid (gpg->pid, &status, 0);
alarm (0);
sigprocmask (SIG_SETMASK, &omask, NULL);
if (retval == (pid_t) -1 && errno == EINTR) {
/* The child is hanging: send a friendly reminder. */
kill (gpg->pid, SIGTERM);
sleep (1);
retval = waitpid (gpg->pid, &status, WNOHANG);
if (retval == (pid_t) 0) {
/* Still hanging; use brute force. */
kill (gpg->pid, SIGKILL);
sleep (1);
retval = waitpid (gpg->pid, &status, WNOHANG);
}
}
if (retval != (pid_t) -1 && WIFEXITED (status))
return WEXITSTATUS (status);
else
return -1;
}
static int
gpg_sign (CamelCipherContext *context, const char *userid, CamelCipherHash hash,
CamelStream *istream, CamelStream *ostream, CamelException *ex)
{
CamelGpgContext *ctx = (CamelGpgContext *) context;
struct _GpgCtx *gpg;
gpg = gpg_ctx_new (context->session, ctx->path);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_SIGN);
gpg_ctx_set_hash (gpg, hash);
gpg_ctx_set_armor (gpg, TRUE);
gpg_ctx_set_userid (gpg, userid);
gpg_ctx_set_istream (gpg, istream);
gpg_ctx_set_ostream (gpg, ostream);
if (gpg_ctx_op_start (gpg) == -1) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to execute gpg."));
gpg_ctx_free (gpg);
return -1;
}
while (!gpg_ctx_op_complete (gpg)) {
if (camel_operation_cancel_check (NULL)) {
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Cancelled."));
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
if (gpg_ctx_op_step (gpg, ex) == -1) {
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
}
if (gpg_ctx_op_wait (gpg) != 0) {
char *diagnostics;
diagnostics = gpg_ctx_get_diagnostics (gpg);
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics);
g_free (diagnostics);
}
gpg_ctx_free (gpg);
return 0;
}
static int
gpg_clearsign (CamelCipherContext *context, const char *userid,
CamelCipherHash hash, CamelStream *istream,
CamelStream *ostream, CamelException *ex)
{
return -1;
}
static char *
swrite (CamelStream *istream)
{
CamelStream *ostream;
char *template;
int fd, ret;
template = g_strdup ("/tmp/evolution-pgp.XXXXXX");
fd = mkstemp (template);
if (fd == -1) {
g_free (template);
return NULL;
}
ostream = camel_stream_fs_new_with_fd (fd);
ret = camel_stream_write_to_stream (istream, ostream);
if (ret != -1) {
ret = camel_stream_flush (ostream);
if (ret != -1)
ret = camel_stream_close (ostream);
}
camel_object_unref (CAMEL_OBJECT (ostream));
if (ret == -1) {
unlink (template);
g_free (template);
return NULL;
}
return template;
}
static CamelCipherValidity *
gpg_verify (CamelCipherContext *context, CamelCipherHash hash,
CamelStream *istream, CamelStream *sigstream,
CamelException *ex)
{
CamelGpgContext *ctx = (CamelGpgContext *) context;
CamelCipherValidity *validity;
char *diagnostics = NULL;
struct _GpgCtx *gpg;
char *sigfile = NULL;
gboolean valid;
if (sigstream != NULL) {
/* We are going to verify a detached signature so save
the signature to a temp file. */
sigfile = swrite (sigstream);
if (sigfile == NULL) {
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
_("Cannot verify this message: couldn't create temp file: %s"),
g_strerror (errno));
return NULL;
}
}
gpg = gpg_ctx_new (context->session, ctx->path);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY);
gpg_ctx_set_hash (gpg, hash);
gpg_ctx_set_sigfile (gpg, sigfile);
gpg_ctx_set_istream (gpg, istream);
if (gpg_ctx_op_start (gpg) == -1) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to execute gpg."));
gpg_ctx_free (gpg);
goto exception;
}
while (!gpg_ctx_op_complete (gpg)) {
if (camel_operation_cancel_check (NULL)) {
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Cancelled."));
gpg_ctx_op_cancel (gpg);
goto exception;
}
if (gpg_ctx_op_step (gpg, ex) == -1) {
gpg_ctx_op_cancel (gpg);
goto exception;
}
}
diagnostics = gpg_ctx_get_diagnostics (gpg);
valid = gpg_ctx_op_wait (gpg) == 0;
gpg_ctx_free (gpg);
validity = camel_cipher_validity_new ();
camel_cipher_validity_set_valid (validity, valid);
camel_cipher_validity_set_description (validity, diagnostics);
g_free (diagnostics);
if (sigfile) {
unlink (sigfile);
g_free (sigfile);
}
return validity;
exception:
gpg_ctx_free (gpg);
if (sigfile) {
unlink (sigfile);
g_free (sigfile);
}
return NULL;
}
static int
gpg_encrypt (CamelCipherContext *context, gboolean sign, const char *userid,
GPtrArray *recipients, CamelStream *istream, CamelStream *ostream,
CamelException *ex)
{
CamelGpgContext *ctx = (CamelGpgContext *) context;
struct _GpgCtx *gpg;
int i;
gpg = gpg_ctx_new (context->session, ctx->path);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_ENCRYPT);
gpg_ctx_set_armor (gpg, TRUE);
gpg_ctx_set_userid (gpg, userid);
gpg_ctx_set_istream (gpg, istream);
gpg_ctx_set_ostream (gpg, ostream);
gpg_ctx_set_always_trust (gpg, TRUE);
for (i = 0; i < recipients->len; i++) {
gpg_ctx_add_recipient (gpg, recipients->pdata[i]);
}
if (gpg_ctx_op_start (gpg) == -1) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to execute gpg."));
gpg_ctx_free (gpg);
return -1;
}
while (!gpg_ctx_op_complete (gpg)) {
if (camel_operation_cancel_check (NULL)) {
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Cancelled."));
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
if (gpg_ctx_op_step (gpg, ex) == -1) {
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
}
if (gpg_ctx_op_wait (gpg) != 0) {
char *diagnostics;
diagnostics = gpg_ctx_get_diagnostics (gpg);
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics);
g_free (diagnostics);
}
gpg_ctx_free (gpg);
return 0;
}
static int
gpg_decrypt (CamelCipherContext *context, CamelStream *istream,
CamelStream *ostream, CamelException *ex)
{
CamelGpgContext *ctx = (CamelGpgContext *) context;
struct _GpgCtx *gpg;
gpg = gpg_ctx_new (context->session, ctx->path);
gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT);
gpg_ctx_set_istream (gpg, istream);
gpg_ctx_set_ostream (gpg, ostream);
if (gpg_ctx_op_start (gpg) == -1) {
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
_("Failed to execute gpg."));
gpg_ctx_free (gpg);
return -1;
}
while (!gpg_ctx_op_complete (gpg)) {
if (camel_operation_cancel_check (NULL)) {
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
_("Cancelled."));
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
if (gpg_ctx_op_step (gpg, ex) == -1) {
gpg_ctx_op_cancel (gpg);
gpg_ctx_free (gpg);
return -1;
}
}
if (gpg_ctx_op_wait (gpg) != 0) {
char *diagnostics;
diagnostics = gpg_ctx_get_diagnostics (gpg);
camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, diagnostics);
g_free (diagnostics);
}
gpg_ctx_free (gpg);
return 0;
}