Dear mutt developers,
I've recently been exposed to "corrupted" PGP-MIME emails: instead of
having an application/pgp-encrypted and an application/octet-stream
parts wrapped in a multipart/encrypted message, these two parts are,
after an empty text/plain, wrapped in a multipart/mixed. After some
investigation, it is likely to come from mails sent via Exchange, at
least from Apple Mail. I've found some references of that bug, but no
fix for mutt:
- http://sourceforge.net/p/enigmail/bugs/384/
- http://www.gossamer-threads.com/lists/gnupg/users/55862
- https://groups.google.com/forum/#!topic/comp.mail.mutt/AHmenhyjcN4
I've written a patch that rewrite such messages so that mutt can decode
them natively. This path can be cut in two parts:
- If a application/pgp-encrypted and a application/octet-stream are in a
multipart/mixed, wrap them together in a multipart/encrypted part
- Handle base64-encoded application/octet-stream part for
pgp_decrypt_part
The second part of the patch is the ugliest one, as I've copied the
decode64 function from another file (I need to write to a file, not to a
STATE). I didn't find any function in the existing code that I could
reuse.
The patch I've attached works again the (heavily patched) gentoo version
of mutt (1.5.23-r6). Comments would be welcome.
Sincerely yours,
Vincent Brillault
--- a/handler.c
+++ b/handler.c
@@ -1612,6 +1612,37 @@
return 0;
}
+void fix_exchange_malformed_body(BODY *b)
+{
+ BODY *p_prev, *p, *new;
+
+ p_prev = b->parts;
+ if (!p_prev || !p_prev->next)
+ return;
+ p = p_prev->next;
+
+ while (p != NULL) {
+ if (p->type == TYPEAPPLICATION && p->subtype &&
+ ascii_strcasecmp ("pgp-encrypted", p->subtype) == 0 &&
+ p->next && p->next->type == TYPEAPPLICATION && p->next->subtype &&
+ ascii_strcasecmp ("octet-stream", p->next->subtype) == 0) {
+ new = mutt_new_body ();
+ new->type = TYPEMULTIPART;
+ new->subtype = safe_strdup("encrypted");
+ mutt_set_parameter("protocol", "application/pgp-encrypted", &new->parameter);
+ new->parts = p;
+ p_prev->next = new;
+ new->next = p->next->next;
+ p->next->next = NULL;
+ new->length = p->length + p->next->length;
+ return;
+ } else {
+ p_prev = p;
+ p = p->next;
+ }
+ }
+}
+
int mutt_body_handler (BODY *b, STATE *s)
{
int decode = 0;
@@ -1669,6 +1700,9 @@
{
char *p;
+ if (ascii_strcasecmp("mixed", b->subtype) == 0)
+ fix_exchange_malformed_body(b);
+
if (ascii_strcasecmp ("alternative", b->subtype) == 0)
handler = alternative_handler;
else if (WithCrypto && ascii_strcasecmp ("signed", b->subtype) == 0)
--- old/pgp.c
+++ new/pgp.c
@@ -61,6 +61,7 @@
#include "mutt_crypt.h"
#include "mutt_menu.h"
+#define BUFI_SIZE 1000
char PgpPass[LONG_STRING];
time_t PgpExptime = 0; /* when does the cached passphrase expire? */
@@ -810,6 +811,92 @@
unset_option(OPTDONTHANDLEPGPKEYS);
}
+
+int decode_base64(FILE *fpin, FILE *fpout, int len)
+{
+ char buf[5];
+ int c1, c2, c3, c4, ch, cr = 0, i;
+ char bufi[BUFI_SIZE];
+ size_t l = 0;
+
+ buf[4] = 0;
+
+ while (len > 0)
+ {
+ for (i = 0 ; i < 4 && len > 0 ; len--)
+ {
+ if ((ch = fgetc (fpin)) == EOF)
+ break;
+ if (ch >= 0 && ch < 128 && (base64val(ch) != -1 || ch == '='))
+ buf[i++] = ch;
+ }
+ if (i != 4)
+ {
+ /* "i" may be zero if there is trailing whitespace, which is not an error */
+ if (i != 0)
+ dprint (2, (debugfile, "%s:%d [decode_base64()]: "
+ "didn't get a multiple of 4 chars.\n", __FILE__, __LINE__));
+ break;
+ }
+
+ c1 = base64val (buf[0]);
+ c2 = base64val (buf[1]);
+ ch = (c1 << 2) | (c2 >> 4);
+
+ if (cr && ch != '\n')
+ bufi[l++] = '\r';
+
+ cr = 0;
+
+ if (ch == '\r')
+ cr = 1;
+ else
+ bufi[l++] = ch;
+
+ if (buf[2] == '=')
+ break;
+ c3 = base64val (buf[2]);
+ ch = ((c2 & 0xf) << 4) | (c3 >> 2);
+
+ if (cr && ch != '\n')
+ bufi[l++] = '\r';
+
+ cr = 0;
+
+ if (ch == '\r')
+ cr = 1;
+ else
+ bufi[l++] = ch;
+
+ if (buf[3] == '=') break;
+ c4 = base64val (buf[3]);
+ ch = ((c3 & 0x3) << 6) | c4;
+
+ if (cr && ch != '\n')
+ bufi[l++] = '\r';
+ cr = 0;
+
+ if (ch == '\r')
+ cr = 1;
+ else
+ bufi[l++] = ch;
+
+ if (l + 8 >= sizeof (bufi)) {
+ if (fwrite(bufi, 1, l, fpout) != l)
+ return -1;
+ l = 0;
+ }
+ }
+
+ if (cr) bufi[l++] = '\r';
+
+ if (fwrite(bufi, 1, l, fpout) != l)
+ return -1;
+
+ return 0;
+}
+
+
BODY *pgp_decrypt_part (BODY *a, STATE *s, FILE *fpout, BODY *p)
{
char buf[LONG_STRING];
@@ -843,7 +930,18 @@
*/
fseeko (s->fpin, a->offset, 0);
- mutt_copy_bytes (s->fpin, pgptmp, a->length);
+ if (a->encoding == ENCBASE64) {
+ if (decode_base64(s->fpin, pgptmp, a->length) != 0) {
+ safe_fclose (&pgptmp);
+ safe_fclose (&pgperr);
+ unlink (pgptmpfile);
+ if (s->flags & M_DISPLAY)
+ state_attach_puts (_("[-- Error: could not base64 decode\n\n"), s);
+ return NULL;
+ }
+ } else {
+ mutt_copy_bytes (s->fpin, pgptmp, a->length);
+ }
safe_fclose (&pgptmp);
if ((thepid = pgp_invoke_decrypt (&pgpin, &pgpout, NULL, -1, -1,