diff options
Diffstat (limited to 'camel/camel-gpg-context.c')
-rw-r--r-- | camel/camel-gpg-context.c | 1179 |
1 files changed, 1179 insertions, 0 deletions
diff --git a/camel/camel-gpg-context.c b/camel/camel-gpg-context.c new file mode 100644 index 0000000000..32764374ed --- /dev/null +++ b/camel/camel-gpg-context.c @@ -0,0 +1,1179 @@ +/* -*- 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; +} |