Aleksander Machniak
2015-03-31 a99c34159d03f2b5b525d6d8cc38509c4ac2f0a1
Enigma: Implemented messages signing and encrypting
1 files added
17 files modified
1168 ■■■■ changed files
plugins/enigma/README 17 ●●●● patch | view | raw | blame | history
plugins/enigma/config.inc.php.dist 6 ●●●●● patch | view | raw | blame | history
plugins/enigma/enigma.js 66 ●●●●● patch | view | raw | blame | history
plugins/enigma/enigma.php 119 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver.php 18 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver_gnupg.php 62 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_driver_phpssl.php 19 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_engine.php 287 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_error.php 29 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_key.php 40 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_mime_message.php 299 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_signature.php 15 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_subkey.php 17 ●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_ui.php 127 ●●●●● patch | view | raw | blame | history
plugins/enigma/lib/enigma_userid.php 15 ●●●● patch | view | raw | blame | history
plugins/enigma/localization/en_US.inc 12 ●●●● patch | view | raw | blame | history
plugins/enigma/localization/ru_RU.inc 1 ●●●● patch | view | raw | blame | history
plugins/enigma/skins/larry/enigma.css 19 ●●●●● patch | view | raw | blame | history
plugins/enigma/README
@@ -6,26 +6,25 @@
Plugin Status:
+ PGP: signed messages verification
+ PGP: signatures verification
+ PGP: messages decryption
+ PGP: Sending of encrypted/signed messages
+ PGP: keys management UI (keys import and delete)
+ Handling of PGP keys attached to incoming messages
TODO (must have):
- Fix issues with enabled messages_cache
- PGP: Sending of encrypted/signed messages
- Per-Identity settings (including keys/certs)
- Test/Make working with gnupg-2.x
- PGP: Handling of signed inside encrypted message
- Make working with gnupg-2.x
- Keys export to file
- Disable Reply/Forward options when viewing encrypted messages
  until they are decrypted successfully
- Handling of replying/forwarding of encrypted messages
- Handling of replying/forwarding of encrypted/signed messages
- Add composer.json file
- Performance improvements:
   - cache decrypted message key id in cache so we can skip
     decryption if we have no password in session
   - cache sig verification status to not verify on every msg preview (optional)
   - cache decrypted message key id so we can skip decryption if we have no password in session
   - cache (last or successful only?) sig verification status to not verify on every msg preview (optional)
TODO (later):
@@ -45,6 +44,8 @@
- Mark keys as trusted/untrasted, display appropriate message in verify/decrypt status
- User-preferences to disable signature verification, decrypting, encrypting or all enigma features
- Change attachment icon on messages list for encrypted messages (like vcard_attachment plugin does)
- Support for multi-server installations (store keys in sql database?)
- Per-Identity settings (including keys/certs)
- S/MIME: Certs generation
- S/MIME: Certs management
plugins/enigma/config.inc.php.dist
@@ -12,3 +12,9 @@
// Keys directory for all users. Default 'enigma/home'.
// Must be writeable by PHP process
$config['enigma_pgp_homedir'] = null;
// Enable signing all messages by default
$config['enigma_sign_all'] = false;
// Enable encrypting all messages by default
$config['enigma_encrypt_all'] = false;
plugins/enigma/enigma.js
@@ -33,6 +33,9 @@
    }
    else if (rcmail.env.task == 'mail') {
        if (rcmail.env.action == 'compose') {
            rcmail.addEventListener('beforesend', function(props) { rcmail.enigma_beforesend_handler(props); })
                .addEventListener('beforesavedraft', function(props) { rcmail.enigma_beforesavedraft_handler(props); });
            $('input,label', $('#enigmamenu')).mouseup(function(e) {
                // don't close the menu on mouse click inside
                e.stopPropagation();
@@ -234,9 +237,45 @@
    list.insert_row(row);
}
/*********************************************************/
/*********        Enigma Message methods         *********/
/*********************************************************/
// handle message send/save action
rcube_webmail.prototype.enigma_beforesend_handler = function(props)
{
    this.env.last_action = 'send';
    this.enigma_compose_handler(props);
}
rcube_webmail.prototype.enigma_beforesavedraft_handler = function(props)
{
    this.env.last_action = 'savedraft';
    this.enigma_compose_handler(props);
}
rcube_webmail.prototype.enigma_compose_handler = function(props)
{
    var form = this.gui_objects.messageform;
    // copy inputs from enigma menu to the form
    $('#enigmamenu input').each(function() {
        var id = this.id + '_cpy', input = $('#' + id);
        if (!input.length) {
            input = $(this).clone();
            input.prop({id: id, type: 'hidden'}).appendTo(form);
        }
        input.val(this.checked ? '1' : '');
    });
    // disable signing when saving drafts
    if (this.env.last_action == 'savedraft') {
        $('input[name="_enigma_sign"]', form).val(0);
    }
}
// Import attached keys/certs file
rcube_webmail.prototype.enigma_import_attachment = function(mime_id)
@@ -249,6 +288,7 @@
    return false;
}
// password request popup
rcube_webmail.prototype.enigma_password_request = function(data)
{
    if (!data || !data.keyid) {
@@ -268,7 +308,8 @@
            .appendTo(myprompt);
    data.key = data.keyid;
    data.keyid = data.keyid.substr(0, 8);
    if (data.keyid.length > 8)
        data.keyid = data.keyid.substr(data.keyid.length - 8);
    $.each(['keyid', 'user'], function() {
        msg = msg.replace('$' + this, data[this]);
@@ -310,8 +351,14 @@
    }
}
// submit entered password
rcube_webmail.prototype.enigma_password_submit = function(keyid, password)
{
    if (this.env.action == 'compose') {
        return this.enigma_password_compose_submit(keyid, password);
    }
    // message preview
    var form = $('<form>').attr({method: 'post', action: location.href, style: 'display:none'})
        .append($('<input>').attr({type: 'hidden', name: '_keyid', value: keyid}))
        .append($('<input>').attr({type: 'hidden', name: '_passwd', value: password}))
@@ -320,3 +367,20 @@
    form.submit();
}
// submit entered password - in mail compose page
rcube_webmail.prototype.enigma_password_compose_submit = function(keyid, password)
{
    var form = this.gui_objects.messageform;
    if (!$('input[name="_keyid"]', form).length) {
        $(form).append($('<input>').attr({type: 'hidden', name: '_keyid', value: keyid}))
            .append($('<input>').attr({type: 'hidden', name: '_passwd', value: password}));
    }
    else {
        $('input[name="_keyid"]', form).val(keyid);
        $('input[name="_passwd"]', form).val(password);
    }
    this.submit_messageform(this.env.last_action == 'savedraft');
}
plugins/enigma/enigma.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | Enigma Plugin for Roundcube                                             |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -43,8 +36,6 @@
        $this->rc = rcube::get_instance();
        if ($this->rc->task == 'mail') {
            $section = rcube_utils::get_input_value('_section', rcube_utils::INPUT_GET);
            // message parse/display hooks
            $this->add_hook('message_part_structure', array($this, 'part_structure'));
            $this->add_hook('message_part_body', array($this, 'part_body'));
@@ -60,12 +51,11 @@
            // message composing
            else if ($this->rc->action == 'compose') {
                $this->load_ui();
                $this->ui->init($section);
                $this->ui->init();
            }
            // message sending (and draft storing)
            else if ($this->rc->action == 'sendmail') {
                //$this->add_hook('outgoing_message_body', array($this, 'msg_encode'));
                //$this->add_hook('outgoing_message_body', array($this, 'msg_sign'));
            else if ($this->rc->action == 'send') {
                $this->add_hook('message_ready', array($this, 'message_ready'));
            }
            $this->password_handler();
@@ -73,13 +63,14 @@
        else if ($this->rc->task == 'settings') {
            // add hooks for Enigma settings
            $this->add_hook('settings_actions', array($this, 'settings_actions'));
//            $this->add_hook('preferences_list', array($this, 'preferences_list'));
//            $this->add_hook('preferences_save', array($this, 'preferences_save'));
            $this->add_hook('preferences_sections_list', array($this, 'preferences_sections_list'));
            $this->add_hook('preferences_list', array($this, 'preferences_list'));
            $this->add_hook('preferences_save', array($this, 'preferences_save'));
            // register handler for keys/certs management
//            $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
            $this->register_action('plugin.enigmakeys', array($this, 'preferences_ui'));
            $this->register_action('plugin.enigmacerts', array($this, 'preferences_ui'));
//            $this->register_action('plugin.enigmacerts', array($this, 'preferences_ui'));
            $this->load_ui();
            $this->ui->add_css();
@@ -209,6 +200,23 @@
    }
    /**
     * Handler for preferences_sections_list hook.
     * Adds Encryption settings section into preferences sections list.
     *
     * @param array Original parameters
     *
     * @return array Modified parameters
     */
    function preferences_sections_list($p)
    {
        $p['list']['enigma'] = array(
            'id' => 'enigma', 'section' => $this->gettext('encryption'),
        );
        return $p;
    }
    /**
     * Handler for preferences_list hook.
     * Adds options blocks into Enigma settings sections in Preferences.
     *
@@ -218,12 +226,52 @@
     */
    function preferences_list($p)
    {
/*
        if ($p['section'] == 'enigmasettings') {
            // This makes that section is not removed from the list
            $p['blocks']['dummy']['options']['dummy'] = array();
        if ($p['section'] != 'enigma') {
            return $p;
        }
*/
        $no_override = array_flip((array)$this->rc->config->get('dont_override'));
        $p['blocks']['main']['name'] = $this->gettext('mainoptions');
        if (!isset($no_override['enigma_sign_all'])) {
            if (!$p['current']) {
                $p['blocks']['main']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_enigma_sign_all';
            $input    = new html_checkbox(array(
                    'name'  => '_enigma_sign_all',
                    'id'    => $field_id,
                    'value' => 1,
            ));
            $p['blocks']['main']['options']['enigma_sign_all'] = array(
                'title'   => html::label($field_id, $this->gettext('signdefault')),
                'content' => $input->show($this->rc->config->get('enigma_sign_all') ? 1 : 0),
            );
        }
        if (!isset($no_override['enigma_encrypt_all'])) {
            if (!$p['current']) {
                $p['blocks']['main']['content'] = true;
                return $p;
            }
            $field_id = 'rcmfd_enigma_encrypt_all';
            $input    = new html_checkbox(array(
                    'name'  => '_enigma_encrypt_all',
                    'id'    => $field_id,
                    'value' => 1,
            ));
            $p['blocks']['main']['options']['enigma_encrypt_all'] = array(
                'title'   => html::label($field_id, $this->gettext('encryptdefault')),
                'content' => $input->show($this->rc->config->get('enigma_encrypt_all') ? 1 : 0),
            );
        }
        return $p;
    }
@@ -237,13 +285,13 @@
     */
    function preferences_save($p)
    {
/*
        if ($p['section'] == 'enigmasettings') {
            $a['prefs'] = array(
                'dummy' => rcube_utils::get_input_value('_dummy', rcube_utils::INPUT_POST),
        if ($p['section'] == 'enigma') {
            $p['prefs'] = array(
                'enigma_sign_all'    => intval(rcube_utils::get_input_value('_enigma_sign_all', rcube_utils::INPUT_POST)),
                'enigma_encrypt_all' => intval(rcube_utils::get_input_value('_enigma_encrypt_all', rcube_utils::INPUT_POST)),
            );
        }
*/
        return $p;
    }
@@ -313,10 +361,21 @@
    function password_handler()
    {
        $this->load_engine();
        $this->engine->password_handler();
    }
    /**
     * Handle message_ready hook (encryption/signing)
     */
    function message_ready($p)
    {
        $this->load_ui();
        return $this->ui->message_ready($p);
    }
    /**
     * Handler for refresh hook.
     */
    function refresh($p)
plugins/enigma/lib/enigma_driver.php
@@ -1,20 +1,14 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Abstract driver for the Enigma Plugin                                   |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -53,7 +47,7 @@
    /**
     * Signing.
     */
    abstract function sign($text, $key, $passwd);
    abstract function sign($text, $key, $passwd, $mode = null);
    /**
     * Signature verification.
plugins/enigma/lib/enigma_driver_gnupg.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | GnuPG (PGP) driver for the Enigma Plugin                                |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -29,6 +22,7 @@
    private $gpg;
    private $homedir;
    private $user;
    function __construct($user)
    {
@@ -86,30 +80,40 @@
        }
    }
    /**
     * Encrypt a message
     *
     * @param string The message
     * @param array  List of keys
     */
    function encrypt($text, $keys)
    {
/*
        foreach ($keys as $key) {
            $this->gpg->addEncryptKey($key);
        try {
            foreach ($keys as $key) {
                $this->gpg->addEncryptKey($key);
            }
            $dec = $this->gpg->encrypt($text, true);
            return $dec;
        }
        $enc = $this->gpg->encrypt($text);
        return $enc;
*/
        catch (Exception $e) {
            return $this->get_error_from_exception($e);
        }
    }
    /**
     * Register private keys and passwords
     * Decrypt a message
     *
     * @param string Encrypted message
     * @param array  List of key-password mapping
     */
    function decrypt($text, $keys = array())
    {
        foreach ($keys as $key => $password) {
            $this->gpg->addDecryptKey($key, $password);
        }
        try {
            foreach ($keys as $key => $password) {
                $this->gpg->addDecryptKey($key, $password);
            }
            $dec = $this->gpg->decrypt($text);
            return $dec;
        }
@@ -118,13 +122,15 @@
        }
    }
    function sign($text, $key, $passwd)
    function sign($text, $key, $passwd, $mode = null)
    {
/*
        $this->gpg->addSignKey($key, $passwd);
        $signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED);
        return $signed;
*/
        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);
        }
    }
    function verify($text, $signature)
plugins/enigma/lib/enigma_driver_phpssl.php
@@ -1,20 +1,13 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | S/MIME driver for the Enigma Plugin                                |
 | S/MIME driver for the Enigma Plugin                                     |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -81,7 +74,7 @@
    {
    }
    function sign($text, $key, $passwd)
    function sign($text, $key, $passwd, $mode = null)
    {
    }
plugins/enigma/lib/enigma_engine.php
@@ -3,23 +3,15 @@
 +-------------------------------------------------------------------------+
 | Engine of the Enigma Plugin                                             |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
 +-------------------------------------------------------------------------+
*/
/*
@@ -40,6 +32,13 @@
    public $signed_parts = array();
    const PASSWORD_TIME = 120;
    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;
    /**
@@ -121,6 +120,175 @@
                '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::E_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::E_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();
        }
        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::E_BADPASS) {
                // ask for password
                $error = array('missing' => array($key->id => $key->name));
                return new enigma_error(enigma_error::E_BADPASS, '', $error);
            }
            return $result;
        }
        // replace message body
        if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
            $message->setTXTBody($body);
        }
        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::E_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::E_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;
        }
    }
@@ -510,7 +678,6 @@
    private function pgp_verify(&$msg_body, $sig_body=null)
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
        $sig = $this->pgp_driver->verify($msg_body, $sig_body);
        if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::E_KEYNOTFOUND)
@@ -533,9 +700,68 @@
    private function pgp_decrypt(&$msg_body)
    {
        // @TODO: Handle big bodies using (temp) files
        // @TODO: caching of verification result
        $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::E_KEYNOTFOUND, enigma_error::E_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::E_KEYNOTFOUND, enigma_error::E_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();
@@ -577,6 +803,39 @@
    }
    /**
     * 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
plugins/enigma/lib/enigma_error.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | Error class for the Enigma Plugin                                       |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -28,14 +21,14 @@
    private $data = array();
    // error codes
    const E_OK = 0;
    const E_INTERNAL = 1;
    const E_NODATA = 2;
    const E_OK          = 0;
    const E_INTERNAL    = 1;
    const E_NODATA      = 2;
    const E_KEYNOTFOUND = 3;
    const E_DELKEY = 4;
    const E_BADPASS = 5;
    const E_EXPIRED = 6;
    const E_UNVERIFIED = 7;
    const E_DELKEY      = 4;
    const E_BADPASS     = 5;
    const E_EXPIRED     = 6;
    const E_UNVERIFIED  = 7;
    function __construct($code = null, $message = '', $data = array())
plugins/enigma/lib/enigma_key.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | Key class for the Enigma Plugin                                         |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -31,6 +24,9 @@
    const TYPE_UNKNOWN = 0;
    const TYPE_KEYPAIR = 1;
    const TYPE_PUBLIC  = 2;
    const CAN_SIGN    = 1;
    const CAN_ENCRYPT = 2;
    /**
     * Keys list sorting callback for usort()
@@ -92,6 +88,28 @@
    }
    /**
     * Get key ID by user email
     */
    function find_subkey($email, $mode)
    {
        $now = time();
        foreach ($this->users as $user) {
            if ($user->email === $email && $user->valid && !$user->revoked) {
                foreach ($this->subkeys as $subkey) {
                    if (!$subkey->revoked && (!$subkey->expires || $subkey->expires > $now)) {
                        if (($mode == self::CAN_ENCRYPT && $subkey->can_encrypt)
                            || ($mode == self::CAN_SIGN && $subkey->has_private)
                        ) {
                            return $subkey;
                        }
                    }
                }
            }
        }
    }
    /**
     * Converts long ID or Fingerprint to short ID
     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
     *
plugins/enigma/lib/enigma_mime_message.php
New file
@@ -0,0 +1,299 @@
<?php
/*
 +-------------------------------------------------------------------------+
 | Mail_mime wrapper 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_mime_message extends Mail_mime
{
    const PGP_SIGNED    = 1;
    const PGP_ENCRYPTED = 2;
    protected $_type;
    protected $_message;
    protected $_body;
    protected $_signature;
    protected $_encrypted;
    /**
     * Object constructor
     *
     * @param Mail_mime Original message
     * @param int       Output message type
     */
    function __construct($message, $type)
    {
        $this->_message = $message;
        $this->_type    = $type;
        // clone parameters
        foreach (array_keys($this->_build_params) as $param) {
            $this->_build_params[$param] = $message->getParam($param);
        }
        // clone headers
        $this->_headers = $message->_headers;
/*
        if ($message->getParam('delay_file_io')) {
            // use common temp dir
            $temp_dir    = $this->config->get('temp_dir');
            $body_file   = tempnam($temp_dir, 'rcmMsg');
            $mime_result = $message->saveMessageBody($body_file);
            if (is_a($mime_result, 'PEAR_Error')) {
                self::raise_error(array('code' => 650, 'type' => 'php',
                    'file' => __FILE__, 'line' => __LINE__,
                    'message' => "Could not create message: ".$mime_result->getMessage()),
                    true, false);
                return false;
            }
            $msg_body = fopen($body_file, 'r');
        }
        else {
*/
            // \r\n is must-have here
            $this->_body = $message->get() . "\r\n";
/*
        }
*/
    }
    /**
     * Check if the message is multipart (requires PGP/MIME)
     *
     * @return bool True if it is multipart, otherwise False
     */
    function isMultipart()
    {
        return $this->_message instanceof enigma_mime_message
            || !empty($this->_message->_parts) || $this->_message->getHTMLBody();
    }
    /**
     * Get e-mail address of message sender
     *
     * @return string Sender address
     */
    function getFromAddress()
    {
        // get sender address
        $headers = $this->_message->headers();
        $from    = rcube_mime::decode_address_list($headers['From'], 1, false, null, true);
        $from    = $from[1];
        return $from;
    }
    /**
     * Get recipients' e-mail addresses
     *
     * @return array Recipients' addresses
     */
    function getRecipients()
    {
        // get sender address
        $headers = $this->_message->headers();
        $to      = rcube_mime::decode_address_list($headers['To'], null, false, null, true);
        $cc      = rcube_mime::decode_address_list($headers['Cc'], null, false, null, true);
        $bcc     = rcube_mime::decode_address_list($headers['Bcc'], null, false, null, true);
        $recipients = array_unique(array_merge($to, $cc, $bcc));
        $recipients = array_diff($recipients, array('undisclosed-recipients:'));
        return $recipients;
    }
    /**
     * Get original message body, to be encrypted/signed
     *
     * @return string Message body
     */
    function getOrigBody()
    {
        $_headers = $this->_message->headers();
        $headers  = array();
        if ($_headers['Content-Transfer-Encoding']) {
            $headers[] = 'Content-Transfer-Encoding: ' . $_headers['Content-Transfer-Encoding'];
        }
        $headers[] = 'Content-Type: ' . $_headers['Content-Type'];
        return implode("\r\n", $headers) . "\r\n\r\n" . $this->_body;
    }
    /**
     * Register signature attachment
     *
     * @param string Signature body
     */
    function addPGPSignature($body)
    {
        $this->_signature = $body;
    }
    /**
     * Register encrypted body
     *
     * @param string Encrypted body
     */
    function setPGPEncryptedBody($body)
    {
        $this->_encrypted = $body;
    }
    /**
     * Builds the multipart message.
     *
     * @param array    $params    Build parameters that change the way the email
     *                            is built. Should be associative. See $_build_params.
     * @param resource $filename  Output file where to save the message instead of
     *                            returning it
     * @param boolean  $skip_head True if you want to return/save only the message
     *                            without headers
     *
     * @return mixed The MIME message content string, null or PEAR error object
     * @access public
     */
    function get($params = null, $filename = null, $skip_head = false)
    {
        if (isset($params)) {
            while (list($key, $value) = each($params)) {
                $this->_build_params[$key] = $value;
            }
        }
        $this->_checkParams();
        if ($this->_type == self::PGP_SIGNED) {
            $body   = "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)";
            $params = array(
                'content_type' => "multipart/signed; micalg=pgp-sha1; protocol=\"application/pgp-signature\"",
                'eol'          => $this->_build_params['eol'],
            );
            $message = new Mail_mimePart($body, $params);
            if (!empty($this->_body)) {
                $headers = $this->_message->headers();
                $params  = array('content_type' => $headers['Content-Type']);
                if ($headers['Content-Transfer-Encoding']) {
                    $params['encoding'] = $headers['Content-Transfer-Encoding'];
                }
                $message->addSubpart($this->_body, $params);
            }
            if (!empty($this->_signature)) {
                $message->addSubpart($this->_signature, array(
                    'filename'     => 'signature.asc',
                    'content_type' => 'application/pgp-signature',
                    'disposition'  => 'attachment',
                    'description'  => 'OpenPGP digital signature',
                ));
            }
        }
        else if ($this->_type == self::PGP_ENCRYPTED) {
            $body   = "This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)";
            $params = array(
                'content_type' => "multipart/encrypted; protocol=\"application/pgp-encrypted\"",
                'eol'          => $this->_build_params['eol'],
            );
            $message = new Mail_mimePart($body, $params);
            $message->addSubpart('Version: 1', array(
                    'content_type' => 'application/pgp-encrypted',
                    'description'  => 'PGP/MIME version identification',
            ));
            $message->addSubpart($this->_encrypted, array(
                    'content_type' => 'application/octet-stream',
                    'description'  => 'PGP/MIME encrypted message',
                    'disposition'  => 'inline',
                    'filename'     => 'encrypted.asc',
            ));
        }
        // Use saved boundary
        if (!empty($this->_build_params['boundary'])) {
            $boundary = $this->_build_params['boundary'];
        }
        else {
            $boundary = null;
        }
        // Write output to file
        if ($filename) {
            // Append mimePart message headers and body into file
            $headers = $message->encodeToFile($filename, $boundary, $skip_head);
            if ($this->_isError($headers)) {
                return $headers;
            }
            $this->_headers = array_merge($this->_headers, $headers);
            return null;
        }
        else {
            $output = $message->encode($boundary, $skip_head);
            if ($this->_isError($output)) {
                return $output;
            }
            $this->_headers = array_merge($this->_headers, $output['headers']);
            return $output['body'];
        }
    }
    /**
     * Get Content-Type and Content-Transfer-Encoding headers of the message
     *
     * @return array Headers array
     * @access private
     */
    function _contentHeaders()
    {
        $this->_checkParams();
        $eol = !empty($this->_build_params['eol']) ? $this->_build_params['eol'] : "\r\n";
        // multipart message: and boundary
        if (!empty($this->_build_params['boundary'])) {
            $boundary = $this->_build_params['boundary'];
        }
        else if (!empty($this->_headers['Content-Type'])
            && preg_match('/boundary="([^"]+)"/', $this->_headers['Content-Type'], $m)
        ) {
            $boundary = $m[1];
        }
        else {
            $boundary = '=_' . md5(rand() . microtime());
        }
        $this->_build_params['boundary'] = $boundary;
        if ($this->_type == self::PGP_SIGNED) {
            $headers['Content-Type'] = "multipart/signed; micalg=pgp-sha1;$eol"
                ." protocol=\"application/pgp-signature\";$eol"
                ." boundary=\"$boundary\"";
        }
        else if ($this->_type == self::PGP_ENCRYPTED) {
            $headers['Content-Type'] = "multipart/encrypted;$eol"
                ." protocol=\"application/pgp-encrypted\";$eol"
                ." boundary=\"$boundary\"";
        }
        return $headers;
    }
}
plugins/enigma/lib/enigma_signature.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | Signature class for the Enigma Plugin                                   |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
plugins/enigma/lib/enigma_subkey.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | SubKey class for the Enigma Plugin                                      |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -31,7 +24,7 @@
    public $has_private;
    public $can_sign;
    public $can_encrypt;
    /**
     * Converts internal ID to short ID
     * Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
plugins/enigma/lib/enigma_ui.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | User Interface for the Enigma Plugin                                    |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
@@ -45,7 +38,7 @@
     *
     * @param string Preferences section
     */
    function init($section='')
    function init()
    {
        $this->add_js();
@@ -150,7 +143,12 @@
        $data = array('keyid' => key($data), 'user' => $data[key($data)]);
        $this->rc->output->set_env('enigma_password_request', $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',
@@ -176,7 +174,7 @@
        $attrib['name'] = $attrib['id'];
        $this->rc->output->set_env('contentframe', $attrib['name']);
        $this->rc->output->set_env('blankpage', $attrib['src'] ?
        $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);
@@ -223,9 +221,6 @@
        $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);
        // define list of cols to be displayed
//        $a_show_cols = array('name');
        // Get the list
        $list = $this->enigma->engine->list_keys($search);
@@ -233,24 +228,21 @@
            $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);
        else if (is_array($list)) {
            // Save the size
            $listsize = count($list);
                // Sort the list by key (user) name
                usort($list, array('enigma_key', 'cmp'));
            // Sort the list by key (user) name
            usort($list, array('enigma_key', 'cmp'));
                // Slice current page
                $list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
            // Slice current page
            $list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
            $size = count($list);
                $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));
                }
            // Add rows
            foreach ($list as $key) {
                $this->rc->output->command('enigma_add_list_row',
                    array('name' => rcube::Q($key->name), 'id' => $key->id));
            }
        }
@@ -285,11 +277,12 @@
     */
    private function get_rowcount_text($all=0, $curr_count=0, $page=1)
    {
        if (!$curr_count)
        if (!$curr_count) {
            $out = $this->enigma->gettext('nokeysfound');
        }
        else {
            $pagesize = $this->rc->config->get('pagesize', 100);
            $first = ($page - 1) * $pagesize;
            $first    = ($page - 1) * $pagesize;
            $out = $this->enigma->gettext(array(
                'name' => 'keysfromto',
@@ -485,18 +478,16 @@
    private function compose_ui()
    {
/*
        $this->add_css();
        // Options menu button
        // @TODO: make this work with non-default skins
        $this->enigma->add_button(array(
            'type'     => 'link',
            'command'  => 'plugin.enigma',
            'onclick'  => "rcmail.command('menu-open', 'enigmamenu', event.target, event)",
            'class'    => 'button enigma',
            'title'    => 'securityoptions',
            'label'    => 'securityoptions',
            'title'    => 'encryptionoptions',
            'label'    => 'encryption',
            'domain'   => $this->enigma->ID,
            'width'    => 32,
            'height'   => 32
@@ -504,30 +495,27 @@
        // Options menu contents
        $this->enigma->add_hook('render_page', array($this, 'compose_menu'));
*/
    }
    function compose_menu($p)
    {
        $menu = new html_table(array('cols' => 2));
        $menu  = new html_table(array('cols' => 2));
        $chbox = new html_checkbox(array('value' => 1));
        $menu->add(null, html::label(array('for' => 'enigmadefaultopt'),
            rcube::Q($this->enigma->gettext('identdefault'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_default', 'id' => 'enigmadefaultopt')));
        $menu->add(null, html::label(array('for' => 'enigmasignopt'),
            rcube::Q($this->enigma->gettext('signmsg'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_sign', 'id' => 'enigmasignopt')));
        $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' => 'enigmacryptopt'),
        $menu->add(null, html::label(array('for' => 'enigmaencryptopt'),
            rcube::Q($this->enigma->gettext('encryptmsg'))));
        $menu->add(null, $chbox->show(1, array('name' => '_enigma_crypt', 'id' => 'enigmacryptopt')));
        $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());
        $p['content'] = preg_replace('/(<form name="form"[^>]+>)/i', '\\1'."\n$menu", $p['content']);
        $p['content'] .= $menu;
        return $p;
    }
@@ -715,4 +703,47 @@
        return $p;
    }
    /**
     * Handle message_ready hook (encryption/signing)
     */
    function message_ready($p)
    {
        $savedraft = !empty($_POST['_draft']) && empty($_GET['_saveonly']);
        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::E_KEYNOTFOUND) {
                $vars = array('email' => $status->getData('missing'));
                $msg  = 'enigma.' . $mode . 'nokey';
            }
            else if ($code == enigma_error::E_BADPASS) {
                $msg  = 'enigma.' . $mode . 'badpass';
                $type = 'warning';
                $this->password_prompt($status);
            }
            else {
                $msg = 'enigma.' . $mode . 'error';
            }
            $this->rc->output->show_message($msg, $type ?: 'error', $vars);
            $this->rc->output->send('iframe');
        }
        return $p;
    }
}
plugins/enigma/lib/enigma_userid.php
@@ -3,18 +3,11 @@
 +-------------------------------------------------------------------------+
 | User ID class for the Enigma Plugin                                     |
 |                                                                         |
 | This program is free software; you can redistribute it and/or modify    |
 | it under the terms of the GNU General Public License version 2          |
 | as published by the Free Software Foundation.                           |
 | Copyright (C) 2010-2015 The Roundcube Dev Team                          |
 |                                                                         |
 | This program is distributed in the hope that it will be useful,         |
 | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
 | GNU General Public License for more details.                            |
 |                                                                         |
 | You should have received a copy of the GNU General Public License along |
 | with this program; if not, write to the Free Software Foundation, Inc., |
 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.             |
 | 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>                              |
plugins/enigma/localization/en_US.inc
@@ -1,7 +1,7 @@
<?php
$labels = array();
$labels['enigma'] = 'Enigma';
$labels['encryption'] = 'Encryption';
$labels['enigmacerts'] = 'S/MIME Certificates';
$labels['enigmakeys'] = 'PGP Keys';
$labels['keysfromto'] = 'Keys $from to $to of $count';
@@ -18,6 +18,9 @@
$labels['keyattfound'] = 'This message contains attached PGP key(s).';
$labels['keyattimport'] = 'Import key(s)';
$labels['signdefault'] = 'Sign all messages by default';
$labels['encryptdefault'] = 'Encrypt all messages by default';
$labels['createkeys'] = 'Create a new key pair';
$labels['importkeys'] = 'Import key(s)';
$labels['exportkeys'] = 'Export key(s)';
@@ -28,7 +31,7 @@
$labels['keysend'] = 'Send public key in a message';
$labels['keychpass'] = 'Change password';
$labels['securityoptions'] = 'Message security options...';
$labels['encryptionoptions'] = 'Encryption options...';
$labels['identdefault'] = 'Use settings of selected identity';
$labels['encryptmsg'] = 'Encrypt this message';
$labels['signmsg'] = 'Digitally sign this message';
@@ -46,6 +49,11 @@
$messages['decrypterror'] = 'Decryption failed.';
$messages['decryptnokey'] = 'Decryption failed. Private key not found. Key ID: $keyid.';
$messages['decryptbadpass'] = 'Decryption failed. Bad password.';
$messages['signerror'] = 'Signing failed.';
$messages['signnokey'] = 'Signing failed. Private key not found.';
$messages['signbadpass'] = 'Signing failed. Bad password.';
$messages['encrypterror'] = 'Encryption failed.';
$messages['encryptnokey'] = 'Encryption failed. Public key not found for $email.';
$messages['nokeysfound'] = 'No keys found';
$messages['keyopenerror'] = 'Unable to get key information! Internal error.';
$messages['keylisterror'] = 'Unable to list keys! Internal error.';
plugins/enigma/localization/ru_RU.inc
@@ -16,7 +16,6 @@
@version 2010-12-23
*/
$labels['enigma'] = 'Enigma';
$labels['enigmacerts'] = 'Enigma: Сертификаты (S/MIME)';
$labels['enigmakeys'] = 'Enigma: Ключи (PGP)';
$labels['keysfromto'] = 'Ключи от $from к $to в количестве $count';
plugins/enigma/skins/larry/enigma.css
@@ -65,9 +65,14 @@
/***** E-mail Compose Page *****/
#messagetoolbar a.button.enigma {
  text-indent: -5000px;
  background: url(enigma_icons.png) center -122px no-repeat;
}
#enigmamenu {
  color: white;
  padding: 2px 5px;
}
/***** Keys/Certs Management *****/
@@ -83,6 +88,18 @@
  background-repeat: no-repeat;
}
#sections-table #rcmrowenigma .section {
  background-image: url(enigma_icons.png);
  background-position: 5px -297px;
  background-repeat: no-repeat;
}
#sections-table #rcmrowenigma.selected .section {
  background-image: url(enigma_icons.png);
  background-position: 5px -321px;
  background-repeat: no-repeat;
}
#mainscreen.enigma #settings-sections,
#mainscreen.enigma #settings-right
{