diff options
-rw-r--r-- | mail/ChangeLog | 27 | ||||
-rw-r--r-- | mail/Makefile.am | 1 | ||||
-rw-r--r-- | mail/mail-crypto.c | 405 | ||||
-rw-r--r-- | mail/mail-display.c | 42 | ||||
-rw-r--r-- | mail/mail-display.h | 6 | ||||
-rw-r--r-- | mail/mail-format.c | 414 | ||||
-rw-r--r-- | mail/mail.h | 6 | ||||
-rw-r--r-- | mail/session.c | 71 |
8 files changed, 879 insertions, 93 deletions
diff --git a/mail/ChangeLog b/mail/ChangeLog index fe12023521..2d89ced95d 100644 --- a/mail/ChangeLog +++ b/mail/ChangeLog @@ -1,3 +1,30 @@ +2000-08-01 Dan Winship <danw@helixcode.com> + + * mail-crypto.c: New code to spawn off GPG/PGP to do stuff. + Currently only deals with decryption. From Nathan Thompson-Amato + <ndt@jps.net>, with bunches of changes from me. + + * session.c (mail_request_dialog): Expose the password dialog to + the rest of the app (for use by the GPG/PGP code). + + * mail-format.c (handle_text_plain): Handle special inline data + types. (Currently uuencoding, BinHex, and PGP encryption.) This is + not the best way to deal with it, but it works for now. + (try_inline_pgp): Convert an inline PGP-encrypted message into a + multipart/encrypted part. + (try_inline_binhex): Convert an inline BinHex attachment into an + application/mac-binhex40 part (which we currently don't deal + with...) + (try_uudecoding): Convert a uuencoded attachment to an + application/octet-stream part. + (handle_multipart_encrypted): Deal with RFC2015 MIME-encoded PGP + encrypted messages. (From ndt.) + + * mail-display.c (mail_text_write, mail_error_write): New utility + functions. + + * Makefile.am (evolution_mail_SOURCES): add mail-crypto.c + 2000-07-31 Christopher James Lahey <clahey@helixcode.com> * component-factory.c, folder-browser.c: Fixed some warnings. diff --git a/mail/Makefile.am b/mail/Makefile.am index c5752ac822..7204c237dd 100644 --- a/mail/Makefile.am +++ b/mail/Makefile.am @@ -41,6 +41,7 @@ evolution_mail_SOURCES = \ folder-browser.h \ folder-browser-factory.c \ mail-config.c \ + mail-crypto.c \ mail-display.c \ mail-display.h \ mail-format.c \ diff --git a/mail/mail-crypto.c b/mail/mail-crypto.c new file mode 100644 index 0000000000..4db4357b44 --- /dev/null +++ b/mail/mail-crypto.c @@ -0,0 +1,405 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * mail-crypto.h: OpenPGP en/decryption & signature code + * + * FIXME FIXME FIXME: This should be in its own library or component + */ + +/* + * Authors: + * Nathan Thompson-Amato <ndt@jps.net> + * Dan Winship <danw@helixcode.com> + * + * Copyright 2000, Helix Code, Inc. (http://www.helixcode.com) + * Copyright 2000, Nathan Thompson-Amato + * Copyright 1999, 2000, Anthony Mulcahy + * + * 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. + * + */ + +#include "config.h" + +#ifdef PGP_PROGRAM +#include <stdlib.h> +#include <string.h> +#include <glib.h> +#include <gnome.h> + +#include "mail.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <signal.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <termios.h> +#include <unistd.h> +#include <signal.h> + +static int +cleanup_child (pid_t child) +{ + int status; + pid_t wait_result; + sigset_t mask, omask; + + /* PGP5 closes fds before exiting, meaning this might be called + * too early. So wait a bit for the result. + */ + sigemptyset (&mask); + sigaddset (&mask, SIGALRM); + sigprocmask (SIG_BLOCK, &mask, &omask); + alarm (1); + wait_result = waitpid (child, &status, 0); + alarm (0); + sigprocmask (SIG_SETMASK, &omask, NULL); + + if (wait_result == -1 && errno == EINTR) { + /* The child is hanging: send a friendly reminder. */ + kill (child, SIGTERM); + sleep (1); + wait_result = waitpid (child, &status, WNOHANG); + if (wait_result == 0) { + /* Still hanging; use brute force. */ + kill (child, SIGKILL); + sleep (1); + wait_result = waitpid (child, &status, WNOHANG); + } + } + + if (wait_result != -1 && WIFEXITED (status)) + return WEXITSTATUS (status); + else + return -1; +} + +static void +cleanup_before_exec (int fd) +{ + int maxfd, i; + + maxfd = sysconf (_SC_OPEN_MAX); + if (maxfd < 0) + return; + + /* Loop over all fds. */ + for (i = 0; i < maxfd; i++) { + if ((STDIN_FILENO != i) && + (STDOUT_FILENO != i) && + (STDERR_FILENO != i) && + (fd != i)) + close (i); + } +} + +static int +crypto_exec_with_passwd (char *path, char *argv[], const char *input, + int passwd_fds[], const char *passphrase, + char **output, char **diagnostics) +{ + fd_set fdset, write_fdset; + int ip_fds[2], op_fds[2], diag_fds[2]; + int select_result, read_len, write_len; + size_t tmp_len; + pid_t child; + char *buf, *diag_buf; + const char *passwd_next, *input_next; + size_t size, alloc_size, diag_size, diag_alloc_size; + gboolean eof_seen, diag_eof_seen, passwd_eof_seen, input_eof_seen; + size_t passwd_remaining, passwd_incr, input_remaining, input_incr; + struct timeval timeout; + long tmp; + + if ((pipe (ip_fds) < 0 ) || + (pipe (op_fds) < 0 ) || + (pipe (diag_fds) < 0 )) { + *diagnostics = g_strdup_printf ("Couldn't create pipe to %s: " + "%s", PGP_PROGRAM, + g_strerror (errno)); + return 0; + } + + if (!(child = fork ())) { + /* In child */ + + if ((dup2 (ip_fds[0], STDIN_FILENO) < 0 ) || + (dup2 (op_fds[1], STDOUT_FILENO) < 0 ) || + (dup2 (diag_fds[1], STDERR_FILENO) < 0 )) { + _exit (255); + } + + /* Dissociate from evolution-mail's controlling + * terminal so that pgp/gpg won't be able to read from + * it: PGP 2 will fall back to asking for the password + * on /dev/tty if the passed-in password is incorrect. + * This will make that fail rather than hanging. + */ + setsid (); + + /* Close excess fds */ + cleanup_before_exec(passwd_fds[0]); + + execvp (path, argv); + fprintf (stderr, "Could not execute %s: %s\n", argv[0], + g_strerror (errno)); + _exit (255); + } else if (child < 0) { + *diagnostics = g_strdup_printf ("Cannot fork %s: %s", + argv[0], g_strerror (errno)); + return 0; + } + + /* Parent */ + close (ip_fds[0]); + close (op_fds[1]); + close (diag_fds[1]); + close (passwd_fds[0]); + + timeout.tv_sec = 10; /* timeout in seconds */ + timeout.tv_usec = 0; + + size = diag_size = 0; + alloc_size = 4096; + diag_alloc_size = 1024; + eof_seen = diag_eof_seen = FALSE; + + buf = g_malloc (alloc_size); + diag_buf = g_malloc (diag_alloc_size); + + passwd_next = passphrase; + passwd_remaining = strlen (passphrase); + passwd_incr = fpathconf (passwd_fds[1], _PC_PIPE_BUF); + /* Use a reasonable default value on error. */ + if (passwd_incr <= 0) + passwd_incr = 1024; + passwd_eof_seen = FALSE; + + input_next = input; + input_remaining = strlen (input); + input_incr = fpathconf (ip_fds[1], _PC_PIPE_BUF); + if (input_incr <= 0) + input_incr = 1024; + input_eof_seen = FALSE; + + while (!(eof_seen && diag_eof_seen)) { + FD_ZERO (&fdset); + if (!eof_seen) + FD_SET (op_fds[0], &fdset); + if (!diag_eof_seen) + FD_SET (diag_fds[0], &fdset); + + FD_ZERO (&write_fdset); + if (!passwd_eof_seen) + FD_SET (passwd_fds[1], &write_fdset); + if (!input_eof_seen) + FD_SET (ip_fds[1], &write_fdset); + + select_result = select (FD_SETSIZE, &fdset, &write_fdset, + NULL, &timeout); + if (select_result < 0) { + if (errno == EINTR) + continue; + break; + } + if (select_result == 0) { + /* timeout */ + break; + } + + if (FD_ISSET (op_fds[0], &fdset)) { + /* More output is available. */ + + if (size + 4096 > alloc_size) { + alloc_size += 4096; + buf = g_realloc (buf , alloc_size); + } + read_len = read (op_fds[0], &buf[size], + alloc_size - size - 1); + if (read_len < 0) { + if (errno == EINTR) + continue; + break; + } + if (read_len == 0) + eof_seen = TRUE; + size += read_len; + } + + if (FD_ISSET(diag_fds[0], &fdset) ) { + /* More stderr is available. */ + + if (diag_size + 1024 > diag_alloc_size) { + diag_alloc_size += 1024; + diag_buf = g_realloc (diag_buf, + diag_alloc_size); + } + + read_len = read (diag_fds[0], &diag_buf[diag_size], + diag_alloc_size - diag_size - 1); + if (read_len < 0) { + if (errno == EINTR) + continue; + break; + } + if (read_len == 0) + diag_eof_seen = TRUE; + diag_size += read_len; + } + + if (FD_ISSET(passwd_fds[1], &write_fdset)) { + /* Ready for more password input. */ + + tmp_len = passwd_incr; + if (tmp_len > passwd_remaining) + tmp_len = passwd_remaining; + write_len = write (passwd_fds[1], passwd_next, + tmp_len); + if (write_len < 0) { + if (errno == EINTR) + continue; + break; + } + passwd_next += write_len; + passwd_remaining -= write_len; + if (passwd_remaining == 0) { + close (passwd_fds[1]); + passwd_eof_seen = TRUE; + } + } + + if (FD_ISSET(ip_fds[1], &write_fdset)) { + /* Ready for more ciphertext input. */ + + tmp_len = input_incr; + if (tmp_len > input_remaining) + tmp_len = input_remaining; + write_len = write (ip_fds[1], input_next, tmp_len); + if (write_len < 0) { + if (errno == EINTR) + continue; + break; + } + input_next += write_len; + input_remaining -= write_len; + if (input_remaining == 0 ) { + close (ip_fds[1]); + input_eof_seen = TRUE; + } + } + } + + buf[size] = 0; + diag_buf[diag_size] = 0; + close (op_fds[0]); + close (diag_fds[0]); + + *output = buf; + *diagnostics = diag_buf; + + return cleanup_child (child); +} + +/*----------------------------------------------------------------------* + * Public crypto functions + *----------------------------------------------------------------------*/ + + +char * +mail_crypto_openpgp_decrypt (const char *ciphertext, const char *passphrase, + CamelException *ex) +{ + int retval; + char *path, *argv[12]; + int i; + char *plaintext = NULL; + char *diagnostics = NULL; + int passwd_fds[2]; + char passwd_fd[32]; + +#ifndef PGP_PROGRAM + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + "No GPG/PGP program available."); + return NULL; +#endif + + if (pipe (passwd_fds) < 0) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Couldn't create pipe to GPG/PGP: %s"), + g_strerror (errno)); + return NULL; + } + + i = 0; +#if defined(GPG_PATH) + path = GPG_PATH; + + argv[i++] = "gpg"; + argv[i++] = "--verbose"; + argv[i++] = "--yes"; + argv[i++] = "--batch"; + + argv[i++] = "--output"; + argv[i++] = "-"; /* output to stdout */ + + argv[i++] = "--decrypt"; + + argv[i++] = "--passphrase-fd"; + sprintf (passwd_fd, "%d", passwd_fds[0]); + argv[i++] = passwd_fd; +#elif defined(PGP5_PATH) + path = PGP5_PATH; + + argv[i++] = "pgpv"; + argv[i++] = "-f"; + argv[i++] = "+batchmode=1"; + + sprintf (passwd_fd, "PGPPASSFD=%d", passwd_fds[0]); + putenv (passwd_fd); +#else + path = PGP_PATH; + + argv[i++] = "pgp"; + argv[i++] = "-f"; + + sprintf (passwd_fd, "PGPPASSFD=%d", passwd_fds[0]); + putenv (passwd_fd); +#endif + argv[i++] = NULL; + + retval = crypto_exec_with_passwd (path, argv, ciphertext, passwd_fds, + passphrase, &plaintext, + &diagnostics); + if (retval != 0 || !*plaintext) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + "%s", diagnostics); + g_free (plaintext); + g_free (diagnostics); + return NULL; + } + + g_free (diagnostics); + return plaintext; +} + +#endif /* PGP_PROGRAM */ diff --git a/mail/mail-display.c b/mail/mail-display.c index a0f6acbbe0..3af2ec1b15 100644 --- a/mail/mail-display.c +++ b/mail/mail-display.c @@ -16,6 +16,7 @@ #include <gnome.h> #include "e-util/e-setup.h" #include "e-util/e-util.h" +#include "e-util/e-html-utils.h" #include "mail-display.h" #include "mail.h" @@ -355,6 +356,47 @@ mail_html_write (GtkHTML *html, GtkHTMLStream *stream, g_free (buf); } +void +mail_text_write (GtkHTML *html, GtkHTMLStream *stream, + const char *format, ...) +{ + char *buf, *htmltext; + va_list ap; + + va_start (ap, format); + buf = g_strdup_vprintf (format, ap); + va_end (ap); + + htmltext = e_text_to_html (buf, + E_TEXT_TO_HTML_CONVERT_URLS | + E_TEXT_TO_HTML_CONVERT_NL | + E_TEXT_TO_HTML_CONVERT_SPACES); + gtk_html_write (html, stream, "<tt>", 4); + gtk_html_write (html, stream, htmltext, strlen (htmltext)); + gtk_html_write (html, stream, "</tt>", 5); + g_free (htmltext); + g_free (buf); +} + +void +mail_error_write (GtkHTML *html, GtkHTMLStream *stream, + const char *format, ...) +{ + char *buf, *htmltext; + va_list ap; + + va_start (ap, format); + buf = g_strdup_vprintf (format, ap); + va_end (ap); + + htmltext = e_text_to_html (buf, E_TEXT_TO_HTML_CONVERT_NL); + gtk_html_write (html, stream, "<em><font color=red>", 20); + gtk_html_write (html, stream, htmltext, strlen (htmltext)); + gtk_html_write (html, stream, "</font></em><br>", 16); + g_free (htmltext); + g_free (buf); +} + /** * mail_display_set_message: diff --git a/mail/mail-display.h b/mail/mail-display.h index 7348029c47..d839978371 100644 --- a/mail/mail-display.h +++ b/mail/mail-display.h @@ -45,5 +45,11 @@ void mail_display_set_message (MailDisplay *mail_display, void mail_html_write (GtkHTML *html, GtkHTMLStream *stream, const char *format, ...); +void mail_text_write (GtkHTML *html, + GtkHTMLStream *stream, + const char *format, ...); +void mail_error_write (GtkHTML *html, + GtkHTMLStream *stream, + const char *format, ...); #endif /* _MAIL_DISPLAY_H_ */ diff --git a/mail/mail-format.c b/mail/mail-format.c index 5304e05e2a..df09cace9c 100644 --- a/mail/mail-format.c +++ b/mail/mail-format.c @@ -43,6 +43,10 @@ struct mail_format_data { GtkHTMLStream *stream; }; +static char *try_inline_pgp (char *start, struct mail_format_data *mfd); +static char *try_uudecoding (char *start, struct mail_format_data *mfd); +static char *try_inline_binhex (char *start, struct mail_format_data *mfd); + static gboolean handle_text_plain (CamelMimePart *part, const char *mime_type, struct mail_format_data *mfd); @@ -69,6 +73,9 @@ static gboolean handle_multipart_alternative (CamelMimePart *part, static gboolean handle_multipart_appledouble (CamelMimePart *part, const char *mime_type, struct mail_format_data *mfd); +static gboolean handle_multipart_encrypted (CamelMimePart *part, + const char *mime_type, + struct mail_format_data *mfd); static gboolean handle_audio (CamelMimePart *part, const char *mime_type, struct mail_format_data *mfd); @@ -171,6 +178,54 @@ get_cid (CamelMimePart *part, struct mail_format_data *mfd) return cid; } +static const char * +get_url_for_icon (const char *icon_name, struct mail_format_data *mfd) +{ + static GHashTable *icons; + char *icon_path, buf[1024], *url; + GByteArray *ba; + + if (!icons) + icons = g_hash_table_new (g_str_hash, g_str_equal); + + if (*icon_name == '/') + icon_path = g_strdup (icon_name); + else { + icon_path = gnome_pixmap_file (icon_name); + if (!icon_path) + return "file:///dev/null"; + } + + ba = g_hash_table_lookup (icons, icon_path); + if (!ba) { + int fd, nread; + + fd = open (icon_path, O_RDONLY); + if (fd == -1) { + g_free (icon_path); + return "file:///dev/null"; + } + + ba = g_byte_array_new (); + + while (1) { + nread = read (fd, buf, sizeof (buf)); + if (nread < 1) + break; + g_byte_array_append (ba, buf, nread); + } + close (fd); + + g_hash_table_insert (icons, icon_path, ba); + } + g_free (icon_path); + + url = g_strdup_printf ("x-evolution-data:%p", ba); + g_hash_table_insert (mfd->urls, url, ba); + + return url; +} + /* We're maintaining a hashtable of mimetypes -> functions; @@ -218,6 +273,8 @@ setup_function_table (void) handle_multipart_mixed); g_hash_table_insert (mime_function_table, "multipart/appledouble", handle_multipart_appledouble); + g_hash_table_insert (mime_function_table, "multipart/encrypted", + handle_multipart_encrypted); /* RFC 2046 says unrecognized text subtypes can be treated * as text/plain (as long as you recognize the character set), @@ -476,38 +533,77 @@ get_data_wrapper_text (CamelDataWrapper *data) * Mime handling functions *----------------------------------------------------------------------*/ +struct { + char *start; + char * (*handler) (char *start, struct mail_format_data *mfd); +} text_specials[] = { + { "-----BEGIN PGP MESSAGE-----\n", try_inline_pgp }, + { "begin ", try_uudecoding }, + { "(This file must be converted with BinHex 4.0)\n", try_inline_binhex } +}; +#define NSPECIALS (sizeof (text_specials) / sizeof (*text_specials)) + static gboolean handle_text_plain (CamelMimePart *part, const char *mime_type, struct mail_format_data *mfd) { CamelDataWrapper *wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); - char *text, *htmltext; + char *text, *p, *start, *subtext; GMimeContentField *type; const char *format; + int i; text = get_data_wrapper_text (wrapper); if (!text) return FALSE; - + /* Check for RFC 2646 flowed text. */ type = camel_mime_part_get_content_type (part); format = gmime_content_field_get_parameter (type, "format"); if (format && !g_strcasecmp (format, "flowed")) return handle_text_plain_flowed (text, mfd); - mail_html_write (mfd->html, mfd->stream, - "\n<!-- text/plain -->\n<tt>\n"); + mail_html_write (mfd->html, mfd->stream, "\n<!-- text/plain -->\n"); - htmltext = e_text_to_html (text, - E_TEXT_TO_HTML_CONVERT_URLS | - E_TEXT_TO_HTML_CONVERT_NL | - E_TEXT_TO_HTML_CONVERT_SPACES); - g_free (text); - mail_html_write (mfd->html, mfd->stream, "%s", htmltext); - g_free (htmltext); + p = text; + while (p) { + /* Look for special cases. */ + for (i = 0; i < NSPECIALS; i++) { + start = strstr (p, text_specials[i].start); + if (start && (start == p || start[-1] == '\n')) + break; + } + if (!start) + break; - mail_html_write (mfd->html, mfd->stream, "</tt>\n"); + /* Deal with special case */ + if (start != p) { + subtext = g_strndup (p, start - p); + mail_text_write (mfd->html, mfd->stream, + "%s", subtext); + g_free (subtext); + } + p = text_specials[i].handler (start, mfd); + if (p == start) { + /* Oops. That failed. Output this line normally and + * skip over it. + */ + p = strchr (start, '\n'); + if (!p++) + break; + subtext = g_strndup (start, p - start); + mail_text_write (mfd->html, mfd->stream, + "%s", subtext); + g_free (subtext); + } else if (p) + mail_html_write (mfd->html, mfd->stream, "<hr>"); + } + /* Finish up (or do the whole thing if there were no specials). */ + if (p) + mail_text_write (mfd->html, mfd->stream, "%s", p); + + g_free (text); return TRUE; } @@ -573,6 +669,148 @@ handle_text_plain_flowed (char *buf, struct mail_format_data *mfd) return TRUE; } +static CamelMimePart * +fake_mime_part_from_data (const char *data, int len, const char *type) +{ + CamelStream *memstream; + CamelDataWrapper *wrapper; + CamelMimePart *part; + + memstream = camel_stream_mem_new_with_buffer (data, len); + wrapper = camel_data_wrapper_new (); + camel_data_wrapper_construct_from_stream (wrapper, memstream); + camel_data_wrapper_set_mime_type (wrapper, type); + gtk_object_unref (GTK_OBJECT (memstream)); + part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (part), wrapper); + gtk_object_unref (GTK_OBJECT (wrapper)); + return part; +} + +static void +destroy_part (GtkObject *root, GtkObject *part) +{ + gtk_object_unref (part); +} + +static char * +try_inline_pgp (char *start, struct mail_format_data *mfd) +{ + char *end; + CamelMimePart *part; + CamelMultipart *mp; + + /* FIXME: This should deal with converting to multipart/signed + * as well. + */ + + end = strstr (start, "-----END PGP MESSAGE-----"); + if (!end) + return start; + + end += sizeof ("-----END PGP MESSAGE-----") - 1; + + /* Build a multipart/encrypted. */ + mp = camel_multipart_new (); + camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (mp), + "multipart/encrypted"); + + part = fake_mime_part_from_data ("Version: 1\n", 11, + "application/pgp-encrypted"); + camel_multipart_add_part (mp, part); + gtk_object_unref (GTK_OBJECT (part)); + + part = fake_mime_part_from_data (start, end - start, + "application/octet-stream"); + camel_multipart_add_part (mp, part); + gtk_object_unref (GTK_OBJECT (part)); + + part = camel_mime_part_new (); + camel_medium_set_content_object (CAMEL_MEDIUM (part), + CAMEL_DATA_WRAPPER (mp)); + gtk_object_unref (GTK_OBJECT (mp)); + + gtk_signal_connect (GTK_OBJECT (mfd->root), "destroy", + destroy_part, part); + mail_html_write (mfd->html, mfd->stream, "<hr>"); + call_handler_function (part, mfd); + + return end; +} + +static char * +try_uudecoding (char *start, struct mail_format_data *mfd) +{ + int mode, len, state = 0; + char *filename, *estart, *p, *out, uulen = 0; + guint32 save = 0; + CamelMimePart *part; + + /* Make sure it's a real uudecode begin line: + * begin [0-7]+ .* + */ + mode = strtoul (start + 6, &p, 8); + if (p == start + 6 || *p != ' ') + return start; + estart = strchr (start, '\n'); + if (!estart) + return start; + + while (isspace ((unsigned char)*p)) + p++; + filename = g_strndup (p, estart++ - p); + + /* Make sure there's an end line. */ + p = strstr (p, "\nend\n"); + if (!p) { + g_free (filename); + return start; + } + + out = g_malloc (p - estart); + len = uudecode_step (estart, p - estart, out, &state, &save, &uulen); + + part = fake_mime_part_from_data (out, len, "application/octet-stream"); + g_free (out); + camel_mime_part_set_filename (part, filename); + g_free (filename); + gtk_signal_connect (GTK_OBJECT (mfd->root), "destroy", + destroy_part, part); + + mail_html_write (mfd->html, mfd->stream, "<hr>"); + call_handler_function (part, mfd); + + return p + 4; +} + +static char * +try_inline_binhex (char *start, struct mail_format_data *mfd) +{ + char *p; + CamelMimePart *part; + + /* Find data start. */ + p = strstr (start, "\n:"); + if (!p) + return start; + + /* And data end. */ + p = strchr (p + 2, ':'); + if (!p || (*(p + 1) != '\n' && *(p + 1) != '\0')) + return start; + p += 2; + + part = fake_mime_part_from_data (start, p - start, + "application/mac-binhex40"); + gtk_signal_connect (GTK_OBJECT (mfd->root), "destroy", + destroy_part, part); + + mail_html_write (mfd->html, mfd->stream, "<hr>"); + call_handler_function (part, mfd); + + return p; +} + static void free_byte_array (GtkWidget *widget, gpointer user_data) { @@ -788,6 +1026,110 @@ handle_multipart_mixed (CamelMimePart *part, const char *mime_type, return TRUE; } +static gboolean +is_rfc2015 (CamelMimePart *part) +{ + int nparts; + char *text; + CamelDataWrapper *wrapper; + CamelMultipart *mp; + GMimeContentField *type; + + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); + mp = CAMEL_MULTIPART (wrapper); + nparts = camel_multipart_get_number (mp); + if (nparts != 2) + return FALSE; + + /* Check for application/pgp-encrypted in the first part. */ + part = camel_multipart_get_part (mp, 0); + type = camel_mime_part_get_content_type (part); + if (!gmime_content_field_is_type (type, "application", "pgp-encrypted")) + return FALSE; + + /* Check version. */ + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); + text = get_data_wrapper_text (wrapper); + if (!text || !strstr(text, "Version: 1")) { + g_free(text); + return FALSE; + } + g_free(text); + + /* Check for application/octet-stream in the second part. */ + part = camel_multipart_get_part(mp, 1); + type = camel_mime_part_get_content_type (part); + if (!gmime_content_field_is_type (type, "application", "octet-stream")) + return FALSE; + + return TRUE; +} + +static gboolean +handle_multipart_encrypted (CamelMimePart *part, const char *mime_type, + struct mail_format_data *mfd) +{ + CamelDataWrapper *wrapper = + camel_medium_get_content_object (CAMEL_MEDIUM (part)); + CamelMultipart *mp; + char *ciphertext, *passphrase, *plaintext; + CamelException ex; + + g_return_val_if_fail (CAMEL_IS_MULTIPART (wrapper), FALSE); + mp = CAMEL_MULTIPART (wrapper); + + /* Currently we only handle RFC2015-style PGP encryption. */ + if (!is_rfc2015 (part)) + return handle_multipart_mixed (part, mime_type, mfd); + + part = camel_multipart_get_part (mp, 1); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (part)); + ciphertext = get_data_wrapper_text (wrapper); + if (!ciphertext) + return FALSE; + + camel_exception_init (&ex); + +#ifdef PGP_PROGRAM + /* Get the passphrase. */ + passphrase = mail_request_dialog ( + "Please enter your PGP/GPG passphrase.", TRUE, "pgp"); + if (passphrase) { + plaintext = mail_crypto_openpgp_decrypt (ciphertext, + passphrase, &ex); + g_free (passphrase); + } else { + camel_exception_set (&ex, CAMEL_EXCEPTION_SYSTEM, + "No password provided."); + } +#else + camel_exception_set (&ex, CAMEL_EXCEPTION_SYSTEM, + "No GPG/PGP support available in this copy " + "of Evolution."); +#endif + g_free (ciphertext); + + if (camel_exception_is_set (&ex)) { + mail_html_write (mfd->html, mfd->stream, + "<table><tr valign=top><td>" + "<table border=2><tr><td>" + "<img src=\"%s\"></td></tr></table><td>", + get_url_for_icon ("gnome-lockscreen.png", + mfd)); + mail_error_write (mfd->html, mfd->stream, + "(Encrypted message not displayed)\n\n%s", + camel_exception_get_description (&ex)); + mail_html_write (mfd->html, mfd->stream, "</td></tr></table>"); + + camel_exception_clear (&ex); + } else { + mail_text_write (mfd->html, mfd->stream, "%s", plaintext); + g_free (plaintext); + } + + return TRUE; +} + /* As seen in RFC 2387! */ static gboolean handle_multipart_related (CamelMimePart *part, const char *mime_type, @@ -915,54 +1257,6 @@ handle_multipart_appledouble (CamelMimePart *part, const char *mime_type, return call_handler_function (part, mfd); } -static const char * -get_url_for_icon (const char *icon_name, struct mail_format_data *mfd) -{ - static GHashTable *icons; - char *icon_path, buf[1024], *url; - GByteArray *ba; - - if (!icons) - icons = g_hash_table_new (g_str_hash, g_str_equal); - - if (*icon_name == '/') - icon_path = g_strdup (icon_name); - else { - icon_path = gnome_pixmap_file (icon_name); - if (!icon_path) - return "file:///dev/null"; - } - - ba = g_hash_table_lookup (icons, icon_path); - if (!ba) { - int fd, nread; - - fd = open (icon_path, O_RDONLY); - if (fd == -1) { - g_free (icon_path); - return "file:///dev/null"; - } - - ba = g_byte_array_new (); - - while (1) { - nread = read (fd, buf, sizeof (buf)); - if (nread < 1) - break; - g_byte_array_append (ba, buf, nread); - } - close (fd); - - g_hash_table_insert (icons, icon_path, ba); - } - g_free (icon_path); - - url = g_strdup_printf ("x-evolution-data:%p", ba); - g_hash_table_insert (mfd->urls, url, ba); - - return url; -} - static void handle_mystery (CamelMimePart *part, struct mail_format_data *mfd, const char *url, const char *icon_name, const char *id, diff --git a/mail/mail.h b/mail/mail.h index 9cf857cd7f..83f5001605 100644 --- a/mail/mail.h +++ b/mail/mail.h @@ -32,6 +32,11 @@ BonoboControl *folder_browser_factory_new_control (const char *uri); /* folder-browser */ CamelFolder *mail_uri_to_folder (const char *uri); +/* mail-crypto */ +char *mail_crypto_openpgp_decrypt (const char *ciphertext, + const char *passphrase, + CamelException *ex); +/* FIXME: add encryption & signing functions */ /* mail-format */ void mail_format_mime_message (CamelMimeMessage *mime_message, @@ -63,5 +68,6 @@ void providers_config (BonoboUIHandler *uih, void *user_data, const char *path); /* session */ void session_init (void); +char *mail_request_dialog (const char *prompt, gboolean secret, const char *key); void forget_passwords (BonoboUIHandler *uih, void *user_data, const char *path); extern CamelSession *session; diff --git a/mail/session.c b/mail/session.c index 53ec9370db..7eaefd390c 100644 --- a/mail/session.c +++ b/mail/session.c @@ -37,15 +37,44 @@ request_callback (gchar *string, gpointer data) } #endif -static char * -evolution_auth_callback (CamelAuthCallbackMode mode, char *data, - gboolean secret, CamelService *service, char *item, - CamelException *ex) +char * +mail_request_dialog (const char *prompt, gboolean secret, const char *key) { #ifndef ASYNC_AUTH_CALLBACK GtkWidget *dialog; #endif + char *ans; + + if (!passwords) + passwords = g_hash_table_new (g_str_hash, g_str_equal); + + ans = g_hash_table_lookup (passwords, key); + if (ans) + return g_strdup (ans); + +#ifndef ASYNC_AUTH_CALLBACK + /* XXX parent window? */ + dialog = gnome_request_dialog (secret, prompt, NULL, 0, + request_callback, &ans, NULL); + if (!dialog) + return NULL; + if (gnome_dialog_run_and_close (GNOME_DIALOG (dialog)) == -1 || + ans == NULL) + return NULL; +#else + if (!mail_op_get_password (data, secret, &ans)) + return NULL; +#endif + + g_hash_table_insert (passwords, g_strdup (key), g_strdup (ans)); + return ans; +} + +static char * +auth_callback (CamelAuthCallbackMode mode, char *data, gboolean secret, + CamelService *service, char *item, CamelException *ex) +{ char *key, *ans; if (!passwords) @@ -75,38 +104,14 @@ evolution_auth_callback (CamelAuthCallbackMode mode, char *data, return NULL; } - ans = g_hash_table_lookup (passwords, key); - if (ans) { - g_free (key); - return g_strdup (ans); - } + ans = mail_request_dialog (data, secret, key); + g_free (key); -#ifndef ASYNC_AUTH_CALLBACK - /* XXX parent window? */ - dialog = gnome_request_dialog (secret, data, NULL, 0, - request_callback, &ans, NULL); - if (!dialog) { - camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, - "Could not create dialog box."); - g_free (key); - return NULL; - } - if (gnome_dialog_run_and_close (GNOME_DIALOG (dialog)) == -1 || - ans == NULL) { + if (!ans) { camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, - "User cancelled query."); - g_free (key); - return NULL; + "User canceled operation."); } -#else - if( mail_op_get_password( data, secret, &ans ) == FALSE ) { - camel_exception_set( ex, CAMEL_EXCEPTION_USER_CANCEL, ans ); - g_free( key ); - return NULL; - } -#endif - g_hash_table_insert (passwords, key, g_strdup (ans)); return ans; } @@ -116,7 +121,7 @@ session_init (void) e_setup_base_dir (); camel_init (); - session = camel_session_new (evolution_auth_callback); + session = camel_session_new (auth_callback); } static gboolean |