From 53fa08d8ae051d10bed7fb08840d2f873e75c264 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Mon, 25 Jan 2016 03:53:33 -0500
Subject: [PATCH] Enigma: Code refactoring with better handling of encrypted-inside-encrypted (e.g. forwarded) and signed+ncrypted messages

---
 program/lib/Roundcube/rcube_mime_decode.php |   12 ++--
 plugins/enigma/lib/enigma_engine.php        |  130 ++++++++++++++++++++-----------------------
 2 files changed, 66 insertions(+), 76 deletions(-)

diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index 17e87b6..41d31a3 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -302,17 +302,18 @@
      * Handler for message_part_structure hook.
      * Called for every part of the message.
      *
-     * @param array Original parameters
+     * @param array  Original parameters
+     * @param string Part body (will be set if used internally)
      *
      * @return array Modified parameters
      */
-    function part_structure($p)
+    function part_structure($p, $body = null)
     {
         if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
-            $this->parse_plain($p);
+            $this->parse_plain($p, $body);
         }
         else if ($p['mimetype'] == 'multipart/signed') {
-            $this->parse_signed($p);
+            $this->parse_signed($p, $body);
         }
         else if ($p['mimetype'] == 'multipart/encrypted') {
             $this->parse_encrypted($p);
@@ -355,9 +356,10 @@
     /**
      * Handler for plain/text message.
      *
-     * @param array Reference to hook's parameters
+     * @param array  Reference to hook's parameters
+     * @param string Part body (will be set if used internally)
      */
-    function parse_plain(&$p)
+    function parse_plain(&$p, $body = null)
     {
         $part = $p['structure'];
 
@@ -367,7 +369,9 @@
         }
 
         // Get message body from IMAP server
-        $body = $this->get_part_body($p['object'], $part);
+        if ($body === null) {
+            $body = $this->get_part_body($p['object'], $part);
+        }
 
         // @TODO: big message body could be a file resource
         // PGP signed message
@@ -383,15 +387,16 @@
     /**
      * Handler for multipart/signed message.
      *
-     * @param array Reference to hook's parameters
+     * @param array  Reference to hook's parameters
+     * @param string Part body (will be set if used internally)
      */
-    function parse_signed(&$p)
+    function parse_signed(&$p, $body = null)
     {
         $struct = $p['structure'];
 
         // S/MIME
         if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
-            $this->parse_smime_signed($p);
+            $this->parse_smime_signed($p, $body);
         }
         // PGP/MIME: RFC3156
         // The multipart/signed body MUST consist of exactly two parts.
@@ -403,7 +408,7 @@
             && count($struct->parts) == 2
             && $struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature'
         ) {
-            $this->parse_pgp_signed($p);
+            $this->parse_pgp_signed($p, $body);
         }
     }
 
@@ -499,9 +504,10 @@
      * Handler for PGP/MIME signed message.
      * Verifies signature.
      *
-     * @param array Reference to hook's parameters
+     * @param array  Reference to hook's parameters
+     * @param string Part body (will be set if used internally)
      */
-    private function parse_pgp_signed(&$p)
+    private function parse_pgp_signed(&$p, $body = null)
     {
         if (!$this->rc->config->get('enigma_signatures', true)) {
             return;
@@ -520,8 +526,14 @@
         // Get bodies
         // Note: The first part body need to be full part body with headers
         //       it also cannot be decoded
-        $msg_body = $this->get_part_body($p['object'], $msg_part, true);
-        $sig_body = $this->get_part_body($p['object'], $sig_part);
+        if ($body !== null) {
+            // set signed part body
+            list($msg_body, $sig_body) = $this->explode_signed_body($body, $struct->ctype_parameters['boundary']);
+        }
+        else {
+            $msg_body = $this->get_part_body($p['object'], $msg_part, true);
+            $sig_body = $this->get_part_body($p['object'], $sig_part);
+        }
 
         // Verify
         $sig = $this->pgp_verify($msg_body, $sig_body);
@@ -543,38 +555,16 @@
      * Handler for S/MIME signed message.
      * Verifies signature.
      *
-     * @param array Reference to hook's parameters
+     * @param array  Reference to hook's parameters
+     * @param string Part body (will be set if used internally)
      */
-    private function parse_smime_signed(&$p)
+    private function parse_smime_signed(&$p, $body = null)
     {
-        return; // @TODO
-
         if (!$this->rc->config->get('enigma_signatures', true)) {
             return;
         }
 
-        // Verify signature
-        if ($this->rc->action == 'show' || $this->rc->action == 'preview' || $this->rc->action == 'print') {
-            $this->load_smime_driver();
-
-            $struct   = $p['structure'];
-            $msg_part = $struct->parts[0];
-
-            // Verify
-            $sig = $this->smime_driver->verify($struct, $p['object']);
-
-            // Store signature data for display
-            $this->signatures[$struct->mime_id] = $sig;
-
-            // Message can be multipart (assign signature to each subpart)
-            if (!empty($msg_part->parts)) {
-                foreach ($msg_part->parts as $part)
-                    $this->signed_parts[$part->mime_id] = $struct->mime_id;
-            }
-            else {
-                $this->signed_parts[$msg_part->mime_id] = $struct->mime_id;
-            }
-        }
+        // @TODO
     }
 
     /**
@@ -682,23 +672,15 @@
             // Parse decrypted message
             $struct = $this->parse_body($body);
 
-            // If there's signed content verify the signature
-            if ($struct->mimetype == 'multipart/signed') {
-                // set signed part body
-                $body = $this->extract_signed_body($body, $struct->ctype_parameters['boundary']);
-
-                $struct->parts[0]->enigma_body = $body;
-                $struct->parts[1]->enigma_body = $struct->parts[1]->body;
-
-                $this->part_structure(array(
-                        'object'    => $p['object'],
-                        'structure' => $struct,
-                        'mimetype'  => $struct->mimetype
-                ));
-            }
-
             // Modify original message structure
             $this->modify_structure($p, $struct);
+
+            // Parse the structure (there may be encrypted/signed parts inside
+            $this->part_structure(array(
+                    'object'    => $p['object'],
+                    'structure' => $struct,
+                    'mimetype'  => $struct->mimetype
+                ), $body);
 
             // Attach the decryption message to all parts
             $this->decryptions[$struct->mime_id] = $result;
@@ -730,7 +712,7 @@
             return;
         }
 
-//        $this->load_smime_driver();
+        // @TODO
     }
 
     /**
@@ -1125,13 +1107,7 @@
     {
         // @TODO: Handle big bodies using file handles
 
-        // $enigma_body is set if this is a part already extracted
-        // from encrypted message
-        if ($part->enigma_body) {
-            $body = $part->enigma_body;
-            unset($part->enigma_body);
-        }
-        else if ($full) {
+        if ($full) {
             $storage = $this->rc->get_storage();
             $body    = $storage->get_raw_headers($msg->uid, $part->mime_id);
             $body   .= $storage->get_raw_body($msg->uid, null, $part->mime_id);
@@ -1171,6 +1147,7 @@
     {
         // modify mime_parts property of the message object
         $old_id = $p['structure']->mime_id;
+
         foreach (array_keys($p['object']->mime_parts) as $idx) {
             if (!$old_id || $idx == $old_id || strpos($idx, $old_id . '.') === 0) {
                 unset($p['object']->mime_parts[$idx]);
@@ -1214,17 +1191,30 @@
     }
 
     /**
-     * Extracts body of the multipart/signed part
+     * Extracts body and signature of multipart/signed message body
      */
-    private function extract_signed_body($body, $boundary)
+    private function explode_signed_body($body, $boundary)
     {
+        if (!$body) {
+            return array();
+        }
+
         $boundary     = '--' . $boundary;
         $boundary_len = strlen($boundary) + 2;
-        $start        = strpos($body, $boundary) + $boundary_len;
-        $end          = strpos($body, $boundary, $start);
-        $body         = substr($body, $start, $end - $start - 2);
 
-        return $body;
+        // Find boundaries
+        $start = strpos($body, $boundary) + $boundary_len;
+        $end   = strpos($body, $boundary, $start);
+
+        // Get signed body and signature
+        $sig  = substr($body, $end + $boundary_len);
+        $body = substr($body, $start, $end - $start - 2);
+
+        // Cleanup signature
+        $sig = substr($sig, strpos($sig, "\r\n\r\n") + 4);
+        $sig = substr($sig, 0, strpos($sig, $boundary));
+
+        return array($body, $sig);
     }
 
     /**
diff --git a/program/lib/Roundcube/rcube_mime_decode.php b/program/lib/Roundcube/rcube_mime_decode.php
index f624f8d..a865c25 100644
--- a/program/lib/Roundcube/rcube_mime_decode.php
+++ b/program/lib/Roundcube/rcube_mime_decode.php
@@ -67,18 +67,18 @@
     /**
      * Performs the decoding process.
      *
-     * @param string $input The input to decode
+     * @param string $input   The input to decode
+     * @param bool   $convert Convert result to rcube_message_part structure
      *
      * @return object|bool Decoded results or False on failure
      */
-    public function decode($input)
+    public function decode($input, $convert = true)
     {
         list($header, $body) = $this->splitBodyHeader($input);
 
-        // @TODO: Since this is a part of Roundcube Framework
-        // we should return rcube_message_part structure
+        $struct = $this->do_decode($header, $body);
 
-        if ($struct = $this->do_decode($header, $body)) {
+        if ($struct && $convert) {
             $struct = $this->structure_part($struct);
         }
 
@@ -194,7 +194,7 @@
 
             case 'message/rfc822':
                 $obj = new rcube_mime_decode($this->params);
-                $return->parts[] = $obj->decode($body);
+                $return->parts[] = $obj->decode($body, false);
                 unset($obj);
                 break;
 

--
Gitblit v1.9.1