/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Jeffrey Stedfast */ /* 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef G_OS_WIN32 #include #include #include #endif #include "camel-debug.h" #include "camel-gpg-context.h" #include "camel-iconv.h" #include "camel-internet-address.h" #include "camel-mime-filter-canon.h" #include "camel-mime-filter-charset.h" #include "camel-mime-part.h" #include "camel-multipart-encrypted.h" #include "camel-multipart-signed.h" #include "camel-operation.h" #include "camel-session.h" #include "camel-stream-filter.h" #include "camel-stream-fs.h" #include "camel-stream-mem.h" #include "camel-stream-null.h" #define d(x) #ifdef GPG_LOG static gint logid; #endif #define CHECK_CALL(x) G_STMT_START { \ if ((x) == -1) { \ g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \ } \ } G_STMT_END #define CAMEL_GPG_CONTEXT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), CAMEL_TYPE_GPG_CONTEXT, CamelGpgContextPrivate)) struct _CamelGpgContextPrivate { gboolean always_trust; }; enum { PROP_0, PROP_ALWAYS_TRUST }; G_DEFINE_TYPE (CamelGpgContext, camel_gpg_context, CAMEL_TYPE_CIPHER_CONTEXT) enum _GpgCtxMode { GPG_CTX_MODE_SIGN, GPG_CTX_MODE_VERIFY, GPG_CTX_MODE_ENCRYPT, GPG_CTX_MODE_DECRYPT }; 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; GSList *userids; gchar *sigfile; GPtrArray *recipients; CamelCipherHash hash; gint stdin_fd; gint stdout_fd; gint stderr_fd; gint status_fd; gint passwd_fd; /* only needed for sign/decrypt */ /* status-fd buffer */ guchar *statusbuf; guchar *statusptr; guint statusleft; gchar *need_id; gchar *passwd; CamelStream *istream; CamelStream *ostream; GByteArray *diagbuf; CamelStream *diagnostics; gint exit_status; guint exited : 1; guint complete : 1; guint seen_eof1 : 1; guint seen_eof2 : 1; guint always_trust : 1; guint armor : 1; guint need_passwd : 1; guint send_passwd : 1; guint bad_passwds : 2; guint anonymous_recipient : 1; guint hadsig : 1; guint badsig : 1; guint errsig : 1; guint goodsig : 1; guint validsig : 1; guint nopubkey : 1; guint nodata : 1; guint trust : 3; guint processing : 1; guint bad_decrypt : 1; guint noseckey : 1; GString *signers; guint diagflushed : 1; guint utf8 : 1; guint padding : 10; }; static struct _GpgCtx * gpg_ctx_new (CamelCipherContext *context) { struct _GpgCtx *gpg; const gchar *charset; CamelStream *stream; CamelSession *session; session = camel_cipher_context_get_session (context); gpg = g_new (struct _GpgCtx, 1); gpg->mode = GPG_CTX_MODE_SIGN; gpg->session = g_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->userids = 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->anonymous_recipient = FALSE; 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->processing = FALSE; gpg->bad_decrypt = FALSE; gpg->noseckey = FALSE; gpg->signers = NULL; gpg->istream = NULL; gpg->ostream = NULL; gpg->diagbuf = g_byte_array_new (); gpg->diagflushed = FALSE; stream = camel_stream_mem_new_with_byte_array (gpg->diagbuf); if ((charset = camel_iconv_locale_charset ()) && g_ascii_strcasecmp (charset, "UTF-8") != 0) { CamelMimeFilter *filter; CamelStream *fstream; gpg->utf8 = FALSE; if ((filter = camel_mime_filter_charset_new (charset, "UTF-8"))) { fstream = camel_stream_filter_new (stream); camel_stream_filter_add ( CAMEL_STREAM_FILTER (fstream), filter); g_object_unref (filter); g_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 gchar *userid) { g_slist_free_full (gpg->userids, g_free); gpg->userids = NULL; if (userid && *userid) { gchar **uids = g_strsplit (userid, " ", -1); if (!uids) { gpg->userids = g_slist_append (gpg->userids, g_strdup (userid)); } else { gint ii; for (ii = 0; uids[ii]; ii++) { const gchar *uid = uids[ii]; if (*uid) { gpg->userids = g_slist_append (gpg->userids, g_strdup (uid)); } } g_strfreev (uids); } } } static void gpg_ctx_add_recipient (struct _GpgCtx *gpg, const gchar *keyid) { gchar *safe_keyid; if (gpg->mode != GPG_CTX_MODE_ENCRYPT) return; if (!gpg->recipients) gpg->recipients = g_ptr_array_new (); g_return_if_fail (keyid != NULL); /* If the recipient looks like an email address, * enclose it in brackets to ensure an exact match. */ if (strchr (keyid, '@') != NULL) { safe_keyid = g_strdup_printf ("<%s>", keyid); } else { safe_keyid = g_strdup (keyid); } g_ptr_array_add (gpg->recipients, safe_keyid); } static void gpg_ctx_set_sigfile (struct _GpgCtx *gpg, const gchar *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) { g_object_ref (istream); if (gpg->istream) g_object_unref (gpg->istream); gpg->istream = istream; } static void gpg_ctx_set_ostream (struct _GpgCtx *gpg, CamelStream *ostream) { g_object_ref (ostream); if (gpg->ostream) g_object_unref (gpg->ostream); gpg->ostream = ostream; gpg->seen_eof1 = FALSE; } static const gchar * gpg_ctx_get_diagnostics (struct _GpgCtx *gpg) { if (!gpg->diagflushed) { gpg->diagflushed = TRUE; camel_stream_flush (gpg->diagnostics, NULL, NULL); if (gpg->diagbuf->len == 0) return NULL; g_byte_array_append (gpg->diagbuf, (guchar *) "", 1); } return (const gchar *) 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) { gint i; if (gpg == NULL) return; if (gpg->session) g_object_unref (gpg->session); g_hash_table_foreach (gpg->userid_hint, userid_hint_free, NULL); g_hash_table_destroy (gpg->userid_hint); g_slist_free_full (gpg->userids, g_free); 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) g_object_unref (gpg->istream); if (gpg->ostream) g_object_unref (gpg->ostream); g_object_unref (gpg->diagnostics); if (gpg->signers) g_string_free (gpg->signers, TRUE); g_free (gpg); } static const gchar * gpg_ctx_get_executable_name (void) { static gint index = -1; const gchar *names[] = { "gpg", "gpg2", /* Prefer gpg for now, because gpg2 doesn't save passwords (neither for the session) */ NULL }; if (index == -1) { for (index = 0; names[index]; index++) { gchar *path = g_find_program_in_path (names[index]); if (path) { g_free (path); break; } } if (!names[index]) index = 0; } return names[index]; } #ifndef G_OS_WIN32 static const gchar * 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_SHA256: return "--digest-algo=SHA256"; case CAMEL_CIPHER_HASH_SHA384: return "--digest-algo=SHA384"; case CAMEL_CIPHER_HASH_SHA512: return "--digest-algo=SHA512"; case CAMEL_CIPHER_HASH_RIPEMD160: return "--digest-algo=RIPEMD160"; default: return NULL; } } static GPtrArray * gpg_ctx_get_argv (struct _GpgCtx *gpg, gint status_fd, gchar **sfd, gint passwd_fd, gchar **pfd) { const gchar *hash_str; GPtrArray *argv; gchar *buf; gint i; argv = g_ptr_array_new (); g_ptr_array_add (argv, (guint8 *) gpg_ctx_get_executable_name ()); g_ptr_array_add (argv, (guint8 *) "--verbose"); g_ptr_array_add (argv, (guint8 *) "--no-secmem-warning"); g_ptr_array_add (argv, (guint8 *) "--no-greeting"); g_ptr_array_add (argv, (guint8 *) "--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, (guint8 *) "--batch"); g_ptr_array_add (argv, (guint8 *) "--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, (guint8 *) "--sign"); g_ptr_array_add (argv, (guint8 *) "--detach"); if (gpg->armor) g_ptr_array_add (argv, (guint8 *) "--armor"); hash_str = gpg_hash_str (gpg->hash); if (hash_str) g_ptr_array_add (argv, (guint8 *) hash_str); if (gpg->userids) { GSList *uiter; for (uiter = gpg->userids; uiter; uiter = uiter->next) { g_ptr_array_add (argv, (guint8 *) "-u"); g_ptr_array_add (argv, (guint8 *) uiter->data); } } g_ptr_array_add (argv, (guint8 *) "--output"); g_ptr_array_add (argv, (guint8 *) "-"); break; case GPG_CTX_MODE_VERIFY: if (!camel_session_get_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, (guint8 *) "--keyserver-options"); g_ptr_array_add (argv, (guint8 *) "no-auto-key-retrieve"); } g_ptr_array_add (argv, (guint8 *) "--verify"); if (gpg->sigfile) g_ptr_array_add (argv, gpg->sigfile); g_ptr_array_add (argv, (guint8 *) "-"); break; case GPG_CTX_MODE_ENCRYPT: g_ptr_array_add (argv, (guint8 *) "--encrypt"); if (gpg->armor) g_ptr_array_add (argv, (guint8 *) "--armor"); if (gpg->always_trust) g_ptr_array_add (argv, (guint8 *) "--always-trust"); if (gpg->userids) { GSList *uiter; for (uiter = gpg->userids; uiter; uiter = uiter->next) { g_ptr_array_add (argv, (guint8 *) "-u"); g_ptr_array_add (argv, (guint8 *) uiter->data); } } if (gpg->recipients) { for (i = 0; i < gpg->recipients->len; i++) { g_ptr_array_add (argv, (guint8 *) "-r"); g_ptr_array_add (argv, gpg->recipients->pdata[i]); } } g_ptr_array_add (argv, (guint8 *) "--output"); g_ptr_array_add (argv, (guint8 *) "-"); break; case GPG_CTX_MODE_DECRYPT: g_ptr_array_add (argv, (guint8 *) "--decrypt"); g_ptr_array_add (argv, (guint8 *) "--output"); g_ptr_array_add (argv, (guint8 *) "-"); break; } g_ptr_array_add (argv, NULL); return argv; } #endif static gboolean gpg_ctx_op_start (struct _GpgCtx *gpg, GError **error) { #ifndef G_OS_WIN32 gchar *status_fd = NULL, *passwd_fd = NULL; gint i, maxfd, errnosave, fds[10]; GPtrArray *argv; gint 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]) { if (fcntl (i, F_SETFD, FD_CLOEXEC) == -1) { /* Do nothing here. Cannot use CHECK_CALL() macro here, because it makes the process stuck, possibly due to the debug print. */ } } } /* run gpg */ execvp (gpg_ctx_get_executable_name (), (gchar **) 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); CHECK_CALL (fcntl (gpg->passwd_fd, F_SETFL, flags | O_NONBLOCK)); } flags = fcntl (gpg->stdin_fd, F_GETFL); CHECK_CALL (fcntl (gpg->stdin_fd, F_SETFL, flags | O_NONBLOCK)); flags = fcntl (gpg->stdout_fd, F_GETFL); CHECK_CALL (fcntl (gpg->stdout_fd, F_SETFL, flags | O_NONBLOCK)); flags = fcntl (gpg->stderr_fd, F_GETFL); CHECK_CALL (fcntl (gpg->stderr_fd, F_SETFL, flags | O_NONBLOCK)); flags = fcntl (gpg->status_fd, F_GETFL); CHECK_CALL (fcntl (gpg->status_fd, F_SETFL, flags | O_NONBLOCK)); return TRUE; exception: errnosave = errno; for (i = 0; i < 10; i++) { if (fds[i] != -1) close (fds[i]); } errno = errnosave; #else /* FIXME: Port me */ g_warning ("%s: Not implemented", G_STRFUNC); errno = EINVAL; #endif if (errno != 0) g_set_error ( error, G_IO_ERROR, g_io_error_from_errno (errno), _("Failed to execute gpg: %s"), g_strerror (errno)); else g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to execute gpg: %s"), _("Unknown")); return FALSE; } #ifndef G_OS_WIN32 static const gchar * next_token (const gchar *in, gchar **token) { const gchar *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 gint gpg_ctx_parse_status (struct _GpgCtx *gpg, GError **error) { register guchar *inptr; const guchar *status; gsize nread, nwritten; gint 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 ((const gchar *) status, "[GNUPG:] ", 9) != 0) { gchar *message; message = g_locale_to_utf8 ( (const gchar *) status, -1, NULL, NULL, NULL); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Unexpected GnuPG status message encountered:\n\n%s"), message); g_free (message); return -1; } status += 9; if (!strncmp ((gchar *) status, "ENC_TO ", 7)) { gchar *key = NULL; status += 7; next_token ((gchar *) status, &key); if (key) { gboolean all_zero = *key == '0'; gint i = 0; while (key[i] && all_zero) { all_zero = key[i] == '0'; i++; } gpg->anonymous_recipient = all_zero; g_free (key); } } else if (!strncmp ((gchar *) status, "USERID_HINT ", 12)) { gchar *hint, *user; status += 12; status = (const guchar *) next_token ((gchar *) status, &hint); if (!hint) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("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 ((gchar *) status, -1, &nread, &nwritten, NULL))) user = g_strdup ((gchar *) status); g_strstrip (user); g_hash_table_insert (gpg->userid_hint, hint, user); } else if (!strncmp ((gchar *) status, "NEED_PASSPHRASE ", 16)) { gchar *userid; status += 16; next_token ((gchar *) status, &userid); if (!userid) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to parse gpg passphrase request.")); return -1; } g_free (gpg->need_id); gpg->need_id = userid; } else if (!strncmp ((gchar *) status, "NEED_PASSPHRASE_PIN ", 20)) { gchar *userid; status += 20; next_token ((gchar *) status, &userid); if (!userid) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to parse gpg passphrase request.")); return -1; } g_free (gpg->need_id); gpg->need_id = userid; } else if (!strncmp ((gchar *) status, "GET_HIDDEN ", 11)) { const gchar *name = NULL; gchar *prompt, *passwd; guint32 flags; GError *local_error = NULL; status += 11; if (gpg->need_id && !(name = g_hash_table_lookup (gpg->userid_hint, gpg->need_id))) name = gpg->need_id; else if (!name) name = ""; if (!strncmp ((gchar *) status, "passphrase.pin.ask", 18)) { prompt = g_markup_printf_escaped ( _("You need a PIN to unlock the key for your\n" "SmartCard: \"%s\""), name); } else if (!strncmp ((gchar *) status, "passphrase.enter", 16)) { prompt = g_markup_printf_escaped ( _("You need a passphrase to unlock the key for\n" "user: \"%s\""), name); } else { next_token ((gchar *) status, &prompt); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Unexpected request from GnuPG for '%s'"), prompt); g_free (prompt); return -1; } if (gpg->anonymous_recipient) { gchar *tmp = prompt; /* FIXME Reword prompt message. */ prompt = g_strconcat ( tmp, "\n", _("Note the encrypted content doesn't contain " "information about a recipient, thus there " "will be a password prompt for each of stored " "private key."), NULL); g_free (tmp); } flags = CAMEL_SESSION_PASSWORD_SECRET | CAMEL_SESSION_PASSPHRASE; if ((passwd = camel_session_get_password (gpg->session, NULL, prompt, gpg->need_id, flags, &local_error))) { if (!gpg->utf8) { gchar *opasswd = passwd; if ((passwd = g_locale_to_utf8 (passwd, -1, &nread, &nwritten, NULL))) { memset (opasswd, 0, strlen (opasswd)); g_free (opasswd); } else { passwd = opasswd; } } gpg->passwd = g_strdup_printf ("%s\n", passwd); memset (passwd, 0, strlen (passwd)); g_free (passwd); gpg->send_passwd = TRUE; } else { if (local_error == NULL) g_set_error ( error, G_IO_ERROR, G_IO_ERROR_CANCELLED, _("Cancelled")); g_propagate_error (error, local_error); return -1; } g_free (prompt); } else if (!strncmp ((gchar *) status, "GOOD_PASSPHRASE", 15)) { gpg->bad_passwds = 0; } else if (!strncmp ((gchar *) status, "BAD_PASSPHRASE", 14)) { /* with anonymous recipient is user asked for his/her password for each stored key, * thus here cannot be counted wrong passwords */ if (!gpg->anonymous_recipient) { gpg->bad_passwds++; camel_session_forget_password (gpg->session, NULL, gpg->need_id, error); if (gpg->bad_passwds == 3) { g_set_error ( error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE, _("Failed to unlock secret key: " "3 bad passphrases given.")); return -1; } } } else if (!strncmp ((const gchar *) status, "UNEXPECTED ", 11)) { /* this is an error */ gchar *message; message = g_locale_to_utf8 ( (const gchar *) status + 11, -1, NULL, NULL, NULL); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Unexpected response from GnuPG: %s"), message); g_free (message); return -1; } else if (!strncmp ((gchar *) 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 { if (!strncmp ((gchar *) status, "BEGIN_", 6)) { gpg->processing = TRUE; } else if (!strncmp ((gchar *) status, "END_", 4)) { gpg->processing = FALSE; } /* check to see if we are complete */ switch (gpg->mode) { case GPG_CTX_MODE_SIGN: if (!strncmp ((gchar *) status, "SIG_CREATED ", 12)) { /* SIG_CREATED */ const gchar *str, *p; gint i = 0; str = (const gchar *) status + 12; while (p = strchr (str, ' '), i < 2 && p) { str = p + 1; i++; } if (*str && i == 2) { struct { gint gpg_hash_algo; CamelCipherHash camel_hash_algo; } hash_algos[] = { /* the rest are deprecated/not supported by gpg any more */ { 2, CAMEL_CIPHER_HASH_SHA1 }, { 3, CAMEL_CIPHER_HASH_RIPEMD160 }, { 8, CAMEL_CIPHER_HASH_SHA256 }, { 9, CAMEL_CIPHER_HASH_SHA384 }, { 10, CAMEL_CIPHER_HASH_SHA512 } }; gint gpg_hash = strtoul (str, NULL, 10); for (i = 0; i < G_N_ELEMENTS (hash_algos); i++) { if (hash_algos[i].gpg_hash_algo == gpg_hash) { gpg->hash = hash_algos[i].camel_hash_algo; break; } } } } break; case GPG_CTX_MODE_DECRYPT: if (!strncmp ((gchar *) status, "BEGIN_DECRYPTION", 16)) { gpg->bad_decrypt = FALSE; /* nothing to do... but we know to expect data on stdout soon */ break; } else if (!strncmp ((gchar *) status, "END_DECRYPTION", 14)) { /* nothing to do, but we know the end is near? */ break; } else if (!strncmp ((gchar *) status, "NO_SECKEY ", 10)) { gpg->noseckey = TRUE; break; } else if (!strncmp ((gchar *) status, "DECRYPTION_FAILED", 17)) { gpg->bad_decrypt = TRUE; break; } /* let if fall through to verify possible signatures too */ /* break; */ case GPG_CTX_MODE_VERIFY: if (!strncmp ((gchar *) status, "TRUST_", 6)) { status += 6; if (!strncmp ((gchar *) status, "NEVER", 5)) { gpg->trust = GPG_TRUST_NEVER; } else if (!strncmp ((gchar *) status, "MARGINAL", 8)) { gpg->trust = GPG_TRUST_MARGINAL; } else if (!strncmp ((gchar *) status, "FULLY", 5)) { gpg->trust = GPG_TRUST_FULLY; } else if (!strncmp ((gchar *) status, "ULTIMATE", 8)) { gpg->trust = GPG_TRUST_ULTIMATE; } else if (!strncmp ((gchar *) status, "UNDEFINED", 9)) { gpg->trust = GPG_TRUST_UNDEFINED; } } else if (!strncmp ((gchar *) status, "GOODSIG ", 8)) { gpg->goodsig = TRUE; gpg->hadsig = TRUE; status += 8; /* there's a key ID, then the email address */ status = (const guchar *) strchr ((const gchar *) status, ' '); if (status) { const gchar *str = (const gchar *) status + 1; const gchar *eml = strchr (str, '<'); if (eml && eml > str) { eml--; if (strchr (str, ' ') >= eml) eml = NULL; } else { eml = NULL; } if (gpg->signers) { g_string_append (gpg->signers, ", "); } else { gpg->signers = g_string_new (""); } if (eml) { g_string_append (gpg->signers, "\""); g_string_append_len (gpg->signers, str, eml - str); g_string_append (gpg->signers, "\""); g_string_append (gpg->signers, eml); } else { g_string_append (gpg->signers, str); } } } else if (!strncmp ((gchar *) status, "VALIDSIG ", 9)) { gpg->validsig = TRUE; } else if (!strncmp ((gchar *) status, "BADSIG ", 7)) { gpg->badsig = FALSE; gpg->hadsig = TRUE; } else if (!strncmp ((gchar *) status, "ERRSIG ", 7)) { /* Note: NO_PUBKEY often comes after an ERRSIG */ gpg->errsig = FALSE; gpg->hadsig = TRUE; } else if (!strncmp ((gchar *) status, "NO_PUBKEY ", 10)) { gpg->nopubkey = TRUE; } break; case GPG_CTX_MODE_ENCRYPT: if (!strncmp ((gchar *) status, "BEGIN_ENCRYPTION", 16)) { /* nothing to do... but we know to expect data on stdout soon */ } else if (!strncmp ((gchar *) status, "END_ENCRYPTION", 14)) { /* nothing to do, but we know the end is near? */ } else if (!strncmp ((gchar *) status, "NO_RECP", 7)) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to encrypt: No valid recipients specified.")); return -1; } 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; } #endif #define status_backup(gpg, start, len) G_STMT_START { \ if (gpg->statusleft <= len) { \ guint 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) { #ifndef G_OS_WIN32 pid_t retval; gint 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); } #endif } static gint gpg_ctx_op_step (struct _GpgCtx *gpg, GCancellable *cancellable, GError **error) { #ifndef G_OS_WIN32 GPollFD polls[6]; gint status, i; gboolean read_data = FALSE, wrote_data = FALSE; for (i = 0; i < 6; i++) { polls[i].fd = -1; polls[i].events = 0; } if (!gpg->seen_eof1) { polls[0].fd = gpg->stdout_fd; polls[0].events = G_IO_IN; } if (!gpg->seen_eof2) { polls[1].fd = gpg->stderr_fd; polls[1].events = G_IO_IN; } if (!gpg->complete) { polls[2].fd = gpg->status_fd; polls[2].events = G_IO_IN; } polls[3].fd = gpg->stdin_fd; polls[3].events = G_IO_OUT; polls[4].fd = gpg->passwd_fd; polls[4].events = G_IO_OUT; polls[5].fd = g_cancellable_get_fd (cancellable); polls[5].events = G_IO_IN; do { for (i = 0; i < 6; i++) polls[i].revents = 0; status = g_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 & G_IO_IN) && g_cancellable_set_error_if_cancelled (cancellable, error)) { 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 & (G_IO_IN | G_IO_HUP)) { /* read the status message and decide what to do... */ gchar buffer[4096]; gssize nread; d (printf ("reading from gpg's status-fd...\n")); do { nread = read (gpg->status_fd, buffer, sizeof (buffer)); d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, 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, error) == -1) return -1; } else { gpg->complete = TRUE; } } if ((polls[0].revents & (G_IO_IN | G_IO_HUP)) && gpg->ostream) { gchar buffer[4096]; gssize nread; d (printf ("reading gpg's stdout...\n")); do { nread = read (gpg->stdout_fd, buffer, sizeof (buffer)); d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, buffer)); } while (nread == -1 && (errno == EINTR || errno == EAGAIN)); if (nread == -1) goto exception; if (nread > 0) { gsize written = camel_stream_write ( gpg->ostream, buffer, (gsize) nread, cancellable, error); if (written != nread) return -1; } else { gpg->seen_eof1 = TRUE; } read_data = TRUE; } if (polls[1].revents & (G_IO_IN | G_IO_HUP)) { gchar buffer[4096]; gssize nread; d (printf ("reading gpg's stderr...\n")); do { nread = read (gpg->stderr_fd, buffer, sizeof (buffer)); d (printf (" read %d bytes (%.*s)\n", (gint) nread, (gint) nread, buffer)); } while (nread == -1 && (errno == EINTR || errno == EAGAIN)); if (nread == -1) goto exception; if (nread > 0) { camel_stream_write ( gpg->diagnostics, buffer, nread, cancellable, error); } else { gpg->seen_eof2 = TRUE; } } if ((polls[4].revents & (G_IO_OUT | G_IO_HUP)) && gpg->need_passwd && gpg->send_passwd) { gssize w, nwritten = 0; gsize 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 & (G_IO_OUT | G_IO_HUP)) && gpg->istream) { gchar buffer[4096]; gssize 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), cancellable, NULL); if (nread > 0) { gssize 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", (gint) nwritten, (gint) nread)); wrote_data = TRUE; } if (camel_stream_eos (gpg->istream)) { d (printf ("closing gpg's stdin\n")); close (gpg->stdin_fd); gpg->stdin_fd = -1; } } if (gpg->need_id && !gpg->processing && !read_data && !wrote_data) { /* do not ask more than hundred times per second when looking for a pass phrase, * in case user has the use-agent set, it'll not use the all CPU when * agent is asking for a pass phrase, instead of us */ g_usleep (G_USEC_PER_SEC / 100); } return 0; exception: /* always called on an i/o error */ g_set_error ( error, G_IO_ERROR, g_io_error_from_errno (errno), _("Failed to execute gpg: %s"), g_strerror (errno)); gpg_ctx_op_cancel (gpg); #endif 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; gint 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 gint gpg_ctx_op_wait (struct _GpgCtx *gpg) { #ifndef G_OS_WIN32 sigset_t mask, omask; pid_t retval; gint 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; #else return -1; #endif } static gchar * swrite (CamelMimePart *sigpart, GCancellable *cancellable, GError **error) { GFile *file; GFileIOStream *base_stream = NULL; CamelStream *stream = NULL; CamelDataWrapper *wrapper; gchar *path = NULL; gint ret; file = g_file_new_tmp ("evolution-pgp.XXXXXX", &base_stream, error); /* Sanity check. */ g_return_val_if_fail ( ((file != NULL) && (base_stream != NULL)) || ((file == NULL) && (base_stream == NULL)), NULL); if (base_stream != NULL) { stream = camel_stream_new (G_IO_STREAM (base_stream)); g_object_unref (base_stream); } if (stream == NULL) return NULL; wrapper = camel_medium_get_content (CAMEL_MEDIUM (sigpart)); if (wrapper == NULL) wrapper = CAMEL_DATA_WRAPPER (sigpart); ret = camel_data_wrapper_decode_to_stream_sync ( wrapper, stream, cancellable, error); if (ret != -1) { ret = camel_stream_flush (stream, cancellable, error); if (ret != -1) ret = camel_stream_close (stream, cancellable, error); } if (ret != -1) path = g_file_get_path (file); g_object_unref (file); g_object_unref (stream); return path; } static void add_signers (CamelCipherValidity *validity, const GString *signers) { CamelInternetAddress *address; gint i, count; g_return_if_fail (validity != NULL); if (!signers || !signers->str || !*signers->str) return; address = camel_internet_address_new (); g_return_if_fail (address != NULL); count = camel_address_decode (CAMEL_ADDRESS (address), signers->str); for (i = 0; i < count; i++) { const gchar *name = NULL, *email = NULL; if (!camel_internet_address_get (address, i, &name, &email)) break; camel_cipher_validity_add_certinfo (validity, CAMEL_CIPHER_VALIDITY_SIGN, name, email); } g_object_unref (address); } /* ********************************************************************** */ static void gpg_context_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ALWAYS_TRUST: camel_gpg_context_set_always_trust ( CAMEL_GPG_CONTEXT (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void gpg_context_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_ALWAYS_TRUST: g_value_set_boolean ( value, camel_gpg_context_get_always_trust ( CAMEL_GPG_CONTEXT (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static const gchar * 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_SHA256: return "pgp-sha256"; case CAMEL_CIPHER_HASH_SHA384: return "pgp-sha384"; case CAMEL_CIPHER_HASH_SHA512: return "pgp-sha512"; 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 gchar *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-sha256")) return CAMEL_CIPHER_HASH_SHA256; else if (!strcmp (id, "pgp-sha384")) return CAMEL_CIPHER_HASH_SHA384; else if (!strcmp (id, "pgp-sha512")) return CAMEL_CIPHER_HASH_SHA512; 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; } static gboolean gpg_sign_sync (CamelCipherContext *context, const gchar *userid, CamelCipherHash hash, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { struct _GpgCtx *gpg = NULL; CamelCipherContextClass *class; CamelStream *ostream = camel_stream_mem_new (), *istream; CamelDataWrapper *dw; CamelContentType *ct; CamelMimePart *sigpart; CamelMultipartSigned *mps; gboolean success = FALSE; /* Note: see rfc2015 or rfc3156, section 5 */ class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); /* 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, NULL, error) == -1) { g_prefix_error ( error, _("Could not generate signing data: ")); goto fail; } #ifdef GPG_LOG if (camel_debug_start ("gpg:sign")) { gchar *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, NULL); if (out) { printf ("Writing gpg signing data to '%s'\n", name); camel_stream_write_to_stream (istream, out, NULL, NULL); g_seekable_seek ( G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); g_object_unref (out); } g_free (name); camel_debug_end (); } #endif gpg = gpg_ctx_new (context); 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, error)) goto fail; while (!gpg_ctx_op_complete (gpg)) { if (gpg_ctx_op_step (gpg, cancellable, error) == -1) { gpg_ctx_op_cancel (gpg); goto fail; } } if (gpg_ctx_op_wait (gpg) != 0) { const gchar *diagnostics; diagnostics = gpg_ctx_get_diagnostics (gpg); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", (diagnostics != NULL && *diagnostics != '\0') ? diagnostics : _("Failed to execute gpg.")); goto fail; } success = TRUE; dw = camel_data_wrapper_new (); g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL); camel_data_wrapper_construct_from_stream_sync ( dw, ostream, NULL, NULL); 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 ((CamelMedium *) sigpart, dw); g_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_context_hash_to_id (context, hash == CAMEL_CIPHER_HASH_DEFAULT ? gpg->hash : hash)); camel_content_type_set_param (ct, "protocol", class->sign_protocol); camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) mps, ct); camel_content_type_unref (ct); camel_multipart_set_boundary ((CamelMultipart *) mps, NULL); camel_multipart_signed_set_signature (mps, sigpart); camel_multipart_signed_set_content_stream (mps, istream); g_object_unref (sigpart); g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) mps); fail: g_object_unref (ostream); if (gpg) gpg_ctx_free (gpg); return success; } static CamelCipherValidity * gpg_verify_sync (CamelCipherContext *context, CamelMimePart *ipart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; CamelCipherValidity *validity; const gchar *diagnostics = NULL; struct _GpgCtx *gpg = NULL; gchar *sigfile = NULL; CamelContentType *ct; CamelMimePart *sigpart; CamelStream *istream = NULL, *canon_stream; CamelMultipart *mps; CamelStream *filter; CamelMimeFilter *canon; class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); mps = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) ipart); ct = ((CamelDataWrapper *) mps)->mime_type; /* Inline signature (using our fake mime type) or PGP/Mime signature */ if (camel_content_type_is (ct, "multipart", "signed")) { /* PGP/Mime Signature */ const gchar *tmp; tmp = camel_content_type_param (ct, "protocol"); if (!CAMEL_IS_MULTIPART_SIGNED (mps) || tmp == NULL || g_ascii_strcasecmp (tmp, class->sign_protocol) != 0) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot verify message signature: " "Incorrect message format")); return NULL; } if (!(istream = camel_multipart_signed_get_content_stream ((CamelMultipartSigned *) mps, NULL))) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot verify message signature: " "Incorrect message format")); return NULL; } if (!(sigpart = camel_multipart_get_part (mps, CAMEL_MULTIPART_SIGNED_SIGNATURE))) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot verify message signature: " "Incorrect message format")); g_object_unref (istream); return NULL; } } else if (camel_content_type_is (ct, "application", "x-inlinepgp-signed")) { /* Inline Signed */ CamelDataWrapper *content; content = camel_medium_get_content ((CamelMedium *) ipart); istream = camel_stream_mem_new (); if (!camel_data_wrapper_decode_to_stream_sync ( content, istream, cancellable, error)) goto exception; g_seekable_seek ( G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); sigpart = NULL; } else { /* Invalid Mimetype */ g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot verify message signature: " "Incorrect message format")); return NULL; } /* Now start the real work of verifying the message */ #ifdef GPG_LOG if (camel_debug_start ("gpg:sign")) { gchar *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, NULL); if (out) { printf ("Writing gpg verify data to '%s'\n", name); camel_stream_write_to_stream (istream, out, NULL, NULL); g_seekable_seek ( G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); g_object_unref (out); } g_free (name); if (sigpart) { 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, NULL); if (out) { printf ("Writing gpg verify signature to '%s'\n", name); camel_data_wrapper_write_to_stream_sync ( CAMEL_DATA_WRAPPER (sigpart), out, NULL, NULL); g_object_unref (out); } g_free (name); } camel_debug_end (); } #endif if (sigpart) { sigfile = swrite (sigpart, cancellable, error); if (sigfile == NULL) { g_prefix_error ( error, _("Cannot verify message signature: ")); goto exception; } } g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); canon_stream = camel_stream_mem_new (); /* strip trailing white-spaces */ filter = camel_stream_filter_new (istream); canon = camel_mime_filter_canon_new (CAMEL_MIME_FILTER_CANON_CRLF | CAMEL_MIME_FILTER_CANON_STRIP); camel_stream_filter_add (CAMEL_STREAM_FILTER (filter), canon); g_object_unref (canon); camel_stream_write_to_stream (filter, canon_stream, NULL, NULL); g_object_unref (filter); g_object_unref (istream); istream = NULL; g_seekable_seek (G_SEEKABLE (canon_stream), 0, G_SEEK_SET, NULL, NULL); gpg = gpg_ctx_new (context); gpg_ctx_set_mode (gpg, GPG_CTX_MODE_VERIFY); if (sigfile) gpg_ctx_set_sigfile (gpg, sigfile); gpg_ctx_set_istream (gpg, canon_stream); if (!gpg_ctx_op_start (gpg, error)) goto exception; while (!gpg_ctx_op_complete (gpg)) { if (gpg_ctx_op_step (gpg, cancellable, error) == -1) { gpg_ctx_op_cancel (gpg); goto exception; } } /* report error only when no data or didn't found signature */ if (gpg_ctx_op_wait (gpg) != 0 && (gpg->nodata || !gpg->hadsig)) { const gchar *diagnostics; diagnostics = gpg_ctx_get_diagnostics (gpg); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", (diagnostics != NULL && *diagnostics != '\0') ? diagnostics : _("Failed to execute gpg.")); goto exception; } 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 || gpg->trust == GPG_TRUST_MARGINAL) 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 if (gpg->nopubkey) { validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_NEED_PUBLIC_KEY; } else { validity->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD; } add_signers (validity, gpg->signers); gpg_ctx_free (gpg); if (sigfile) { g_unlink (sigfile); g_free (sigfile); } g_object_unref (canon_stream); return validity; exception: if (gpg != NULL) gpg_ctx_free (gpg); if (istream) g_object_unref (istream); if (sigfile) { g_unlink (sigfile); g_free (sigfile); } return NULL; } static gboolean gpg_encrypt_sync (CamelCipherContext *context, const gchar *userid, GPtrArray *recipients, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { CamelCipherContextClass *class; CamelGpgContext *ctx = (CamelGpgContext *) context; struct _GpgCtx *gpg; CamelStream *istream, *ostream, *vstream; CamelMimePart *encpart, *verpart; CamelDataWrapper *dw; CamelContentType *ct; CamelMultipartEncrypted *mpe; gboolean success = FALSE; gint i; class = CAMEL_CIPHER_CONTEXT_GET_CLASS (context); ostream = camel_stream_mem_new (); istream = camel_stream_mem_new (); if (camel_cipher_canonical_to_stream ( ipart, CAMEL_MIME_FILTER_CANON_CRLF, istream, NULL, error) == -1) { g_prefix_error ( error, _("Could not generate encrypting data: ")); goto fail1; } gpg = gpg_ctx_new (context); 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->priv->always_trust); for (i = 0; i < recipients->len; i++) { gpg_ctx_add_recipient (gpg, recipients->pdata[i]); } if (!gpg_ctx_op_start (gpg, error)) goto fail; /* FIXME: move this to a common routine */ while (!gpg_ctx_op_complete (gpg)) { if (gpg_ctx_op_step (gpg, cancellable, error) == -1) { gpg_ctx_op_cancel (gpg); goto fail; } } if (gpg_ctx_op_wait (gpg) != 0) { const gchar *diagnostics; diagnostics = gpg_ctx_get_diagnostics (gpg); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", (diagnostics != NULL && *diagnostics != '\0') ? diagnostics : _("Failed to execute gpg.")); goto fail; } success = TRUE; dw = camel_data_wrapper_new (); camel_data_wrapper_construct_from_stream_sync ( dw, ostream, NULL, NULL); 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 ((CamelMedium *) encpart, dw); g_object_unref (dw); camel_mime_part_set_description (encpart, _("This is a digitally encrypted message part")); vstream = camel_stream_mem_new (); camel_stream_write_string (vstream, "Version: 1\n", NULL, NULL); g_seekable_seek (G_SEEKABLE (vstream), 0, G_SEEK_SET, NULL, NULL); verpart = camel_mime_part_new (); dw = camel_data_wrapper_new (); camel_data_wrapper_set_mime_type (dw, class->encrypt_protocol); camel_data_wrapper_construct_from_stream_sync ( dw, vstream, NULL, NULL); g_object_unref (vstream); camel_medium_set_content ((CamelMedium *) verpart, dw); g_object_unref (dw); mpe = camel_multipart_encrypted_new (); ct = camel_content_type_new ("multipart", "encrypted"); camel_content_type_set_param (ct, "protocol", class->encrypt_protocol); camel_data_wrapper_set_mime_type_field ((CamelDataWrapper *) mpe, ct); camel_content_type_unref (ct); camel_multipart_set_boundary ((CamelMultipart *) mpe, NULL); camel_multipart_add_part ((CamelMultipart *) mpe, verpart); g_object_unref (verpart); camel_multipart_add_part ((CamelMultipart *) mpe, encpart); g_object_unref (encpart); camel_medium_set_content ((CamelMedium *) opart, (CamelDataWrapper *) mpe); fail: gpg_ctx_free (gpg); fail1: g_object_unref (istream); g_object_unref (ostream); return success; } static CamelCipherValidity * gpg_decrypt_sync (CamelCipherContext *context, CamelMimePart *ipart, CamelMimePart *opart, GCancellable *cancellable, GError **error) { struct _GpgCtx *gpg = NULL; CamelCipherValidity *valid = NULL; CamelStream *ostream, *istream; CamelDataWrapper *content; CamelMimePart *encrypted; CamelMultipart *mp; CamelContentType *ct; gboolean success; if (!ipart) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot decrypt message: Incorrect message format")); return NULL; } content = camel_medium_get_content ((CamelMedium *) ipart); if (!content) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot decrypt message: Incorrect message format")); return NULL; } ct = camel_data_wrapper_get_mime_type_field (content); /* Encrypted part (using our fake mime type) or PGP/Mime multipart */ if (camel_content_type_is (ct, "multipart", "encrypted")) { mp = (CamelMultipart *) camel_medium_get_content ((CamelMedium *) ipart); if (!(encrypted = camel_multipart_get_part (mp, CAMEL_MULTIPART_ENCRYPTED_CONTENT))) { g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to decrypt MIME part: " "protocol error")); return NULL; } content = camel_medium_get_content ((CamelMedium *) encrypted); } else if (camel_content_type_is (ct, "application", "x-inlinepgp-encrypted")) { content = camel_medium_get_content ((CamelMedium *) ipart); } else { /* Invalid Mimetype */ g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Cannot decrypt message: Incorrect message format")); return NULL; } istream = camel_stream_mem_new (); if (!camel_data_wrapper_decode_to_stream_sync ( content, istream, cancellable, error)) { g_object_unref (istream); return NULL; } g_seekable_seek (G_SEEKABLE (istream), 0, G_SEEK_SET, NULL, NULL); ostream = camel_stream_mem_new (); camel_stream_mem_set_secure ((CamelStreamMem *) ostream); gpg = gpg_ctx_new (context); gpg_ctx_set_mode (gpg, GPG_CTX_MODE_DECRYPT); gpg_ctx_set_istream (gpg, istream); gpg_ctx_set_ostream (gpg, ostream); gpg->bad_decrypt = TRUE; if (!gpg_ctx_op_start (gpg, error)) goto fail; while (!gpg_ctx_op_complete (gpg)) { if (gpg_ctx_op_step (gpg, cancellable, error) == -1) { gpg_ctx_op_cancel (gpg); goto fail; } } /* Report errors only if nothing was decrypted; missing sender's key used * for signature of a signed and encrypted messages causes GPG to return * failure, thus count with it. */ if (gpg_ctx_op_wait (gpg) != 0 && (gpg->nodata || (gpg->bad_decrypt && !gpg->noseckey))) { const gchar *diagnostics; diagnostics = gpg_ctx_get_diagnostics (gpg); g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, "%s", (diagnostics != NULL && *diagnostics != '\0') ? diagnostics : _("Failed to execute gpg.")); goto fail; } g_seekable_seek (G_SEEKABLE (ostream), 0, G_SEEK_SET, NULL, NULL); if (gpg->bad_decrypt && gpg->noseckey) { success = FALSE; g_set_error ( error, CAMEL_ERROR, CAMEL_ERROR_GENERIC, _("Failed to decrypt MIME part: Secret key not found")); } else if (camel_content_type_is (ct, "multipart", "encrypted")) { CamelDataWrapper *dw; CamelStream *null = camel_stream_null_new (); /* Multipart encrypted - parse a full mime part */ success = camel_data_wrapper_construct_from_stream_sync ( CAMEL_DATA_WRAPPER (opart), ostream, NULL, error); dw = camel_medium_get_content ((CamelMedium *) opart); if (!camel_data_wrapper_decode_to_stream_sync ( dw, null, cancellable, NULL)) { /* nothing had been decoded from the stream, it doesn't * contain any header, like Content-Type or such, thus * write it as a message body */ success = camel_data_wrapper_construct_from_stream_sync ( dw, ostream, cancellable, error); } g_object_unref (null); } else { /* Inline signed - raw data (may not be a mime part) */ CamelDataWrapper *dw; dw = camel_data_wrapper_new (); success = camel_data_wrapper_construct_from_stream_sync ( dw, ostream, NULL, error); camel_data_wrapper_set_mime_type (dw, "application/octet-stream"); camel_medium_set_content ((CamelMedium *) opart, dw); g_object_unref (dw); /* Set mime/type of this new part to application/octet-stream to force type snooping */ camel_mime_part_set_content_type (opart, "application/octet-stream"); } if (success) { 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_NEED_PUBLIC_KEY; } else { valid->sign.status = CAMEL_CIPHER_VALIDITY_SIGN_BAD; } add_signers (valid, gpg->signers); } } fail: g_object_unref (ostream); g_object_unref (istream); gpg_ctx_free (gpg); return valid; } static void camel_gpg_context_class_init (CamelGpgContextClass *class) { GObjectClass *object_class; CamelCipherContextClass *cipher_context_class; g_type_class_add_private (class, sizeof (CamelGpgContextPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = gpg_context_set_property; object_class->get_property = gpg_context_get_property; cipher_context_class = CAMEL_CIPHER_CONTEXT_CLASS (class); cipher_context_class->sign_protocol = "application/pgp-signature"; cipher_context_class->encrypt_protocol = "application/pgp-encrypted"; cipher_context_class->key_protocol = "application/pgp-keys"; cipher_context_class->hash_to_id = gpg_hash_to_id; cipher_context_class->id_to_hash = gpg_id_to_hash; cipher_context_class->sign_sync = gpg_sign_sync; cipher_context_class->verify_sync = gpg_verify_sync; cipher_context_class->encrypt_sync = gpg_encrypt_sync; cipher_context_class->decrypt_sync = gpg_decrypt_sync; g_object_class_install_property ( object_class, PROP_ALWAYS_TRUST, g_param_spec_boolean ( "always-trust", "Always Trust", NULL, FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void camel_gpg_context_init (CamelGpgContext *context) { context->priv = CAMEL_GPG_CONTEXT_GET_PRIVATE (context); } /** * 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) { g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); return g_object_new ( CAMEL_TYPE_GPG_CONTEXT, "session", session, NULL); } /** * camel_gpg_context_get_always_trust: * @context: a #CamelGpgContext * * Since: 2.32 **/ gboolean camel_gpg_context_get_always_trust (CamelGpgContext *context) { g_return_val_if_fail (CAMEL_IS_GPG_CONTEXT (context), FALSE); return context->priv->always_trust; } /** * camel_gpg_context_set_always_trust: * @context: 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 *context, gboolean always_trust) { g_return_if_fail (CAMEL_IS_GPG_CONTEXT (context)); if (context->priv->always_trust == always_trust) return; context->priv->always_trust = always_trust; g_object_notify (G_OBJECT (context), "always-trust"); }