From a99c34159d03f2b5b525d6d8cc38509c4ac2f0a1 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 31 Mar 2015 18:56:32 +0200 Subject: Enigma: Implemented messages signing and encrypting --- plugins/enigma/README | 17 +- plugins/enigma/config.inc.php.dist | 6 + plugins/enigma/enigma.js | 66 +++++- plugins/enigma/enigma.php | 119 ++++++++--- plugins/enigma/lib/enigma_driver.php | 18 +- plugins/enigma/lib/enigma_driver_gnupg.php | 62 +++--- plugins/enigma/lib/enigma_driver_phpssl.php | 19 +- plugins/enigma/lib/enigma_engine.php | 287 ++++++++++++++++++++++++-- plugins/enigma/lib/enigma_error.php | 29 +-- plugins/enigma/lib/enigma_key.php | 40 +++- plugins/enigma/lib/enigma_mime_message.php | 299 ++++++++++++++++++++++++++++ plugins/enigma/lib/enigma_signature.php | 15 +- plugins/enigma/lib/enigma_subkey.php | 17 +- plugins/enigma/lib/enigma_ui.php | 127 +++++++----- plugins/enigma/lib/enigma_userid.php | 15 +- plugins/enigma/localization/en_US.inc | 12 +- plugins/enigma/localization/ru_RU.inc | 1 - plugins/enigma/skins/larry/enigma.css | 19 +- 18 files changed, 947 insertions(+), 221 deletions(-) create mode 100644 plugins/enigma/lib/enigma_mime_message.php diff --git a/plugins/enigma/README b/plugins/enigma/README index 3026b8442..7aadbd0f6 100644 --- a/plugins/enigma/README +++ b/plugins/enigma/README @@ -6,26 +6,25 @@ WARNING: Don't use with gnupg-2.x! 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 @@ TODO (later): - 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 diff --git a/plugins/enigma/config.inc.php.dist b/plugins/enigma/config.inc.php.dist index 2adb4d9f6..b58ce8bad 100644 --- a/plugins/enigma/config.inc.php.dist +++ b/plugins/enigma/config.inc.php.dist @@ -12,3 +12,9 @@ $config['enigma_smime_driver'] = 'phpssl'; // 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; diff --git a/plugins/enigma/enigma.js b/plugins/enigma/enigma.js index a7bc43f93..4048d8d85 100644 --- a/plugins/enigma/enigma.js +++ b/plugins/enigma/enigma.js @@ -33,6 +33,9 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { } 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,10 +237,46 @@ rcube_webmail.prototype.enigma_add_list_row = function(r) 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 @@ rcube_webmail.prototype.enigma_import_attachment = function(mime_id) return false; } +// password request popup rcube_webmail.prototype.enigma_password_request = function(data) { if (!data || !data.keyid) { @@ -268,7 +308,8 @@ rcube_webmail.prototype.enigma_password_request = function(data) .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 @@ rcube_webmail.prototype.enigma_password_request = function(data) } } +// 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 = $('
').attr({method: 'post', action: location.href, style: 'display:none'}) .append($('').attr({type: 'hidden', name: '_keyid', value: keyid})) .append($('').attr({type: 'hidden', name: '_passwd', value: password})) @@ -320,3 +367,20 @@ rcube_webmail.prototype.enigma_password_submit = function(keyid, password) 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($('').attr({type: 'hidden', name: '_keyid', value: keyid})) + .append($('').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'); +} diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php index c190dd6f5..0c732906b 100644 --- a/plugins/enigma/enigma.php +++ b/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 | @@ -43,8 +36,6 @@ class enigma extends rcube_plugin $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 @@ class enigma extends rcube_plugin // 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 @@ class enigma extends rcube_plugin 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(); @@ -208,6 +199,23 @@ class enigma extends rcube_plugin return $args; } + /** + * 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 @@ class enigma extends rcube_plugin */ 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 @@ class enigma extends rcube_plugin */ 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,9 +361,20 @@ class enigma extends rcube_plugin 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. */ diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php index c0a91ac27..49208b39d 100644 --- a/plugins/enigma/lib/enigma_driver.php +++ b/plugins/enigma/lib/enigma_driver.php @@ -1,20 +1,14 @@ | @@ -53,7 +47,7 @@ abstract class enigma_driver /** * Signing. */ - abstract function sign($text, $key, $passwd); + abstract function sign($text, $key, $passwd, $mode = null); /** * Signature verification. diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php index 09e23d36c..52a0ad66c 100644 --- a/plugins/enigma/lib/enigma_driver_gnupg.php +++ b/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 | @@ -30,6 +23,7 @@ class enigma_driver_gnupg extends enigma_driver private $homedir; private $user; + function __construct($user) { $this->rc = rcmail::get_instance(); @@ -86,30 +80,40 @@ class enigma_driver_gnupg extends enigma_driver } } + /** + * 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; + } + catch (Exception $e) { + return $this->get_error_from_exception($e); } - $enc = $this->gpg->encrypt($text); - return $enc; -*/ } /** - * 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 @@ class enigma_driver_gnupg extends enigma_driver } } - 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) diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php index 6686d7dfa..0250893d2 100644 --- a/plugins/enigma/lib/enigma_driver_phpssl.php +++ b/plugins/enigma/lib/enigma_driver_phpssl.php @@ -1,20 +1,13 @@ | @@ -81,7 +74,7 @@ class enigma_driver_phpssl extends enigma_driver { } - function sign($text, $key, $passwd) + function sign($text, $key, $passwd, $mode = null) { } diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php index c3a2e503f..9b929947b 100644 --- a/plugins/enigma/lib/enigma_engine.php +++ b/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 | +-------------------------------------------------------------------------+ - */ /* @@ -41,6 +33,13 @@ class enigma_engine 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; + /** * Plugin initialization. @@ -124,6 +123,175 @@ class enigma_engine } } + /** + * 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; + } + } + /** * Handler for message_part_structure hook. * Called for every part of the message. @@ -510,7 +678,6 @@ class enigma_engine 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,7 +700,6 @@ class enigma_engine 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); @@ -553,6 +719,66 @@ class enigma_engine 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(); + 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 keys listing. * @@ -576,6 +802,39 @@ class enigma_engine 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. * diff --git a/plugins/enigma/lib/enigma_error.php b/plugins/enigma/lib/enigma_error.php index 7122e8e7b..1717a7c46 100644 --- a/plugins/enigma/lib/enigma_error.php +++ b/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 | @@ -28,14 +21,14 @@ class enigma_error 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()) diff --git a/plugins/enigma/lib/enigma_key.php b/plugins/enigma/lib/enigma_key.php index 66670c5d5..8c61cbd99 100644 --- a/plugins/enigma/lib/enigma_key.php +++ b/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 | @@ -32,6 +25,9 @@ class enigma_key const TYPE_KEYPAIR = 1; const TYPE_PUBLIC = 2; + const CAN_SIGN = 1; + const CAN_ENCRYPT = 2; + /** * Keys list sorting callback for usort() */ @@ -91,6 +87,28 @@ class enigma_key return false; } + /** + * 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 diff --git a/plugins/enigma/lib/enigma_mime_message.php b/plugins/enigma/lib/enigma_mime_message.php new file mode 100644 index 000000000..feed78e03 --- /dev/null +++ b/plugins/enigma/lib/enigma_mime_message.php @@ -0,0 +1,299 @@ + | + +-------------------------------------------------------------------------+ +*/ + +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; + } +} diff --git a/plugins/enigma/lib/enigma_signature.php b/plugins/enigma/lib/enigma_signature.php index 65990903b..2e63a80f4 100644 --- a/plugins/enigma/lib/enigma_signature.php +++ b/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 | diff --git a/plugins/enigma/lib/enigma_subkey.php b/plugins/enigma/lib/enigma_subkey.php index 1b9fb95ad..cd57611c0 100644 --- a/plugins/enigma/lib/enigma_subkey.php +++ b/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 | @@ -31,7 +24,7 @@ class enigma_subkey 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 diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php index 26396f1dd..e866ba335 100644 --- a/plugins/enigma/lib/enigma_ui.php +++ b/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 | @@ -45,7 +38,7 @@ class enigma_ui * * @param string Preferences section */ - function init($section='') + function init() { $this->add_js(); @@ -150,7 +143,12 @@ class enigma_ui $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 @@ class enigma_ui $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 @@ class enigma_ui $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 @@ class enigma_ui $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 @@ class enigma_ui */ 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 @@ class enigma_ui 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 @@ class enigma_ui // 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('/(]+>)/i', '\\1'."\n$menu", $p['content']); + $p['content'] .= $menu; return $p; } @@ -715,4 +703,47 @@ class enigma_ui 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; + } + } diff --git a/plugins/enigma/lib/enigma_userid.php b/plugins/enigma/lib/enigma_userid.php index 36185e718..da0358445 100644 --- a/plugins/enigma/lib/enigma_userid.php +++ b/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 | diff --git a/plugins/enigma/localization/en_US.inc b/plugins/enigma/localization/en_US.inc index 5e8889faf..d8e80a871 100644 --- a/plugins/enigma/localization/en_US.inc +++ b/plugins/enigma/localization/en_US.inc @@ -1,7 +1,7 @@