aboutsummaryrefslogtreecommitdiffstats
path: root/camel/camel-gpg-context.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/camel-gpg-context.c')
-rw-r--r--camel/camel-gpg-context.c1817
1 files changed, 1817 insertions, 0 deletions
diff --git a/camel/camel-gpg-context.c b/camel/camel-gpg-context.c
new file mode 100644
index 0000000000..acd00494ae
--- /dev/null
+++ b/camel/camel-gpg-context.c
@@ -0,0 +1,1817 @@
+/* -*- 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.
+ *
+ */
+
+/* Debug states:
+ gpg:sign dump canonicalised to-be-signed data to a file
+ gpg:verify dump canonicalised verification and signature data to file
+ gpg:status print gpg status-fd output to stdout
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <termios.h>
+#include <signal.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "gal/util/e-iconv.h"
+
+#include "camel-gpg-context.h"
+#include "camel-mime-filter-charset.h"
+#include "camel-stream-filter.h"
+#include "camel-stream-mem.h"
+#include "camel-stream-fs.h"
+#include "camel-operation.h"
+#include "camel-mime-part.h"
+#include "camel-mime-filter-canon.h"
+
+#include "camel-multipart-signed.h"
+#include "camel-multipart-encrypted.h"
+
+#define d(x)
+
+#define GPG_LOG
+
+#ifdef GPG_LOG
+#include "camel-debug.h"
+static int logid;
+#endif
+
+static CamelCipherContextClass *parent_class = NULL;
+
+/**
+ * camel_gpg_context_new:
+ * @session: session
+ *
+ * Creates a new gpg cipher context object.
+ *
+ * Returns a new gpg cipher context object.
+ **/
+CamelCipherContext *
+camel_gpg_context_new (CamelSession *session)
+{
+ CamelCipherContext *cipher;
+ CamelGpgContext *ctx;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ ctx = (CamelGpgContext *) camel_object_new (camel_gpg_context_get_type ());
+
+ cipher = (CamelCipherContext *) ctx;
+ cipher->session = session;
+ camel_object_ref (session);
+
+ return cipher;
+}
+
+
+/**
+ * camel_gpg_context_set_always_trust:
+ * @ctx: gpg context
+ * @always_trust always truct flag
+ *
+ * Sets the @always_trust flag on the gpg context which is used for
+ * encryption.
+ **/
+void
+camel_gpg_context_set_always_trust (CamelGpgContext *ctx, gboolean always_trust)
+{
+ g_return_if_fail (CAMEL_IS_GPG_CONTEXT (ctx));
+
+ ctx->always_trust = always_trust;
+}
+
+
+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";
+ case CAMEL_CIPHER_HASH_TIGER192:
+ return "pgp-tiger192";
+ case CAMEL_CIPHER_HASH_HAVAL5160:
+ return "pgp-haval-5-160";
+ }
+
+ return NULL;
+}
+
+static CamelCipherHash
+gpg_id_to_hash (CamelCipherContext *context, const char *id)
+{
+ if (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;
+ else if (!strcmp (id, "tiger192"))
+ return CAMEL_CIPHER_HASH_TIGER192;
+ else if (!strcmp (id, "haval-5-160"))
+ return CAMEL_CIPHER_HASH_HAVAL5160;
+ }
+
+ return CAMEL_CIPHER_HASH_DEFAULT;
+}
+
+
+enum _GpgCtxMode {
+ GPG_CTX_MODE_SIGN,
+ GPG_CTX_MODE_VERIFY,
+ GPG_CTX_MODE_ENCRYPT,
+ GPG_CTX_MODE_DECRYPT,
+ GPG_CTX_MODE_IMPORT,
+ GPG_CTX_MODE_EXPORT,
+};
+
+enum _GpgTrustMetric {
+ GPG_TRUST_NONE,
+ GPG_TRUST_NEVER,
+ GPG_TRUST_UNDEFINED,
+ GPG_TRUST_MARGINAL,
+ GPG_TRUST_FULLY,
+ GPG_TRUST_ULTIMATE
+};
+
+struct _GpgCtx {
+ enum _GpgCtxMode mode;
+ CamelSession *session;
+ GHashTable *userid_hint;
+ pid_t pid;
+
+ char *userid;
+ char *sigfile;
+ GPtrArray *recipients;
+ CamelCipherHash hash;
+
+ int stdin_fd;
+ int stdout_fd;
+ int stderr_fd;
+ int status_fd;
+ int passwd_fd; /* only needed for sign/decrypt */
+
+ /* status-fd buffer */
+ unsigned char *statusbuf;
+ unsigned char *statusptr;
+ unsigned int statusleft;
+
+ char *need_id;
+ char *passwd;
+
+ CamelStream *istream;
+ CamelStream *ostream;
+
+ GByteArray *diagbuf;
+ CamelStream *diagnostics;
+
+ int exit_status;
+
+ unsigned int exited:1;
+ unsigned int complete:1;
+ unsigned int seen_eof1:1;
+ unsigned int seen_eof2:1;
+ unsigned int always_trust:1;
+ unsigned int armor:1;
+ unsigned int need_passwd:1;
+ unsigned int send_passwd:1;
+
+ unsigned int bad_passwds:2;
+
+ unsigned int hadsig:1;
+ unsigned int badsig:1;
+ unsigned int errsig:1;
+ unsigned int goodsig:1;
+ unsigned int validsig:1;
+ unsigned int nopubkey:1;
+ unsigned int nodata:1;
+ unsigned int trust:3;
+
+ unsigned int diagflushed:1;
+
+ unsigned int utf8:1;
+
+ unsigned int padding:10;
+};
+
+static struct _GpgCtx *
+gpg_ctx_new (CamelSession *session)
+{
+ struct _GpgCtx *gpg;
+ const char *charset;
+ CamelStream *stream;
+
+ gpg = g_new (struct _GpgCtx, 1);
+ gpg->mode = GPG_CTX_MODE_SIGN;
+ gpg->session = session;
+ camel_object_ref (session);
+ gpg->userid_hint = g_hash_table_new (g_str_hash, g_str_equal);
+ gpg->complete = FALSE;
+ gpg->seen_eof1 = TRUE;
+ gpg->seen_eof2 = FALSE;
+ gpg->pid = (pid_t) -1;
+ gpg->exit_status = 0;
+ gpg->exited = FALSE;
+
+ gpg->userid = NULL;
+ gpg->sigfile = NULL;
+ gpg->recipients = NULL;
+ gpg->hash = CAMEL_CIPHER_HASH_DEFAULT;
+ gpg->always_trust = FALSE;
+ gpg->armor = FALSE;
+
+ gpg->stdin_fd = -1;
+ gpg->stdout_fd = -1;
+ gpg->stderr_fd = -1;
+ gpg->status_fd = -1;
+ gpg->passwd_fd = -1;
+
+ gpg->statusbuf = g_malloc (128);
+ gpg->statusptr = gpg->statusbuf;
+ gpg->statusleft = 128;
+
+ gpg->bad_passwds = 0;
+ gpg->need_passwd = FALSE;
+ gpg->send_passwd = FALSE;
+ gpg->need_id = NULL;
+ gpg->passwd = NULL;
+
+ gpg->nodata = FALSE;
+ gpg->hadsig = FALSE;
+ gpg->badsig = FALSE;
+ gpg->errsig = FALSE;
+ gpg->goodsig = FALSE;
+ gpg->validsig = FALSE;
+ gpg->nopubkey = FALSE;
+ gpg->trust = GPG_TRUST_NONE;
+
+ gpg->istream = NULL;
+ gpg->ostream = NULL;
+
+ stream = camel_stream_mem_new ();
+ gpg->diagbuf = CAMEL_STREAM_MEM (stream)->buffer;
+ gpg->diagflushed = FALSE;
+
+ if ((charset = e_iconv_locale_charset ()) && strcasecmp (charset, "UTF-8") != 0) {
+ CamelMimeFilterCharset *filter;
+ CamelStreamFilter *fstream;
+
+ gpg->utf8 = FALSE;
+
+ if ((filter = camel_mime_filter_charset_new_convert (charset, "UTF-8"))) {
+ fstream = camel_stream_filter_new_with_stream (stream);
+ camel_stream_filter_add (fstream, (CamelMimeFilter *) filter);
+ camel_object_unref (filter);
+ camel_object_unref (stream);
+
+ stream = (CamelStream *) fstream;
+ }
+ } else {
+ gpg->utf8 = TRUE;
+ }
+
+ gpg->diagnostics = stream;
+
+ return gpg;
+}
+
+static void
+gpg_ctx_set_mode (struct _GpgCtx *gpg, enum _GpgCtxMode mode)
+{
+ gpg->mode = mode;
+ gpg->need_passwd = ((gpg->mode == GPG_CTX_MODE_SIGN) || (gpg->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 && gpg->mode != GPG_CTX_MODE_EXPORT)
+ 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 (istream);
+ if (gpg->istream)
+ camel_object_unref (gpg->istream);
+ gpg->istream = istream;
+}
+
+static void
+gpg_ctx_set_ostream (struct _GpgCtx *gpg, CamelStream *ostream)
+{
+ camel_object_ref (ostream);
+ if (gpg->ostream)
+ camel_object_unref (gpg->ostream);
+ gpg->ostream = ostream;
+ gpg->seen_eof1 = FALSE;
+}
+
+static const char *
+gpg_ctx_get_diagnostics (struct _GpgCtx *gpg)
+{
+ if (!gpg->diagflushed) {
+ gpg->diagflushed = TRUE;
+ camel_stream_flush (gpg->diagnostics);
+ if (gpg->diagbuf->len == 0)
+ return NULL;
+
+ g_byte_array_append (gpg->diagbuf, "", 1);
+ }
+
+ return gpg->diagbuf->data;
+}
+
+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 == NULL)
+ return;
+
+ if (gpg->session)
+ camel_object_unref (gpg->session);
+
+ g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL);
+ g_hash_table_destroy (gpg->userid_hint);
+
+ 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_fd != -1)
+ close (gpg->stdin_fd);
+ if (gpg->stdout_fd != -1)
+ close (gpg->stdout_fd);
+ if (gpg->stderr_fd != -1)
+ close (gpg->stderr_fd);
+ if (gpg->status_fd != -1)
+ close (gpg->status_fd);
+ if (gpg->passwd_fd != -1)
+ close (gpg->passwd_fd);
+
+ g_free (gpg->statusbuf);
+
+ g_free (gpg->need_id);
+
+ if (gpg->passwd) {
+ memset (gpg->passwd, 0, strlen (gpg->passwd));
+ g_free (gpg->passwd);
+ }
+
+ if (gpg->istream)
+ camel_object_unref (gpg->istream);
+
+ if (gpg->ostream)
+ camel_object_unref (gpg->ostream);
+
+ camel_object_unref (gpg->diagnostics);
+
+ g_free (gpg);
+}
+
+static const char *
+gpg_hash_str (CamelCipherHash hash)
+{
+ switch (hash) {
+ case CAMEL_CIPHER_HASH_MD2:
+ return "--digest-algo=MD2";
+ case CAMEL_CIPHER_HASH_MD5:
+ return "--digest-algo=MD5";
+ case CAMEL_CIPHER_HASH_SHA1:
+ return "--digest-algo=SHA1";
+ case CAMEL_CIPHER_HASH_RIPEMD160:
+ return "--digest-algo=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, "--no-tty");
+ if (passwd_fd == -1) {
+ /* only use batch mode if we don't intend on using the
+ interactive --command-fd option */
+ g_ptr_array_add (argv, "--batch");
+ g_ptr_array_add (argv, "--yes");
+ }
+
+ *sfd = buf = g_strdup_printf ("--status-fd=%d", status_fd);
+ g_ptr_array_add (argv, buf);
+
+ if (passwd_fd != -1) {
+ *pfd = buf = g_strdup_printf ("--command-fd=%d", passwd_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, "--detach");
+ if (gpg->armor)
+ g_ptr_array_add (argv, "--armor");
+ hash_str = gpg_hash_str (gpg->hash);
+ if (hash_str)
+ 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)) {
+ /* this is a deprecated flag to gpg since 1.0.7 */
+ /*g_ptr_array_add (argv, "--no-auto-key-retrieve");*/
+ g_ptr_array_add (argv, "--keyserver-options");
+ g_ptr_array_add (argv, "no-auto-key-retrieve");
+ }
+ 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;
+ case GPG_CTX_MODE_IMPORT:
+ g_ptr_array_add (argv, "--import");
+ g_ptr_array_add (argv, "-");
+ break;
+ case GPG_CTX_MODE_EXPORT:
+ if (gpg->armor)
+ g_ptr_array_add (argv, "--armor");
+ g_ptr_array_add (argv, "--export");
+ for (i = 0; i < gpg->recipients->len; i++)
+ g_ptr_array_add (argv, gpg->recipients->pdata[i]);
+ break;
+ }
+
+ 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, errnosave, fds[10];
+ GPtrArray *argv;
+ int flags;
+
+ 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[7], &status_fd, fds[8], &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);
+ /* Loop over all fds. */
+ for (i = 3; i < maxfd; i++) {
+ /* don't close the status-fd or passwd-fd */
+ if (i != fds[7] && i != fds[8])
+ fcntl (i, F_SETFD, FD_CLOEXEC);
+ }
+
+ /* run gpg */
+ execvp ("gpg", (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_fd = fds[1];
+ gpg->stdout_fd = fds[2];
+ close (fds[3]);
+ gpg->stderr_fd = 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];
+ flags = fcntl (gpg->passwd_fd, F_GETFL);
+ fcntl (gpg->passwd_fd, F_SETFL, flags | O_NONBLOCK);
+ }
+
+ flags = fcntl (gpg->stdin_fd, F_GETFL);
+ fcntl (gpg->stdin_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->stdout_fd, F_GETFL);
+ fcntl (gpg->stdout_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->stderr_fd, F_GETFL);
+ fcntl (gpg->stderr_fd, F_SETFL, flags | O_NONBLOCK);
+
+ flags = fcntl (gpg->status_fd, F_GETFL);
+ fcntl (gpg->status_fd, F_SETFL, flags | O_NONBLOCK);
+
+ return 0;
+
+ exception:
+
+ errnosave = errno;
+
+ for (i = 0; i < 10; i++) {
+ if (fds[i] != -1)
+ close (fds[i]);
+ }
+
+ errno = errnosave;
+
+ 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, CamelException *ex)
+{
+ register unsigned char *inptr;
+ const unsigned char *status;
+ size_t nread, nwritten;
+ int len;
+
+ parse:
+
+ inptr = gpg->statusbuf;
+ while (inptr < gpg->statusptr && *inptr != '\n')
+ inptr++;
+
+ if (*inptr != '\n') {
+ /* we don't have enough data buffered to parse this status line */
+ return 0;
+ }
+
+ *inptr++ = '\0';
+ status = gpg->statusbuf;
+
+ if (camel_debug("gpg:status"))
+ printf ("status: %s\n", status);
+
+ if (strncmp (status, "[GNUPG:] ", 9) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected GnuPG status message encountered:\n\n%s"),
+ status);
+ 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);
+ goto recycle;
+ }
+
+ if (gpg->utf8 || !(user = g_locale_to_utf8 (status, -1, &nread, &nwritten, NULL)))
+ user = g_strdup (status);
+
+ g_strstrip (user);
+
+ g_hash_table_insert (gpg->userid_hint, hint, user);
+ } else if (!strncmp (status, "NEED_PASSPHRASE ", 16)) {
+ char *userid;
+
+ status += 16;
+
+ status = next_token (status, &userid);
+ if (!userid) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to parse gpg passphrase request."));
+ return -1;
+ }
+
+ g_free (gpg->need_id);
+ gpg->need_id = userid;
+ } else if (!strncmp (status, "GET_HIDDEN passphrase.enter", 27)) {
+ char *prompt, *passwd;
+ const char *name;
+
+ name = g_hash_table_lookup (gpg->userid_hint, gpg->need_id);
+ if (!name)
+ name = gpg->need_id;
+
+ prompt = g_strdup_printf (_("You need a passphrase to unlock the key for\n"
+ "user: \"%s\""), name);
+
+ if ((passwd = camel_session_get_password (gpg->session, NULL, NULL, prompt, gpg->need_id, CAMEL_SESSION_PASSWORD_SECRET, ex)) && !gpg->utf8) {
+ char *opasswd = passwd;
+
+ if ((passwd = g_locale_to_utf8 (passwd, -1, &nread, &nwritten, NULL))) {
+ memset (opasswd, 0, strlen (opasswd));
+ g_free (opasswd);
+ } else {
+ passwd = opasswd;
+ }
+ }
+ g_free (prompt);
+
+ if (passwd == NULL) {
+ if (!camel_exception_is_set (ex))
+ camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
+ return -1;
+ }
+
+ gpg->passwd = g_strdup_printf ("%s\n", passwd);
+ memset (passwd, 0, strlen (passwd));
+ g_free (passwd);
+
+ gpg->send_passwd = TRUE;
+ } else if (!strncmp (status, "GOOD_PASSPHRASE", 15)) {
+ gpg->bad_passwds = 0;
+ } else if (!strncmp (status, "BAD_PASSPHRASE", 14)) {
+ gpg->bad_passwds++;
+
+ camel_session_forget_password (gpg->session, NULL, NULL, gpg->need_id, ex);
+
+ if (gpg->bad_passwds == 3) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+ _("Failed to unlock secret key: 3 bad passphrases given."));
+ return -1;
+ }
+ } else if (!strncmp (status, "UNEXPECTED ", 11)) {
+ /* this is an error */
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected response from GnuPG: %s"),
+ status + 11);
+ return -1;
+ } else if (!strncmp (status, "NODATA", 6)) {
+ /* this is an error */
+ /* But we ignore it anyway, we should get other response codes to say why */
+ gpg->nodata = TRUE;
+ } else {
+ /* check to see if we are complete */
+ switch (gpg->mode) {
+ case GPG_CTX_MODE_SIGN:
+ if (!strncmp (status, "SIG_CREATED ", 12)) {
+ /* FIXME: save this state? */
+ }
+ break;
+ case GPG_CTX_MODE_VERIFY:
+ if (!strncmp (status, "TRUST_", 6)) {
+ status += 6;
+ if (!strncmp (status, "NEVER", 5)) {
+ gpg->trust = GPG_TRUST_NEVER;
+ } else if (!strncmp (status, "MARGINAL", 8)) {
+ gpg->trust = GPG_TRUST_MARGINAL;
+ } else if (!strncmp (status, "FULLY", 5)) {
+ gpg->trust = GPG_TRUST_FULLY;
+ } else if (!strncmp (status, "ULTIMATE", 8)) {
+ gpg->trust = GPG_TRUST_ULTIMATE;
+ } else if (!strncmp (status, "UNDEFINED", 9)) {
+ gpg->trust = GPG_TRUST_UNDEFINED;
+ }
+ } else if (!strncmp (status, "GOODSIG ", 8)) {
+ gpg->goodsig = TRUE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "VALIDSIG ", 9)) {
+ gpg->validsig = TRUE;
+ } else if (!strncmp (status, "BADSIG ", 7)) {
+ gpg->badsig = FALSE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "ERRSIG ", 7)) {
+ /* Note: NO_PUBKEY often comes after an ERRSIG */
+ gpg->errsig = FALSE;
+ gpg->hadsig = TRUE;
+ } else if (!strncmp (status, "NO_PUBKEY ", 10)) {
+ gpg->nopubkey = TRUE;
+ }
+ break;
+ case GPG_CTX_MODE_ENCRYPT:
+ if (!strncmp (status, "BEGIN_ENCRYPTION", 16)) {
+ /* nothing to do... but we know to expect data on stdout soon */
+ } else if (!strncmp (status, "END_ENCRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ } else if (!strncmp (status, "NO_RECP", 7)) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to encrypt: No valid recipients specified."));
+ return -1;
+ }
+ break;
+ case GPG_CTX_MODE_DECRYPT:
+ if (!strncmp (status, "BEGIN_DECRYPTION", 16)) {
+ /* nothing to do... but we know to expect data on stdout soon */
+ } else if (!strncmp (status, "END_DECRYPTION", 14)) {
+ /* nothing to do, but we know the end is near? */
+ }
+ break;
+ case GPG_CTX_MODE_IMPORT:
+ /* noop */
+ break;
+ case GPG_CTX_MODE_EXPORT:
+ /* noop */
+ break;
+ }
+ }
+
+ recycle:
+
+ /* recycle our statusbuf by moving inptr to the beginning of statusbuf */
+ len = gpg->statusptr - inptr;
+ memmove (gpg->statusbuf, inptr, len);
+
+ len = inptr - gpg->statusbuf;
+ gpg->statusleft += len;
+ gpg->statusptr -= len;
+
+ /* if we have more data, try parsing the next line? */
+ if (gpg->statusptr > gpg->statusbuf)
+ goto parse;
+
+ return 0;
+}
+
+#define status_backup(gpg, start, len) G_STMT_START { \
+ if (gpg->statusleft <= len) { \
+ unsigned int slen, soff; \
+ \
+ slen = soff = gpg->statusptr - gpg->statusbuf; \
+ slen = slen ? slen : 1; \
+ \
+ while (slen < soff + len) \
+ slen <<= 1; \
+ \
+ gpg->statusbuf = g_realloc (gpg->statusbuf, slen + 1); \
+ gpg->statusptr = gpg->statusbuf + soff; \
+ gpg->statusleft = slen - soff; \
+ } \
+ \
+ memcpy (gpg->statusptr, start, len); \
+ gpg->statusptr += len; \
+ gpg->statusleft -= len; \
+} G_STMT_END
+
+static void
+gpg_ctx_op_cancel (struct _GpgCtx *gpg)
+{
+ pid_t retval;
+ int status;
+
+ if (gpg->exited)
+ return;
+
+ kill (gpg->pid, SIGTERM);
+ sleep (1);
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ if (retval == (pid_t) 0) {
+ /* no more mr nice guy... */
+ kill (gpg->pid, SIGKILL);
+ sleep (1);
+ waitpid (gpg->pid, &status, WNOHANG);
+ }
+}
+
+static int
+gpg_ctx_op_step (struct _GpgCtx *gpg, CamelException *ex)
+{
+ struct pollfd polls[6];
+ int status, i, cancel_fd;
+
+ for (i=0;i<6;i++)
+ polls[i].fd = -1;
+
+ if (!gpg->seen_eof1) {
+ polls[0].fd = gpg->stdout_fd;
+ polls[0].events = POLLIN;
+ }
+
+ if (!gpg->seen_eof2) {
+ polls[1].fd = gpg->stderr_fd;
+ polls[1].events = POLLIN;
+ }
+
+ if (!gpg->complete) {
+ polls[2].fd = gpg->status_fd;
+ polls[2].events = POLLIN;
+ }
+
+ polls[3].fd = gpg->stdin_fd;
+ polls[3].events = POLLOUT;
+ polls[4].fd = gpg->passwd_fd;
+ polls[4].events = POLLOUT;
+ cancel_fd = camel_operation_cancel_fd(NULL);
+ polls[5].fd = cancel_fd;
+ polls[5].events = POLLIN;
+
+ do {
+ for (i=0;i<6;i++)
+ polls[i].revents = 0;
+ status = poll(polls, 6, 30*1000);
+ } while (status == -1 && errno == EINTR);
+
+ if (status == 0)
+ return 0; /* timed out */
+ else if (status == -1)
+ goto exception;
+
+ if ((polls[5].revents & POLLIN) && camel_operation_cancel_check(NULL)) {
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled."));
+ gpg_ctx_op_cancel(gpg);
+ return -1;
+ }
+
+ /* Test each and every file descriptor to see if it's 'ready',
+ and if so - do what we can with it and then drop through to
+ the next file descriptor and so on until we've done what we
+ can to all of them. If one fails along the way, return
+ -1. */
+
+ if (polls[2].revents & (POLLIN|POLLHUP)) {
+ /* read the status message and decide what to do... */
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading from gpg's status-fd...\n"));
+
+ do {
+ nread = read (gpg->status_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ status_backup (gpg, buffer, nread);
+
+ if (gpg_ctx_parse_status (gpg, ex) == -1)
+ return -1;
+ } else {
+ gpg->complete = TRUE;
+ }
+ }
+
+ if ((polls[0].revents & (POLLIN|POLLHUP)) && gpg->ostream) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading gpg's stdout...\n"));
+
+ do {
+ nread = read (gpg->stdout_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ if (camel_stream_write (gpg->ostream, buffer, (size_t) nread) == -1)
+ goto exception;
+ } else {
+ gpg->seen_eof1 = TRUE;
+ }
+ }
+
+ if (polls[1].revents & (POLLIN|POLLHUP)) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("reading gpg's stderr...\n"));
+
+ do {
+ nread = read (gpg->stderr_fd, buffer, sizeof (buffer));
+ } while (nread == -1 && (errno == EINTR || errno == EAGAIN));
+ if (nread == -1)
+ goto exception;
+
+ if (nread > 0) {
+ camel_stream_write (gpg->diagnostics, buffer, nread);
+ } else {
+ gpg->seen_eof2 = TRUE;
+ }
+ }
+
+ if ((polls[4].revents & (POLLOUT|POLLHUP)) && gpg->need_passwd && gpg->send_passwd) {
+ ssize_t w, nwritten = 0;
+ size_t n;
+
+ d(printf ("sending gpg our passphrase...\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);
+
+ /* zero and free our passwd buffer */
+ memset (gpg->passwd, 0, n);
+ g_free (gpg->passwd);
+ gpg->passwd = NULL;
+
+ if (w == -1)
+ goto exception;
+
+ gpg->send_passwd = FALSE;
+ }
+
+ if ((polls[3].revents & (POLLOUT|POLLHUP)) && gpg->istream) {
+ char buffer[4096];
+ ssize_t nread;
+
+ d(printf ("writing to gpg's stdin...\n"));
+
+ /* write our stream to gpg's stdin */
+ nread = camel_stream_read (gpg->istream, buffer, sizeof (buffer));
+ if (nread > 0) {
+ ssize_t w, nwritten = 0;
+
+ do {
+ do {
+ w = write (gpg->stdin_fd, buffer + nwritten, nread - nwritten);
+ } while (w == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (w > 0)
+ nwritten += w;
+ } while (nwritten < nread && w != -1);
+
+ if (w == -1)
+ goto exception;
+
+ d(printf ("wrote %d (out of %d) bytes to gpg's stdin\n", nwritten, nread));
+ }
+
+ if (camel_stream_eos (gpg->istream)) {
+ d(printf ("closing gpg's stdin\n"));
+ close (gpg->stdin_fd);
+ gpg->stdin_fd = -1;
+ }
+ }
+
+ return 0;
+
+exception:
+ /* always called on an i/o error */
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to execute gpg: %s"), g_strerror(errno));
+ gpg_ctx_op_cancel(gpg);
+
+ return -1;
+}
+
+static gboolean
+gpg_ctx_op_complete (struct _GpgCtx *gpg)
+{
+ return gpg->complete && gpg->seen_eof1 && gpg->seen_eof2;}
+
+
+#if 0
+static gboolean
+gpg_ctx_op_exited (struct _GpgCtx *gpg)
+{
+ pid_t retval;
+ int status;
+
+ if (gpg->exited)
+ return TRUE;
+
+ retval = waitpid (gpg->pid, &status, WNOHANG);
+ if (retval == gpg->pid) {
+ gpg->exit_status = status;
+ gpg->exited = TRUE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+#endif
+
+static int
+gpg_ctx_op_wait (struct _GpgCtx *gpg)
+{
+ sigset_t mask, omask;
+ pid_t retval;
+ int status;
+
+ if (!gpg->exited) {
+ 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);
+ }
+ }
+ } else {
+ status = gpg->exit_status;
+ retval = gpg->pid;
+ }
+
+ if (retval != (pid_t) -1 && WIFEXITED (status))
+ return WEXITSTATUS (status);
+ else
+ return -1;
+}
+
+
+
+static int
+gpg_sign (CamelCipherContext *context, const char *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ struct _GpgCtx *gpg = NULL;
+ CamelStream *ostream = camel_stream_mem_new(), *istream;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ int res = -1;
+ CamelMimePart *sigpart;
+ CamelMultipartSigned *mps;
+
+ /* Note: see rfc2015 or rfc3156, section 5 */
+
+ /* FIXME: stream this, we stream output at least */
+ istream = camel_stream_mem_new();
+ if (camel_cipher_canonical_to_stream(ipart, CAMEL_MIME_FILTER_CANON_STRIP|CAMEL_MIME_FILTER_CANON_CRLF|CAMEL_MIME_FILTER_CANON_FROM,
+ istream) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not generate signing data: %s"), g_strerror(errno));
+ goto fail;
+ }
+
+#ifdef GPG_LOG
+ if (camel_debug_start("gpg:sign")) {
+ char *name;
+ CamelStream *out;
+
+ name = g_strdup_printf("camel-gpg.%d.sign-data", logid++);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg signing data to '%s'\n", name);
+ camel_stream_write_to_stream(istream, out);
+ camel_stream_reset(istream);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ camel_debug_end();
+ }
+#endif
+
+ gpg = gpg_ctx_new (context->session);
+ 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_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"), g_strerror (errno));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+
+ dw = camel_data_wrapper_new();
+ camel_stream_reset(ostream);
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+
+ sigpart = camel_mime_part_new();
+ ct = camel_content_type_new("application", "pgp-signature");
+ camel_content_type_set_param(ct, "name", "signature.asc");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)sigpart, dw);
+ camel_object_unref(dw);
+
+ camel_mime_part_set_description(sigpart, _("This is a digitally signed message part"));
+
+ mps = camel_multipart_signed_new();
+ ct = camel_content_type_new("multipart", "signed");
+ camel_content_type_set_param(ct, "micalg", camel_cipher_hash_to_id(context, hash));
+ camel_content_type_set_param(ct, "protocol", context->sign_protocol);
+ camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mps, ct);
+ camel_content_type_unref(ct);
+ camel_multipart_set_boundary((CamelMultipart *)mps, NULL);
+
+ mps->signature = sigpart;
+ mps->contentraw = istream;
+ camel_stream_reset(istream);
+ camel_object_ref(istream);
+
+ camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mps);
+fail:
+ camel_object_unref(ostream);
+
+ if (gpg)
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+
+static char *
+swrite (CamelMimePart *sigpart)
+{
+ 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;
+ }
+
+ /* TODO: This should probably just write the decoded message content out, not the part + headers */
+
+ ostream = camel_stream_fs_new_with_fd (fd);
+ ret = camel_data_wrapper_write_to_stream((CamelDataWrapper *)sigpart, ostream);
+ if (ret != -1) {
+ ret = camel_stream_flush (ostream);
+ if (ret != -1)
+ ret = camel_stream_close (ostream);
+ }
+
+ camel_object_unref(ostream);
+
+ if (ret == -1) {
+ unlink (template);
+ g_free (template);
+ return NULL;
+ }
+
+ return template;
+}
+
+static CamelCipherValidity *
+gpg_verify (CamelCipherContext *context, CamelMimePart *ipart, CamelException *ex)
+{
+ CamelCipherValidity *validity;
+ const char *diagnostics = NULL, *tmp;
+ struct _GpgCtx *gpg = NULL;
+ char *sigfile = NULL;
+ CamelContentType *ct;
+ CamelMimePart *sigpart;
+ CamelStream *istream = NULL;
+ CamelMultipart *mps;
+
+ mps = (CamelMultipart *)camel_medium_get_content_object((CamelMedium *)ipart);
+ ct = ((CamelDataWrapper *)mps)->mime_type;
+ tmp = camel_content_type_param(ct, "protocol");
+ if (!camel_content_type_is(ct, "multipart", "signed")
+ || !CAMEL_IS_MULTIPART_SIGNED(mps)
+ || tmp == NULL
+ || g_ascii_strcasecmp(tmp, context->sign_protocol) != 0) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(istream = camel_multipart_signed_get_content_stream ((CamelMultipartSigned *) mps, NULL))) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ return NULL;
+ }
+
+ if (!(sigpart = camel_multipart_get_part (mps, CAMEL_MULTIPART_SIGNED_SIGNATURE))) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: Incorrect message format"));
+ camel_object_unref (istream);
+ return NULL;
+ }
+
+#ifdef GPG_LOG
+ if (camel_debug_start("gpg:sign")) {
+ char *name;
+ CamelStream *out;
+
+ name = g_strdup_printf("camel-gpg.%d.verify.data", logid);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg verify data to '%s'\n", name);
+ camel_stream_write_to_stream(istream, out);
+ camel_stream_reset(istream);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ name = g_strdup_printf("camel-gpg.%d.verify.signature", logid++);
+ out = camel_stream_fs_new_with_name(name, O_CREAT|O_TRUNC|O_WRONLY, 0666);
+ if (out) {
+ printf("Writing gpg verify signature to '%s'\n", name);
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)sigpart, out);
+ camel_object_unref(out);
+ }
+ g_free(name);
+ camel_debug_end();
+ }
+#endif
+
+ sigfile = swrite (sigpart);
+ if (sigfile == NULL) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Cannot verify message signature: could not create temp file: %s"),
+ g_strerror (errno));
+ goto exception;
+ }
+
+ camel_stream_reset(istream);
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY);
+ gpg_ctx_set_hash (gpg, camel_cipher_id_to_hash(context, camel_content_type_param(ct, "micalg")));
+ 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."));
+ goto exception;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto exception;
+ }
+
+ gpg_ctx_op_wait (gpg);
+ validity = camel_cipher_validity_new ();
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_cipher_validity_set_description (validity, diagnostics);
+ if (gpg->validsig) {
+ if (gpg->trust == GPG_TRUST_UNDEFINED || gpg->trust == GPG_TRUST_NONE)
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ else if (gpg->trust != GPG_TRUST_NEVER)
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_GOOD;
+ else
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ } else {
+ validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+
+ gpg_ctx_free (gpg);
+
+ if (sigfile) {
+ unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ return validity;
+
+ exception:
+
+ if (gpg != NULL)
+ gpg_ctx_free (gpg);
+
+ if (istream)
+ camel_object_unref(istream);
+
+ if (sigfile) {
+ unlink (sigfile);
+ g_free (sigfile);
+ }
+
+ return NULL;
+}
+
+static int
+gpg_encrypt (CamelCipherContext *context, const char *userid, GPtrArray *recipients, struct _CamelMimePart *ipart, struct _CamelMimePart *opart, CamelException *ex)
+{
+ CamelGpgContext *ctx = (CamelGpgContext *) context;
+ struct _GpgCtx *gpg;
+ int i, res = -1;
+ CamelStream *istream, *ostream, *vstream;
+ CamelMimePart *encpart, *verpart;
+ CamelDataWrapper *dw;
+ CamelContentType *ct;
+ CamelMultipartEncrypted *mpe;
+
+ ostream = camel_stream_mem_new();
+ istream = camel_stream_mem_new();
+ if (camel_cipher_canonical_to_stream(ipart, CAMEL_MIME_FILTER_CANON_CRLF, istream) == -1) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Could not generate encrypting data: %s"), g_strerror(errno));
+ goto fail1;
+ }
+
+ gpg = gpg_ctx_new (context->session);
+ 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, ctx->always_trust);
+
+ 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."));
+ goto fail;
+ }
+
+ /* FIXME: move tihs to a common routine */
+ while (!gpg_ctx_op_complete(gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics : _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+
+ dw = camel_data_wrapper_new();
+ camel_data_wrapper_construct_from_stream(dw, ostream);
+
+ encpart = camel_mime_part_new();
+ ct = camel_content_type_new("application", "octet-stream");
+ camel_content_type_set_param(ct, "name", "encrypted.asc");
+ camel_data_wrapper_set_mime_type_field(dw, ct);
+ camel_content_type_unref(ct);
+
+ camel_medium_set_content_object((CamelMedium *)encpart, dw);
+ camel_object_unref(dw);
+
+ camel_mime_part_set_description(encpart, _("This is a digitally encrypted message part"));
+
+ vstream = camel_stream_mem_new();
+ camel_stream_write(vstream, "Version: 1\n", strlen("Version: 1\n"));
+ camel_stream_reset(vstream);
+
+ verpart = camel_mime_part_new();
+ dw = camel_data_wrapper_new();
+ camel_data_wrapper_set_mime_type(dw, context->encrypt_protocol);
+ camel_data_wrapper_construct_from_stream(dw, vstream);
+ camel_object_unref(vstream);
+ camel_medium_set_content_object((CamelMedium *)verpart, dw);
+ camel_object_unref(dw);
+
+ mpe = camel_multipart_encrypted_new();
+ ct = camel_content_type_new("multipart", "encrypted");
+ camel_content_type_set_param(ct, "protocol", context->encrypt_protocol);
+ camel_data_wrapper_set_mime_type_field((CamelDataWrapper *)mpe, ct);
+ camel_content_type_unref(ct);
+ camel_multipart_set_boundary((CamelMultipart *)mpe, NULL);
+
+ mpe->decrypted = ipart;
+ camel_object_ref(ipart);
+
+ camel_multipart_add_part((CamelMultipart *)mpe, verpart);
+ camel_object_unref(verpart);
+ camel_multipart_add_part((CamelMultipart *)mpe, encpart);
+ camel_object_unref(encpart);
+
+ camel_medium_set_content_object((CamelMedium *)opart, (CamelDataWrapper *)mpe);
+fail:
+ gpg_ctx_free(gpg);
+fail1:
+ camel_object_unref(istream);
+ camel_object_unref(ostream);
+
+ return res;
+}
+
+static CamelCipherValidity *
+gpg_decrypt(CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ CamelCipherValidity *valid = NULL;
+ CamelStream *ostream, *istream;
+ CamelDataWrapper *content;
+ CamelMimePart *encrypted;
+ CamelMultipart *mp;
+
+ mp = (CamelMultipart *) camel_medium_get_content_object ((CamelMedium *) ipart);
+ if (!(encrypted = camel_multipart_get_part (mp, CAMEL_MULTIPART_ENCRYPTED_CONTENT))) {
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to decrypt MIME part: protocol error"));
+ return NULL;
+ }
+
+ content = camel_medium_get_content_object ((CamelMedium *) encrypted);
+
+ istream = camel_stream_mem_new();
+ camel_data_wrapper_decode_to_stream (content, istream);
+ camel_stream_reset(istream);
+
+ ostream = camel_stream_mem_new();
+ camel_stream_mem_set_secure((CamelStreamMem *)ostream);
+
+ gpg = gpg_ctx_new (context->session);
+ 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."));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ camel_stream_reset(ostream);
+ if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)opart, ostream) != -1) {
+ valid = camel_cipher_validity_new();
+ valid->encrypt.description = g_strdup(_("Encrypted content"));
+ valid->encrypt.status = CAMEL_CIPHER_VALIDITY_ENCRYPT_ENCRYPTED;
+
+ if (gpg->hadsig) {
+ if (gpg->validsig) {
+ if (gpg->trust == GPG_TRUST_UNDEFINED || gpg->trust == GPG_TRUST_NONE)
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ else if (gpg->trust != GPG_TRUST_NEVER)
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_GOOD;
+ else
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ } else if (gpg->nopubkey) {
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_UNKNOWN;
+ } else {
+ valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD;
+ }
+ }
+ } else {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unable to parse message content"));
+ }
+
+ fail:
+ camel_object_unref(ostream);
+ camel_object_unref(istream);
+ gpg_ctx_free (gpg);
+
+ return valid;
+}
+
+static int
+gpg_import_keys (CamelCipherContext *context, CamelStream *istream, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ int res = -1;
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_IMPORT);
+ gpg_ctx_set_istream (gpg, istream);
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"),
+ errno ? g_strerror (errno) : _("Unknown"));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+fail:
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+static int
+gpg_export_keys (CamelCipherContext *context, GPtrArray *keys, CamelStream *ostream, CamelException *ex)
+{
+ struct _GpgCtx *gpg;
+ int i;
+ int res = -1;
+
+ gpg = gpg_ctx_new (context->session);
+ gpg_ctx_set_mode (gpg, GPG_CTX_MODE_EXPORT);
+ gpg_ctx_set_armor (gpg, TRUE);
+ gpg_ctx_set_ostream (gpg, ostream);
+
+ for (i = 0; i < keys->len; i++) {
+ gpg_ctx_add_recipient (gpg, keys->pdata[i]);
+ }
+
+ if (gpg_ctx_op_start (gpg) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Failed to execute gpg: %s"),
+ errno ? g_strerror (errno) : _("Unknown"));
+ goto fail;
+ }
+
+ while (!gpg_ctx_op_complete (gpg)) {
+ if (gpg_ctx_op_step (gpg, ex) == -1)
+ goto fail;
+ }
+
+ if (gpg_ctx_op_wait (gpg) != 0) {
+ const char *diagnostics;
+
+ diagnostics = gpg_ctx_get_diagnostics (gpg);
+ camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+ diagnostics && *diagnostics ? diagnostics :
+ _("Failed to execute gpg."));
+ goto fail;
+ }
+
+ res = 0;
+fail:
+ gpg_ctx_free (gpg);
+
+ return res;
+}
+
+/* ********************************************************************** */
+
+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->hash_to_id = gpg_hash_to_id;
+ cipher_class->id_to_hash = gpg_id_to_hash;
+ cipher_class->sign = gpg_sign;
+ cipher_class->verify = gpg_verify;
+ cipher_class->encrypt = gpg_encrypt;
+ cipher_class->decrypt = gpg_decrypt;
+ cipher_class->import_keys = gpg_import_keys;
+ cipher_class->export_keys = gpg_export_keys;
+}
+
+static void
+camel_gpg_context_init (CamelGpgContext *context)
+{
+ CamelCipherContext *cipher = (CamelCipherContext *) context;
+
+ context->always_trust = FALSE;
+
+ cipher->sign_protocol = "application/pgp-signature";
+ cipher->encrypt_protocol = "application/pgp-encrypted";
+ cipher->key_protocol = "application/pgp-keys";
+}
+
+static void
+camel_gpg_context_finalise (CamelObject *object)
+{
+ ;
+}
+
+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;
+}
+
+