Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F223601
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
153 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/plugins/enigma/README b/plugins/enigma/README
index c4e474be2..3026b8442 100644
--- a/plugins/enigma/README
+++ b/plugins/enigma/README
@@ -1,36 +1,55 @@
------------------------------------------------------------------
THIS IS NOT EVEN AN "ALPHA" STATE. USE ONLY FOR DEVELOPMENT!!!!!!!
------------------------------------------------------------------
WARNING: Don't use with gnupg-2.x!
-Enigma Plugin Status:
+Plugin Status:
-* DONE:
++ PGP: signed messages verification
++ PGP: messages decryption
++ PGP: keys management UI (keys import and delete)
++ Handling of PGP keys attached to incoming messages
-- PGP signed messages verification
-- Handling of PGP keys files attached to incoming messages
-- PGP encrypted messages decryption (started)
-- PGP keys management UI (started)
-- S/MIME signatures verification (started)
+TODO (must have):
-* TODO (must have):
-
-- Parsing of decrypted messages into array (see rcube_mime_struct) and then into rcube_message_part structure
- (create core class rcube_mime_parser or take over PEAR::Mail_mimeDecode package and improve it)
-- Sending encrypted/signed messages (probably some changes in core will be needed)
+- Fix issues with enabled messages_cache
+- PGP: Sending of encrypted/signed messages
- Per-Identity settings (including keys/certs)
-- Handling big messages with temp files (including changes in Roundcube core)
-- Performance improvements (some caching, code review)
-- better (and more) icons
-
-* TODO (later):
-
-- Keys generation
-- Certs generation
-- Keys/Certs info in Contacts details page (+ split Contact details page into tabs)
-- Key server support
-- S/MIME signed messages verification
-- S/MIME encrypted messages decryption
-- Handling of S/MIME certs files attached to incoming messages
-- SSL (S/MIME) Certs management
+- Test/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
+- 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)
+
+TODO (later):
+
+- Handling of big messages with temp files
+- Server-side keys generation (warning: no-entropy issue, max_execution_time issue)
+- Client-side keys generation (with OpenPGP.js?)
+- Key info in contact details page (optional)
+- Extended key management:
+ - disable,
+ - revoke,
+ - change expiration date, change passphrase, add photo,
+ - manage user IDs
+- Generate revocation certs
+- Search filter to see invalid/expired keys
+- Key server(s) support (search, import, upload, refresh)
+- Attaching public keys to email
+- 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)
+
+- S/MIME: Certs generation
+- S/MIME: Certs management
+- S/MIME: signed messages verification
+- S/MIME: encrypted messages decryption
+- S/MIME: Sending signed/encrypted messages
+- S/MIME: Handling of certs attached to incoming messages
+- S/MIME: Certificate info in Contacts details page (optional)
diff --git a/plugins/enigma/enigma.js b/plugins/enigma/enigma.js
index 29c648224..a7bc43f93 100644
--- a/plugins/enigma/enigma.js
+++ b/plugins/enigma/enigma.js
@@ -1,206 +1,322 @@
/* Enigma Plugin */
-if (window.rcmail)
-{
- rcmail.addEventListener('init', function(evt)
- {
- if (rcmail.env.task == 'settings') {
- rcmail.register_command('plugin.enigma', function() { rcmail.goto_url('plugin.enigma') }, true);
- rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import() }, true);
- rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_key_export() }, true);
-
- if (rcmail.gui_objects.keyslist)
- {
- var p = rcmail;
- rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist,
- {multiselect:false, draggable:false, keyboard:false});
- rcmail.keys_list.addEventListener('select', function(o){ p.enigma_key_select(o); });
- rcmail.keys_list.init();
- rcmail.keys_list.focus();
-
- rcmail.enigma_list();
-
- rcmail.register_command('firstpage', function(props) {return rcmail.enigma_list_page('first'); });
- rcmail.register_command('previouspage', function(props) {return rcmail.enigma_list_page('previous'); });
- rcmail.register_command('nextpage', function(props) {return rcmail.enigma_list_page('next'); });
- rcmail.register_command('lastpage', function(props) {return rcmail.enigma_list_page('last'); });
- }
+window.rcmail && rcmail.addEventListener('init', function(evt) {
+ if (rcmail.env.task == 'settings') {
+ rcmail.register_command('plugin.enigma', function() { rcmail.goto_url('plugin.enigma') }, true);
+ rcmail.register_command('plugin.enigma-key-import', function() { rcmail.enigma_key_import() }, true);
+// rcmail.register_command('plugin.enigma-key-export', function() { rcmail.enigma_key_export() }, true);
+ rcmail.register_command('plugin.enigma-key-delete', function(props) { return rcmail.enigma_key_delete(); });
+
+ if (rcmail.gui_objects.keyslist) {
+ rcmail.keys_list = new rcube_list_widget(rcmail.gui_objects.keyslist,
+ {multiselect:false, draggable:false, keyboard:false});
+ rcmail.keys_list
+ .addEventListener('select', function(o) { rcmail.enigma_keylist_select(o); })
+ .addEventListener('keypress', function(o) { rcmail.enigma_keylist_keypress(o); })
+ .init()
+ .focus();
+
+ rcmail.enigma_list();
+
+ rcmail.register_command('firstpage', function(props) { return rcmail.enigma_list_page('first'); });
+ rcmail.register_command('previouspage', function(props) { return rcmail.enigma_list_page('previous'); });
+ rcmail.register_command('nextpage', function(props) { return rcmail.enigma_list_page('next'); });
+ rcmail.register_command('lastpage', function(props) { return rcmail.enigma_list_page('last'); });
+ }
- if (rcmail.env.action == 'edit-prefs') {
- rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true);
- rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true);
- }
- else if (rcmail.env.action == 'plugin.enigma') {
- rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import() }, true);
- rcmail.register_command('plugin.enigma-export', function() { rcmail.enigma_export() }, true);
+ if (rcmail.env.action == 'plugin.enigmakeys') {
+ rcmail.register_command('search', function(props) {return rcmail.enigma_search(props); }, true);
+ rcmail.register_command('reset-search', function(props) {return rcmail.enigma_search_reset(props); }, true);
+ rcmail.register_command('plugin.enigma-import', function() { rcmail.enigma_import(); }, true);
+// rcmail.register_command('plugin.enigma-export', function() { rcmail.enigma_export(); }, true);
+ }
+ }
+ else if (rcmail.env.task == 'mail') {
+ if (rcmail.env.action == 'compose') {
+ $('input,label', $('#enigmamenu')).mouseup(function(e) {
+ // don't close the menu on mouse click inside
+ e.stopPropagation();
+ });
+ }
+ else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
+ if (rcmail.env.enigma_password_request) {
+ rcmail.enigma_password_request(rcmail.env.enigma_password_request);
}
}
- });
-}
+ }
+});
+
/*********************************************************/
/********* Enigma Settings/Keys/Certs UI *********/
/*********************************************************/
// Display key(s) import form
rcube_webmail.prototype.enigma_key_import = function()
{
- this.enigma_loadframe(null, '&_a=keyimport');
+ this.enigma_loadframe('&_action=plugin.enigmakeys&_a=import');
+};
+
+// Delete key(s)
+rcube_webmail.prototype.enigma_key_delete = function()
+{
+ var keys = this.keys_list.get_selection();
+
+ if (!keys.length || !confirm(this.get_label('enigma.keyremoveconfirm')))
+ return;
+
+ var lock = this.display_message(this.get_label('enigma.keyremoving'), 'loading'),
+ post = {_a: 'delete', _keys: keys};
+
+ // send request to server
+ this.http_post('plugin.enigmakeys', post, lock);
};
-// Submit key(s) form
+// Submit key(s) import form
rcube_webmail.prototype.enigma_import = function()
{
var form, file;
+
if (form = this.gui_objects.importform) {
file = document.getElementById('rcmimportfile');
if (file && !file.value) {
alert(this.get_label('selectimportfile'));
return;
}
+
+ var lock = this.set_busy(true, 'importwait');
+
+ form.action = this.add_url(form.action, '_unlock', lock);
form.submit();
- this.set_busy(true, 'importwait');
+
this.lock_form(form, true);
}
};
// list row selection handler
-rcube_webmail.prototype.enigma_key_select = function(list)
+rcube_webmail.prototype.enigma_keylist_select = function(list)
{
var id;
if (id = list.get_single_selection())
- this.enigma_loadframe(id);
+ this.enigma_loadframe('&_action=plugin.enigmakeys&_a=info&_id=' + id);
+
+ this.enable_command('plugin.enigma-key-delete', list.selection.length > 0);
+};
+
+rcube_webmail.prototype.enigma_keylist_keypress = function(list)
+{
+ if (list.modkey == CONTROL_KEY)
+ return;
+
+ if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
+ this.command('plugin.enigma-key-delete');
+ else if (list.key_pressed == 33)
+ this.command('previouspage');
+ else if (list.key_pressed == 34)
+ this.command('nextpage');
};
// load key frame
-rcube_webmail.prototype.enigma_loadframe = function(id, url)
+rcube_webmail.prototype.enigma_loadframe = function(url)
{
var frm, win;
+
if (this.env.contentframe && window.frames && (frm = window.frames[this.env.contentframe])) {
- if (!id && !url && (win = window.frames[this.env.contentframe])) {
- if (win.location && win.location.href.indexOf(this.env.blankpage)<0)
+ if (!url && (win = window.frames[this.env.contentframe])) {
+ if (win.location && win.location.href.indexOf(this.env.blankpage) < 0)
win.location.href = this.env.blankpage;
return;
}
+
this.set_busy(true);
- if (!url)
- url = '&_a=keyinfo&_id='+id;
- frm.location.href = this.env.comm_path+'&_action=plugin.enigma&_framed=1' + url;
+ frm.location.href = this.env.comm_path + '&_framed=1' + url;
}
};
// Search keys/certs
rcube_webmail.prototype.enigma_search = function(props)
{
if (!props && this.gui_objects.qsearchbox)
props = this.gui_objects.qsearchbox.value;
if (props || this.env.search_request) {
- var params = {'_a': 'keysearch', '_q': urlencode(props)},
+ var params = {'_a': 'search', '_q': urlencode(props)},
lock = this.set_busy(true, 'searching');
// if (this.gui_objects.search_filter)
// addurl += '&_filter=' + this.gui_objects.search_filter.value;
- this.env.current_page = 1;
+ this.env.current_page = 1;
this.enigma_loadframe();
this.enigma_clear_list();
- this.http_post('plugin.enigma', params, lock);
+ this.http_post('plugin.enigmakeys', params, lock);
}
return false;
}
// Reset search filter and the list
rcube_webmail.prototype.enigma_search_reset = function(props)
{
var s = this.env.search_request;
this.reset_qsearch();
if (s) {
this.enigma_loadframe();
this.enigma_clear_list();
// refresh the list
this.enigma_list();
}
return false;
}
// Keys/certs listing
rcube_webmail.prototype.enigma_list = function(page)
{
- var params = {'_a': 'keylist'},
+ var params = {'_a': 'list'},
lock = this.set_busy(true, 'loading');
this.env.current_page = page ? page : 1;
if (this.env.search_request)
params._q = this.env.search_request;
if (page)
params._p = page;
this.enigma_clear_list();
- this.http_post('plugin.enigma', params, lock);
+ this.http_post('plugin.enigmakeys', params, lock);
}
// Change list page
rcube_webmail.prototype.enigma_list_page = function(page)
{
if (page == 'next')
page = this.env.current_page + 1;
else if (page == 'last')
page = this.env.pagecount;
else if (page == 'prev' && this.env.current_page > 1)
page = this.env.current_page - 1;
else if (page == 'first' && this.env.current_page > 1)
page = 1;
this.enigma_list(page);
}
// Remove list rows
rcube_webmail.prototype.enigma_clear_list = function()
{
this.enigma_loadframe();
if (this.keys_list)
this.keys_list.clear(true);
}
// Adds a row to the list
rcube_webmail.prototype.enigma_add_list_row = function(r)
{
if (!this.gui_objects.keyslist || !this.keys_list)
return false;
var list = this.keys_list,
tbody = this.gui_objects.keyslist.tBodies[0],
rowcount = tbody.rows.length,
even = rowcount%2,
css_class = 'message'
+ (even ? ' even' : ' odd'),
// for performance use DOM instead of jQuery here
row = document.createElement('tr'),
col = document.createElement('td');
row.id = 'rcmrow' + r.id;
row.className = css_class;
col.innerHTML = r.name;
row.appendChild(col);
list.insert_row(row);
}
/*********************************************************/
/********* Enigma Message methods *********/
/*********************************************************/
// Import attached keys/certs file
rcube_webmail.prototype.enigma_import_attachment = function(mime_id)
{
- var lock = this.set_busy(true, 'loading');
- this.http_post('plugin.enigmaimport', '_uid='+this.env.uid+'&_mbox='
- +urlencode(this.env.mailbox)+'&_part='+urlencode(mime_id), lock);
+ var lock = this.set_busy(true, 'loading'),
+ post = {_uid: this.env.uid, _mbox: this.env.mailbox, _part: mime_id};
+
+ this.http_post('plugin.enigmaimport', post, lock);
return false;
-};
+}
+
+rcube_webmail.prototype.enigma_password_request = function(data)
+{
+ if (!data || !data.keyid) {
+ return;
+ }
+
+ var ref = this,
+ msg = this.get_label('enigma.enterkeypass'),
+ myprompt = $('<div class="prompt">'),
+ myprompt_content = $('<div class="message">')
+ .appendTo(myprompt),
+ myprompt_input = $('<input>').attr({type: 'password', size: 30})
+ .keypress(function(e) {
+ if (e.which == 13)
+ (ref.is_framed() ? window.parent.$ : $)('.ui-dialog-buttonpane button.mainaction:visible').click();
+ })
+ .appendTo(myprompt);
+
+ data.key = data.keyid;
+ data.keyid = data.keyid.substr(0, 8);
+
+ $.each(['keyid', 'user'], function() {
+ msg = msg.replace('$' + this, data[this]);
+ });
+
+ myprompt_content.text(msg);
+ this.show_popup_dialog(myprompt, this.get_label('enigma.enterkeypasstitle'),
+ [{
+ text: this.get_label('save'),
+ 'class': 'mainaction',
+ click: function(e) {
+ e.stopPropagation();
+
+ var jq = ref.is_framed() ? window.parent.$ : $,
+ pass = myprompt_input.val();
+
+ if (!pass) {
+ myprompt_input.focus();
+ return;
+ }
+
+ ref.enigma_password_submit(data.key, pass);
+ jq(this).remove();
+ }
+ },
+ {
+ text: this.get_label('cancel'),
+ click: function(e) {
+ var jq = ref.is_framed() ? window.parent.$ : $;
+ e.stopPropagation();
+ jq(this).remove();
+ }
+ }], {width: 400});
+
+ if (this.is_framed() && parent.rcmail.message_list) {
+ // this fixes bug when pressing Enter on "Save" button in the dialog
+ parent.rcmail.message_list.blur();
+ }
+}
+
+rcube_webmail.prototype.enigma_password_submit = function(keyid, password)
+{
+ 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}))
+ .append($('<input>').attr({type: 'hidden', name: '_token', value: this.env.request_token}))
+ .appendTo(document.body);
+
+ form.submit();
+}
diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php
index 870b923b6..c190dd6f5 100644
--- a/plugins/enigma/enigma.php
+++ b/plugins/enigma/enigma.php
@@ -1,485 +1,330 @@
<?php
/*
+-------------------------------------------------------------------------+
| Enigma Plugin for Roundcube |
- | Version 0.1 |
| |
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/*
This class contains only hooks and action handlers.
Most plugin logic is placed in enigma_engine and enigma_ui classes.
*/
class enigma extends rcube_plugin
{
public $task = 'mail|settings';
public $rc;
public $engine;
- private $env_loaded;
- private $message;
- private $keys_parts = array();
- private $keys_bodies = array();
+ private $env_loaded = false;
/**
* Plugin initialization.
*/
function init()
{
- $rcmail = rcmail::get_instance();
- $this->rc = $rcmail;
-
- $section = rcube_utils::get_input_value('_section', rcube_utils::INPUT_GET);
+ $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, 'parse_structure'));
+ $this->add_hook('message_part_structure', array($this, 'part_structure'));
+ $this->add_hook('message_part_body', array($this, 'part_body'));
$this->add_hook('message_body_prefix', array($this, 'status_message'));
+ $this->register_action('plugin.enigmaimport', array($this, 'import_file'));
+
// message displaying
- if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
+ if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$this->add_hook('message_load', array($this, 'message_load'));
$this->add_hook('template_object_messagebody', array($this, 'message_output'));
- $this->register_action('plugin.enigmaimport', array($this, 'import_file'));
}
// message composing
- else if ($rcmail->action == 'compose') {
+ else if ($this->rc->action == 'compose') {
$this->load_ui();
$this->ui->init($section);
}
// message sending (and draft storing)
- else if ($rcmail->action == 'sendmail') {
+ 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'));
}
+
+ $this->password_handler();
}
else if ($this->rc->task == 'settings') {
// add hooks for Enigma settings
- $this->add_hook('preferences_sections_list', array($this, 'preferences_section'));
- $this->add_hook('preferences_list', array($this, 'preferences_list'));
- $this->add_hook('preferences_save', array($this, 'preferences_save'));
+ $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'));
// register handler for keys/certs management
- $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
+// $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'));
- // grab keys/certs management iframe requests
- if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
- $this->load_ui();
- $this->ui->init($section);
- }
+ $this->load_ui();
+ $this->ui->add_css();
}
+
+ $this->add_hook('refresh', array($this, 'refresh'));
}
/**
* Plugin environment initialization.
*/
function load_env()
{
- if ($this->env_loaded)
+ if ($this->env_loaded) {
return;
+ }
$this->env_loaded = true;
// Add include path for Enigma classes and drivers
$include_path = $this->home . '/lib' . PATH_SEPARATOR;
$include_path .= ini_get('include_path');
set_include_path($include_path);
// load the Enigma plugin configuration
$this->load_config();
// include localization (if wasn't included before)
$this->add_texts('localization/');
}
/**
* Plugin UI initialization.
*/
- function load_ui()
+ function load_ui($all = false)
{
- if ($this->ui)
- return;
+ if (!$this->ui) {
+ // load config/localization
+ $this->load_env();
- // load config/localization
- $this->load_env();
+ // Load UI
+ $this->ui = new enigma_ui($this, $this->home);
+ }
- // Load UI
- $this->ui = new enigma_ui($this, $this->home);
+ if ($all) {
+ $this->ui->add_css();
+ $this->ui->add_js();
+ }
}
/**
* Plugin engine initialization.
*/
function load_engine()
{
- if ($this->engine)
- return;
+ if ($this->engine) {
+ return $this->engine;
+ }
// load config/localization
$this->load_env();
- $this->engine = new enigma_engine($this);
+ return $this->engine = new enigma_engine($this);
}
/**
* Handler for message_part_structure hook.
* Called for every part of the message.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
- function parse_structure($p)
+ function part_structure($p)
{
-// $struct = $p['structure'];
+ $this->load_engine();
- if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
- $this->parse_plain($p);
- }
- else if ($p['mimetype'] == 'multipart/signed') {
- $this->parse_signed($p);
- }
- else if ($p['mimetype'] == 'multipart/encrypted') {
- $this->parse_encrypted($p);
- }
- else if ($p['mimetype'] == 'application/pkcs7-mime') {
- $this->parse_encrypted($p);
- }
+ return $this->engine->part_structure($p);
+ }
- return $p;
+ /**
+ * Handler for message_part_body hook.
+ * Called to get body of a message part.
+ *
+ * @param array Original parameters
+ *
+ * @return array Modified parameters
+ */
+ function part_body($p)
+ {
+ $this->load_engine();
+
+ return $this->engine->part_body($p);
}
/**
- * Handler for preferences_sections_list hook.
- * Adds Enigma settings sections into preferences sections list.
+ * Handler for settings_actions hook.
+ * Adds Enigma settings section into preferences.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
- function preferences_section($p)
+ function settings_actions($args)
{
// add labels
$this->add_texts('localization/');
+
+ // register as settings action
+ $args['actions'][] = array(
+ 'action' => 'plugin.enigmakeys',
+ 'class' => 'enigma keys',
+ 'label' => 'enigmakeys',
+ 'title' => 'enigmakeys',
+ 'domain' => 'enigma',
+ );
/*
- $p['list']['enigmasettings'] = array(
- 'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
+ $args['actions'][] = array(
+ 'action' => 'plugin.enigmacerts',
+ 'class' => 'enigma certs',
+ 'label' => 'enigmacerts',
+ 'title' => 'enigmacerts',
+ 'domain' => 'enigma',
);
*/
- $p['list']['enigmacerts'] = array(
- 'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
- );
- $p['list']['enigmakeys'] = array(
- 'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
- );
-
- return $p;
+ return $args;
}
/**
* Handler for preferences_list hook.
* Adds options blocks into Enigma settings sections in Preferences.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_list($p)
{
/*
if ($p['section'] == 'enigmasettings') {
// This makes that section is not removed from the list
$p['blocks']['dummy']['options']['dummy'] = array();
}
- else */
- if ($p['section'] == 'enigmacerts') {
- // This makes that section is not removed from the list
- $p['blocks']['dummy']['options']['dummy'] = array();
- }
- else if ($p['section'] == 'enigmakeys') {
- // This makes that section is not removed from the list
- $p['blocks']['dummy']['options']['dummy'] = array();
- }
-
+*/
return $p;
}
/**
* Handler for preferences_save hook.
* Executed on Enigma settings form submit.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function preferences_save($p)
{
+/*
if ($p['section'] == 'enigmasettings') {
$a['prefs'] = array(
-// 'dummy' => rcube_utils::get_input_value('_dummy', rcube_utils::INPUT_POST),
+ 'dummy' => rcube_utils::get_input_value('_dummy', rcube_utils::INPUT_POST),
);
}
-
+*/
return $p;
}
/**
* Handler for keys/certs management UI template.
*/
function preferences_ui()
{
$this->load_ui();
+
$this->ui->init();
}
/**
* Handler for message_body_prefix hook.
* Called for every displayed (content) part of the message.
* Adds infobox about signature verification and/or decryption
* status above the body.
*
* @param array Original parameters
*
* @return array Modified parameters
*/
function status_message($p)
{
- $part_id = $p['part']->mime_id;
-
- // skip: not a message part
- if ($p['part'] instanceof rcube_message)
- return $p;
-
- // skip: message has no signed/encoded content
- if (!$this->engine)
- return $p;
-
- // Decryption status
- if (isset($this->engine->decryptions[$part_id])) {
-
- // get decryption status
- $status = $this->engine->decryptions[$part_id];
-
- // Load UI and add css script
- $this->load_ui();
- $this->ui->add_css();
-
- // display status info
- $attrib['id'] = 'enigma-message';
-
- if ($status instanceof enigma_error) {
- $attrib['class'] = 'enigmaerror';
- $code = $status->getCode();
- if ($code == enigma_error::E_KEYNOTFOUND)
- $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
- $this->gettext('decryptnokey')));
- else if ($code == enigma_error::E_BADPASS)
- $msg = rcube::Q($this->gettext('decryptbadpass'));
- else
- $msg = rcube::Q($this->gettext('decrypterror'));
- }
- else {
- $attrib['class'] = 'enigmanotice';
- $msg = rcube::Q($this->gettext('decryptok'));
- }
-
- $p['prefix'] .= html::div($attrib, $msg);
- }
-
- // Signature verification status
- if (isset($this->engine->signed_parts[$part_id])
- && ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
- ) {
- // add css script
- $this->load_ui();
- $this->ui->add_css();
-
- // display status info
- $attrib['id'] = 'enigma-message';
-
- if ($sig instanceof enigma_signature) {
- $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
-
- if ($sig->valid === enigma_error::E_UNVERIFIED) {
- $attrib['class'] = 'enigmawarning';
- $msg = str_replace('$sender', $sender, $this->gettext('sigunverified'));
- $msg = str_replace('$keyid', $sig->id, $msg);
- $msg = rcube::Q($msg);
- }
- else if ($sig->valid) {
- $attrib['class'] = 'enigmanotice';
- $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
- }
- else {
- $attrib['class'] = 'enigmawarning';
- $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
- }
- }
- else if ($sig && $sig->getCode() == enigma_error::E_KEYNOTFOUND) {
- $attrib['class'] = 'enigmawarning';
- $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
- $this->gettext('signokey')));
- }
- else {
- $attrib['class'] = 'enigmaerror';
- $msg = rcube::Q($this->gettext('sigerror'));
- }
-/*
- $msg .= ' ' . html::a(array('href' => "#sigdetails",
- 'onclick' => rcmail_output::JS_OBJECT_NAME.".command('enigma-sig-details')"),
- rcube::Q($this->gettext('showdetails')));
-*/
- // test
-// $msg .= '<br /><pre>'.$sig->body.'</pre>';
-
- $p['prefix'] .= html::div($attrib, $msg);
-
- // Display each signature message only once
- unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
- }
-
- return $p;
- }
+ $this->load_ui();
- /**
- * Handler for plain/text message.
- *
- * @param array Reference to hook's parameters (see enigma::parse_structure())
- */
- private function parse_plain(&$p)
- {
- $this->load_engine();
- $this->engine->parse_plain($p);
- }
-
- /**
- * Handler for multipart/signed message.
- * Verifies signature.
- *
- * @param array Reference to hook's parameters (see enigma::parse_structure())
- */
- private function parse_signed(&$p)
- {
- $this->load_engine();
- $this->engine->parse_signed($p);
+ return $this->ui->status_message($p);
}
- /**
- * Handler for multipart/encrypted and application/pkcs7-mime message.
- *
- * @param array Reference to hook's parameters (see enigma::parse_structure())
- */
- private function parse_encrypted(&$p)
- {
- $this->load_engine();
- $this->engine->parse_encrypted($p);
- }
-
/**
* Handler for message_load hook.
* Check message bodies and attachments for keys/certs.
*/
function message_load($p)
{
- $this->message = $p['object'];
-
- // handle attachments vcard attachments
- foreach ((array)$this->message->attachments as $attachment) {
- if ($this->is_keys_part($attachment)) {
- $this->keys_parts[] = $attachment->mime_id;
- }
- }
- // the same with message bodies
- foreach ((array)$this->message->parts as $part) {
- if ($this->is_keys_part($part)) {
- $this->keys_parts[] = $part->mime_id;
- $this->keys_bodies[] = $part->mime_id;
- }
- }
- // @TODO: inline PGP keys
+ $this->load_ui();
- if ($this->keys_parts) {
- $this->add_texts('localization');
- }
+ return $this->ui->message_load($p);
}
/**
* Handler for template_object_messagebody hook.
* This callback function adds a box below the message content
* if there is a key/cert attachment available
*/
function message_output($p)
{
- $attach_script = false;
-
- foreach ($this->keys_parts as $part) {
-
- // remove part's body
- if (in_array($part, $this->keys_bodies))
- $p['content'] = '';
-
- $style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
- ." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
-
- // add box below message body
- $p['content'] .= html::p(array('style' => $style),
- html::a(array(
- 'href' => "#",
- 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".enigma_import_attachment('".rcube::JQ($part)."')",
- 'title' => $this->gettext('keyattimport')),
- html::img(array('src' => $this->url('skins/classic/key_add.png'), 'style' => "vertical-align:middle")))
- . ' ' . html::span(null, $this->gettext('keyattfound')));
-
- $attach_script = true;
- }
-
- if ($attach_script) {
- $this->include_script('enigma.js');
- }
+ $this->load_ui();
- return $p;
+ return $this->ui->message_output($p);
}
/**
* Handler for attached keys/certs import
*/
function import_file()
{
$this->load_engine();
+
$this->engine->import_file();
}
/**
- * Checks if specified message part is a PGP-key or S/MIME cert data
- *
- * @param rcube_message_part Part object
- *
- * @return boolean True if part is a key/cert
+ * Handle password submissions
*/
- private function is_keys_part($part)
+ function password_handler()
{
- // @TODO: S/MIME
- return (
- // Content-Type: application/pgp-keys
- $part->mimetype == 'application/pgp-keys'
- );
+ $this->load_engine();
+ $this->engine->password_handler();
+ }
+
+ /**
+ * Handler for refresh hook.
+ */
+ function refresh($p)
+ {
+ // calling enigma_engine constructor to remove passwords
+ // stored in session after expiration time
+ $this->load_engine();
+
+ return $p;
}
}
diff --git a/plugins/enigma/lib/enigma_driver.php b/plugins/enigma/lib/enigma_driver.php
index a9a3e4715..c0a91ac27 100644
--- a/plugins/enigma/lib/enigma_driver.php
+++ b/plugins/enigma/lib/enigma_driver.php
@@ -1,106 +1,109 @@
<?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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
abstract class enigma_driver
{
/**
* Class constructor.
*
* @param string User name (email address)
*/
abstract function __construct($user);
/**
* Driver initialization.
*
* @return mixed NULL on success, enigma_error on failure
*/
abstract function init();
/**
* Encryption.
*/
abstract function encrypt($text, $keys);
/**
* Decryption..
+ *
+ * @param string Encrypted message
+ * @param array List of key-password mapping
*/
- abstract function decrypt($text, $key, $passwd);
+ abstract function decrypt($text, $keys = array());
/**
* Signing.
*/
abstract function sign($text, $key, $passwd);
/**
* Signature verification.
*
* @param string Message body
* @param string Signature, if message is of type PGP/MIME and body doesn't contain it
*
* @return mixed Signature information (enigma_signature) or enigma_error
*/
abstract function verify($text, $signature);
/**
* Key/Cert file import.
*
* @param string File name or file content
* @param bollean True if first argument is a filename
*
* @return mixed Import status array or enigma_error
*/
abstract function import($content, $isfile=false);
/**
* Keys listing.
*
* @param string Optional pattern for key ID, user ID or fingerprint
*
* @return mixed Array of enigma_key objects or enigma_error
*/
abstract function list_keys($pattern='');
-
+
/**
* Single key information.
*
* @param string Key ID, user ID or fingerprint
*
* @return mixed Key (enigma_key) object or enigma_error
*/
abstract function get_key($keyid);
/**
* Key pair generation.
*
* @param array Key/User data
*
* @return mixed Key (enigma_key) object or enigma_error
*/
abstract function gen_key($data);
-
+
/**
* Key deletion.
*/
- abstract function del_key($keyid);
+ abstract function delete_key($keyid);
}
diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php
index c4280a089..09e23d36c 100644
--- a/plugins/enigma/lib/enigma_driver_gnupg.php
+++ b/plugins/enigma/lib/enigma_driver_gnupg.php
@@ -1,303 +1,320 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
require_once 'Crypt/GPG.php';
class enigma_driver_gnupg extends enigma_driver
{
private $rc;
private $gpg;
private $homedir;
private $user;
function __construct($user)
{
- $rcmail = rcmail::get_instance();
- $this->rc = $rcmail;
+ $this->rc = rcmail::get_instance();
$this->user = $user;
}
/**
* Driver initialization and environment checking.
* Should only return critical errors.
*
* @return mixed NULL on success, enigma_error on failure
*/
function init()
{
- $homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . '/plugins/enigma/home');
+ $homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . 'plugins/enigma/home');
if (!$homedir)
return new enigma_error(enigma_error::E_INTERNAL,
"Option 'enigma_pgp_homedir' not specified");
// check if homedir exists (create it if not) and is readable
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory doesn't exists: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory isn't writeable: $homedir");
$homedir = $homedir . '/' . $this->user;
// check if user's homedir exists (create it if not) and is readable
if (!file_exists($homedir))
mkdir($homedir, 0700);
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to create keys directory: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to write to keys directory: $homedir");
$this->homedir = $homedir;
// Create Crypt_GPG object
try {
$this->gpg = new Crypt_GPG(array(
'homedir' => $this->homedir,
-// 'debug' => true,
+ // 'binary' => '/usr/bin/gpg2',
+ // 'debug' => true,
));
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
function encrypt($text, $keys)
{
/*
foreach ($keys as $key) {
$this->gpg->addEncryptKey($key);
}
$enc = $this->gpg->encrypt($text);
return $enc;
*/
}
- function decrypt($text, $key, $passwd)
+ /**
+ * Register private keys and passwords
+ *
+ * @param string Encrypted message
+ * @param array List of key-password mapping
+ */
+ function decrypt($text, $keys = array())
{
-// $this->gpg->addDecryptKey($key, $passwd);
+ foreach ($keys as $key => $password) {
+ $this->gpg->addDecryptKey($key, $password);
+ }
+
try {
$dec = $this->gpg->decrypt($text);
return $dec;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
function sign($text, $key, $passwd)
{
/*
$this->gpg->addSignKey($key, $passwd);
$signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED);
return $signed;
*/
}
function verify($text, $signature)
{
try {
$verified = $this->gpg->verify($text, $signature);
return $this->parse_signature($verified[0]);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function import($content, $isfile=false)
{
try {
if ($isfile)
return $this->gpg->importKeyFile($content);
else
return $this->gpg->importKey($content);
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function list_keys($pattern='')
{
try {
$keys = $this->gpg->getKeys($pattern);
$result = array();
-//print_r($keys);
+
foreach ($keys as $idx => $key) {
$result[] = $this->parse_key($key);
unset($keys[$idx]);
}
-//print_r($result);
+
return $result;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
public function get_key($keyid)
{
$list = $this->list_keys($keyid);
if (is_array($list))
return array_shift($list);
// error
return $list;
}
public function gen_key($data)
{
}
- public function del_key($keyid)
+ public function delete_key($keyid)
{
-// $this->get_key($keyid);
+ // delete public key
+ $result = $this->delete_pubkey($keyid);
+
+ // if not found, delete private key
+ if ($result !== true && $result->getCode() == enigma_error::E_KEYNOTFOUND) {
+ $result = $this->delete_privkey($keyid);
+ }
+
+ return $result;
}
- public function del_privkey($keyid)
+ public function delete_privkey($keyid)
{
try {
$this->gpg->deletePrivateKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
- public function del_pubkey($keyid)
+ public function delete_pubkey($keyid)
{
try {
$this->gpg->deletePublicKey($keyid);
return true;
}
catch (Exception $e) {
return $this->get_error_from_exception($e);
}
}
/**
* Converts Crypt_GPG exception into Enigma's error object
*
* @param mixed Exception object
*
* @return enigma_error Error object
*/
private function get_error_from_exception($e)
{
$data = array();
if ($e instanceof Crypt_GPG_KeyNotFoundException) {
$error = enigma_error::E_KEYNOTFOUND;
$data['id'] = $e->getKeyId();
}
else if ($e instanceof Crypt_GPG_BadPassphraseException) {
$error = enigma_error::E_BADPASS;
$data['bad'] = $e->getBadPassphrases();
$data['missing'] = $e->getMissingPassphrases();
}
else if ($e instanceof Crypt_GPG_NoDataException)
$error = enigma_error::E_NODATA;
else if ($e instanceof Crypt_GPG_DeletePrivateKeyException)
$error = enigma_error::E_DELKEY;
else
$error = enigma_error::E_INTERNAL;
$msg = $e->getMessage();
return new enigma_error($error, $msg, $data);
}
/**
* Converts Crypt_GPG_Signature object into Enigma's signature object
*
* @param Crypt_GPG_Signature Signature object
*
* @return enigma_signature Signature object
*/
private function parse_signature($sig)
{
$user = $sig->getUserId();
$data = new enigma_signature();
$data->id = $sig->getId();
$data->valid = $sig->isValid();
$data->fingerprint = $sig->getKeyFingerprint();
$data->created = $sig->getCreationDate();
$data->expires = $sig->getExpirationDate();
$data->name = $user->getName();
$data->comment = $user->getComment();
$data->email = $user->getEmail();
return $data;
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
* @param Crypt_GPG_Key Key object
*
* @return enigma_key Key object
*/
private function parse_key($key)
{
$ekey = new enigma_key();
foreach ($key->getUserIds() as $idx => $user) {
$id = new enigma_userid();
$id->name = $user->getName();
$id->comment = $user->getComment();
$id->email = $user->getEmail();
$id->valid = $user->isValid();
$id->revoked = $user->isRevoked();
$ekey->users[$idx] = $id;
}
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
foreach ($key->getSubKeys() as $idx => $subkey) {
$skey = new enigma_subkey();
$skey->id = $subkey->getId();
$skey->revoked = $subkey->isRevoked();
$skey->created = $subkey->getCreationDate();
$skey->expires = $subkey->getExpirationDate();
$skey->fingerprint = $subkey->getFingerprint();
$skey->has_private = $subkey->hasPrivate();
$skey->can_sign = $subkey->canSign();
$skey->can_encrypt = $subkey->canEncrypt();
$ekey->subkeys[$idx] = $skey;
};
$ekey->id = $ekey->subkeys[0]->id;
return $ekey;
}
}
diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php
index fcd15db73..6686d7dfa 100644
--- a/plugins/enigma/lib/enigma_driver_phpssl.php
+++ b/plugins/enigma/lib/enigma_driver_phpssl.php
@@ -1,238 +1,237 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_driver_phpssl extends enigma_driver
{
private $rc;
- //private $gpg;
private $homedir;
private $user;
function __construct($user)
{
$rcmail = rcmail::get_instance();
$this->rc = $rcmail;
$this->user = $user;
}
/**
* Driver initialization and environment checking.
* Should only return critical errors.
*
* @return mixed NULL on success, enigma_error on failure
*/
function init()
{
$homedir = $this->rc->config->get('enigma_smime_homedir', INSTALL_PATH . '/plugins/enigma/home');
if (!$homedir)
return new enigma_error(enigma_error::E_INTERNAL,
"Option 'enigma_smime_homedir' not specified");
// check if homedir exists (create it if not) and is readable
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory doesn't exists: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Keys directory isn't writeable: $homedir");
$homedir = $homedir . '/' . $this->user;
// check if user's homedir exists (create it if not) and is readable
if (!file_exists($homedir))
mkdir($homedir, 0700);
if (!file_exists($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to create keys directory: $homedir");
if (!is_writable($homedir))
return new enigma_error(enigma_error::E_INTERNAL,
"Unable to write to keys directory: $homedir");
$this->homedir = $homedir;
}
function encrypt($text, $keys)
{
}
- function decrypt($text, $key, $passwd)
+ function decrypt($text, $keys = array())
{
}
function sign($text, $key, $passwd)
{
}
function verify($struct, $message)
{
// use common temp dir
$temp_dir = $this->rc->config->get('temp_dir');
$msg_file = tempnam($temp_dir, 'rcmMsg');
$cert_file = tempnam($temp_dir, 'rcmCert');
$fh = fopen($msg_file, "w");
if ($struct->mime_id) {
$message->get_part_body($struct->mime_id, false, 0, $fh);
}
else {
$this->rc->storage->get_raw_body($message->uid, $fh);
}
fclose($fh);
// @TODO: use stored certificates
// try with certificate verification
$sig = openssl_pkcs7_verify($msg_file, 0, $cert_file);
$validity = true;
if ($sig !== true) {
// try without certificate verification
$sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file);
$validity = enigma_error::E_UNVERIFIED;
}
if ($sig === true) {
$sig = $this->parse_sig_cert($cert_file, $validity);
}
else {
$errorstr = $this->get_openssl_error();
$sig = new enigma_error(enigma_error::E_INTERNAL, $errorstr);
}
// remove temp files
@unlink($msg_file);
@unlink($cert_file);
return $sig;
}
public function import($content, $isfile=false)
{
}
public function list_keys($pattern='')
{
}
public function get_key($keyid)
{
}
public function gen_key($data)
{
}
- public function del_key($keyid)
+ public function delete_key($keyid)
{
}
- public function del_privkey($keyid)
+ public function delete_privkey($keyid)
{
}
- public function del_pubkey($keyid)
+ public function delete_pubkey($keyid)
{
}
/**
* Converts Crypt_GPG_Key object into Enigma's key object
*
* @param Crypt_GPG_Key Key object
*
* @return enigma_key Key object
*/
private function parse_key($key)
{
/*
$ekey = new enigma_key();
foreach ($key->getUserIds() as $idx => $user) {
$id = new enigma_userid();
$id->name = $user->getName();
$id->comment = $user->getComment();
$id->email = $user->getEmail();
$id->valid = $user->isValid();
$id->revoked = $user->isRevoked();
$ekey->users[$idx] = $id;
}
$ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>');
foreach ($key->getSubKeys() as $idx => $subkey) {
$skey = new enigma_subkey();
$skey->id = $subkey->getId();
$skey->revoked = $subkey->isRevoked();
$skey->created = $subkey->getCreationDate();
$skey->expires = $subkey->getExpirationDate();
$skey->fingerprint = $subkey->getFingerprint();
$skey->has_private = $subkey->hasPrivate();
$skey->can_sign = $subkey->canSign();
$skey->can_encrypt = $subkey->canEncrypt();
$ekey->subkeys[$idx] = $skey;
};
$ekey->id = $ekey->subkeys[0]->id;
return $ekey;
*/
}
private function get_openssl_error()
{
$tmp = array();
while ($errorstr = openssl_error_string()) {
$tmp[] = $errorstr;
}
return join("\n", array_values($tmp));
}
private function parse_sig_cert($file, $validity)
{
$cert = openssl_x509_parse(file_get_contents($file));
if (empty($cert) || empty($cert['subject'])) {
$errorstr = $this->get_openssl_error();
return new enigma_error(enigm_error::E_INTERNAL, $errorstr);
}
$data = new enigma_signature();
$data->id = $cert['hash']; //?
$data->valid = $validity;
$data->fingerprint = $cert['serialNumber'];
$data->created = $cert['validFrom_time_t'];
$data->expires = $cert['validTo_time_t'];
$data->name = $cert['subject']['CN'];
// $data->comment = '';
$data->email = $cert['subject']['emailAddress'];
return $data;
}
}
diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php
index e4972c6a9..c3a2e503f 100644
--- a/plugins/enigma/lib/enigma_engine.php
+++ b/plugins/enigma/lib/enigma_engine.php
@@ -1,554 +1,836 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
/*
RFC2440: OpenPGP Message Format
RFC3156: MIME Security with OpenPGP
RFC3851: S/MIME
*/
class enigma_engine
{
private $rc;
private $enigma;
private $pgp_driver;
private $smime_driver;
- public $decryptions = array();
- public $signatures = array();
+ public $decryptions = array();
+ public $signatures = array();
public $signed_parts = array();
+ const PASSWORD_TIME = 120;
+
/**
* Plugin initialization.
*/
function __construct($enigma)
{
- $rcmail = rcmail::get_instance();
- $this->rc = $rcmail;
+ $this->rc = rcmail::get_instance();
$this->enigma = $enigma;
+
+ // this will remove passwords from session after some time
+ $this->get_passwords();
}
/**
* PGP driver initialization.
*/
function load_pgp_driver()
{
- if ($this->pgp_driver)
+ if ($this->pgp_driver) {
return;
+ }
- $driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
+ $driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
$username = $this->rc->user->get_username();
// Load driver
$this->pgp_driver = new $driver($username);
if (!$this->pgp_driver) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load PGP driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->pgp_driver->init();
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
/**
* S/MIME driver initialization.
*/
function load_smime_driver()
{
- if ($this->smime_driver)
+ if ($this->smime_driver) {
return;
+ }
- $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
+ $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
$username = $this->rc->user->get_username();
// Load driver
$this->smime_driver = new $driver($username);
if (!$this->smime_driver) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: Unable to load S/MIME driver: $driver"
), true, true);
}
// Initialise driver
$result = $this->smime_driver->init();
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: ".$result->getMessage()
), true, true);
}
}
+ /**
+ * Handler for message_part_structure hook.
+ * Called for every part of the message.
+ *
+ * @param array Original parameters
+ *
+ * @return array Modified parameters
+ */
+ function part_structure($p)
+ {
+ if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
+ $this->parse_plain($p);
+ }
+ else if ($p['mimetype'] == 'multipart/signed') {
+ $this->parse_signed($p);
+ }
+ else if ($p['mimetype'] == 'multipart/encrypted') {
+ $this->parse_encrypted($p);
+ }
+ else if ($p['mimetype'] == 'application/pkcs7-mime') {
+ $this->parse_encrypted($p);
+ }
+
+ return $p;
+ }
+
+ /**
+ * Handler for message_part_body hook.
+ *
+ * @param array Original parameters
+ *
+ * @return array Modified parameters
+ */
+ function part_body($p)
+ {
+ // encrypted attachment, see parse_plain_encrypted()
+ if ($p['part']->need_decryption && $p['part']->body === null) {
+ $storage = $this->rc->get_storage();
+ $body = $storage->get_message_part($p['object']->uid, $p['part']->mime_id, $p['part'], null, null, true, 0, false);
+ $result = $this->pgp_decrypt($body);
+
+ // @TODO: what to do on error?
+ if ($result === true) {
+ $p['part']->body = $body;
+ $p['part']->size = strlen($body);
+ $p['part']->body_modified = true;
+ }
+ }
+
+ return $p;
+ }
+
/**
* Handler for plain/text message.
*
* @param array Reference to hook's parameters
*/
function parse_plain(&$p)
{
$part = $p['structure'];
+ // exit, if we're already inside a decrypted message
+ if ($part->encrypted) {
+ return;
+ }
+
// Get message body from IMAP server
- $this->set_part_body($part, $p['object']->uid);
+ $body = $this->get_part_body($p['object'], $part->mime_id);
- // @TODO: big message body can be a file resource
+ // @TODO: big message body could be a file resource
// PGP signed message
- if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $part->body)) {
- $this->parse_plain_signed($p);
+ if (preg_match('/^-----BEGIN PGP SIGNED MESSAGE-----/', $body)) {
+ $this->parse_plain_signed($p, $body);
}
// PGP encrypted message
- else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $part->body)) {
- $this->parse_plain_encrypted($p);
+ else if (preg_match('/^-----BEGIN PGP MESSAGE-----/', $body)) {
+ $this->parse_plain_encrypted($p, $body);
}
}
/**
* Handler for multipart/signed message.
*
* @param array Reference to hook's parameters
*/
function parse_signed(&$p)
{
$struct = $p['structure'];
// S/MIME
if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
$this->parse_smime_signed($p);
}
- // PGP/MIME:
+ // PGP/MIME: RFC3156
// The multipart/signed body MUST consist of exactly two parts.
// The first part contains the signed data in MIME canonical format,
// including a set of appropriate content headers describing the data.
// The second body MUST contain the PGP digital signature. It MUST be
// labeled with a content type of "application/pgp-signature".
- else if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature') {
+ else if ($struct->ctype_parameters['protocol'] == 'application/pgp-signature'
+ && count($struct->parts) == 2
+ && $struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature'
+ ) {
$this->parse_pgp_signed($p);
}
}
/**
* Handler for multipart/encrypted message.
*
* @param array Reference to hook's parameters
*/
function parse_encrypted(&$p)
{
$struct = $p['structure'];
// S/MIME
if ($struct->mimetype == 'application/pkcs7-mime') {
$this->parse_smime_encrypted($p);
}
- // PGP/MIME:
- // The multipart/encrypted MUST consist of exactly two parts. The first
+ // PGP/MIME: RFC3156
+ // The multipart/encrypted MUST consist of exactly two parts. The first
// MIME body part must have a content type of "application/pgp-encrypted".
// This body contains the control information.
// The second MIME body part MUST contain the actual encrypted data. It
// must be labeled with a content type of "application/octet-stream".
- else if ($struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted' &&
- $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
+ else if ($struct->ctype_parameters['protocol'] == 'application/pgp-encrypted'
+ && count($struct->parts) == 2
+ && $struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted'
+ && $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
) {
$this->parse_pgp_encrypted($p);
}
}
/**
* Handler for plain signed message.
* Excludes message and signature bodies and verifies signature.
*
- * @param array Reference to hook's parameters
+ * @param array Reference to hook's parameters
+ * @param string Message (part) body
*/
- private function parse_plain_signed(&$p)
+ private function parse_plain_signed(&$p, $body)
{
$this->load_pgp_driver();
$part = $p['structure'];
// Verify signature
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
- $sig = $this->pgp_verify($part->body);
+ $sig = $this->pgp_verify($body);
}
// @TODO: Handle big bodies using (temp) files
// In this way we can use fgets on string as on file handle
$fh = fopen('php://memory', 'br+');
// @TODO: fopen/fwrite errors handling
if ($fh) {
- fwrite($fh, $part->body);
+ fwrite($fh, $body);
rewind($fh);
}
- $part->body = null;
+
+ $body = $part->body = null;
+ $part->body_modified = true;
// Extract body (and signature?)
while (!feof($fh)) {
$line = fgets($fh, 1024);
if ($part->body === null)
$part->body = '';
else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
break;
else
$part->body .= $line;
}
// Remove "Hash" Armor Headers
$part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body);
// de-Dash-Escape (RFC2440)
$part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body);
// Store signature data for display
if (!empty($sig)) {
$this->signed_parts[$part->mime_id] = $part->mime_id;
$this->signatures[$part->mime_id] = $sig;
}
fclose($fh);
}
/**
* Handler for PGP/MIME signed message.
* Verifies signature.
*
- * @param array Reference to hook's parameters
+ * @param array Reference to hook's parameters
*/
private function parse_pgp_signed(&$p)
{
// Verify signature
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$this->load_pgp_driver();
$struct = $p['structure'];
$msg_part = $struct->parts[0];
$sig_part = $struct->parts[1];
// Get bodies
- $this->set_part_body($msg_part, $p['object']->uid);
- $this->set_part_body($sig_part, $p['object']->uid);
+ // Note: The first part body need to be full part body with headers
+ // it also cannot be decoded
+ $msg_body = $this->get_part_body($p['object'], $msg_part->mime_id, true);
+ $sig_body = $this->get_part_body($p['object'], $sig_part->mime_id);
// Verify
- $sig = $this->pgp_verify($msg_part->body, $sig_part->body);
+ $sig = $this->pgp_verify($msg_body, $sig_body);
// Store signature data for display
$this->signatures[$struct->mime_id] = $sig;
// Message can be multipart (assign signature to each subpart)
if (!empty($msg_part->parts)) {
foreach ($msg_part->parts as $part)
$this->signed_parts[$part->mime_id] = $struct->mime_id;
}
- else
+ else {
$this->signed_parts[$msg_part->mime_id] = $struct->mime_id;
+ }
- // Remove signature file from attachments list
+ // Remove signature file from attachments list (?)
unset($struct->parts[1]);
}
}
/**
* Handler for S/MIME signed message.
* Verifies signature.
*
* @param array Reference to hook's parameters
*/
private function parse_smime_signed(&$p)
{
+ return; // @TODO
+
// Verify signature
if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
$this->load_smime_driver();
$struct = $p['structure'];
$msg_part = $struct->parts[0];
// Verify
$sig = $this->smime_driver->verify($struct, $p['object']);
// Store signature data for display
$this->signatures[$struct->mime_id] = $sig;
// Message can be multipart (assign signature to each subpart)
if (!empty($msg_part->parts)) {
foreach ($msg_part->parts as $part)
$this->signed_parts[$part->mime_id] = $struct->mime_id;
}
else {
$this->signed_parts[$msg_part->mime_id] = $struct->mime_id;
}
// Remove signature file from attachments list
unset($struct->parts[1]);
}
}
/**
* Handler for plain encrypted message.
*
- * @param array Reference to hook's parameters
+ * @param array Reference to hook's parameters
+ * @param string Message (part) body
*/
- private function parse_plain_encrypted(&$p)
+ private function parse_plain_encrypted(&$p, $body)
{
$this->load_pgp_driver();
$part = $p['structure'];
- // Get body
- $this->set_part_body($part, $p['object']->uid);
-
// Decrypt
- $result = $this->pgp_decrypt($part->body);
+ $result = $this->pgp_decrypt($body);
// Store decryption status
$this->decryptions[$part->mime_id] = $result;
// Parse decrypted message
if ($result === true) {
- // @TODO
+ $part->body = $body;
+ $part->body_modified = true;
+ $part->encrypted = true;
+
+ // Encrypted plain message may contain encrypted attachments
+ // in such case attachments have .pgp extension and application/octet-stream.
+ // This is what happens when you select "Encrypt each attachment separately
+ // and send the message using inline PGP" in Thunderbird's Enigmail.
+
+ // find parent part ID
+ if (strpos($part->mime_id, '.')) {
+ $items = explode('.', $part->mime_id);
+ array_pop($items);
+ $parent = implode('.', $items);
+ }
+ else {
+ $parent = 0;
+ }
+
+ if ($p['object']->mime_parts[$parent]) {
+ foreach ((array)$p['object']->mime_parts[$parent]->parts as $p) {
+ if ($p->disposition == 'attachment' && $p->mimetype == 'application/octet-stream'
+ && preg_match('/^(.*)\.pgp$/i', $p->filename, $m)
+ ) {
+ // modify filename
+ $p->filename = $m[1];
+ // flag the part, it will be decrypted when needed
+ $p->need_decryption = true;
+ // disable caching
+ $p->body_modified = true;
+ }
+ }
+ }
}
}
/**
* Handler for PGP/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_pgp_encrypted(&$p)
{
$this->load_pgp_driver();
+
$struct = $p['structure'];
- $part = $struct->parts[1];
-
+ $part = $struct->parts[1];
+
// Get body
- $this->set_part_body($part, $p['object']->uid);
+ $body = $this->get_part_body($p['object'], $part->mime_id);
// Decrypt
- $result = $this->pgp_decrypt($part->body);
+ $result = $this->pgp_decrypt($body);
- $this->decryptions[$part->mime_id] = $result;
-//print_r($part);
- // Parse decrypted message
if ($result === true) {
- // @TODO
+ // Parse decrypted message
+ $struct = $this->parse_body($body);
+
+ // Modify original message structure
+ $this->modify_structure($p, $struct);
+
+ // Attach the decryption message to all parts
+ $this->decryptions[$struct->mime_id] = $result;
+ foreach ((array) $struct->parts as $sp) {
+ $this->decryptions[$sp->mime_id] = $result;
+ }
}
else {
+ $this->decryptions[$part->mime_id] = $result;
+
// Make sure decryption status message will be displayed
$part->type = 'content';
$p['object']->parts[] = $part;
}
}
/**
* Handler for S/MIME encrypted message.
*
* @param array Reference to hook's parameters
*/
private function parse_smime_encrypted(&$p)
{
// $this->load_smime_driver();
}
/**
* PGP signature verification.
*
* @param mixed Message body
* @param mixed Signature body (for MIME messages)
*
* @return mixed enigma_signature or enigma_error
*/
private function pgp_verify(&$msg_body, $sig_body=null)
{
// @TODO: Handle big bodies using (temp) files
// @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)
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $sig->getMessage()
), true, false);
return $sig;
}
/**
* PGP message decryption.
*
* @param mixed Message body
*
* @return mixed True or enigma_error
*/
private function pgp_decrypt(&$msg_body)
{
// @TODO: Handle big bodies using (temp) files
// @TODO: caching of verification result
- $key = ''; $pass = ''; // @TODO
- $result = $this->pgp_driver->decrypt($msg_body, $key, $pass);
+ $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;
+ $msg_body = $result;
+
return true;
}
/**
* PGP keys listing.
*
* @param mixed Key ID/Name pattern
*
* @return mixed Array of keys or enigma_error
*/
- function list_keys($pattern='')
+ function list_keys($pattern = '')
{
$this->load_pgp_driver();
$result = $this->pgp_driver->list_keys($pattern);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
return $result;
}
/**
* PGP key details.
*
* @param mixed Key ID
*
* @return mixed enigma_key or enigma_error
*/
function get_key($keyid)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->get_key($keyid);
-
+
+ if ($result instanceof enigma_error) {
+ rcube::raise_error(array(
+ 'code' => 600, 'type' => 'php',
+ 'file' => __FILE__, 'line' => __LINE__,
+ 'message' => "Enigma plugin: " . $result->getMessage()
+ ), true, false);
+ }
+
+ return $result;
+ }
+
+ /**
+ * PGP key delete.
+ *
+ * @param string Key ID
+ *
+ * @return enigma_error|bool True on success
+ */
+ function delete_key($keyid)
+ {
+ $this->load_pgp_driver();
+ $result = $this->pgp_driver->delete_key($keyid);
+
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
-
+
return $result;
}
/**
* PGP keys/certs importing.
*
* @param mixed Import file name or content
* @param boolean True if first argument is a filename
*
* @return mixed Import status data array or enigma_error
*/
function import_key($content, $isfile=false)
{
$this->load_pgp_driver();
$result = $this->pgp_driver->import($content, $isfile);
if ($result instanceof enigma_error) {
rcube::raise_error(array(
'code' => 600, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Enigma plugin: " . $result->getMessage()
), true, false);
}
else {
$result['imported'] = $result['public_imported'] + $result['private_imported'];
$result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged'];
}
return $result;
}
/**
* Handler for keys/certs import request action
*/
function import_file()
{
$uid = rcube_utils::get_input_value('_uid', rcube_utils::INPUT_POST);
$mbox = rcube_utils::get_input_value('_mbox', rcube_utils::INPUT_POST);
$mime_id = rcube_utils::get_input_value('_part', rcube_utils::INPUT_POST);
$storage = $this->rc->get_storage();
if ($uid && $mime_id) {
$storage->set_folder($mbox);
$part = $storage->get_message_part($uid, $mime_id);
}
if ($part && is_array($result = $this->import_key($part))) {
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
}
else
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
$this->rc->output->send();
}
+ function password_handler()
+ {
+ $keyid = rcube_utils::get_input_value('_keyid', rcube_utils::INPUT_POST);
+ $passwd = rcube_utils::get_input_value('_passwd', rcube_utils::INPUT_POST, true);
+
+ if ($keyid && $passwd !== null && strlen($passwd)) {
+ $this->save_password($keyid, $passwd);
+ }
+ }
+
+ function save_password($keyid, $password)
+ {
+ // we store passwords in session for specified time
+ if ($config = $_SESSION['enigma_pass']) {
+ $config = $this->rc->decrypt($config);
+ $config = @unserialize($config);
+ }
+
+ $config[$keyid] = array($password, time());
+
+ $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
+ }
+
+ function get_passwords()
+ {
+ if ($config = $_SESSION['enigma_pass']) {
+ $config = $this->rc->decrypt($config);
+ $config = @unserialize($config);
+ }
+
+ $threshold = time() - self::PASSWORD_TIME;
+ $keys = array();
+
+ // delete expired passwords
+ foreach ((array) $config as $key => $value) {
+ if ($value[1] < $threshold) {
+ unset($config[$key]);
+ $modified = true;
+ }
+ else {
+ $keys[$key] = $value[0];
+ }
+ }
+
+ if ($modified) {
+ $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
+ }
+
+ return $keys;
+ }
+
/**
- * Checks if specified message part contains body data.
- * If body is not set it will be fetched from IMAP server.
+ * Get message part body.
*
- * @param rcube_message_part Message part object
- * @param integer Message UID
+ * @param rcube_message Message object
+ * @param string Message part ID
+ * @param bool Return raw body with headers
*/
- private function set_part_body($part, $uid)
+ private function get_part_body($msg, $part_id, $full = false)
{
- // @TODO: Create such function in core
// @TODO: Handle big bodies using file handles
- if (!isset($part->body)) {
- $part->body = $this->rc->storage->get_message_part(
- $uid, $part->mime_id, $part);
+ if ($full) {
+ $storage = $this->rc->get_storage();
+ $body = $storage->get_raw_headers($msg->uid, $part_id);
+ $body .= $storage->get_raw_body($msg->uid, null, $part_id);
+ }
+ else {
+ $body = $msg->get_part_body($part_id, false);
+ }
+
+ return $body;
+ }
+
+ /**
+ * Parse decrypted message body into structure
+ *
+ * @param string Message body
+ *
+ * @return array Message structure
+ */
+ private function parse_body(&$body)
+ {
+ // Mail_mimeDecode need \r\n end-line, but gpg may return \n
+ $body = preg_replace('/\r?\n/', "\r\n", $body);
+
+ // parse the body into structure
+ $struct = rcube_mime::parse_message($body);
+
+ return $struct;
+ }
+
+ /**
+ * Replace message encrypted structure with decrypted message structure
+ *
+ * @param array
+ * @param rcube_message_part
+ */
+ private function modify_structure(&$p, $struct)
+ {
+ // modify mime_parts property of the message object
+ $old_id = $p['structure']->mime_id;
+ foreach (array_keys($p['object']->mime_parts) as $idx) {
+ if (!$old_id || $idx == $old_id || strpos($idx, $old_id . '.') === 0) {
+ unset($p['object']->mime_parts[$idx]);
+ }
}
+
+ // modify the new structure to be correctly handled by Roundcube
+ $this->modify_structure_part($struct, $p['object'], $old_id);
+
+ // replace old structure with the new one
+ $p['structure'] = $struct;
+ $p['mimetype'] = $struct->mimetype;
+ }
+
+ /**
+ * Modify decrypted message part
+ *
+ * @param rcube_message_part
+ * @param rcube_message
+ */
+ private function modify_structure_part($part, $msg, $old_id)
+ {
+ // never cache the body
+ $part->body_modified = true;
+ $part->encoding = 'stream';
+
+ // Cache the fact it was decrypted
+ $part->encrypted = true;
+
+ // modify part identifier
+ if ($old_id) {
+ $part->mime_id = !$part->mime_id ? $old_id : ($old_id . '.' . $part->mime_id);
+ }
+
+ $msg->mime_parts[$part->mime_id] = $part;
+
+ // modify sub-parts
+ foreach ((array) $part->parts as $p) {
+ $this->modify_structure_part($p, $msg, $old_id);
+ }
+ }
+
+ /**
+ * Checks if specified message part is a PGP-key or S/MIME cert data
+ *
+ * @param rcube_message_part Part object
+ *
+ * @return boolean True if part is a key/cert
+ */
+ public function is_keys_part($part)
+ {
+ // @TODO: S/MIME
+ return (
+ // Content-Type: application/pgp-keys
+ $part->mimetype == 'application/pgp-keys'
+ );
}
}
diff --git a/plugins/enigma/lib/enigma_error.php b/plugins/enigma/lib/enigma_error.php
index ab8d01557..7122e8e7b 100644
--- a/plugins/enigma/lib/enigma_error.php
+++ b/plugins/enigma/lib/enigma_error.php
@@ -1,64 +1,67 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_error
{
private $code;
private $message;
private $data = array();
// error codes
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;
+
function __construct($code = null, $message = '', $data = array())
{
- $this->code = $code;
+ $this->code = $code;
$this->message = $message;
- $this->data = $data;
+ $this->data = $data;
}
function getCode()
{
return $this->code;
}
function getMessage()
{
return $this->message;
}
function getData($name)
{
- if ($name)
+ if ($name) {
return $this->data[$name];
- else
+ }
+ else {
return $this->data;
+ }
}
}
diff --git a/plugins/enigma/lib/enigma_key.php b/plugins/enigma/lib/enigma_key.php
index 520c36b0b..66670c5d5 100644
--- a/plugins/enigma/lib/enigma_key.php
+++ b/plugins/enigma/lib/enigma_key.php
@@ -1,129 +1,132 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_key
{
public $id;
public $name;
- public $users = array();
+ public $users = array();
public $subkeys = array();
const TYPE_UNKNOWN = 0;
const TYPE_KEYPAIR = 1;
- const TYPE_PUBLIC = 2;
+ const TYPE_PUBLIC = 2;
/**
* Keys list sorting callback for usort()
*/
static function cmp($a, $b)
{
return strcmp($a->name, $b->name);
}
/**
* Returns key type
*/
function get_type()
{
if ($this->subkeys[0]->has_private)
return enigma_key::TYPE_KEYPAIR;
else if (!empty($this->subkeys[0]))
return enigma_key::TYPE_PUBLIC;
return enigma_key::TYPE_UNKNOWN;
}
/**
* Returns true if all user IDs are revoked
- */
+ */
function is_revoked()
{
foreach ($this->subkeys as $subkey)
if (!$subkey->revoked)
return false;
return true;
}
/**
* Returns true if any user ID is valid
- */
+ */
function is_valid()
{
foreach ($this->users as $user)
if ($user->valid)
return true;
return false;
}
-
+
/**
* Returns true if any of subkeys is not expired
- */
+ */
function is_expired()
{
$now = time();
-
+
foreach ($this->subkeys as $subkey)
if (!$subkey->expires || $subkey->expires > $now)
return true;
-
+
return false;
}
/**
* Converts long ID or Fingerprint to short ID
* Crypt_GPG uses internal, but e.g. Thunderbird's Enigmail displays short ID
*
* @param string Key ID or fingerprint
* @return string Key short ID
*/
static function format_id($id)
{
// E.g. 04622F2089E037A5 => 89E037A5
-
+
return substr($id, -8);
}
/**
* Formats fingerprint string
*
* @param string Key fingerprint
*
* @return string Formatted fingerprint (with spaces)
*/
static function format_fingerprint($fingerprint)
{
- if (!$fingerprint)
+ if (!$fingerprint) {
return '';
-
+ }
+
$result = '';
for ($i=0; $i<40; $i++) {
- if ($i % 4 == 0)
+ if ($i % 4 == 0) {
$result .= ' ';
+ }
$result .= $fingerprint[$i];
}
+
return $result;
}
}
diff --git a/plugins/enigma/lib/enigma_ui.php b/plugins/enigma/lib/enigma_ui.php
index 2e95938f2..26396f1dd 100644
--- a/plugins/enigma/lib/enigma_ui.php
+++ b/plugins/enigma/lib/enigma_ui.php
@@ -1,455 +1,718 @@
<?php
/*
+-------------------------------------------------------------------------+
| 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. |
| |
| 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. |
| |
+-------------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-------------------------------------------------------------------------+
*/
class enigma_ui
{
private $rc;
private $enigma;
private $home;
- private $css_added;
+ private $css_loaded;
+ private $js_loaded;
private $data;
+ private $keys_parts = array();
+ private $keys_bodies = array();
function __construct($enigma_plugin, $home='')
{
$this->enigma = $enigma_plugin;
- $this->rc = $enigma_plugin->rc;
- // we cannot use $enigma_plugin->home here
- $this->home = $home;
+ $this->rc = $enigma_plugin->rc;
+ $this->home = $home; // we cannot use $enigma_plugin->home here
}
/**
* UI initialization and requests handlers.
*
* @param string Preferences section
*/
function init($section='')
{
- $this->enigma->include_script('enigma.js');
+ $this->add_js();
- // Enigma actions
- if ($this->rc->action == 'plugin.enigma') {
- $action = rcube_utils::get_input_value('_a', rcube_utils::INPUT_GPC);
+ $action = rcube_utils::get_input_value('_a', rcube_utils::INPUT_GPC);
+ if ($this->rc->action == 'plugin.enigmakeys') {
switch ($action) {
- case 'keyedit':
+ case 'delete':
+ $this->key_delete();
+ break;
+/*
+ case 'edit':
$this->key_edit();
break;
- case 'keyimport':
+*/
+ case 'import':
$this->key_import();
break;
- case 'keysearch':
- case 'keylist':
+
+ case 'search':
+ case 'list':
$this->key_list();
break;
- case 'keyinfo':
- default:
+
+ case 'info':
$this->key_info();
+ break;
}
+
+ $this->rc->output->add_handlers(array(
+ 'keyslist' => array($this, 'tpl_keys_list'),
+ 'keyframe' => array($this, 'tpl_key_frame'),
+ 'countdisplay' => array($this, 'tpl_keys_rowcount'),
+ 'searchform' => array($this->rc->output, 'search_form'),
+ ));
+
+ $this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
+ $this->rc->output->send('enigma.keys');
}
+/*
+ // Preferences UI
+ else if ($this->rc->action == 'plugin.enigmacerts') {
+ $this->rc->output->add_handlers(array(
+ 'keyslist' => array($this, 'tpl_certs_list'),
+ 'keyframe' => array($this, 'tpl_cert_frame'),
+ 'countdisplay' => array($this, 'tpl_certs_rowcount'),
+ 'searchform' => array($this->rc->output, 'search_form'),
+ ));
+
+ $this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
+ $this->rc->output->send('enigma.certs');
+ }
+*/
// Message composing UI
else if ($this->rc->action == 'compose') {
$this->compose_ui();
}
- // Preferences UI
- else { // if ($this->rc->action == 'edit-prefs') {
- if ($section == 'enigmacerts') {
- $this->rc->output->add_handlers(array(
- 'keyslist' => array($this, 'tpl_certs_list'),
- 'keyframe' => array($this, 'tpl_cert_frame'),
- 'countdisplay' => array($this, 'tpl_certs_rowcount'),
- 'searchform' => array($this->rc->output, 'search_form'),
- ));
- $this->rc->output->set_pagetitle($this->enigma->gettext('enigmacerts'));
- $this->rc->output->send('enigma.certs');
- }
- else {
- $this->rc->output->add_handlers(array(
- 'keyslist' => array($this, 'tpl_keys_list'),
- 'keyframe' => array($this, 'tpl_key_frame'),
- 'countdisplay' => array($this, 'tpl_keys_rowcount'),
- 'searchform' => array($this->rc->output, 'search_form'),
- ));
- $this->rc->output->set_pagetitle($this->enigma->gettext('enigmakeys'));
- $this->rc->output->send('enigma.keys');
- }
- }
}
- /**
+ /**
* Adds CSS style file to the page header.
*/
function add_css()
{
if ($this->css_loaded)
return;
$skin_path = $this->enigma->local_skin_path();
if (is_file($this->home . "/$skin_path/enigma.css")) {
$this->enigma->include_stylesheet("$skin_path/enigma.css");
}
- $this->css_added = true;
+ $this->css_loaded = true;
+ }
+
+ /**
+ * Adds javascript file to the page header.
+ */
+ function add_js()
+ {
+ if ($this->js_loaded) {
+ return;
+ }
+
+ $this->enigma->include_script('enigma.js');
+
+ $this->js_loaded = true;
+ }
+
+ /**
+ * Initializes key password prompt
+ *
+ * @param enigma_error Error object with key info
+ */
+ function password_prompt($status)
+ {
+ $data = $status->getData('missing');
+
+ if (empty($data)) {
+ $data = $status->getData('bad');
+ }
+
+ $data = array('keyid' => key($data), 'user' => $data[key($data)]);
+
+ $this->rc->output->set_env('enigma_password_request', $data);
+
+ // add some labels to client
+ $this->rc->output->add_label('enigma.enterkeypasstitle', 'enigma.enterkeypass',
+ 'save', 'cancel');
+
+ $this->add_css();
+ $this->add_js();
}
/**
* Template object for key info/edit frame.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_key_frame($attrib)
{
if (!$attrib['id']) {
$attrib['id'] = 'rcmkeysframe';
}
$attrib['name'] = $attrib['id'];
$this->rc->output->set_env('contentframe', $attrib['name']);
$this->rc->output->set_env('blankpage', $attrib['src'] ?
$this->rc->output->abs_url($attrib['src']) : 'program/resources/blank.gif');
return $this->rc->output->frame($attrib);
}
/**
* Template object for list of keys.
*
* @param array Object attributes
*
* @return string HTML content
*/
function tpl_keys_list($attrib)
{
// add id to message list table if not specified
if (!strlen($attrib['id'])) {
$attrib['id'] = 'rcmenigmakeyslist';
}
// define list of cols to be displayed
$a_show_cols = array('name');
// create XHTML table
$out = $this->rc->table_output($attrib, array(), $a_show_cols, 'id');
// set client env
$this->rc->output->add_gui_object('keyslist', $attrib['id']);
$this->rc->output->include_script('list.js');
// add some labels to client
- $this->rc->output->add_label('enigma.keyconfirmdelete');
+ $this->rc->output->add_label('enigma.keyremoveconfirm', 'enigma.keyremoving');
return $out;
}
/**
* Key listing (and searching) request handler
*/
private function key_list()
{
$this->enigma->load_engine();
$pagesize = $this->rc->config->get('pagesize', 100);
$page = max(intval(rcube_utils::get_input_value('_p', rcube_utils::INPUT_GPC)), 1);
$search = rcube_utils::get_input_value('_q', rcube_utils::INPUT_GPC);
// define list of cols to be displayed
// $a_show_cols = array('name');
// Get the list
$list = $this->enigma->engine->list_keys($search);
if ($list && ($list instanceof enigma_error))
$this->rc->output->show_message('enigma.keylisterror', 'error');
else if (empty($list))
$this->rc->output->show_message('enigma.nokeysfound', 'notice');
else {
if (is_array($list)) {
// Save the size
$listsize = count($list);
// Sort the list by key (user) name
usort($list, array('enigma_key', 'cmp'));
// Slice current page
$list = array_slice($list, ($page - 1) * $pagesize, $pagesize);
$size = count($list);
// Add rows
foreach ($list as $key) {
$this->rc->output->command('enigma_add_list_row',
array('name' => rcube::Q($key->name), 'id' => $key->id));
}
}
}
$this->rc->output->set_env('search_request', $search);
$this->rc->output->set_env('pagecount', ceil($listsize/$pagesize));
$this->rc->output->set_env('current_page', $page);
$this->rc->output->command('set_rowcount',
$this->get_rowcount_text($listsize, $size, $page));
$this->rc->output->send();
}
/**
* Template object for list records counter.
*
* @param array Object attributes
*
* @return string HTML output
*/
function tpl_keys_rowcount($attrib)
{
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$this->rc->output->add_gui_object('countdisplay', $attrib['id']);
return html::span($attrib, $this->get_rowcount_text());
}
/**
* Returns text representation of list records counter
*/
private function get_rowcount_text($all=0, $curr_count=0, $page=1)
{
if (!$curr_count)
$out = $this->enigma->gettext('nokeysfound');
else {
$pagesize = $this->rc->config->get('pagesize', 100);
$first = ($page - 1) * $pagesize;
$out = $this->enigma->gettext(array(
'name' => 'keysfromto',
'vars' => array(
'from' => $first + 1,
'to' => $first + $curr_count,
'count' => $all)
));
}
return $out;
}
/**
* Key information page handler
*/
private function key_info()
{
- $id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
-
$this->enigma->load_engine();
+
+ $id = rcube_utils::get_input_value('_id', rcube_utils::INPUT_GET);
$res = $this->enigma->engine->get_key($id);
- if ($res instanceof enigma_key)
+ if ($res instanceof enigma_key) {
$this->data = $res;
+ }
else { // error
$this->rc->output->show_message('enigma.keyopenerror', 'error');
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->send('iframe');
}
$this->rc->output->add_handlers(array(
'keyname' => array($this, 'tpl_key_name'),
'keydata' => array($this, 'tpl_key_data'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyinfo'));
$this->rc->output->send('enigma.keyinfo');
}
/**
* Template object for key name
*/
function tpl_key_name($attrib)
{
return rcube::Q($this->data->name);
}
/**
* Template object for key information page content
*/
function tpl_key_data($attrib)
{
$out = '';
$table = new html_table(array('cols' => 2));
// Key user ID
$table->add('title', $this->enigma->gettext('keyuserid'));
$table->add(null, rcube::Q($this->data->name));
// Key ID
$table->add('title', $this->enigma->gettext('keyid'));
$table->add(null, $this->data->subkeys[0]->get_short_id());
// Key type
$keytype = $this->data->get_type();
if ($keytype == enigma_key::TYPE_KEYPAIR)
$type = $this->enigma->gettext('typekeypair');
else if ($keytype == enigma_key::TYPE_PUBLIC)
$type = $this->enigma->gettext('typepublickey');
$table->add('title', $this->enigma->gettext('keytype'));
$table->add(null, $type);
// Key fingerprint
$table->add('title', $this->enigma->gettext('fingerprint'));
$table->add(null, $this->data->subkeys[0]->get_fingerprint());
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('basicinfo')) . $table->show($attrib));
-
+/*
// Subkeys
$table = new html_table(array('cols' => 6));
// Columns: Type, ID, Algorithm, Size, Created, Expires
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('subkeys')) . $table->show($attrib));
// Additional user IDs
$table = new html_table(array('cols' => 2));
// Columns: User ID, Validity
$out .= html::tag('fieldset', null,
html::tag('legend', null,
$this->enigma->gettext('userids')) . $table->show($attrib));
-
+*/
return $out;
}
/**
* Key import page handler
*/
private function key_import()
{
// Import process
if ($_FILES['_file']['tmp_name'] && is_uploaded_file($_FILES['_file']['tmp_name'])) {
$this->enigma->load_engine();
$result = $this->enigma->engine->import_key($_FILES['_file']['tmp_name'], true);
if (is_array($result)) {
// reload list if any keys has been added
if ($result['imported']) {
$this->rc->output->command('parent.enigma_list', 1);
}
else
$this->rc->output->command('parent.enigma_loadframe');
$this->rc->output->show_message('enigma.keysimportsuccess', 'confirmation',
array('new' => $result['imported'], 'old' => $result['unchanged']));
$this->rc->output->send('iframe');
}
- else
+ else {
$this->rc->output->show_message('enigma.keysimportfailed', 'error');
+ }
}
else if ($err = $_FILES['_file']['error']) {
if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) {
$this->rc->output->show_message('filesizeerror', 'error',
array('size' => $this->rc->show_bytes(parse_bytes(ini_get('upload_max_filesize')))));
} else {
$this->rc->output->show_message('fileuploaderror', 'error');
}
}
$this->rc->output->add_handlers(array(
'importform' => array($this, 'tpl_key_import_form'),
));
$this->rc->output->set_pagetitle($this->enigma->gettext('keyimport'));
$this->rc->output->send('enigma.keyimport');
}
/**
* Template object for key import (upload) form
*/
function tpl_key_import_form($attrib)
{
$attrib += array('id' => 'rcmKeyImportForm');
$upload = new html_inputfield(array('type' => 'file', 'name' => '_file',
'id' => 'rcmimportfile', 'size' => 30));
$form = html::p(null,
rcube::Q($this->enigma->gettext('keyimporttext'), 'show')
. html::br() . html::br() . $upload->show()
);
$this->rc->output->add_label('selectimportfile', 'importwait');
$this->rc->output->add_gui_object('importform', $attrib['id']);
$out = $this->rc->output->form_tag(array(
- 'action' => $this->rc->url(array('action' => 'plugin.enigma', 'a' => 'keyimport')),
+ 'action' => $this->rc->url(array('action' => $this->rc->action, 'a' => 'import')),
'method' => 'post',
'enctype' => 'multipart/form-data') + $attrib,
$form);
return $out;
}
+ /**
+ * Key deleting
+ */
+ private function key_delete()
+ {
+ $keys = rcube_utils::get_input_value('_keys', rcube_utils::INPUT_POST);
+
+ $this->enigma->load_engine();
+
+ foreach ((array)$keys as $key) {
+ $res = $this->enigma->engine->delete_key($key);
+
+ if ($res !== true) {
+ $this->rc->output->show_message('enigma.keyremoveerror', 'error');
+ $this->rc->output->command('enigma_list');
+ $this->rc->output->send();
+ }
+ }
+
+ $this->rc->output->command('enigma_list');
+ $this->rc->output->show_message('enigma.keyremovesuccess', 'confirmation');
+ $this->rc->output->send();
+ }
+
private function compose_ui()
{
+/*
+ $this->add_css();
+
// Options menu button
// @TODO: make this work with non-default skins
$this->enigma->add_button(array(
- 'name' => 'enigmamenu',
- 'imagepas' => 'skins/classic/enigma.png',
- 'imageact' => 'skins/classic/enigma.png',
- 'onclick' => "rcmail_ui.show_popup('enigmamenu', true); return false",
- 'title' => 'securityoptions',
- 'domain' => 'enigma',
+ 'type' => 'link',
+ 'command' => 'plugin.enigma',
+ 'onclick' => "rcmail.command('menu-open', 'enigmamenu', event.target, event)",
+ 'class' => 'button enigma',
+ 'title' => 'securityoptions',
+ 'label' => 'securityoptions',
+ 'domain' => $this->enigma->ID,
+ 'width' => 32,
+ 'height' => 32
), 'toolbar');
// Options menu contents
$this->enigma->add_hook('render_page', array($this, 'compose_menu'));
+*/
}
function compose_menu($p)
{
$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, html::label(array('for' => 'enigmacryptopt'),
rcube::Q($this->enigma->gettext('encryptmsg'))));
$menu->add(null, $chbox->show(1, array('name' => '_enigma_crypt', 'id' => 'enigmacryptopt')));
$menu = html::div(array('id' => 'enigmamenu', 'class' => 'popupmenu'),
$menu->show());
$p['content'] = preg_replace('/(<form name="form"[^>]+>)/i', '\\1'."\n$menu", $p['content']);
return $p;
+ }
+
+ /**
+ * Handler for message_body_prefix hook.
+ * Called for every displayed (content) part of the message.
+ * Adds infobox about signature verification and/or decryption
+ * status above the body.
+ *
+ * @param array Original parameters
+ *
+ * @return array Modified parameters
+ */
+ function status_message($p)
+ {
+ // skip: not a message part
+ if ($p['part'] instanceof rcube_message) {
+ return $p;
+ }
+ // skip: message has no signed/encoded content
+ if (!$this->enigma->engine) {
+ return $p;
+ }
+
+ $engine = $this->enigma->engine;
+ $part_id = $p['part']->mime_id;
+
+ // Decryption status
+ if (isset($engine->decryptions[$part_id])) {
+ $attach_scripts = true;
+
+ // get decryption status
+ $status = $engine->decryptions[$part_id];
+
+ // display status info
+ $attrib['id'] = 'enigma-message';
+
+ if ($status instanceof enigma_error) {
+ $attrib['class'] = 'enigmaerror';
+ $code = $status->getCode();
+
+ if ($code == enigma_error::E_KEYNOTFOUND) {
+ $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
+ $this->enigma->gettext('decryptnokey')));
+ }
+ else if ($code == enigma_error::E_BADPASS) {
+ $msg = rcube::Q($this->enigma->gettext('decryptbadpass'));
+ $this->password_prompt($status);
+ }
+ else {
+ $msg = rcube::Q($this->enigma->gettext('decrypterror'));
+ }
+ }
+ else {
+ $attrib['class'] = 'enigmanotice';
+ $msg = rcube::Q($this->enigma->gettext('decryptok'));
+ }
+
+ $p['prefix'] .= html::div($attrib, $msg);
+ }
+
+ // Signature verification status
+ if (isset($engine->signed_parts[$part_id])
+ && ($sig = $engine->signatures[$engine->signed_parts[$part_id]])
+ ) {
+ $attach_scripts = true;
+
+ // display status info
+ $attrib['id'] = 'enigma-message';
+
+ if ($sig instanceof enigma_signature) {
+ $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
+
+ if ($sig->valid === enigma_error::E_UNVERIFIED) {
+ $attrib['class'] = 'enigmawarning';
+ $msg = str_replace('$sender', $sender, $this->enigma->gettext('sigunverified'));
+ $msg = str_replace('$keyid', $sig->id, $msg);
+ $msg = rcube::Q($msg);
+ }
+ else if ($sig->valid) {
+ $attrib['class'] = 'enigmanotice';
+ $msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext('sigvalid')));
+ }
+ else {
+ $attrib['class'] = 'enigmawarning';
+ $msg = rcube::Q(str_replace('$sender', $sender, $this->enigma->gettext('siginvalid')));
+ }
+ }
+ else if ($sig && $sig->getCode() == enigma_error::E_KEYNOTFOUND) {
+ $attrib['class'] = 'enigmawarning';
+ $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
+ $this->enigma->gettext('signokey')));
+ }
+ else {
+ $attrib['class'] = 'enigmaerror';
+ $msg = rcube::Q($this->enigma->gettext('sigerror'));
+ }
+/*
+ $msg .= ' ' . html::a(array('href' => "#sigdetails",
+ 'onclick' => rcmail_output::JS_OBJECT_NAME.".command('enigma-sig-details')"),
+ rcube::Q($this->enigma->gettext('showdetails')));
+*/
+ // test
+// $msg .= '<br /><pre>'.$sig->body.'</pre>';
+
+ $p['prefix'] .= html::div($attrib, $msg);
+
+ // Display each signature message only once
+ unset($engine->signatures[$engine->signed_parts[$part_id]]);
+ }
+
+ if ($attach_scripts) {
+ // add css and js script
+ $this->add_css();
+ $this->add_js();
+ }
+
+ return $p;
+ }
+
+ /**
+ * Handler for message_load hook.
+ * Check message bodies and attachments for keys/certs.
+ */
+ function message_load($p)
+ {
+ $engine = $this->enigma->load_engine();
+
+ // handle attachments vcard attachments
+ foreach ((array) $p['object']->attachments as $attachment) {
+ if ($engine->is_keys_part($attachment)) {
+ $this->keys_parts[] = $attachment->mime_id;
+ }
+ }
+
+ // the same with message bodies
+ foreach ((array) $p['object']->parts as $part) {
+ if ($engine->is_keys_part($part)) {
+ $this->keys_parts[] = $part->mime_id;
+ $this->keys_bodies[] = $part->mime_id;
+ }
+ }
+
+ // @TODO: inline PGP keys
+
+ if ($this->keys_parts) {
+ $this->enigma->add_texts('localization');
+ }
+
+ return $p;
+ }
+
+ /**
+ * Handler for template_object_messagebody hook.
+ * This callback function adds a box below the message content
+ * if there is a key/cert attachment available
+ */
+ function message_output($p)
+ {
+ foreach ($this->keys_parts as $part) {
+ // remove part's body
+ if (in_array($part, $this->keys_bodies)) {
+ $p['content'] = '';
+ }
+
+ // add box below message body
+ $p['content'] .= html::p(array('class' => 'enigmaattachment'),
+ html::a(array(
+ 'href' => "#",
+ 'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".enigma_import_attachment('".rcube::JQ($part)."')",
+ 'title' => $this->enigma->gettext('keyattimport')),
+ html::span(null, $this->enigma->gettext('keyattfound'))));
+
+ $attach_scripts = true;
+ }
+
+ if ($attach_scripts) {
+ // add css and js script
+ $this->add_css();
+ $this->add_js();
+ }
+
+ return $p;
}
}
diff --git a/plugins/enigma/localization/en_US.inc b/plugins/enigma/localization/en_US.inc
index f1ff8d1fb..5e8889faf 100644
--- a/plugins/enigma/localization/en_US.inc
+++ b/plugins/enigma/localization/en_US.inc
@@ -1,54 +1,60 @@
<?php
$labels = array();
-$labels['enigmasettings'] = 'Enigma Settings';
+$labels['enigma'] = 'Enigma';
$labels['enigmacerts'] = 'S/MIME Certificates';
$labels['enigmakeys'] = 'PGP Keys';
$labels['keysfromto'] = 'Keys $from to $to of $count';
$labels['keyname'] = 'Name';
$labels['keyid'] = 'Key ID';
$labels['keyuserid'] = 'User ID';
$labels['keytype'] = 'Key type';
$labels['fingerprint'] = 'Fingerprint';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = 'Basic Information';
$labels['userids'] = 'Additional User IDs';
$labels['typepublickey'] = 'public key';
$labels['typekeypair'] = 'key pair';
$labels['keyattfound'] = 'This message contains attached PGP key(s).';
$labels['keyattimport'] = 'Import key(s)';
$labels['createkeys'] = 'Create a new key pair';
$labels['importkeys'] = 'Import key(s)';
$labels['exportkeys'] = 'Export key(s)';
-$labels['deletekeys'] = 'Delete key(s)';
$labels['keyactions'] = 'Key actions...';
-$labels['keydisable'] = 'Disable key';
-$labels['keyrevoke'] = 'Revoke key';
+$labels['keyremove'] = 'Remove';
+$labels['keydisable'] = 'Disable';
+$labels['keyrevoke'] = 'Revoke';
$labels['keysend'] = 'Send public key in a message';
$labels['keychpass'] = 'Change password';
$labels['securityoptions'] = 'Message security options...';
$labels['identdefault'] = 'Use settings of selected identity';
$labels['encryptmsg'] = 'Encrypt this message';
$labels['signmsg'] = 'Digitally sign this message';
+$labels['enterkeypasstitle'] = 'Enter key passphrase';
+$labels['enterkeypass'] = 'A passphrase is needed to unlock the secret key ($keyid) for user: $user.';
+
$messages = array();
$messages['sigvalid'] = 'Verified signature from $sender.';
$messages['siginvalid'] = 'Invalid signature from $sender.';
$messages['sigunverified'] = 'Unverified signature. Certificate not verified. Certificate ID: $keyid.';
$messages['signokey'] = 'Unverified signature. Public key not found. Key ID: $keyid.';
$messages['sigerror'] = 'Unverified signature. Internal error.';
$messages['decryptok'] = 'Message decrypted.';
$messages['decrypterror'] = 'Decryption failed.';
$messages['decryptnokey'] = 'Decryption failed. Private key not found. Key ID: $keyid.';
$messages['decryptbadpass'] = 'Decryption failed. Bad password.';
$messages['nokeysfound'] = 'No keys found';
$messages['keyopenerror'] = 'Unable to get key information! Internal error.';
$messages['keylisterror'] = 'Unable to list keys! Internal error.';
$messages['keysimportfailed'] = 'Unable to import key(s)! Internal error.';
$messages['keysimportsuccess'] = 'Key(s) imported successfully. Imported: $new, unchanged: $old.';
-$messages['keyconfirmdelete'] = 'Are you sure, you want to delete selected key(s)?';
+$messages['keyremoving'] = 'Removing key(s)...';
+$messages['keyremoveconfirm'] = 'Are you sure, you want to delete selected key(s)?';
+$messages['keyremovesuccess'] = 'Key(s) deleted successfulyl';
+$messages['keyremoveerror'] = 'Unable to delete selected key(s).';
$messages['keyimporttext'] = 'You can import private and public key(s) or revocation signatures in ASCII-Armor format.';
?>
diff --git a/plugins/enigma/localization/ja_JP.inc b/plugins/enigma/localization/ja_JP.inc
index 60a920a5b..142e7f65d 100644
--- a/plugins/enigma/localization/ja_JP.inc
+++ b/plugins/enigma/localization/ja_JP.inc
@@ -1,49 +1,49 @@
<?php
-$labels['enigmasettings'] = 'Enigma: 設定';
+$labels['enigmasettings'] = 'Enigma';
$labels['enigmacerts'] = 'Enigma: 証明書 (S/MIME)';
$labels['enigmakeys'] = 'Enigma: 鍵 (PGP)';
$labels['keysfromto'] = '鍵の一覧 $from ~ $to (合計: $count )';
$labels['keyname'] = '名前';
$labels['keyid'] = '鍵 ID';
$labels['keyuserid'] = 'ユーザー ID';
$labels['keytype'] = '鍵の種類';
$labels['fingerprint'] = '指紋';
$labels['subkeys'] = 'Subkeys';
$labels['basicinfo'] = '基本情報';
$labels['userids'] = '追加のユーザー ID';
$labels['typepublickey'] = '公開鍵';
$labels['typekeypair'] = '鍵のペア';
$labels['keyattfound'] = 'このメールは PGP 鍵の添付があります。';
$labels['keyattimport'] = '鍵のインポート';
$labels['createkeys'] = '新しい鍵のペアを作成する';
$labels['importkeys'] = '鍵のインポート';
$labels['exportkeys'] = '鍵のエクスポート';
$labels['deletekeys'] = '鍵の削除';
$labels['keyactions'] = '鍵の操作...';
$labels['keydisable'] = '鍵を無効にする';
$labels['keyrevoke'] = '鍵を取り消す';
$labels['keysend'] = 'メッセージに公開鍵を含んで送信する';
$labels['keychpass'] = 'パスワードの変更';
$labels['securityoptions'] = 'メールのセキュリティ オプション...';
$labels['identdefault'] = '選択した識別子の設定を使う';
$labels['encryptmsg'] = 'このメールの暗号化';
$labels['signmsg'] = 'このメールのデジタル署名';
$messages['sigvalid'] = '$sender からの署名を検証しました。';
$messages['siginvalid'] = '$sender からの署名が正しくありません。';
$messages['signokey'] = '署名は未検証です。公開鍵が見つかりません。鍵 ID: $keyid';
$messages['sigerror'] = '署名は未検証です。内部エラーです。';
$messages['decryptok'] = 'メールを復号しました。';
$messages['decrypterror'] = '復号に失敗しました。';
$messages['decryptnokey'] = '復号に失敗しました。秘密鍵が見つかりません。鍵 ID: $keyid.';
$messages['decryptbadpass'] = '復号に失敗しました。パスワードが正しくありません。';
$messages['nokeysfound'] = '鍵が見つかりません。';
$messages['keyopenerror'] = '鍵情報の取得に失敗しました! 内部エラーです。';
$messages['keylisterror'] = '鍵情報のリストに失敗しました! 内部エラーです。';
$messages['keysimportfailed'] = '鍵のインポートに失敗しました! 内部エラーです。';
$messages['keysimportsuccess'] = '鍵をインポートしました。インポート: $new, 未変更: $old';
$messages['keyconfirmdelete'] = '選択した鍵を本当に削除しますか?';
$messages['keyimporttext'] = '秘密鍵と公開鍵のインポート、または ASCII 形式の署名を無効にできます。';
?>
diff --git a/plugins/enigma/localization/ru_RU.inc b/plugins/enigma/localization/ru_RU.inc
index 20db6b1cb..68599912b 100644
--- a/plugins/enigma/localization/ru_RU.inc
+++ b/plugins/enigma/localization/ru_RU.inc
@@ -1,59 +1,59 @@
<?php
/*
+-----------------------------------------------------------------------+
| plugins/enigma/localization/ru_RU.inc |
| |
| Russian translation for roundcube/enigma plugin |
| Copyright (C) 2010 |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
| Author: Sergey Dukachev <iam@dukess.ru> |
| Updates: |
+-----------------------------------------------------------------------+
@version 2010-12-23
*/
-$labels['enigmasettings'] = 'Enigma: Настройки';
+$labels['enigma'] = 'Enigma';
$labels['enigmacerts'] = 'Enigma: Сертификаты (S/MIME)';
$labels['enigmakeys'] = 'Enigma: Ключи (PGP)';
$labels['keysfromto'] = 'Ключи от $from к $to в количестве $count';
$labels['keyname'] = 'Имя';
$labels['keyid'] = 'Идентификатор ключа';
$labels['keyuserid'] = 'Идентификатор пользователя';
$labels['keytype'] = 'Тип ключа';
$labels['fingerprint'] = 'Отпечаток (хэш) ключа';
$labels['subkeys'] = 'Подразделы';
$labels['basicinfo'] = 'Основные сведения';
$labels['userids'] = 'Дополнительные идентификаторы пользователя';
$labels['typepublickey'] = 'Открытый ключ';
$labels['typekeypair'] = 'пара ключей';
$labels['keyattfound'] = 'Это сообщение содержит один или несколько ключей PGP.';
$labels['keyattimport'] = 'Импортировать ключи';
$labels['createkeys'] = 'Создать новую пару ключей';
$labels['importkeys'] = 'Импортировать ключ(и)';
$labels['exportkeys'] = 'Экспортировать ключ(и)';
$labels['deletekeys'] = 'Удалить ключ(и)';
$labels['keyactions'] = 'Действия с ключами...';
$labels['keydisable'] = 'Отключить ключ';
$labels['keyrevoke'] = 'Отозвать ключ';
$labels['keysend'] = 'Отправить публичный ключ в собщении';
$labels['keychpass'] = 'Изменить пароль';
$messages['sigvalid'] = 'Проверенная подпись у $sender.';
$messages['siginvalid'] = 'Неверная подпись у $sender.';
$messages['signokey'] = 'Непроверяемая подпись. Открытый ключ не найден. Идентификатор ключа: $keyid.';
$messages['sigerror'] = 'Непроверяемая подпись. Внутренняя ошибка.';
$messages['decryptok'] = 'Сообщение расшифровано.';
$messages['decrypterror'] = 'Расшифровка не удалась.';
$messages['decryptnokey'] = 'Расшифровка не удалась. Секретный ключ не найден. Идентификатор ключа: $keyid.';
$messages['decryptbadpass'] = 'Расшифровка не удалась. Неправильный пароль.';
$messages['nokeysfound'] = 'Ключи не найдены';
$messages['keyopenerror'] = 'Невозможно получить информацию о ключе! Внутренняя ошибка.';
$messages['keylisterror'] = 'Невозможно сделать список ключей! Внутренняя ошибка.';
$messages['keysimportfailed'] = 'Невозможно импортировать ключ(и)! Внутренняя ошибка.';
$messages['keysimportsuccess'] = 'Ключи успешно импортированы. Импортировано: $new, без изменений: $old.';
$messages['keyconfirmdelete'] = 'Вы точно хотите удалить выбранные ключи?';
$messages['keyimporttext'] = 'Вы можете импортировать открытые и секретные ключи или сообщения об отзыве ключей в формате ASCII-Armor.';
?>
diff --git a/plugins/enigma/skins/classic/enigma.css b/plugins/enigma/skins/classic/enigma.css
index b1c656f82..bbc4af99d 100644
--- a/plugins/enigma/skins/classic/enigma.css
+++ b/plugins/enigma/skins/classic/enigma.css
@@ -1,182 +1,208 @@
/*** Style for Enigma plugin ***/
/***** Messages displaying *****/
#enigma-message,
/* fixes border-top */
#messagebody div #enigma-message
{
margin: 0;
margin-bottom: 5px;
min-height: 20px;
padding: 10px 10px 6px 46px;
}
div.enigmaerror,
/* fixes border-top */
#messagebody div.enigmaerror
{
background: url(enigma_error.png) 6px 1px no-repeat;
background-color: #EF9398;
border: 1px solid #DC5757;
}
div.enigmanotice,
/* fixes border-top */
#messagebody div.enigmanotice
{
background: url(enigma.png) 6px 1px no-repeat;
background-color: #A6EF7B;
border: 1px solid #76C83F;
}
div.enigmawarning,
/* fixes border-top */
#messagebody div.enigmawarning
{
background: url(enigma.png) 6px 1px no-repeat;
background-color: #F7FDCB;
border: 1px solid #C2D071;
}
#enigma-message a
{
color: #666666;
padding-left: 10px;
}
#enigma-message a:hover
{
color: #333333;
}
+p.enigmaattachment
+{
+ margin: 0.5em 1em;
+ border: 1px solid #999;
+ border-radius: 4px;
+ width: auto;
+}
+
+p.enigmaattachment a
+{
+ display: block;
+ background: url(key_add.png) 10px center no-repeat;
+ padding: 1em 0.5em 1em 50px;
+}
+
+
+/***** E-mail Compose Page *****/
+
+#messagetoolbar a.button.enigma {
+ text-indent: -5000px;
+ background: url(enigma.png) 0 0 no-repeat;
+}
+
/***** Keys/Certs Management *****/
+#mainscreen.enigma
+{
+ top: 80px;
+}
+
div.enigmascreen
{
position: absolute;
- top: 65px;
- right: 10px;
- bottom: 10px;
- left: 10px;
+ top: 40px;
+ right: 0;
+ bottom: 0;
+ left: 0;
+}
+
+.enigma #quicksearchbar
+{
+ top: 10px;
+ right: 0;
}
#enigmacontent-box
{
position: absolute;
top: 0px;
left: 290px;
right: 0px;
bottom: 0px;
border: 1px solid #999999;
overflow: hidden;
}
#enigmakeyslist
{
position: absolute;
top: 0;
bottom: 0;
left: 0;
border: 1px solid #999999;
background-color: #F9F9F9;
overflow: hidden;
}
#keylistcountbar
{
margin-top: 4px;
margin-left: 4px;
}
#keys-table
{
width: 100%;
table-layout: fixed;
}
#keys-table td
{
cursor: default;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
}
#key-details table td.title
{
font-weight: bold;
text-align: right;
}
#keystoolbar
{
position: absolute;
- top: 30px;
+ top: 0;
left: 10px;
height: 35px;
}
#keystoolbar a
{
padding-right: 10px;
}
#keystoolbar a.button,
#keystoolbar a.buttonPas,
#keystoolbar span.separator {
display: block;
float: left;
width: 32px;
height: 32px;
padding: 0;
margin-right: 10px;
overflow: hidden;
background: url(keys_toolbar.png) 0 0 no-repeat transparent;
opacity: 0.99; /* this is needed to make buttons appear correctly in Chrome */
}
#keystoolbar a.buttonPas {
opacity: 0.35;
}
#keystoolbar a.createSel {
background-position: 0 -32px;
}
#keystoolbar a.create {
background-position: 0 0;
}
-#keystoolbar a.deleteSel {
- background-position: -32px -32px;
-}
-
-#keystoolbar a.delete {
- background-position: -32px 0;
-}
-
#keystoolbar a.importSel {
background-position: -64px -32px;
}
#keystoolbar a.import {
background-position: -64px 0;
}
#keystoolbar a.exportSel {
background-position: -96px -32px;
}
#keystoolbar a.export {
background-position: -96px 0;
}
#keystoolbar a.keymenu {
background-position: -128px 0;
width: 36px;
}
#keystoolbar span.separator {
width: 5px;
background-position: -166px 0;
}
diff --git a/plugins/enigma/skins/classic/templates/keys.html b/plugins/enigma/skins/classic/templates/keys.html
index f581c457b..f64c7353a 100644
--- a/plugins/enigma/skins/classic/templates/keys.html
+++ b/plugins/enigma/skins/classic/templates/keys.html
@@ -1,80 +1,89 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
<script type="text/javascript" src="/functions.js"></script>
<script type="text/javascript" src="/splitter.js"></script>
<style type="text/css">
#enigmakeyslist { width: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter-5 : 210" />px; }
#enigmacontent-box { left: <roundcube:exp expression="!empty(cookie:enigmaviewsplitter) ? cookie:enigmaviewsplitter+5 : 220" />px;
<roundcube:exp expression="browser:ie ? ('width:expression((parseInt(this.parentNode.offsetWidth)-'.(!empty(cookie:enigmaeviewsplitter) ? cookie:enigmaviewsplitter+5 : 220).')+\\'px\\');') : ''" />
}
</style>
</head>
-<body class="iframe">
+<body>
-<div id="prefs-title" class="boxtitle"><roundcube:label name="enigma.enigmakeys" /></div>
-<div id="prefs-details" class="boxcontent">
+<roundcube:include file="/includes/taskbar.html" />
+<roundcube:include file="/includes/header.html" />
+<roundcube:include file="/includes/settingstabs.html" />
+
+<div id="mainscreen" class="enigma">
<div id="keystoolbar">
+<!--
<roundcube:button command="plugin.enigma-key-create" type="link" class="buttonPas create" classAct="button create" classSel="button createSel" title="enigma.createkeys" content=" " />
- <roundcube:button command="plugin.enigma-key-delete" type="link" class="buttonPas delete" classAct="button delete" classSel="button deleteSel" title="enigma.deletekeys" content=" " />
<span class="separator"> </span>
+-->
<roundcube:button command="plugin.enigma-key-import" type="link" class="buttonPas import" classAct="button import" classSel="button importSel" title="enigma.importkeys" content=" " />
+<!--
<roundcube:button command="plugin.enigma-key-export" type="link" class="buttonPas export" classAct="button export" classSel="button exportSel" title="enigma.exportkeys" content=" " />
+-->
<roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button keymenu" title="enigma.keyactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " />
</div>
-<div id="quicksearchbar" style="top: 35px; right: 10px;">
+<div id="quicksearchbar">
<roundcube:button name="searchmenulink" id="searchmenulink" image="/images/icons/glass.png" />
<roundcube:object name="searchform" id="quicksearchbox" />
<roundcube:button command="reset-search" id="searchreset" image="/images/icons/reset.gif" title="resetsearch" />
</div>
<div class="enigmascreen">
<div id="enigmakeyslist">
<div class="boxtitle"><roundcube:label name="enigma.keyname" /></div>
<div class="boxlistcontent">
<roundcube:object name="keyslist" id="keys-table" class="records-table" cellspacing="0" noheader="true" />
</div>
<div class="boxfooter">
<div id="keylistcountbar" class="pagenav">
<roundcube:button command="firstpage" type="link" class="buttonPas firstpage" classAct="button firstpage" classSel="button firstpageSel" title="firstpage" content=" " />
<roundcube:button command="previouspage" type="link" class="buttonPas prevpage" classAct="button prevpage" classSel="button prevpageSel" title="previouspage" content=" " />
<roundcube:object name="countdisplay" style="padding:0 .5em; float:left" />
<roundcube:button command="nextpage" type="link" class="buttonPas nextpage" classAct="button nextpage" classSel="button nextpageSel" title="nextpage" content=" " />
<roundcube:button command="lastpage" type="link" class="buttonPas lastpage" classAct="button lastpage" classSel="button lastpageSel" title="lastpage" content=" " />
</div>
</div>
</div>
<script type="text/javascript">
var enigmaviewsplit = new rcube_splitter({id:'enigmaviewsplitter', p1: 'enigmakeyslist', p2: 'enigmacontent-box', orientation: 'v', relative: true, start: 215});
rcmail.add_onload('enigmaviewsplit.init()');
</script>
<div id="enigmacontent-box">
<roundcube:object name="keyframe" id="keyframe" width="100%" height="100%" frameborder="0" src="/watermark.html" />
</div>
</div>
</div>
<div id="messagemenu" class="popupmenu">
- <ul class="toolbarmenu">
- <li><roundcube:button class="disablelink" command="enigma.key-disable" label="enigma.keydisable" target="_blank" classAct="disablelink active" /></li>
- <li><roundcube:button class="revokelink" command="enigma.key-revoke" label="enigma.keyrevoke" classAct="revokelink active" /></li>
- <li class="separator_below"><roundcube:button class="sendlink" command="enigma.key-send" label="enigma.keysend" classAct="sendlink active" /></li>
- <li><roundcube:button class="chpasslink" command="enigma.key-chpass" label="enigma.keychpass" classAct="chpasslink active" /></li>
+ <ul>
+ <li><roundcube:button class="deletelink" command="plugin.enigma-key-delete" label="enigma.keyremove" classAct="deletelink active" /></li>
+<!--
+ <li><roundcube:button class="disablelink" command="plugin.enigma-key-disable" label="enigma.keydisable" classAct="disablelink active" /></li>
+ <li><roundcube:button class="revokelink" command="plugin.enigma-key-revoke" label="enigma.keyrevoke" classAct="revokelink active" /></li>
+ <li class="separator_below"><roundcube:button class="sendlink" command="plugin.enigma-key-send" label="enigma.keysend" classAct="sendlink active" /></li>
+ <li><roundcube:button class="chpasslink" command="plugin.enigma-key-chpass" label="enigma.keychpass" classAct="chpasslink active" /></li>
+-->
</ul>
</div>
<script type="text/javascript">
rcube_init_mail_ui();
</script>
</body>
</html>
diff --git a/plugins/enigma/skins/larry/enigma.css b/plugins/enigma/skins/larry/enigma.css
new file mode 100644
index 000000000..046697c56
--- /dev/null
+++ b/plugins/enigma/skins/larry/enigma.css
@@ -0,0 +1,148 @@
+/*** Style for Enigma plugin ***/
+
+/***** Messages displaying *****/
+
+#enigma-message,
+#messagebody div #enigma-message
+{
+ margin: 0;
+ margin-bottom: 5px;
+ padding: 6px 12px 6px 30px;
+ font-weight: bold;
+}
+
+div.enigmaerror,
+#messagebody div.enigmaerror
+{
+ background: url(enigma_icons.png) 3px -201px no-repeat #f2cccd;
+ border: 1px solid #c00005;
+ color: #c00005;
+}
+
+div.enigmanotice,
+#messagebody div.enigmanotice
+{
+ background: url(enigma_icons.png) 3px -171px no-repeat #c9e6d3;
+ border: 1px solid #008a2e;
+ color: #008a2e;
+}
+
+div.enigmawarning,
+#messagebody div.enigmawarning
+{
+ background: url(enigma_icons.png) 3px -231px no-repeat #fef893;
+ border: 1px solid #ffdf0e;
+ color: #960;
+}
+
+#enigma-message a
+{
+ color: #666666;
+ padding-left: 10px;
+}
+
+#enigma-message a:hover
+{
+ color: #333333;
+}
+
+p.enigmaattachment {
+ margin: 0.5em 1em;
+ width: auto;
+ background: #f9f9f9;
+ border: 1px solid #d3d3d3;
+ border-radius: 4px;
+ box-shadow: 0 0 2px #ccc;
+ -webkit-box-shadow: 0 0 2px #ccc;
+}
+
+p.enigmaattachment a {
+ display: block;
+ background: url(enigma_icons.png) 8px -78px no-repeat;
+ padding: 1em 0.5em 1em 46px;
+}
+
+/***** E-mail Compose Page *****/
+
+#messagetoolbar a.button.enigma {
+ text-indent: -5000px;
+ background: url(enigma_icons.png) center -122px no-repeat;
+}
+
+/***** Keys/Certs Management *****/
+
+#settings-sections .enigma.keys a {
+ background-image: url(enigma_icons.png);
+ background-position: 7px -345px;
+ background-repeat: no-repeat;
+}
+
+#settings-sections .enigma.keys.selected a {
+ background-image: url(enigma_icons.png);
+ background-position: 7px -368px;
+ background-repeat: no-repeat;
+}
+
+#mainscreen.enigma #settings-sections,
+#mainscreen.enigma #settings-right
+{
+ top: 44px;
+}
+
+#enigmacontent-box
+{
+ position: absolute;
+ top: 0px;
+ left: 272px;
+ right: 0px;
+ bottom: 0px;
+}
+
+#enigmakeyslist
+{
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 260px;
+}
+
+#keylistcountbar
+{
+ margin-top: 4px;
+ margin-left: 4px;
+}
+
+#keys-table
+{
+ width: 100%;
+ table-layout: fixed;
+}
+
+#keys-table td
+{
+ text-overflow: ellipsis;
+}
+
+#keystoolbar
+{
+ position: absolute;
+ top: -6px;
+ left: 0;
+ height: 40px;
+ white-space: nowrap;
+ z-index: 10;
+}
+
+#keystoolbar a.button
+{
+ background: url(enigma_icons.png) 0 0 no-repeat transparent;
+}
+
+#keystoolbar a.import {
+ background-position: center 0;
+}
+
+#keystoolbar a.export {
+ background-position: center 0;
+}
diff --git a/plugins/enigma/skins/larry/enigma_icons.png b/plugins/enigma/skins/larry/enigma_icons.png
new file mode 100644
index 000000000..ce4d76fb7
Binary files /dev/null and b/plugins/enigma/skins/larry/enigma_icons.png differ
diff --git a/plugins/enigma/skins/larry/templates/keyimport.html b/plugins/enigma/skins/larry/templates/keyimport.html
new file mode 100644
index 000000000..83191184c
--- /dev/null
+++ b/plugins/enigma/skins/larry/templates/keyimport.html
@@ -0,0 +1,23 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
+</head>
+<body class="iframe">
+
+<h1 class="boxtitle"><roundcube:label name="enigma.importkeys" /></h1>
+
+<div id="import-form" class="boxcontent">
+ <roundcube:object name="importform" />
+ <br>
+ <div class="formbuttons">
+ <roundcube:button command="plugin.enigma-import" type="input" class="button mainaction" label="import" />
+ </div>
+</div>
+
+<roundcube:include file="/includes/footer.html" />
+
+</body>
+</html>
diff --git a/plugins/enigma/skins/larry/templates/keyinfo.html b/plugins/enigma/skins/larry/templates/keyinfo.html
new file mode 100644
index 000000000..3db760ad4
--- /dev/null
+++ b/plugins/enigma/skins/larry/templates/keyinfo.html
@@ -0,0 +1,19 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
+</head>
+<body class="iframe">
+
+<h1 class="boxtitle"><roundcube:object name="keyname" part="name" /></h1>
+
+<div id="key-details" class="boxcontent propform">
+ <roundcube:object name="keydata" class="propform" />
+</div>
+
+<roundcube:include file="/includes/footer.html" />
+
+</body>
+</html>
diff --git a/plugins/enigma/skins/larry/templates/keys.html b/plugins/enigma/skins/larry/templates/keys.html
new file mode 100644
index 000000000..35d179933
--- /dev/null
+++ b/plugins/enigma/skins/larry/templates/keys.html
@@ -0,0 +1,83 @@
+<roundcube:object name="doctype" value="html5" />
+<html>
+<head>
+<title><roundcube:object name="pagetitle" /></title>
+<roundcube:include file="/includes/links.html" />
+<link rel="stylesheet" type="text/css" href="/this/enigma.css" />
+</head>
+<roundcube:if condition="env:extwin" /><body class="noscroll extwin"><roundcube:else /><body class="noscroll"><roundcube:endif />
+
+<roundcube:include file="/includes/header.html" />
+
+<div id="mainscreen" class="enigma">
+ <h1 class="voice"><roundcube:label name="settings" /> : <roundcube:label name="enigma.enigmakeys" /></h1>
+
+ <!-- toolbar -->
+ <h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
+ <div id="keystoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
+ <roundcube:button command="plugin.enigma-key-import" type="link" class="button import disabled" classAct="button import" classSel="button import pressed" label="import" title="enigma.importkeys" />
+<!--
+ <roundcube:button command="plugin.enigma-key-export" type="link" class="button export disabled" classAct="button export" classSel="button export pressed" label="export" title="enigma.exportkeys" />
+-->
+ </div>
+
+ <div id="quicksearchbar" class="searchbox" role="search" aria-labelledby="aria-label-searchform">
+ <h2 id="aria-label-searchform" class="voice"><roundcube:label name="enigma.arialabelkeysearchform" /></h2>
+ <label for="quicksearchbox" class="voice"><roundcube:label name="arialabelmailquicksearchbox" /></label>
+ <roundcube:object name="searchform" id="quicksearchbox" />
+ <a id="searchmenulink" class="iconbutton searchicon" > </a>
+ <roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" label="resetsearch" />
+ </div>
+
+ <roundcube:include file="/includes/settingstabs.html" />
+
+ <div id="settings-right" role="main" aria-labelledby="aria-label-enigmakeyslist">
+ <div id="enigmakeyslist" class="uibox listbox" role="navigation" aria-labelledby="enigmakeyslist-header">
+ <div id="enigmakeyslist-header" class="boxtitle"><roundcube:label name="enigma.enigmakeys" /></div>
+ <div class="scroller winfooter">
+ <roundcube:object name="keyslist" id="keys-table" class="listing" role="listbox" cellspacing="0" noheader="true" />
+ </div>
+ <div class="boxpagenav">
+ <roundcube:button command="firstpage" type="link" class="icon firstpage disabled" classAct="icon firstpage" title="firstpage" label="first" />
+ <roundcube:button command="previouspage" type="link" class="icon prevpage disabled" classAct="icon prevpage" title="previouspage" label="previous" />
+ <roundcube:button command="nextpage" type="link" class="icon nextpage disabled" classAct="icon nextpage" title="nextpage" label="next" />
+ <roundcube:button command="lastpage" type="link" class="icon lastpage disabled" classAct="icon lastpage" title="lastpage" label="last" />
+ </div>
+ <div class="boxfooter">
+ <roundcube:button command="plugin.enigma-key-create" type="link" title="enigma.keycreate" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="enigma.keyadd" /><roundcube:button name="moreactions" id="keyoptionslink" type="link" title="enigma.keyactions" class="listbutton groupactions" onclick="return UI.toggle_popup('keyoptions',event)" innerClass="inner" label="enigma.arialabelkeyoptions" aria-haspopup="true" aria-expanded="false" aria-owns="keyoptionsmenu" />
+ <span class="countdisplay" aria-live="polite" aria-relevant="text">
+ <span class="voice"><roundcube:label name="enigma.enigmakeys" /></span>
+ <roundcube:object name="countdisplay" />
+ </span>
+ </div>
+ </div>
+
+ <div id="enigmacontent-box" class="uibox">
+ <div class="iframebox">
+ <roundcube:object name="keyframe" id="keyframe" width="100%" height="100%" frameborder="0" src="/watermark.html" />
+ </div>
+ </div>
+ </div>
+</div>
+
+<div id="keyoptions" class="popupmenu">
+ <ul class="toolbarmenu">
+ <li><roundcube:button class="deletelink" command="plugin.enigma-key-delete" label="enigma.keyremove" target="_blank" classAct="deletelink active" /></li>
+<!--
+ <li><roundcube:button class="disablelink" command="enigma.key-disable" label="enigma.keydisable" target="_blank" classAct="disablelink active" /></li>
+ <li><roundcube:button class="revokelink" command="enigma.key-revoke" label="enigma.keyrevoke" classAct="revokelink active" /></li>
+ <li class="separator_below"><roundcube:button class="sendlink" command="enigma.key-send" label="enigma.keysend" classAct="sendlink active" /></li>
+ <li><roundcube:button class="chpasslink" command="enigma.key-chpass" label="enigma.keychpass" classAct="chpasslink active" /></li>
+-->
+ </ul>
+</div>
+
+<roundcube:include file="/includes/footer.html" />
+
+<script type="text/javascript">
+ new rcube_splitter({ id:'enigmakeyssplitter', p1:'#enigmakeyslist', p2:'#enigmacontent-box',
+ orientation:'v', relative:true, start:266, min:180, size:12 }).init();
+</script>
+
+</body>
+</html>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Mar 1, 4:18 AM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165835
Default Alt Text
(153 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment