Enigma: Larry support, finished PGP decryption, other fixes and improvements
5 files added
15 files modified
| | |
| | | |
| | | 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 |
| | | - 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): |
| | | 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 |
| | | - 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) |
| | |
| | | /* 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); |
| | | 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) |
| | | { |
| | | 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(); |
| | | 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.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'); }); |
| | | } |
| | | 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'); |
| | | }; |
| | | |
| | | // Submit key(s) form |
| | | // 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) 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; |
| | | } |
| | | }; |
| | | |
| | |
| | | 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; |
| | |
| | | // 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; |
| | |
| | | params._p = page; |
| | | |
| | | this.enigma_clear_list(); |
| | | this.http_post('plugin.enigma', params, lock); |
| | | this.http_post('plugin.enigmakeys', params, lock); |
| | | } |
| | | |
| | | // Change list page |
| | |
| | | // 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(); |
| | | } |
| | |
| | | /* |
| | | +-------------------------------------------------------------------------+ |
| | | | 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 | |
| | |
| | | public $rc; |
| | | public $engine; |
| | | |
| | | private $env_loaded; |
| | | private $message; |
| | | private $keys_parts = array(); |
| | | private $keys_bodies = array(); |
| | | private $env_loaded = false; |
| | | |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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')); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | function load_env() |
| | | { |
| | | if ($this->env_loaded) |
| | | if ($this->env_loaded) { |
| | | return; |
| | | } |
| | | |
| | | $this->env_loaded = true; |
| | | |
| | |
| | | /** |
| | | * 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(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @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 $p; |
| | | return $this->engine->part_structure($p); |
| | | } |
| | | |
| | | /** |
| | | * Handler for preferences_sections_list hook. |
| | | * Adds Enigma settings sections into preferences sections list. |
| | | * Handler for message_part_body hook. |
| | | * Called to get body of a message part. |
| | | * |
| | | * @param array Original parameters |
| | | * |
| | | * @return array Modified parameters |
| | | */ |
| | | function preferences_section($p) |
| | | function part_body($p) |
| | | { |
| | | $this->load_engine(); |
| | | |
| | | return $this->engine->part_body($p); |
| | | } |
| | | |
| | | /** |
| | | * Handler for settings_actions hook. |
| | | * Adds Enigma settings section into preferences. |
| | | * |
| | | * @param array Original parameters |
| | | * |
| | | * @return array Modified parameters |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | // 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; |
| | | } |
| | | |
| | |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | |
| | | function preferences_ui() |
| | | { |
| | | $this->load_ui(); |
| | | |
| | | $this->ui->init(); |
| | | } |
| | | |
| | |
| | | */ |
| | | function status_message($p) |
| | | { |
| | | $part_id = $p['part']->mime_id; |
| | | $this->load_ui(); |
| | | |
| | | // 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; |
| | | return $this->ui->status_message($p); |
| | | } |
| | | |
| | | /** |
| | | * 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); |
| | | } |
| | | |
| | | /** |
| | | * 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']; |
| | | $this->load_ui(); |
| | | |
| | | // 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 |
| | | |
| | | if ($this->keys_parts) { |
| | | $this->add_texts('localization'); |
| | | } |
| | | return $this->ui->message_load($p); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | function message_output($p) |
| | | { |
| | | $attach_script = false; |
| | | $this->load_ui(); |
| | | |
| | | 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'); |
| | | } |
| | | |
| | | return $p; |
| | | return $this->ui->message_output($p); |
| | | } |
| | | |
| | | /** |
| | |
| | | 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; |
| | | } |
| | | } |
| | |
| | | |
| | | /** |
| | | * 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. |
| | |
| | | * @return mixed Array of enigma_key objects or enigma_error |
| | | */ |
| | | abstract function list_keys($pattern=''); |
| | | |
| | | |
| | | /** |
| | | * Single key information. |
| | | * |
| | |
| | | * @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); |
| | | } |
| | |
| | | |
| | | function __construct($user) |
| | | { |
| | | $rcmail = rcmail::get_instance(); |
| | | $this->rc = $rcmail; |
| | | $this->rc = rcmail::get_instance(); |
| | | $this->user = $user; |
| | | } |
| | | |
| | |
| | | */ |
| | | 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, |
| | |
| | | try { |
| | | $this->gpg = new Crypt_GPG(array( |
| | | 'homedir' => $this->homedir, |
| | | // 'debug' => true, |
| | | // 'binary' => '/usr/bin/gpg2', |
| | | // 'debug' => true, |
| | | )); |
| | | } |
| | | catch (Exception $e) { |
| | |
| | | */ |
| | | } |
| | | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | { |
| | | } |
| | | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | |
| | | public function del_pubkey($keyid) |
| | | public function delete_pubkey($keyid) |
| | | { |
| | | try { |
| | | $this->gpg->deletePublicKey($keyid); |
| | |
| | | class enigma_driver_phpssl extends enigma_driver |
| | | { |
| | | private $rc; |
| | | //private $gpg; |
| | | private $homedir; |
| | | private $user; |
| | | |
| | |
| | | { |
| | | } |
| | | |
| | | function decrypt($text, $key, $passwd) |
| | | function decrypt($text, $keys = array()) |
| | | { |
| | | } |
| | | |
| | |
| | | { |
| | | } |
| | | |
| | | 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) |
| | | { |
| | | } |
| | | |
| | |
| | | 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; |
| | | |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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(); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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 |
| | |
| | | */ |
| | | 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 |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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 |
| | |
| | | { |
| | | $part = $p['structure']; |
| | | |
| | | // Get message body from IMAP server |
| | | $this->set_part_body($part, $p['object']->uid); |
| | | // exit, if we're already inside a decrypted message |
| | | if ($part->encrypted) { |
| | | return; |
| | | } |
| | | |
| | | // @TODO: big message body can be a file resource |
| | | // Get message body from IMAP server |
| | | $body = $this->get_part_body($p['object'], $part->mime_id); |
| | | |
| | | // @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); |
| | | } |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | 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 |
| | |
| | | $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)) { |
| | |
| | | * 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) |
| | | { |
| | |
| | | $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; |
| | |
| | | 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]); |
| | | } |
| | | } |
| | |
| | | */ |
| | | private function parse_smime_signed(&$p) |
| | | { |
| | | return; // @TODO |
| | | |
| | | // Verify signature |
| | | if ($this->rc->action == 'show' || $this->rc->action == 'preview') { |
| | | $this->load_smime_driver(); |
| | |
| | | /** |
| | | * 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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | |
| | | 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; |
| | |
| | | { |
| | | // @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(); |
| | |
| | | return $result; |
| | | } |
| | | |
| | | // $msg_body = $result; |
| | | $msg_body = $result; |
| | | |
| | | return true; |
| | | } |
| | | |
| | |
| | | * |
| | | * @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); |
| | |
| | | { |
| | | $this->load_pgp_driver(); |
| | | $result = $this->pgp_driver->get_key($keyid); |
| | | |
| | | |
| | | if ($result instanceof enigma_error) { |
| | | rcube::raise_error(array( |
| | | 'code' => 600, 'type' => 'php', |
| | |
| | | '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; |
| | | } |
| | | |
| | |
| | | $this->rc->output->send(); |
| | | } |
| | | |
| | | /** |
| | | * Checks if specified message part contains body data. |
| | | * If body is not set it will be fetched from IMAP server. |
| | | * |
| | | * @param rcube_message_part Message part object |
| | | * @param integer Message UID |
| | | */ |
| | | private function set_part_body($part, $uid) |
| | | function password_handler() |
| | | { |
| | | // @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); |
| | | $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; |
| | | } |
| | | |
| | | /** |
| | | * Get message part body. |
| | | * |
| | | * @param rcube_message Message object |
| | | * @param string Message part ID |
| | | * @param bool Return raw body with headers |
| | | */ |
| | | private function get_part_body($msg, $part_id, $full = false) |
| | | { |
| | | // @TODO: Handle big bodies using file handles |
| | | 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' |
| | | ); |
| | | } |
| | | } |
| | |
| | | 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() |
| | |
| | | |
| | | function getData($name) |
| | | { |
| | | if ($name) |
| | | if ($name) { |
| | | return $this->data[$name]; |
| | | else |
| | | } |
| | | else { |
| | | return $this->data; |
| | | } |
| | | } |
| | | } |
| | |
| | | { |
| | | 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() |
| | |
| | | |
| | | /** |
| | | * Returns true if all user IDs are revoked |
| | | */ |
| | | */ |
| | | function is_revoked() |
| | | { |
| | | foreach ($this->subkeys as $subkey) |
| | |
| | | |
| | | /** |
| | | * Returns true if any user ID is valid |
| | | */ |
| | | */ |
| | | function is_valid() |
| | | { |
| | | foreach ($this->users as $user) |
| | |
| | | |
| | | 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; |
| | | } |
| | | |
| | |
| | | static function format_id($id) |
| | | { |
| | | // E.g. 04622F2089E037A5 => 89E037A5 |
| | | |
| | | |
| | | return substr($id, -8); |
| | | } |
| | | |
| | |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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 |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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() |
| | |
| | | $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(); |
| | | } |
| | | |
| | | /** |
| | |
| | | $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; |
| | | } |
| | |
| | | */ |
| | | 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'); |
| | |
| | | $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('userids')) . $table->show($attrib)); |
| | | |
| | | */ |
| | | return $out; |
| | | } |
| | | |
| | |
| | | |
| | | $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->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) |
| | |
| | | $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; |
| | | } |
| | | |
| | | } |
| | |
| | | <?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['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['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['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.'; |
| | | |
| | | ?> |
| | |
| | | |
| | | |
| | | |
| | | $labels['enigmasettings'] = 'Enigma: 設定'; |
| | | $labels['enigmasettings'] = 'Enigma'; |
| | | $labels['enigmacerts'] = 'Enigma: 証明書 (S/MIME)'; |
| | | $labels['enigmakeys'] = 'Enigma: 鍵 (PGP)'; |
| | | $labels['keysfromto'] = '鍵の一覧 $from ~ $to (合計: $count )'; |
| | |
| | | @version 2010-12-23 |
| | | |
| | | */ |
| | | $labels['enigmasettings'] = 'Enigma: Настройки'; |
| | | $labels['enigma'] = 'Enigma'; |
| | | $labels['enigmacerts'] = 'Enigma: Сертификаты (S/MIME)'; |
| | | $labels['enigmakeys'] = 'Enigma: Ключи (PGP)'; |
| | | $labels['keysfromto'] = 'Ключи от $from к $to в количестве $count'; |
| | |
| | | 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 |
| | |
| | | #keystoolbar |
| | | { |
| | | position: absolute; |
| | | top: 30px; |
| | | top: 0; |
| | | left: 10px; |
| | | height: 35px; |
| | | } |
| | |
| | | |
| | | #keystoolbar a.create { |
| | | background-position: 0 0; |
| | | } |
| | | |
| | | #keystoolbar a.deleteSel { |
| | | background-position: -32px -32px; |
| | | } |
| | | |
| | | #keystoolbar a.delete { |
| | | background-position: -32px 0; |
| | | } |
| | | |
| | | #keystoolbar a.importSel { |
| | |
| | | } |
| | | </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 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> |
| | | |
New file |
| | |
| | | /*** 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; |
| | | } |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |
New file |
| | |
| | | <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> |