From 0344b168276f80189e2254c75a762aff5b517b6b Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 22 May 2016 06:32:57 -0400
Subject: [PATCH] Fix priority icon(s) position
---
plugins/enigma/lib/enigma_engine.php | 343 +++++++++++++++++++++++++++++++++++++++-----------------
1 files changed, 237 insertions(+), 106 deletions(-)
diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index 249a4b0..c970933 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -4,7 +4,7 @@
+-------------------------------------------------------------------------+
| Engine of the Enigma Plugin |
| |
- | Copyright (C) 2010-2015 The Roundcube Dev Team |
+ | Copyright (C) 2010-2016 The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -32,8 +32,9 @@
public $decryptions = array();
public $signatures = array();
- public $signed_parts = array();
public $encrypted_parts = array();
+
+ const ENCRYPTED_PARTIALLY = 100;
const SIGN_MODE_BODY = 1;
const SIGN_MODE_SEPARATE = 2;
@@ -187,6 +188,18 @@
// in this mode we'll replace text part
// with the one containing signature
$body = $message->getTXTBody();
+
+ $text_charset = $message->getParam('text_charset');
+ $line_length = $this->rc->config->get('line_length', 72);
+
+ // We can't use format=flowed for signed messages
+ if (strpos($text_charset, 'format=flowed')) {
+ list($charset, $params) = explode(';', $text_charset);
+ $body = rcube_mime::unfold_flowed($body);
+ $body = rcube_mime::wordwrap($body, $line_length, "\r\n", false, $charset);
+
+ $text_charset = str_replace(";\r\n format=flowed", '', $text_charset);
+ }
}
else {
// here we'll build PGP/MIME message
@@ -199,7 +212,7 @@
if ($result !== true) {
if ($result->getCode() == enigma_error::BADPASS) {
// ask for password
- $error = array('missing' => array($key->id => $key->name));
+ $error = array('bad' => array($key->id => $key->name));
return new enigma_error(enigma_error::BADPASS, '', $error);
}
@@ -209,6 +222,7 @@
// replace message body
if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
$message->setTXTBody($body);
+ $message->setParam('text_charset', $text_charset);
}
else {
$mime->addPGPSignature($body);
@@ -299,20 +313,48 @@
}
/**
+ * Handler for attaching public key to a message
+ *
+ * @param Mail_mime Original message
+ *
+ * @return bool True on success, False on failure
+ */
+ function attach_public_key(&$message)
+ {
+ $headers = $message->headers();
+ $from = rcube_mime::decode_address_list($headers['From'], 1, false, null, true);
+ $from = $from[1];
+
+ // find my key
+ if ($from && ($key = $this->find_key($from))) {
+ $pubkey_armor = $this->export_key($key->id);
+
+ if (!$pubkey_armor instanceof enigma_error) {
+ $pubkey_name = '0x' . enigma_key::format_id($key->id) . '.asc';
+ $message->addAttachment($pubkey_armor, 'application/pgp-keys', $pubkey_name, false, '7bit');
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* 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 +397,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,31 +410,94 @@
}
// Get message body from IMAP server
- $body = $this->get_part_body($p['object'], $part->mime_id);
-
- // @TODO: big message body could be a file resource
- // PGP signed message
- if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $body)) {
- $this->parse_plain_signed($p, $body);
+ if ($body === null) {
+ $body = $this->get_part_body($p['object'], $part);
}
- // PGP encrypted message
- else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $body)) {
- $this->parse_plain_encrypted($p, $body);
+
+ // In this way we can use fgets on string as on file handle
+ // Don't use php://temp for security (body may come from an encrypted part)
+ $fd = fopen('php://memory', 'r+');
+ if (!$fd) {
+ return;
+ }
+
+ fwrite($fd, $body);
+ rewind($fd);
+
+ $body = '';
+ $prefix = '';
+ $mode = '';
+ $tokens = array(
+ 'BEGIN PGP SIGNED MESSAGE' => 'signed-start',
+ 'END PGP SIGNATURE' => 'signed-end',
+ 'BEGIN PGP MESSAGE' => 'encrypted-start',
+ 'END PGP MESSAGE' => 'encrypted-end',
+ );
+ $regexp = '/^-----(' . implode('|', array_keys($tokens)) . ')-----[\r\n]*/';
+
+ while (($line = fgets($fd)) !== false) {
+ if ($line[0] === '-' && $line[4] === '-' && preg_match($regexp, $line, $m)) {
+ switch ($tokens[$m[1]]) {
+ case 'signed-start':
+ $body = $line;
+ $mode = 'signed';
+ break;
+
+ case 'signed-end':
+ if ($mode === 'signed') {
+ $body .= $line;
+ }
+ break 2; // ignore anything after this line
+
+ case 'encrypted-start':
+ $body = $line;
+ $mode = 'encrypted';
+ break;
+
+ case 'encrypted-end':
+ if ($mode === 'encrypted') {
+ $body .= $line;
+ }
+ break 2; // ignore anything after this line
+ }
+
+ continue;
+ }
+
+ if ($mode === 'signed') {
+ $body .= $line;
+ }
+ else if ($mode === 'encrypted') {
+ $body .= $line;
+ }
+ else {
+ $prefix .= $line;
+ }
+ }
+
+ fclose($fd);
+
+ if ($mode === 'signed') {
+ $this->parse_plain_signed($p, $body, $prefix);
+ }
+ else if ($mode === 'encrypted') {
+ $this->parse_plain_encrypted($p, $body, $prefix);
}
}
/**
* 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.
@@ -399,11 +505,10 @@
// including a set of appropriate content headers describing the data.
// The second body MUST contain the PGP digital signature. It MUST be
// labeled with a content type of "application/pgp-signature".
- else if ($struct->ctype_parameters['protocol'] == 'application/pgp-signature'
- && count($struct->parts) == 2
+ else if (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);
}
}
@@ -417,7 +522,7 @@
$struct = $p['structure'];
// S/MIME
- if ($struct->mimetype == 'application/pkcs7-mime') {
+ if ($p['mimetype'] == 'application/pkcs7-mime') {
$this->parse_smime_encrypted($p);
}
// PGP/MIME: RFC3156
@@ -426,8 +531,7 @@
// This body contains the control information.
// The second MIME body part MUST contain the actual encrypted data. It
// must be labeled with a content type of "application/octet-stream".
- else if ($struct->ctype_parameters['protocol'] == 'application/pgp-encrypted'
- && count($struct->parts) == 2
+ else if (count($struct->parts) == 2
&& $struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted'
&& $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
) {
@@ -441,36 +545,37 @@
*
* @param array Reference to hook's parameters
* @param string Message (part) body
+ * @param string Body prefix (additional text before the encrypted block)
*/
- private function parse_plain_signed(&$p, $body)
+ private function parse_plain_signed(&$p, $body, $prefix = '')
{
+ if (!$this->rc->config->get('enigma_signatures', true)) {
+ return;
+ }
+
$this->load_pgp_driver();
$part = $p['structure'];
// Verify signature
- if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
- if ($this->rc->config->get('enigma_signatures', true)) {
- $sig = $this->pgp_verify($body);
- }
+ if ($this->rc->action == 'show' || $this->rc->action == 'preview' || $this->rc->action == 'print') {
+ $sig = $this->pgp_verify($body);
}
-
- // @TODO: Handle big bodies using (temp) files
// In this way we can use fgets on string as on file handle
- $fh = fopen('php://memory', 'br+');
- // @TODO: fopen/fwrite errors handling
- if ($fh) {
- fwrite($fh, $body);
- rewind($fh);
+ // Don't use php://temp for security (body may come from an encrypted part)
+ $fd = fopen('php://memory', 'r+');
+ if (!$fd) {
+ return;
}
+
+ fwrite($fd, $body);
+ rewind($fd);
$body = $part->body = null;
$part->body_modified = true;
// Extract body (and signature?)
- while (!feof($fh)) {
- $line = fgets($fh, 1024);
-
+ while (($line = fgets($fd, 1024)) !== false) {
if ($part->body === null)
$part->body = '';
else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
@@ -479,33 +584,38 @@
$part->body .= $line;
}
+ fclose($fd);
+
// Remove "Hash" Armor Headers
$part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body);
// de-Dash-Escape (RFC2440)
$part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body);
- // Store signature data for display
- if (!empty($sig)) {
- $this->signed_parts[$part->mime_id] = $part->mime_id;
- $this->signatures[$part->mime_id] = $sig;
+ if ($prefix) {
+ $part->body = $prefix . $part->body;
}
- fclose($fh);
+ // Store signature data for display
+ if (!empty($sig)) {
+ $sig->partial = !empty($prefix);
+ $this->signatures[$part->mime_id] = $sig;
+ }
}
/**
* 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;
}
- if ($this->rc->action != 'show' && $this->rc->action != 'preview') {
+ if ($this->rc->action != 'show' && $this->rc->action != 'preview' && $this->rc->action != 'print') {
return;
}
@@ -518,61 +628,37 @@
// 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->mime_id, true);
- $sig_body = $this->get_part_body($p['object'], $sig_part->mime_id);
+ 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);
// 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;
- }
+ $this->signatures[$msg_part->mime_id] = $sig;
}
/**
* 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->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
}
/**
@@ -580,8 +666,9 @@
*
* @param array Reference to hook's parameters
* @param string Message (part) body
+ * @param string Body prefix (additional text before the encrypted block)
*/
- private function parse_plain_encrypted(&$p, $body)
+ private function parse_plain_encrypted(&$p, $body, $prefix = '')
{
if (!$this->rc->config->get('enigma_decryption', true)) {
return;
@@ -608,15 +695,18 @@
// Parse decrypted message
if ($result === true) {
- $part->body = $body;
+ $part->body = $prefix . $body;
$part->body_modified = true;
+
+ // it maybe PGP signed inside, verify signature
+ $this->parse_plain($p, $body);
// Remember it was decrypted
$this->encrypted_parts[] = $part->mime_id;
- // PGP signed inside? verify signature
- if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $body)) {
- $this->parse_plain_signed($p, $body);
+ // Inform the user that only a part of the body was encrypted
+ if ($prefix) {
+ $this->decryptions[$part->mime_id] = self::ENCRYPTED_PARTIALLY;
}
// Encrypted plain message may contain encrypted attachments
@@ -671,7 +761,7 @@
$part = $struct->parts[1];
// Get body
- $body = $this->get_part_body($p['object'], $part->mime_id);
+ $body = $this->get_part_body($p['object'], $part);
// Decrypt
$result = $this->pgp_decrypt($body);
@@ -681,7 +771,14 @@
$struct = $this->parse_body($body);
// Modify original message structure
- $this->modify_structure($p, $struct);
+ $this->modify_structure($p, $struct, strlen($body));
+
+ // 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;
@@ -713,7 +810,7 @@
return;
}
-// $this->load_smime_driver();
+ // @TODO
}
/**
@@ -1100,20 +1197,21 @@
/**
* Get message part body.
*
- * @param rcube_message Message object
- * @param string Message part ID
- * @param bool Return raw body with headers
+ * @param rcube_message Message object
+ * @param rcube_message_part Message part
+ * @param bool Return raw body with headers
*/
- private function get_part_body($msg, $part_id, $full = false)
+ private function get_part_body($msg, $part, $full = false)
{
// @TODO: Handle big bodies using file handles
+
if ($full) {
$storage = $this->rc->get_storage();
- $body = $storage->get_raw_headers($msg->uid, $part_id);
- $body .= $storage->get_raw_body($msg->uid, null, $part_id);
+ $body = $storage->get_raw_headers($msg->uid, $part->mime_id);
+ $body .= $storage->get_raw_body($msg->uid, null, $part->mime_id);
}
else {
- $body = $msg->get_part_body($part_id, false);
+ $body = $msg->get_part_body($part->mime_id, false);
}
return $body;
@@ -1140,18 +1238,25 @@
/**
* Replace message encrypted structure with decrypted message structure
*
- * @param array
- * @param rcube_message_part
+ * @param array Hook arguments
+ * @param rcube_message_part Part structure
+ * @param int Part size
*/
- private function modify_structure(&$p, $struct)
+ private function modify_structure(&$p, $struct, $size = 0)
{
// 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]);
}
}
+
+ // set some part params used by Roundcube core
+ $struct->headers = array_merge($p['structure']->headers, $struct->headers);
+ $struct->size = $size;
+ $struct->filename = $p['structure']->filename;
// modify the new structure to be correctly handled by Roundcube
$this->modify_structure_part($struct, $p['object'], $old_id);
@@ -1180,7 +1285,6 @@
// Cache the fact it was decrypted
$this->encrypted_parts[] = $part->mime_id;
-
$msg->mime_parts[$part->mime_id] = $part;
// modify sub-parts
@@ -1190,6 +1294,33 @@
}
/**
+ * Extracts body and signature of multipart/signed message body
+ */
+ private function explode_signed_body($body, $boundary)
+ {
+ if (!$body) {
+ return array();
+ }
+
+ $boundary = '--' . $boundary;
+ $boundary_len = strlen($boundary) + 2;
+
+ // 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);
+ }
+
+ /**
* Checks if specified message part is a PGP-key or S/MIME cert data
*
* @param rcube_message_part Part object
--
Gitblit v1.9.1