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,

Reply via email to