3 +-------------------------------------------------------------------------+
4 | Enigma Plugin for Roundcube |
7 | This program is free software; you can redistribute it and/or modify |
8 | it under the terms of the GNU General Public License version 2 |
9 | as published by the Free Software Foundation. |
11 | This program is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | GNU General Public License for more details. |
16 | You should have received a copy of the GNU General Public License along |
17 | with this program; if not, write to the Free Software Foundation, Inc., |
18 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
20 +-------------------------------------------------------------------------+
21 | Author: Aleksander Machniak <alec@alec.pl> |
22 +-------------------------------------------------------------------------+
26 This class contains only hooks and action handlers.
27 Most plugin logic is placed in enigma_engine and enigma_ui classes.
30 class enigma extends rcube_plugin
32 public $task = 'mail|settings';
38 private $keys_parts = array();
39 private $keys_bodies = array();
43 * Plugin initialization.
47 $rcmail = rcmail::get_instance();
50 if ($this->rc->task == 'mail') {
51 // message parse/display hooks
52 $this->add_hook('message_part_structure', array($this, 'parse_structure'));
53 $this->add_hook('message_body_prefix', array($this, 'status_message'));
56 if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
57 $this->add_hook('message_load', array($this, 'message_load'));
58 $this->add_hook('template_object_messagebody', array($this, 'message_output'));
59 $this->register_action('plugin.enigmaimport', array($this, 'import_file'));
62 else if ($rcmail->action == 'compose') {
64 $this->ui->init($section);
66 // message sending (and draft storing)
67 else if ($rcmail->action == 'sendmail') {
68 //$this->add_hook('outgoing_message_body', array($this, 'msg_encode'));
69 //$this->add_hook('outgoing_message_body', array($this, 'msg_sign'));
72 else if ($this->rc->task == 'settings') {
73 // add hooks for Enigma settings
74 $this->add_hook('preferences_sections_list', array($this, 'preferences_section'));
75 $this->add_hook('preferences_list', array($this, 'preferences_list'));
76 $this->add_hook('preferences_save', array($this, 'preferences_save'));
78 // register handler for keys/certs management
79 $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
81 // grab keys/certs management iframe requests
82 $section = get_input_value('_section', RCUBE_INPUT_GET);
83 if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
85 $this->ui->init($section);
91 * Plugin environment initialization.
95 if ($this->env_loaded)
98 $this->env_loaded = true;
100 // Add include path for Enigma classes and drivers
101 $include_path = $this->home . '/lib' . PATH_SEPARATOR;
102 $include_path .= ini_get('include_path');
103 set_include_path($include_path);
105 // load the Enigma plugin configuration
106 $this->load_config();
108 // include localization (if wasn't included before)
109 $this->add_texts('localization/');
113 * Plugin UI initialization.
120 // load config/localization
124 $this->ui = new enigma_ui($this, $this->home);
128 * Plugin engine initialization.
130 function load_engine()
135 // load config/localization
138 $this->engine = new enigma_engine($this);
142 * Handler for message_part_structure hook.
143 * Called for every part of the message.
145 * @param array Original parameters
147 * @return array Modified parameters
149 function parse_structure($p)
151 $struct = $p['structure'];
153 if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
154 $this->parse_plain($p);
156 else if ($p['mimetype'] == 'multipart/signed') {
157 $this->parse_signed($p);
159 else if ($p['mimetype'] == 'multipart/encrypted') {
160 $this->parse_encrypted($p);
162 else if ($p['mimetype'] == 'application/pkcs7-mime') {
163 $this->parse_encrypted($p);
170 * Handler for preferences_sections_list hook.
171 * Adds Enigma settings sections into preferences sections list.
173 * @param array Original parameters
175 * @return array Modified parameters
177 function preferences_section($p)
180 $this->add_texts('localization/');
182 $p['list']['enigmasettings'] = array(
183 'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
185 $p['list']['enigmacerts'] = array(
186 'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
188 $p['list']['enigmakeys'] = array(
189 'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
196 * Handler for preferences_list hook.
197 * Adds options blocks into Enigma settings sections in Preferences.
199 * @param array Original parameters
201 * @return array Modified parameters
203 function preferences_list($p)
205 if ($p['section'] == 'enigmasettings') {
206 // This makes that section is not removed from the list
207 $p['blocks']['dummy']['options']['dummy'] = array();
209 else if ($p['section'] == 'enigmacerts') {
210 // This makes that section is not removed from the list
211 $p['blocks']['dummy']['options']['dummy'] = array();
213 else if ($p['section'] == 'enigmakeys') {
214 // This makes that section is not removed from the list
215 $p['blocks']['dummy']['options']['dummy'] = array();
222 * Handler for preferences_save hook.
223 * Executed on Enigma settings form submit.
225 * @param array Original parameters
227 * @return array Modified parameters
229 function preferences_save($p)
231 if ($p['section'] == 'enigmasettings') {
233 // 'dummy' => get_input_value('_dummy', RCUBE_INPUT_POST),
241 * Handler for keys/certs management UI template.
243 function preferences_ui()
250 * Handler for message_body_prefix hook.
251 * Called for every displayed (content) part of the message.
252 * Adds infobox about signature verification and/or decryption
253 * status above the body.
255 * @param array Original parameters
257 * @return array Modified parameters
259 function status_message($p)
261 $part_id = $p['part']->mime_id;
263 // skip: not a message part
264 if ($p['part'] instanceof rcube_message)
267 // skip: message has no signed/encoded content
272 if (isset($this->engine->decryptions[$part_id])) {
274 // get decryption status
275 $status = $this->engine->decryptions[$part_id];
277 // Load UI and add css script
279 $this->ui->add_css();
281 // display status info
282 $attrib['id'] = 'enigma-message';
284 if ($status instanceof enigma_error) {
285 $attrib['class'] = 'enigmaerror';
286 $code = $status->getCode();
287 if ($code == enigma_error::E_KEYNOTFOUND)
288 $msg = Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
289 $this->gettext('decryptnokey')));
290 else if ($code == enigma_error::E_BADPASS)
291 $msg = Q($this->gettext('decryptbadpass'));
293 $msg = Q($this->gettext('decrypterror'));
296 $attrib['class'] = 'enigmanotice';
297 $msg = Q($this->gettext('decryptok'));
300 $p['prefix'] .= html::div($attrib, $msg);
303 // Signature verification status
304 if (isset($this->engine->signed_parts[$part_id])
305 && ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
309 $this->ui->add_css();
311 // display status info
312 $attrib['id'] = 'enigma-message';
314 if ($sig instanceof enigma_signature) {
316 $attrib['class'] = 'enigmanotice';
317 $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
318 $msg = Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
321 $attrib['class'] = 'enigmawarning';
322 $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
323 $msg = Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
326 else if ($sig->getCode() == enigma_error::E_KEYNOTFOUND) {
327 $attrib['class'] = 'enigmawarning';
328 $msg = Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
329 $this->gettext('signokey')));
332 $attrib['class'] = 'enigmaerror';
333 $msg = Q($this->gettext('sigerror'));
336 $msg .= ' ' . html::a(array('href' => "#sigdetails",
337 'onclick' => JS_OBJECT_NAME.".command('enigma-sig-details')"),
338 Q($this->gettext('showdetails')));
341 // $msg .= '<br /><pre>'.$sig->body.'</pre>';
343 $p['prefix'] .= html::div($attrib, $msg);
345 // Display each signature message only once
346 unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
353 * Handler for plain/text message.
355 * @param array Reference to hook's parameters (see enigma::parse_structure())
357 private function parse_plain(&$p)
359 $this->load_engine();
360 $this->engine->parse_plain($p);
364 * Handler for multipart/signed message.
365 * Verifies signature.
367 * @param array Reference to hook's parameters (see enigma::parse_structure())
369 private function parse_signed(&$p)
371 $this->load_engine();
372 $this->engine->parse_signed($p);
376 * Handler for multipart/encrypted and application/pkcs7-mime message.
378 * @param array Reference to hook's parameters (see enigma::parse_structure())
380 private function parse_encrypted(&$p)
382 $this->load_engine();
383 $this->engine->parse_encrypted($p);
387 * Handler for message_load hook.
388 * Check message bodies and attachments for keys/certs.
390 function message_load($p)
392 $this->message = $p['object'];
394 // handle attachments vcard attachments
395 foreach ((array)$this->message->attachments as $attachment) {
396 if ($this->is_keys_part($attachment)) {
397 $this->keys_parts[] = $attachment->mime_id;
400 // the same with message bodies
401 foreach ((array)$this->message->parts as $idx => $part) {
402 if ($this->is_keys_part($part)) {
403 $this->keys_parts[] = $part->mime_id;
404 $this->keys_bodies[] = $part->mime_id;
407 // @TODO: inline PGP keys
409 if ($this->keys_parts) {
410 $this->add_texts('localization');
415 * Handler for template_object_messagebody hook.
416 * This callback function adds a box below the message content
417 * if there is a key/cert attachment available
419 function message_output($p)
421 $attach_script = false;
423 foreach ($this->keys_parts as $part) {
425 // remove part's body
426 if (in_array($part, $this->keys_bodies))
429 $style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
430 ." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
432 // add box below messsage body
433 $p['content'] .= html::p(array('style' => $style),
436 'onclick' => "return ".JS_OBJECT_NAME.".enigma_import_attachment('".JQ($part)."')",
437 'title' => $this->gettext('keyattimport')),
438 html::img(array('src' => $this->url('skins/default/key_add.png'), 'style' => "vertical-align:middle")))
439 . ' ' . html::span(null, $this->gettext('keyattfound')));
441 $attach_script = true;
444 if ($attach_script) {
445 $this->include_script('enigma.js');
452 * Handler for attached keys/certs import
454 function import_file()
456 $this->load_engine();
457 $this->engine->import_file();
461 * Checks if specified message part is a PGP-key or S/MIME cert data
463 * @param rcube_message_part Part object
465 * @return boolean True if part is a key/cert
467 private function is_keys_part($part)
471 // Content-Type: application/pgp-keys
472 $part->mimetype == 'application/pgp-keys'