Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2534277
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
92 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php
index 5ddf724be..ab4aa4541 100644
--- a/plugins/enigma/lib/enigma_driver_gnupg.php
+++ b/plugins/enigma/lib/enigma_driver_gnupg.php
@@ -1,444 +1,455 @@
<?php
/**
+-------------------------------------------------------------------------+
| GnuPG (PGP) driver for the Enigma Plugin |
| |
| Copyright (C) 2010-2015 The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
require_once 'Crypt/GPG.php';
class enigma_driver_gnupg extends enigma_driver
{
protected $rc;
protected $gpg;
protected $homedir;
protected $user;
function __construct($user)
{
$this->rc = rcmail::get_instance();
$this->user = $user;
}
/**
* Driver initialization and environment checking.
* Should only return critical errors.
*
* @return mixed NULL on success, enigma_error on failure
*/
function init()
{
$homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . 'plugins/enigma/home');
if (!$homedir)
return new enigma_error(enigma_error::INTERNAL,
"Option 'enigma_pgp_homedir' not specified");
// check if homedir exists (create it if not) and is readable
if (!file_exists($homedir))
return new enigma_error(enigma_error::INTERNAL,
"Keys directory doesn't exists: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::INTERNAL,
"Keys directory isn't writeable: $homedir");
$homedir = $homedir . '/' . $this->user;
// check if user's homedir exists (create it if not) and is readable
if (!file_exists($homedir))
mkdir($homedir, 0700);
if (!file_exists($homedir))
return new enigma_error(enigma_error::INTERNAL,
"Unable to create keys directory: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::INTERNAL,
"Unable to write to keys directory: $homedir");
$this->homedir = $homedir;
// Create Crypt_GPG object
try {
$this->gpg = new Crypt_GPG(array(
'homedir' => $this->homedir,
// 'binary' => '/usr/bin/gpg2',
// 'debug' => true,
));
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Encryption.
*
* @param string Message body
* @param array List of key-password mapping
*
* @return mixed Encrypted message or enigma_error on failure
*/
function encrypt($text, $keys)
{
try {
foreach ($keys as $key) {
$this->gpg->addEncryptKey($key);
}
return $this->gpg->encrypt($text, true);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Decrypt a message
*
* @param string Encrypted message
* @param array List of key-password mapping
*
* @return mixed Decrypted message or enigma_error on failure
*/
function decrypt($text, $keys = array())
{
try {
foreach ($keys as $key => $password) {
$this->gpg->addDecryptKey($key, $password);
}
return $this->gpg->decrypt($text);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Signing.
*
* @param string Message body
* @param string Key ID
* @param string Key password
* @param int Signing mode (enigma_engine::SIGN_*)
*
* @return mixed True on success or enigma_error on failure
*/
function sign($text, $key, $passwd, $mode = null)
{
try {
$this->gpg->addSignKey($key, $passwd);
return $this->gpg->sign($text, $mode, CRYPT_GPG::ARMOR_ASCII, true);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Signature verification.
*
* @param string Message body
* @param string Signature, if message is of type PGP/MIME and body doesn't contain it
*
* @return mixed Signature information (enigma_signature) or enigma_error
*/
function verify($text, $signature)
{
try {
$verified = $this->gpg->verify($text, $signature);
return $this->parse_signature($verified[0]);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key file import.
*
* @param string File name or file content
* @param bollean True if first argument is a filename
*
* @return mixed Import status array or enigma_error
*/
public function import($content, $isfile=false)
{
try {
if ($isfile)
return $this->gpg->importKeyFile($content);
else
return $this->gpg->importKey($content);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key export.
*
* @param string Key ID
*
* @return mixed Key content or enigma_error
*/
public function export($keyid)
{
try {
return $this->gpg->exportPublicKey($keyid, true);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Keys listing.
*
* @param string Optional pattern for key ID, user ID or fingerprint
*
* @return mixed Array of enigma_key objects or enigma_error
*/
public function list_keys($pattern='')
{
try {
$keys = $this->gpg->getKeys($pattern);
$result = array();
foreach ($keys as $idx => $key) {
$result[] = $this->parse_key($key);
unset($keys[$idx]);
}
return $result;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Single key information.
*
* @param string Key ID, user ID or fingerprint
*
* @return mixed Key (enigma_key) object or enigma_error
*/
public function get_key($keyid)
{
$list = $this->list_keys($keyid);
if (is_array($list)) {
return $list[key($list)];
}
// error
return $list;
}
/**
* Key pair generation.
*
* @param array Key/User data (user, email, password, size)
*
* @return mixed Key (enigma_key) object or enigma_error
*/
public function gen_key($data)
{
try {
$keygen = new Crypt_GPG_KeyGenerator(array(
'homedir' => $this->homedir,
// 'binary' => '/usr/bin/gpg2',
// 'debug' => true,
));
$key = $keygen
->setExpirationDate(0)
->setPassphrase($data['password'])
->generateKey($data['user'], $data['email']);
return $this->parse_key($key);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Key deletion.
*
* @param string Key ID
*
* @return mixed True on success or enigma_error
*/
public function delete_key($keyid)
{
// delete public key
$result = $this->delete_pubkey($keyid);
// error handling
if ($result !== true) {
$code = $result->getCode();
// if not found, delete private key
if ($code == enigma_error::KEYNOTFOUND) {
$result = $this->delete_privkey($keyid);
}
// need to delete private key first
else if ($code == enigma_error::DELKEY) {
$key = $this->get_key($keyid);
for ($i = count($key->subkeys) - 1; $i >= 0; $i--) {
$type = ($key->subkeys[$i]->usage & enigma_key::CAN_ENCRYPT) ? 'priv' : 'pub';
$result = $this->{'delete_' . $type . 'key'}($key->subkeys[$i]->id);
if ($result !== true) {
return $result;
}
}
}
}
return $result;
}
/**
* Private key deletion.
*/
protected function delete_privkey($keyid)
{
try {
$this->gpg->deletePrivateKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Public key deletion.
*/
protected function delete_pubkey($keyid)
{
try {
$this->gpg->deletePublicKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
+ public function pubkey_for_attach($email)
+ {
+ try {
+ $pubkey = $this->gpg->exportPublicKey($email, true);
+ return $pubkey;
+ }
+ catch (Exception $e) {
+ return $this->get_error_from_exception($e);
+ }
+ }
+
/**
* Converts Crypt_GPG exception into Enigma's error object
*
* @param mixed Exception object
*
* @return enigma_error Error object
*/
protected function get_error_from_exception($e)
{
$data = array();
if ($e instanceof Crypt_GPG_KeyNotFoundException) {
$error = enigma_error::KEYNOTFOUND;
$data['id'] = $e->getKeyId();
}
else if ($e instanceof Crypt_GPG_BadPassphraseException) {
$error = enigma_error::BADPASS;
$data['bad'] = $e->getBadPassphrases();
$data['missing'] = $e->getMissingPassphrases();
}
else if ($e instanceof Crypt_GPG_NoDataException) {
$error = enigma_error::NODATA;
}
else if ($e instanceof Crypt_GPG_DeletePrivateKeyException) {
$error = enigma_error::DELKEY;
}
else {
$error = enigma_error::INTERNAL;
}
$msg = $e->getMessage();
return new enigma_error($error, $msg, $data);
}
/**
* Converts Crypt_GPG_Signature object into Enigma's signature object
*
* @param Crypt_GPG_Signature Signature object
*
* @return enigma_signature Signature object
*/
protected function parse_signature($sig)
{
$user = $sig->getUserId();
$data = new enigma_signature();
$data->id = $sig->getId();
$data->valid = $sig->isValid();
$data->fingerprint = $sig->getKeyFingerprint();
$data->created = $sig->getCreationDate();
$data->expires = $sig->getExpirationDate();
$data->name = $user->getName();
$data->comment = $user->getComment();
$data->email = $user->getEmail();
return $data;
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
* @param Crypt_GPG_Key Key object
*
* @return enigma_key Key object
*/
protected function parse_key($key)
{
$ekey = new enigma_key();
foreach ($key->getUserIds() as $idx => $user) {
$id = new enigma_userid();
$id->name = $user->getName();
$id->comment = $user->getComment();
$id->email = $user->getEmail();
$id->valid = $user->isValid();
$id->revoked = $user->isRevoked();
$ekey->users[$idx] = $id;
}
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
foreach ($key->getSubKeys() as $idx => $subkey) {
$skey = new enigma_subkey();
$skey->id = $subkey->getId();
$skey->revoked = $subkey->isRevoked();
$skey->created = $subkey->getCreationDate();
$skey->expires = $subkey->getExpirationDate();
$skey->fingerprint = $subkey->getFingerprint();
$skey->has_private = $subkey->hasPrivate();
$skey->algorithm = $subkey->getAlgorithm();
$skey->length = $subkey->getLength();
$skey->usage = $subkey->usage();
$ekey->subkeys[$idx] = $skey;
};
$ekey->id = $ekey->subkeys[0]->id;
return $ekey;
}
}
diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index 96f792d90..d2f3972d2 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -1,1311 +1,1343 @@
<?php
/**
+-------------------------------------------------------------------------+
| Engine of the Enigma Plugin |
| |
| 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. |
| See the README file for a full license statement. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/**
* Enigma plugin engine.
*
* RFC2440: OpenPGP Message Format
* RFC3156: MIME Security with OpenPGP
* RFC3851: S/MIME
*/
class enigma_engine
{
private $rc;
private $enigma;
private $pgp_driver;
private $smime_driver;
private $password_time;
public $decryptions = array();
public $signatures = array();
public $encrypted_parts = array();
const ENCRYPTED_PARTIALLY = 100;
const SIGN_MODE_BODY = 1;
const SIGN_MODE_SEPARATE = 2;
const SIGN_MODE_MIME = 3;
const ENCRYPT_MODE_BODY = 1;
const ENCRYPT_MODE_MIME = 2;
/**
* Plugin initialization.
*/
function __construct($enigma)
{
$this->rc = rcmail::get_instance();
$this->enigma = $enigma;
$this->password_time = $this->rc->config->get('enigma_password_time') * 60;
// this will remove passwords from session after some time
if ($this->password_time) {
$this->get_passwords();
}
}
/**
* PGP driver initialization.
*/
function load_pgp_driver()
{
if ($this->pgp_driver) {
return;
}
$driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
$username = $this->rc->user->get_username();
// Load driver
$this->pgp_driver = new $driver($username);
if (!$this->pgp_driver) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load PGP driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->pgp_driver->init();
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
/**
* S/MIME driver initialization.
*/
function load_smime_driver()
{
if ($this->smime_driver) {
return;
}
$driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
$username = $this->rc->user->get_username();
// Load driver
$this->smime_driver = new $driver($username);
if (!$this->smime_driver) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load S/MIME driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->smime_driver->init();
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
/**
* Handler for message signing
*
* @param Mail_mime Original message
* @param int Encryption mode
*
* @return enigma_error On error returns error object
*/
function sign_message(&$message, $mode = null)
{
$mime = new enigma_mime_message($message, enigma_mime_message::PGP_SIGNED);
$from = $mime->getFromAddress();
// find private key
$key = $this->find_key($from, true);
if (empty($key)) {
return new enigma_error(enigma_error::KEYNOTFOUND);
}
// check if we have password for this key
$passwords = $this->get_passwords();
$pass = $passwords[$key->id];
if ($pass === null) {
// ask for password
$error = array('missing' => array($key->id => $key->name));
return new enigma_error(enigma_error::BADPASS, '', $error);
}
// select mode
switch ($mode) {
case self::SIGN_MODE_BODY:
$pgp_mode = Crypt_GPG::SIGN_MODE_CLEAR;
break;
case self::SIGN_MODE_MIME:
$pgp_mode = Crypt_GPG::SIGN_MODE_DETACHED;
break;
/*
case self::SIGN_MODE_SEPARATE:
$pgp_mode = Crypt_GPG::SIGN_MODE_NORMAL;
break;
*/
default:
if ($mime->isMultipart()) {
$pgp_mode = Crypt_GPG::SIGN_MODE_DETACHED;
}
else {
$pgp_mode = Crypt_GPG::SIGN_MODE_CLEAR;
}
}
// get message body
if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
// 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
$body = $mime->getOrigBody();
}
// sign the body
$result = $this->pgp_sign($body, $key->id, $pass, $pgp_mode);
if ($result !== true) {
if ($result->getCode() == enigma_error::BADPASS) {
// ask for password
$error = array('bad' => array($key->id => $key->name));
return new enigma_error(enigma_error::BADPASS, '', $error);
}
return $result;
}
// replace message body
if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
$message->setTXTBody($body);
$message->setParam('text_charset', $text_charset);
}
else {
$mime->addPGPSignature($body);
$message = $mime;
}
}
/**
* Handler for message encryption
*
* @param Mail_mime Original message
* @param int Encryption mode
* @param bool Is draft-save action - use only sender's key for encryption
*
* @return enigma_error On error returns error object
*/
function encrypt_message(&$message, $mode = null, $is_draft = false)
{
$mime = new enigma_mime_message($message, enigma_mime_message::PGP_ENCRYPTED);
// always use sender's key
$recipients = array($mime->getFromAddress());
// if it's not a draft we add all recipients' keys
if (!$is_draft) {
$recipients = array_merge($recipients, $mime->getRecipients());
}
if (empty($recipients)) {
return new enigma_error(enigma_error::KEYNOTFOUND);
}
$recipients = array_unique($recipients);
// find recipient public keys
foreach ((array) $recipients as $email) {
$key = $this->find_key($email);
if (empty($key)) {
return new enigma_error(enigma_error::KEYNOTFOUND, '', array(
'missing' => $email
));
}
$keys[] = $key->id;
}
// select mode
switch ($mode) {
case self::ENCRYPT_MODE_BODY:
$encrypt_mode = $mode;
break;
case self::ENCRYPT_MODE_MIME:
$encrypt_mode = $mode;
break;
default:
$encrypt_mode = $mime->isMultipart() ? self::ENCRYPT_MODE_MIME : self::ENCRYPT_MODE_BODY;
}
// get message body
if ($encrypt_mode == self::ENCRYPT_MODE_BODY) {
// in this mode we'll replace text part
// with the one containing encrypted message
$body = $message->getTXTBody();
}
else {
// here we'll build PGP/MIME message
$body = $mime->getOrigBody();
}
// sign the body
$result = $this->pgp_encrypt($body, $keys);
if ($result !== true) {
return $result;
}
// replace message body
if ($encrypt_mode == self::ENCRYPT_MODE_BODY) {
$message->setTXTBody($body);
}
else {
$mime->setPGPEncryptedBody($body);
$message = $mime;
}
}
/**
* Handler for message_part_structure hook.
* Called for every part of the message.
*
* @param array Original parameters
* @param string Part body (will be set if used internally)
*
* @return array Modified parameters
*/
function part_structure($p, $body = null)
{
if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
$this->parse_plain($p, $body);
}
else if ($p['mimetype'] == 'multipart/signed') {
$this->parse_signed($p, $body);
}
else if ($p['mimetype'] == 'multipart/encrypted') {
$this->parse_encrypted($p);
}
else if ($p['mimetype'] == 'application/pkcs7-mime') {
$this->parse_encrypted($p);
}
return $p;
}
/**
* Handler for message_part_body hook.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function part_body($p)
{
// encrypted attachment, see parse_plain_encrypted()
if ($p['part']->need_decryption && $p['part']->body === null) {
$this->load_pgp_driver();
$storage = $this->rc->get_storage();
$body = $storage->get_message_part($p['object']->uid, $p['part']->mime_id, $p['part'], null, null, true, 0, false);
$result = $this->pgp_decrypt($body);
// @TODO: what to do on error?
if ($result === true) {
$p['part']->body = $body;
$p['part']->size = strlen($body);
$p['part']->body_modified = true;
}
}
return $p;
}
/**
* Handler for plain/text message.
*
* @param array Reference to hook's parameters
* @param string Part body (will be set if used internally)
*/
function parse_plain(&$p, $body = null)
{
$part = $p['structure'];
// exit, if we're already inside a decrypted message
if (in_array($part->mime_id, $this->encrypted_parts)) {
return;
}
// Get message body from IMAP server
if ($body === null) {
$body = $this->get_part_body($p['object'], $part);
}
// 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 string Part body (will be set if used internally)
*/
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, $body);
}
// PGP/MIME: RFC3156
// The multipart/signed body MUST consist of exactly two parts.
// The first part contains the signed data in MIME canonical format,
// 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 (count($struct->parts) == 2
&& $struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature'
) {
$this->parse_pgp_signed($p, $body);
}
}
/**
* Handler for multipart/encrypted message.
*
* @param array Reference to hook's parameters
*/
function parse_encrypted(&$p)
{
$struct = $p['structure'];
// S/MIME
if ($p['mimetype'] == 'application/pkcs7-mime') {
$this->parse_smime_encrypted($p);
}
// PGP/MIME: RFC3156
// The multipart/encrypted MUST consist of exactly two parts. The first
// MIME body part must have a content type of "application/pgp-encrypted".
// 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 (count($struct->parts) == 2
&& $struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted'
&& $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
) {
$this->parse_pgp_encrypted($p);
}
}
/**
* Handler for plain signed message.
* Excludes message and signature bodies and verifies signature.
*
* @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, $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' || $this->rc->action == 'print') {
$sig = $this->pgp_verify($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 = $part->body = null;
$part->body_modified = true;
// Extract body (and signature?)
while (($line = fgets($fd, 1024)) !== false) {
if ($part->body === null)
$part->body = '';
else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
break;
else
$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);
if ($prefix) {
$part->body = $prefix . $part->body;
}
// 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 string Part body (will be set if used internally)
*/
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' && $this->rc->action != 'print') {
return;
}
$this->load_pgp_driver();
$struct = $p['structure'];
$msg_part = $struct->parts[0];
$sig_part = $struct->parts[1];
// Get bodies
// Note: The first part body need to be full part body with headers
// it also cannot be decoded
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;
$this->signatures[$msg_part->mime_id] = $sig;
}
/**
* Handler for S/MIME signed message.
* Verifies signature.
*
* @param array Reference to hook's parameters
* @param string Part body (will be set if used internally)
*/
private function parse_smime_signed(&$p, $body = null)
{
if (!$this->rc->config->get('enigma_signatures', true)) {
return;
}
// @TODO
}
/**
* Handler for plain encrypted message.
*
* @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, $prefix = '')
{
if (!$this->rc->config->get('enigma_decryption', true)) {
return;
}
$this->load_pgp_driver();
$part = $p['structure'];
// Decrypt
$result = $this->pgp_decrypt($body);
// Store decryption status
$this->decryptions[$part->mime_id] = $result;
// find parent part ID
if (strpos($part->mime_id, '.')) {
$items = explode('.', $part->mime_id);
array_pop($items);
$parent = implode('.', $items);
}
else {
$parent = 0;
}
// Parse decrypted message
if ($result === true) {
$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;
// 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
// in such case attachments have .pgp extension and type application/octet-stream.
// This is what happens when you select "Encrypt each attachment separately
// and send the message using inline PGP" in Thunderbird's Enigmail.
if ($p['object']->mime_parts[$parent]) {
foreach ((array)$p['object']->mime_parts[$parent]->parts as $p) {
if ($p->disposition == 'attachment' && $p->mimetype == 'application/octet-stream'
&& preg_match('/^(.*)\.pgp$/i', $p->filename, $m)
) {
// modify filename
$p->filename = $m[1];
// flag the part, it will be decrypted when needed
$p->need_decryption = true;
// disable caching
$p->body_modified = true;
}
}
}
}
// decryption failed, but the message may have already
// been cached with the modified parts (see above),
// let's bring the original state back
else if ($p['object']->mime_parts[$parent]) {
foreach ((array)$p['object']->mime_parts[$parent]->parts as $p) {
if ($p->need_decryption && !preg_match('/^(.*)\.pgp$/i', $p->filename, $m)) {
// modify filename
$p->filename .= '.pgp';
// flag the part, it will be decrypted when needed
unset($p->need_decryption);
}
}
}
}
/**
* Handler for PGP/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_pgp_encrypted(&$p)
{
if (!$this->rc->config->get('enigma_decryption', true)) {
return;
}
$this->load_pgp_driver();
$struct = $p['structure'];
$part = $struct->parts[1];
// Get body
$body = $this->get_part_body($p['object'], $part);
// Decrypt
$result = $this->pgp_decrypt($body);
if ($result === true) {
// Parse decrypted message
$struct = $this->parse_body($body);
// Modify original message structure
$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;
foreach ((array) $struct->parts as $sp) {
$this->decryptions[$sp->mime_id] = $result;
}
}
else {
$this->decryptions[$part->mime_id] = $result;
// Make sure decryption status message will be displayed
$part->type = 'content';
$p['object']->parts[] = $part;
// don't show encrypted part on attachments list
// don't show "cannot display encrypted message" text
$p['abort'] = true;
}
}
/**
* Handler for S/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_smime_encrypted(&$p)
{
if (!$this->rc->config->get('enigma_decryption', true)) {
return;
}
// @TODO
}
/**
* PGP signature verification.
*
* @param mixed Message body
* @param mixed Signature body (for MIME messages)
*
* @return mixed enigma_signature or enigma_error
*/
private function pgp_verify(&$msg_body, $sig_body=null)
{
// @TODO: Handle big bodies using (temp) files
$sig = $this->pgp_driver->verify($msg_body, $sig_body);
if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::KEYNOTFOUND)
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $sig->getMessage()
), true, false);
return $sig;
}
/**
* PGP message decryption.
*
* @param mixed Message body
*
* @return mixed True or enigma_error
*/
private function pgp_decrypt(&$msg_body)
{
// @TODO: Handle big bodies using (temp) files
$keys = $this->get_passwords();
$result = $this->pgp_driver->decrypt($msg_body, $keys);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
if (!in_array($err_code, array(enigma_error::KEYNOTFOUND, enigma_error::BADPASS)))
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}
$msg_body = $result;
return true;
}
/**
* PGP message signing
*
* @param mixed Message body
* @param string Key ID
* @param string Key passphrase
* @param int Signing mode
*
* @return mixed True or enigma_error
*/
private function pgp_sign(&$msg_body, $keyid, $password, $mode = null)
{
// @TODO: Handle big bodies using (temp) files
$result = $this->pgp_driver->sign($msg_body, $keyid, $password, $mode);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
if (!in_array($err_code, array(enigma_error::KEYNOTFOUND, enigma_error::BADPASS)))
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}
$msg_body = $result;
return true;
}
/**
* PGP message encrypting
*
* @param mixed Message body
* @param array Keys
*
* @return mixed True or enigma_error
*/
private function pgp_encrypt(&$msg_body, $keys)
{
// @TODO: Handle big bodies using (temp) files
$result = $this->pgp_driver->encrypt($msg_body, $keys);
if ($result instanceof enigma_error) {
$err_code = $result->getCode();
if (!in_array($err_code, array(enigma_error::KEYNOTFOUND, enigma_error::BADPASS)))
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}
$msg_body = $result;
return true;
}
/**
* PGP keys listing.
*
* @param mixed Key ID/Name pattern
*
* @return mixed Array of keys or enigma_error
*/
function list_keys($pattern = '')
{
$this->load_pgp_driver();
$result = $this->pgp_driver->list_keys($pattern);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
+ function get_gpg_pubkey_for_attach($email)
+ {
+ $this->load_pgp_driver();
+ $result = $this->pgp_driver->pubkey_for_attach($email);
+
+ if ($result instanceof enigma_error) {
+ rcube::raise_error(array(
+ 'code' => 600, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Enigma plugin: " . $result->getMessage()
+ ), true, false);
+ }
+
+ return $result;
+ }
+
+ function get_keyID($email)
+ {
+ $this->load_pgp_driver();
+ $result = $this->pgp_driver->get_keyID($email);
+
+ if ($result instanceof enigma_error) {
+ rcube::raise_error(array(
+ 'code' => 600, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Enigma plugin: " . $result->getMessage()
+ ), true, false);
+ }
+
+ return $result;
+ }
+
/**
* Find PGP private/public key
*
* @param string E-mail address
* @param bool Need a key for signing?
*
* @return enigma_key The key
*/
function find_key($email, $can_sign = false)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->list_keys($email);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return;
}
$mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT;
// check key validity and type
foreach ($result as $key) {
if ($keyid = $key->find_subkey($email, $mode)) {
return $key;
}
}
}
/**
* PGP key details.
*
* @param mixed Key ID
*
* @return mixed enigma_key or enigma_error
*/
function get_key($keyid)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->get_key($keyid);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP key delete.
*
* @param string Key ID
*
* @return enigma_error|bool True on success
*/
function delete_key($keyid)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->delete_key($keyid);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP keys pair generation.
*
* @param array Key pair parameters
*
* @return mixed enigma_key or enigma_error
*/
function generate_key($data)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->gen_key($data);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP keys/certs importing.
*
* @param mixed Import file name or content
* @param boolean True if first argument is a filename
*
* @return mixed Import status data array or enigma_error
*/
function import_key($content, $isfile=false)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->import($content, $isfile);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
else {
$result['imported'] = $result['public_imported'] + $result['private_imported'];
$result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged'];
}
return $result;
}
/**
* Handler for keys/certs import request action
*/
function import_file()
{
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
$storage = $this->rc->get_storage();
if ($uid && $mime_id) {
$storage->set_folder($mbox);
$part = $storage->get_message_part($uid, $mime_id);
}
if ($part && is_array($result = $this->import_key($part))) {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
}
else
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
$this->rc->output->send();
}
/**
* PGP keys/certs export..
*
* @param string Key ID
* @param resource Optional output stream
*
* @return mixed Key content or enigma_error
*/
function export_key($key, $fp = null)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->export($key, $fp);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
return $result;
}
if ($fp) {
fwrite($fp, $result);
}
else {
return $result;
}
}
/**
* Registers password for specified key/cert sent by the password prompt.
*/
function password_handler()
{
$keyid = rcube_utils::get_input_value('_keyid', rcube_utils::INPUT_POST);
$passwd = rcube_utils::get_input_value('_passwd', rcube_utils::INPUT_POST, true);
if ($keyid && $passwd !== null && strlen($passwd)) {
$this->save_password($keyid, $passwd);
}
}
/**
* Saves key/cert password in user session
*/
function save_password($keyid, $password)
{
// we store passwords in session for specified time
if ($config = $_SESSION['enigma_pass']) {
$config = $this->rc->decrypt($config);
$config = @unserialize($config);
}
$config[$keyid] = array($password, time());
$_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
}
/**
* Returns currently stored passwords
*/
function get_passwords()
{
if ($config = $_SESSION['enigma_pass']) {
$config = $this->rc->decrypt($config);
$config = @unserialize($config);
}
$threshold = $this->password_time ? time() - $this->password_time : 0;
$keys = array();
// delete expired passwords
foreach ((array) $config as $key => $value) {
if ($threshold && $value[1] < $threshold) {
unset($config[$key]);
$modified = true;
}
else {
$keys[$key] = $value[0];
}
}
if ($modified) {
$_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
}
return $keys;
}
/**
* Get message part body.
*
* @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, $full = false)
{
// @TODO: Handle big bodies using file handles
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);
}
else {
$body = $msg->get_part_body($part->mime_id, false);
}
return $body;
}
/**
* Parse decrypted message body into structure
*
* @param string Message body
*
* @return array Message structure
*/
private function parse_body(&$body)
{
// Mail_mimeDecode need \r\n end-line, but gpg may return \n
$body = preg_replace('/\r?\n/', "\r\n", $body);
// parse the body into structure
$struct = rcube_mime::parse_message($body);
return $struct;
}
/**
* Replace message encrypted structure with decrypted message structure
*
* @param array Hook arguments
* @param rcube_message_part Part structure
* @param int Part size
*/
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);
// replace old structure with the new one
$p['structure'] = $struct;
$p['mimetype'] = $struct->mimetype;
}
/**
* Modify decrypted message part
*
* @param rcube_message_part
* @param rcube_message
*/
private function modify_structure_part($part, $msg, $old_id)
{
// never cache the body
$part->body_modified = true;
$part->encoding = 'stream';
// modify part identifier
if ($old_id) {
$part->mime_id = !$part->mime_id ? $old_id : ($old_id . '.' . $part->mime_id);
}
// Cache the fact it was decrypted
$this->encrypted_parts[] = $part->mime_id;
$msg->mime_parts[$part->mime_id] = $part;
// modify sub-parts
foreach ((array) $part->parts as $p) {
$this->modify_structure_part($p, $msg, $old_id);
}
}
/**
* 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
*
* @return boolean True if part is a key/cert
*/
public function is_keys_part($part)
{
// @TODO: S/MIME
return (
// Content-Type: application/pgp-keys
$part->mimetype == 'application/pgp-keys'
);
}
}
diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php
index 6ae69daff..f0963dca6 100644
--- a/plugins/enigma/lib/enigma_ui.php
+++ b/plugins/enigma/lib/enigma_ui.php
@@ -1,1036 +1,1058 @@
<?php
/**
+-------------------------------------------------------------------------+
| User Interface for the Enigma Plugin |
| |
| Copyright (C) 2010-2015 The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_ui
{
private $rc;
private $enigma;
private $home;
private $css_loaded;
private $js_loaded;
private $data;
private $keys_parts = array();
private $keys_bodies = array();
function __construct($enigma_plugin, $home='')
{
$this->enigma = $enigma_plugin;
$this->rc = $enigma_plugin->rc;
$this->home = $home; // we cannot use $enigma_plugin->home here
}
/**
* UI initialization and requests handlers.
*
* @param string Preferences section
*/
function init()
{
$this->add_js();
$action = rcube_utils::get_input_value('_a', rcube_utils::INPUT_GPC);
if ($this->rc->action == 'plugin.enigmakeys') {
switch ($action) {
case 'delete':
$this->key_delete();
break;
/*
case 'edit':
$this->key_edit();
break;
*/
case 'import':
$this->key_import();
break;
case 'export':
$this->key_export();
break;
case 'generate':
$this->key_generate();
break;
case 'create':
$this->key_create();
break;
case 'search':
case 'list':
$this->key_list();
break;
case 'info':
$this->key_info();
break;
}
$this->rc->output->add_handlers(array(
'keyslist' => array($this, 'tpl_keys_list'),
'keyframe' => array($this, 'tpl_key_frame'),
'countdisplay' => array($this, 'tpl_keys_rowcount'),
'searchform' => array($this->rc->output, 'search_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
$this->rc->output->send('enigma.keys');
}
/*
// Preferences UI
else if ($this->rc->action == 'plugin.enigmacerts') {
$this->rc->output->add_handlers(array(
'keyslist' => array($this, 'tpl_certs_list'),
'keyframe' => array($this, 'tpl_cert_frame'),
'countdisplay' => array($this, 'tpl_certs_rowcount'),
'searchform' => array($this->rc->output, 'search_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
$this->rc->output->send('enigma.certs');
}
*/
// Message composing UI
else if ($this->rc->action == 'compose') {
$this->compose_ui();
}
}
/**
* Adds CSS style file to the page header.
*/
function add_css()
{
if ($this->css_loaded)
return;
$skin_path = $this->enigma->local_skin_path();
if (is_file($this->home . "/$skin_path/enigma.css")) {
$this->enigma->include_stylesheet("$skin_path/enigma.css");
}
$this->css_loaded = true;
}
/**
* Adds javascript file to the page header.
*/
function add_js()
{
if ($this->js_loaded) {
return;
}
$this->enigma->include_script('enigma.js');
$this->js_loaded = true;
}
/**
* Initializes key password prompt
*
* @param enigma_error $status Error object with key info
* @param array $params Optional prompt parameters
*/
function password_prompt($status, $params = array())
{
$data = $status->getData('missing');
if (empty($data)) {
$data = $status->getData('bad');
}
$data = array('keyid' => key($data), 'user' => $data[key($data)]);
if (!empty($params)) {
$data = array_merge($params, $data);
}
if ($this->rc->action == 'send') {
$this->rc->output->command('enigma_password_request', $data);
}
else {
$this->rc->output->set_env('enigma_password_request', $data);
}
// add some labels to client
$this->rc->output->add_label('enigma.enterkeypasstitle', 'enigma.enterkeypass',
'save', 'cancel');
$this->add_css();
$this->add_js();
}
/**
* Template object for key info/edit frame.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_key_frame($attrib)
{
if (!$attrib['id']) {
$attrib['id'] = 'rcmkeysframe';
}
$attrib['name'] = $attrib['id'];
$this->rc->output->set_env('contentframe', $attrib['name']);
$this->rc->output->set_env('blankpage', $attrib['src'] ?
$this->rc->output->abs_url($attrib['src']) : 'program/resources/blank.gif');
return $this->rc->output->frame($attrib);
}
/**
* Template object for list of keys.
*
* @param array Object attributes
*
* @return string HTML content
*/
function tpl_keys_list($attrib)
{
// add id to message list table if not specified
if (!strlen($attrib['id'])) {
$attrib['id'] = 'rcmenigmakeyslist';
}
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table
$out = $this->rc->table_output($attrib, array(), $a_show_cols, 'id');
// set client env
$this->rc->output->add_gui_object('keyslist', $attrib['id']);
$this->rc->output->include_script('list.js');
// add some labels to client
$this->rc->output->add_label('enigma.keyremoveconfirm', 'enigma.keyremoving');
return $out;
}
/**
* Key listing (and searching) request handler
*/
private function key_list()
{
$this->enigma->load_engine();
$pagesize = $this->rc->config->get('pagesize', 100);
$page = max(intval(rcube_utils::get_input_value('_p', rcube_utils::INPUT_GPC)), 1);
$search = rcube_utils::get_input_value('_q', rcube_utils::INPUT_GPC);
// Get the list
$list = $this->enigma->engine->list_keys($search);
if ($list && ($list instanceof enigma_error))
$this->rc->output->show_message('enigma.keylisterror', 'error');
else if (empty($list))
$this->rc->output->show_message('enigma.nokeysfound', 'notice');
else if (is_array($list)) {
// Save the size
$listsize = count($list);
// Sort the list by key (user) name
usort($list, array('enigma_key', 'cmp'));
// Slice current page
$list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
$size = count($list);
// Add rows
foreach ($list as $key) {
$this->rc->output->command('enigma_add_list_row',
array('name' => rcube::Q($key->name), 'id' => $key->id));
}
}
$this->rc->output->set_env('rowcount', $size);
$this->rc->output->set_env('search_request', $search);
$this->rc->output->set_env('pagecount', ceil($listsize/$pagesize));
$this->rc->output->set_env('current_page', $page);
$this->rc->output->command('set_rowcount',
$this->get_rowcount_text($listsize, $size, $page));
$this->rc->output->send();
}
/**
* Template object for list records counter.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_keys_rowcount($attrib)
{
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$this->rc->output->add_gui_object('countdisplay', $attrib['id']);
return html::span($attrib, $this->get_rowcount_text());
}
/**
* Returns text representation of list records counter
*/
private function get_rowcount_text($all=0, $curr_count=0, $page=1)
{
if (!$curr_count) {
$out = $this->enigma->gettext('nokeysfound');
}
else {
$pagesize = $this->rc->config->get('pagesize', 100);
$first = ($page - 1) * $pagesize;
$out = $this->enigma->gettext(array(
'name' => 'keysfromto',
'vars' => array(
'from' => $first + 1,
'to' => $first + $curr_count,
'count' => $all)
));
}
return $out;
}
/**
* Key information page handler
*/
private function key_info()
{
$this->enigma->load_engine();
$id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
$res = $this->enigma->engine->get_key($id);
if ($res instanceof enigma_key) {
$this->data = $res;
}
else { // error
$this->rc->output->show_message('enigma.keyopenerror', 'error');
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers(array(
'keyname' => array($this, 'tpl_key_name'),
'keydata' => array($this, 'tpl_key_data'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
$this->rc->output->send('enigma.keyinfo');
}
/**
* Template object for key name
*/
function tpl_key_name($attrib)
{
return rcube::Q($this->data->name);
}
/**
* Template object for key information page content
*/
function tpl_key_data($attrib)
{
$out = '';
$table = new html_table(array('cols' => 2));
// Key user ID
$table->add('title', $this->enigma->gettext('keyuserid'));
$table->add(null, rcube::Q($this->data->name));
// Key ID
$table->add('title', $this->enigma->gettext('keyid'));
$table->add(null, $this->data->subkeys[0]->get_short_id());
// Key type
$keytype = $this->data->get_type();
if ($keytype == enigma_key::TYPE_KEYPAIR) {
$type = $this->enigma->gettext('typekeypair');
}
else if ($keytype == enigma_key::TYPE_PUBLIC) {
$type = $this->enigma->gettext('typepublickey');
}
$table->add('title', $this->enigma->gettext('keytype'));
$table->add(null, $type);
// Key fingerprint
$table->add('title', $this->enigma->gettext('fingerprint'));
$table->add(null, $this->data->subkeys[0]->get_fingerprint());
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('basicinfo')) . $table->show($attrib));
// Subkeys
$table = new html_table(array('cols' => 5, 'id' => 'enigmasubkeytable', 'class' => 'records-table'));
$table->add_header('id', $this->enigma->gettext('subkeyid'));
$table->add_header('algo', $this->enigma->gettext('subkeyalgo'));
$table->add_header('created', $this->enigma->gettext('subkeycreated'));
$table->add_header('expires', $this->enigma->gettext('subkeyexpires'));
$table->add_header('usage', $this->enigma->gettext('subkeyusage'));
$now = time();
$date_format = $this->rc->config->get('date_format', 'Y-m-d');
$usage_map = array(
enigma_key::CAN_ENCRYPT => $this->enigma->gettext('typeencrypt'),
enigma_key::CAN_SIGN => $this->enigma->gettext('typesign'),
enigma_key::CAN_CERTIFY => $this->enigma->gettext('typecert'),
enigma_key::CAN_AUTHENTICATE => $this->enigma->gettext('typeauth'),
);
foreach ($this->data->subkeys as $subkey) {
$algo = $subkey->get_algorithm();
if ($algo && $subkey->length) {
$algo .= ' (' . $subkey->length . ')';
}
$usage = array();
foreach ($usage_map as $key => $text) {
if ($subkey->usage & $key) {
$usage[] = $text;
}
}
$table->add('id', $subkey->get_short_id());
$table->add('algo', $algo);
$table->add('created', $subkey->created ? $this->rc->format_date($subkey->created, $date_format, false) : '');
$table->add('expires', $subkey->expires ? $this->rc->format_date($subkey->expires, $date_format, false) : $this->enigma->gettext('expiresnever'));
$table->add('usage', implode(',', $usage));
$table->set_row_attribs($subkey->revoked || ($subkey->expires && $subkey->expires < $now) ? 'deleted' : '');
}
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('subkeys')) . $table->show());
// Additional user IDs
$table = new html_table(array('cols' => 2, 'id' => 'enigmausertable', 'class' => 'records-table'));
$table->add_header('id', $this->enigma->gettext('userid'));
$table->add_header('valid', $this->enigma->gettext('uservalid'));
foreach ($this->data->users as $user) {
$username = $user->name;
if ($user->comment) {
$username .= ' (' . $user->comment . ')';
}
$username .= ' <' . $user->email . '>';
$table->add('id', rcube::Q(trim($username)));
$table->add('valid', $this->enigma->gettext($user->valid ? 'valid' : 'unknown'));
$table->set_row_attribs($user->revoked || !$user->valid ? 'deleted' : '');
}
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('userids')) . $table->show());
return $out;
}
/**
* Key(s) export handler
*/
private function key_export()
{
$this->rc->request_security_check(rcube_utils::INPUT_GET);
$keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_GPC);
$engine = $this->enigma->load_engine();
$list = $keys == '*' ? $engine->list_keys() : explode(',', $keys);
if (is_array($list)) {
$filename = 'export.pgp';
if (count($list) == 1) {
$filename = (is_object($list[0]) ? $list[0]->id : $list[0]) . '.pgp';
}
// send downlaod headers
header('Content-Type: application/pgp-keys');
header('Content-Disposition: attachment; filename="' . $filename . '"');
if ($fp = fopen('php://output', 'w')) {
foreach ($list as $key) {
$engine->export_key(is_object($key) ? $key->id : $key, $fp);
}
}
}
exit;
}
/**
* Key import (page) handler
*/
private function key_import()
{
// Import process
if ($data = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST)) {
// Import from generation form (ajax request)
$this->enigma->load_engine();
$result = $this->enigma->engine->import_key($data);
if (is_array($result)) {
$this->rc->output->command('enigma_key_create_success');
$this->rc->output->show_message('enigma.keygeneratesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
$this->rc->output->send();
}
else if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) {
$this->enigma->load_engine();
$result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true);
if (is_array($result)) {
// reload list if any keys has been added
if ($result['imported']) {
$this->rc->output->command('parent.enigma_list', 1);
}
else {
$this->rc->output->command('parent.enigma_loadframe');
}
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
$this->rc->output->send('iframe');
}
else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
}
}
else if ($err = $_FILES['_file']['error']) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$this->rc->output->show_message('filesizeerror', 'error',
array('size' => $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
} else {
$this->rc->output->show_message('fileuploaderror', 'error');
}
}
$this->rc->output->add_handlers(array(
'importform' => array($this, 'tpl_key_import_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyimport'));
$this->rc->output->send('enigma.keyimport');
}
/**
* Template object for key import (upload) form
*/
function tpl_key_import_form($attrib)
{
$attrib += array('id' => 'rcmKeyImportForm');
$upload = new html_inputfield(array('type' => 'file', 'name' => '_file',
'id' => 'rcmimportfile', 'size' => 30));
$form = html::p(null,
rcube::Q($this->enigma->gettext('keyimporttext'), 'show')
. html::br() . html::br() . $upload->show()
);
$this->rc->output->add_label('selectimportfile', 'importwait');
$this->rc->output->add_gui_object('importform', $attrib['id']);
$out = $this->rc->output->form_tag(array(
'action' => $this->rc->url(array('action' => $this->rc->action, 'a' => 'import')),
'method' => 'post',
'enctype' => 'multipart/form-data') + $attrib,
$form);
return $out;
}
/**
* Server-side key pair generation handler
*/
private function key_generate()
{
$user = rcube_utils::get_input_value('_user', rcube_utils::INPUT_POST, true);
$pass = rcube_utils::get_input_value('_password', rcube_utils::INPUT_POST, true);
$size = (int) rcube_utils::get_input_value('_size', rcube_utils::INPUT_POST);
if ($size > 4096) {
$size = 4096;
}
$ident = rcube_mime::decode_address_list($user, 1, false);
if (empty($ident)) {
$this->rc->output->show_message('enigma.keygenerateerror', 'error');
$this->rc->output->send();
}
$this->enigma->load_engine();
$result = $this->enigma->engine->generate_key(array(
'user' => $ident[1]['name'],
'email' => $ident[1]['mailto'],
'password' => $pass,
'size' => $size,
));
if ($result instanceof enigma_key) {
$this->rc->output->command('enigma_key_create_success');
$this->rc->output->show_message('enigma.keygeneratesuccess', 'confirmation');
}
else {
$this->rc->output->show_message('enigma.keygenerateerror', 'error');
}
$this->rc->output->send();
}
/**
* Key generation page handler
*/
private function key_create()
{
$this->enigma->include_script('openpgp.min.js');
$this->rc->output->add_handlers(array(
'keyform' => array($this, 'tpl_key_create_form'),
));
$this->rc->output->set_env('enigma_keygen_server', $this->rc->config->get('enigma_keygen_server'));
$this->rc->output->set_pagetitle($this->enigma->gettext('keygenerate'));
$this->rc->output->send('enigma.keycreate');
}
/**
* Template object for key generation form
*/
function tpl_key_create_form($attrib)
{
$attrib += array('id' => 'rcmKeyCreateForm');
$table = new html_table(array('cols' => 2));
// get user's identities
$identities = $this->rc->user->list_identities(null, true);
// Identity
$select = new html_select(array('name' => 'identity', 'id' => 'key-ident'));
foreach ((array) $identities as $idx => $ident) {
$name = empty($ident['name']) ? ('<' . $ident['email'] . '>') : $ident['ident'];
$select->add($name, $idx);
}
$table->add('title', html::label('key-name', rcube::Q($this->enigma->gettext('newkeyident'))));
$table->add(null, $select->show(0));
// Key size
$select = new html_select(array('name' => 'size', 'id' => 'key-size'));
$select->add($this->enigma->gettext('key2048'), '2048');
$select->add($this->enigma->gettext('key4096'), '4096');
$table->add('title', html::label('key-size', rcube::Q($this->enigma->gettext('newkeysize'))));
$table->add(null, $select->show());
// Password and confirm password
$table->add('title', html::label('key-pass', rcube::Q($this->enigma->gettext('newkeypass'))));
$table->add(null, rcube_output::get_edit_field('password', '',
array('id' => 'key-pass', 'size' => $attrib['size'], 'required' => true), 'password'));
$table->add('title', html::label('key-pass-confirm', rcube::Q($this->enigma->gettext('newkeypassconfirm'))));
$table->add(null, rcube_output::get_edit_field('password-confirm', '',
array('id' => 'key-pass-confirm', 'size' => $attrib['size'], 'required' => true), 'password'));
$this->rc->output->add_gui_object('keyform', $attrib['id']);
$this->rc->output->add_label('enigma.keygenerating', 'enigma.formerror',
'enigma.passwordsdiffer', 'enigma.keygenerateerror', 'enigma.nonameident',
'enigma.keygennosupport');
return $this->rc->output->form_tag(array(), $table->show($attrib));
}
/**
* Key deleting
*/
private function key_delete()
{
$keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST);
$engine = $this->enigma->load_engine();
foreach ((array)$keys as $key) {
$res = $engine->delete_key($key);
if ($res !== true) {
$this->rc->output->show_message('enigma.keyremoveerror', 'error');
$this->rc->output->command('enigma_list');
$this->rc->output->send();
}
}
$this->rc->output->command('enigma_list');
$this->rc->output->show_message('enigma.keyremovesuccess', 'confirmation');
$this->rc->output->send();
}
/**
* Init compose UI (add task button and the menu)
*/
private function compose_ui()
{
$this->add_css();
// Options menu button
$this->enigma->add_button(array(
'type' => 'link',
'command' => 'plugin.enigma',
'onclick' => "rcmail.command('menu-open', 'enigmamenu', event.target, event)",
'class' => 'button enigma',
'title' => 'encryptionoptions',
'label' => 'encryption',
'domain' => $this->enigma->ID,
'width' => 32,
'height' => 32
), 'toolbar');
$menu = new html_table(array('cols' => 2));
$chbox = new html_checkbox(array('value' => 1));
$menu->add(null, html::label(array('for' => 'enigmasignopt'),
rcube::Q($this->enigma->gettext('signmsg'))));
$menu->add(null, $chbox->show($this->rc->config->get('enigma_sign_all') ? 1 : 0,
array('name' => '_enigma_sign', 'id' => 'enigmasignopt')));
$menu->add(null, html::label(array('for' => 'enigmaencryptopt'),
rcube::Q($this->enigma->gettext('encryptmsg'))));
$menu->add(null, $chbox->show($this->rc->config->get('enigma_encrypt_all') ? 1 : 0,
array('name' => '_enigma_encrypt', 'id' => 'enigmaencryptopt')));
$menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'), $menu->show());
// Options menu contents
$this->rc->output->add_footer($menu);
}
/**
* Handler for message_body_prefix hook.
* Called for every displayed (content) part of the message.
* Adds infobox about signature verification and/or decryption
* status above the body.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function status_message($p)
{
// skip: not a message part
if ($p['part'] instanceof rcube_message) {
return $p;
}
// skip: message has no signed/encoded content
if (!$this->enigma->engine) {
return $p;
}
$engine = $this->enigma->engine;
$part_id = $p['part']->mime_id;
// Decryption status
if (($found = $this->find_part_id($part_id, $engine->decryptions)) !== null
&& ($status = $engine->decryptions[$found])
) {
$attach_scripts = true;
// show the message only once
unset($engine->decryptions[$found]);
// display status info
$attrib['id'] = 'enigma-message';
if ($status instanceof enigma_error) {
$attrib['class'] = 'enigmaerror';
$code = $status->getCode();
if ($code == enigma_error::KEYNOTFOUND) {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->enigma->gettext('decryptnokey')));
}
else if ($code == enigma_error::BADPASS) {
$missing = $status->getData('missing');
$label = 'decrypt' . (!empty($missing) ? 'no' : 'bad') . 'pass';
$msg = rcube::Q($this->enigma->gettext($label));
$this->password_prompt($status);
}
else {
$msg = rcube::Q($this->enigma->gettext('decrypterror'));
}
}
else if ($status === enigma_engine::ENCRYPTED_PARTIALLY) {
$attrib['class'] = 'enigmawarning';
$msg = rcube::Q($this->enigma->gettext('decryptpartial'));
}
else {
$attrib['class'] = 'enigmanotice';
$msg = rcube::Q($this->enigma->gettext('decryptok'));
}
$p['prefix'] .= html::div($attrib, $msg);
}
// Signature verification status
if (($found = $this->find_part_id($part_id, $engine->signatures)) !== null
&& ($sig = $engine->signatures[$found])
) {
$attach_scripts = true;
// show the message only once
unset($engine->signatures[$found]);
// display status info
$attrib['id'] = 'enigma-message';
if ($sig instanceof enigma_signature) {
$sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
if ($sig->valid === enigma_error::UNVERIFIED) {
$attrib['class'] = 'enigmawarning';
$msg = str_replace('$sender', $sender, $this->enigma->gettext('sigunverified'));
$msg = str_replace('$keyid', $sig->id, $msg);
$msg = rcube::Q($msg);
}
else if ($sig->valid) {
$attrib['class'] = $sig->partial ? 'enigmawarning' : 'enigmanotice';
$label = 'sigvalid' . ($sig->partial ? 'partial' : '');
$msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext($label)));
}
else {
$attrib['class'] = 'enigmawarning';
$msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext('siginvalid')));
}
}
else if ($sig && $sig->getCode() == enigma_error::KEYNOTFOUND) {
$attrib['class'] = 'enigmawarning';
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
$this->enigma->gettext('signokey')));
}
else {
$attrib['class'] = 'enigmaerror';
$msg = rcube::Q($this->enigma->gettext('sigerror'));
}
/*
$msg .= ' ' . html::a(array('href' => "#sigdetails",
'onclick' => rcmail_output::JS_OBJECT_NAME.".command('enigma-sig-details')"),
rcube::Q($this->enigma->gettext('showdetails')));
*/
// test
// $msg .= '<br /><pre>'.$sig->body.'</pre>';
$p['prefix'] .= html::div($attrib, $msg);
}
if ($attach_scripts) {
// add css and js script
$this->add_css();
$this->add_js();
}
return $p;
}
/**
* Handler for message_load hook.
* Check message bodies and attachments for keys/certs.
*/
function message_load($p)
{
$engine = $this->enigma->load_engine();
// handle keys/certs in attachments
foreach ((array) $p['object']->attachments as $attachment) {
if ($engine->is_keys_part($attachment)) {
$this->keys_parts[] = $attachment->mime_id;
}
}
// the same with message bodies
foreach ((array) $p['object']->parts as $part) {
if ($engine->is_keys_part($part)) {
$this->keys_parts[] = $part->mime_id;
$this->keys_bodies[] = $part->mime_id;
}
}
// @TODO: inline PGP keys
if ($this->keys_parts) {
$this->enigma->add_texts('localization');
}
return $p;
}
/**
* Handler for template_object_messagebody hook.
* This callback function adds a box below the message content
* if there is a key/cert attachment available
*/
function message_output($p)
{
foreach ($this->keys_parts as $part) {
// remove part's body
if (in_array($part, $this->keys_bodies)) {
$p['content'] = '';
}
// add box below message body
$p['content'] .= html::p(array('class' => 'enigmaattachment'),
html::a(array(
'href' => "#",
'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".enigma_import_attachment('".rcube::JQ($part)."')",
'title' => $this->enigma->gettext('keyattimport')),
html::span(null, $this->enigma->gettext('keyattfound'))));
$attach_scripts = true;
}
if ($attach_scripts) {
// add css and js script
$this->add_css();
$this->add_js();
}
return $p;
}
/**
- * Handle message_ready hook (encryption/signing)
+ * Handle message_ready hook (encryption/signing/attach public key)
*/
function message_ready($p)
{
$savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']);
+ if (!$savedraft && rcube_utils::get_input_value('_enigma_attachpubkey', rcube_utils::INPUT_POST)) {
+ $p = $this->attach_public($p);
+ }
+
if (!$savedraft && rcube_utils::get_input_value('_enigma_sign', rcube_utils::INPUT_POST)) {
$this->enigma->load_engine();
$status = $this->enigma->engine->sign_message($p['message']);
$mode = 'sign';
}
if ((!$status instanceof enigma_error) && rcube_utils::get_input_value('_enigma_encrypt', rcube_utils::INPUT_POST)) {
$this->enigma->load_engine();
$status = $this->enigma->engine->encrypt_message($p['message'], null, $savedraft);
$mode = 'encrypt';
}
if ($mode && ($status instanceof enigma_error)) {
$code = $status->getCode();
if ($code == enigma_error::KEYNOTFOUND) {
$vars = array('email' => $status->getData('missing'));
$msg = 'enigma.' . $mode . 'nokey';
}
else if ($code == enigma_error::BADPASS) {
$this->password_prompt($status);
}
else {
$msg = 'enigma.' . $mode . 'error';
}
if ($msg) {
$this->rc->output->show_message($msg, $type ?: 'error', $vars);
}
$this->rc->output->send('iframe');
}
return $p;
}
/**
+ * Add sender's public key (PGP).
+ */
+ function attach_public($p)
+ {
+ // get sender's PGP pubkey for attachment
+ $this->enigma->load_engine();
+ $key = $this->enigma->engine->list_keys($p['message']->headers()['From']);
+ $keyID = $key[0]->subkeys[0]->get_short_id();
+ $pubkey_armor = $this->enigma->engine->get_gpg_pubkey_for_attach($p['message']->headers()['From']);
+
+ if(!$pubkey_armor instanceof enigma_error) {
+ $p['message']->addAttachment($pubkey_armor, 'application/pgp-keys', "0x$keyID.asc", false);
+ }
+
+ return $p;
+ }
+
+ /**
* Handler for message_compose_body hook
* Display error when the message cannot be encrypted
* and provide a way to try again with a password.
*/
function message_compose($p)
{
$engine = $this->enigma->load_engine();
// skip: message has no signed/encoded content
if (!$this->enigma->engine) {
return $p;
}
$engine = $this->enigma->engine;
// Decryption status
foreach ($engine->decryptions as $status) {
if ($status instanceof enigma_error) {
$code = $status->getCode();
if ($code == enigma_error::KEYNOTFOUND) {
$msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
$this->enigma->gettext('decryptnokey')));
}
else if ($code == enigma_error::BADPASS) {
$this->password_prompt($status, array('compose-init' => true));
return $p;
}
else {
$msg = rcube::Q($this->enigma->gettext('decrypterror'));
}
}
}
if ($msg) {
$this->rc->output->show_message($msg, 'error');
}
// Check sign/ecrypt options for signed/encrypted drafts
$this->rc->output->set_env('enigma_force_encrypt', !empty($engine->decryptions));
$this->rc->output->set_env('enigma_force_sign', !empty($engine->signatures));
return $p;
}
/**
* Check if the part or its parent exists in the array
* of decryptions/signatures. Returns found ID.
*/
private function find_part_id($part_id, $data)
{
$ids = explode('.', $part_id);
$i = 0;
$count = count($ids);
while ($i < $count && strlen($part = implode('.', array_slice($ids, 0, ++$i)))) {
if (array_key_exists($part, $data)) {
return $part;
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Feb 6, 9:41 AM (3 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
428204
Default Alt Text
(92 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment