| | |
| | | +-------------------------------------------------------------------------+ |
| | | | 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. | |
| | |
| | | |
| | | 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; |
| | |
| | | // 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 |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | // replace message body |
| | | if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) { |
| | | $message->setTXTBody($body); |
| | | $message->setParam('text_charset', $text_charset); |
| | | } |
| | | else { |
| | | $mime->addPGPSignature($body); |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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. |
| | | * |
| | |
| | | $body = $this->get_part_body($p['object'], $part); |
| | | } |
| | | |
| | | // @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); |
| | | // 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; |
| | | } |
| | | // PGP encrypted message |
| | | else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $body)) { |
| | | $this->parse_plain_encrypted($p, $body); |
| | | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | |
| | | // 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, $body); |
| | |
| | | $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 |
| | |
| | | // 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' |
| | | ) { |
| | |
| | | * |
| | | * @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; |
| | |
| | | $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)) |
| | |
| | | $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; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | // 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @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; |
| | |
| | | |
| | | // 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 |
| | |
| | | $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( |
| | |
| | | /** |
| | | * 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; |
| | |
| | | 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); |
| | |
| | | |
| | | // Cache the fact it was decrypted |
| | | $this->encrypted_parts[] = $part->mime_id; |
| | | |
| | | $msg->mime_parts[$part->mime_id] = $part; |
| | | |
| | | // modify sub-parts |