Aleksander Machniak
2015-02-02 e8fc8d303a30658abd70419917a1373131802e28
commit | author | age
48e9c1 1 <?php
T 2 /*
3  +-------------------------------------------------------------------------+
4  | Enigma Plugin for Roundcube                                             |
5  | Version 0.1                                                             |
6  |                                                                         |
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.                           |
10  |                                                                         |
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.                            |
15  |                                                                         |
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.             |
19  |                                                                         |
20  +-------------------------------------------------------------------------+
21  | Author: Aleksander Machniak <alec@alec.pl>                              |
22  +-------------------------------------------------------------------------+
23 */
24
25 /*
26     This class contains only hooks and action handlers.
27     Most plugin logic is placed in enigma_engine and enigma_ui classes.
28 */
29
30 class enigma extends rcube_plugin
31 {
32     public $task = 'mail|settings';
33     public $rc;
34     public $engine;
35
36     private $env_loaded;
37     private $message;
38     private $keys_parts = array();
39     private $keys_bodies = array();
40
41
42     /**
43      * Plugin initialization.
44      */
45     function init()
46     {
47         $rcmail = rcmail::get_instance();
48         $this->rc = $rcmail;
49
ce89ec 50         $section = rcube_utils::get_input_value('_section', rcube_utils::INPUT_GET);
AM 51
48e9c1 52         if ($this->rc->task == 'mail') {
T 53             // message parse/display hooks
54             $this->add_hook('message_part_structure', array($this, 'parse_structure'));
55             $this->add_hook('message_body_prefix', array($this, 'status_message'));
56
57             // message displaying
58             if ($rcmail->action == 'show' || $rcmail->action == 'preview') {
59                 $this->add_hook('message_load', array($this, 'message_load'));
60                 $this->add_hook('template_object_messagebody', array($this, 'message_output'));
61                 $this->register_action('plugin.enigmaimport', array($this, 'import_file'));
62             }
63             // message composing
64             else if ($rcmail->action == 'compose') {
65                 $this->load_ui();
66                 $this->ui->init($section);
67             }
68             // message sending (and draft storing)
69             else if ($rcmail->action == 'sendmail') {
70                 //$this->add_hook('outgoing_message_body', array($this, 'msg_encode'));
71                 //$this->add_hook('outgoing_message_body', array($this, 'msg_sign'));
72             }
73         }
74         else if ($this->rc->task == 'settings') {
75             // add hooks for Enigma settings
76             $this->add_hook('preferences_sections_list', array($this, 'preferences_section'));
77             $this->add_hook('preferences_list', array($this, 'preferences_list'));
78             $this->add_hook('preferences_save', array($this, 'preferences_save'));
79
80             // register handler for keys/certs management
81             $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
82
83             // grab keys/certs management iframe requests
84             if ($this->rc->action == 'edit-prefs' && preg_match('/^enigma(certs|keys)/', $section)) {
85                 $this->load_ui();
86                 $this->ui->init($section);
87             }
88         }
89     }
90
91     /**
92      * Plugin environment initialization.
93      */
94     function load_env()
95     {
96         if ($this->env_loaded)
97             return;
98
99         $this->env_loaded = true;
100
101         // Add include path for Enigma classes and drivers
102         $include_path = $this->home . '/lib' . PATH_SEPARATOR;
103         $include_path .= ini_get('include_path');
104         set_include_path($include_path);
105
106         // load the Enigma plugin configuration
107         $this->load_config();
108
109         // include localization (if wasn't included before)
110         $this->add_texts('localization/');
111     }
112
113     /**
114      * Plugin UI initialization.
115      */
116     function load_ui()
117     {
118         if ($this->ui)
119             return;
120
121         // load config/localization
122         $this->load_env();
123
124         // Load UI
125         $this->ui = new enigma_ui($this, $this->home);
126     }
127
128     /**
129      * Plugin engine initialization.
130      */
131     function load_engine()
132     {
133         if ($this->engine)
134             return;
135
136         // load config/localization
137         $this->load_env();
138
139         $this->engine = new enigma_engine($this);
140     }
141
142     /**
143      * Handler for message_part_structure hook.
144      * Called for every part of the message.
145      *
146      * @param array Original parameters
147      *
148      * @return array Modified parameters
149      */
150     function parse_structure($p)
151     {
2193f6 152 //        $struct = $p['structure'];
48e9c1 153
T 154         if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
155             $this->parse_plain($p);
156         }
157         else if ($p['mimetype'] == 'multipart/signed') {
158             $this->parse_signed($p);
159         }
160         else if ($p['mimetype'] == 'multipart/encrypted') {
161             $this->parse_encrypted($p);
162         }
163         else if ($p['mimetype'] == 'application/pkcs7-mime') {
164             $this->parse_encrypted($p);
165         }
166
167         return $p;
168     }
169
170     /**
171      * Handler for preferences_sections_list hook.
172      * Adds Enigma settings sections into preferences sections list.
173      *
174      * @param array Original parameters
175      *
176      * @return array Modified parameters
177      */
178     function preferences_section($p)
179     {
180         // add labels
181         $this->add_texts('localization/');
3e98f8 182 /*
48e9c1 183         $p['list']['enigmasettings'] = array(
T 184             'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
185         );
3e98f8 186 */
48e9c1 187         $p['list']['enigmacerts'] = array(
T 188             'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
189         );
190         $p['list']['enigmakeys'] = array(
191             'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
192         );
193
194         return $p;
195     }
196
197     /**
198      * Handler for preferences_list hook.
199      * Adds options blocks into Enigma settings sections in Preferences.
200      *
201      * @param array Original parameters
202      *
203      * @return array Modified parameters
204      */
205     function preferences_list($p)
206     {
3e98f8 207 /*
48e9c1 208         if ($p['section'] == 'enigmasettings') {
T 209             // This makes that section is not removed from the list
210             $p['blocks']['dummy']['options']['dummy'] = array();
211         }
3e98f8 212         else */
AM 213         if ($p['section'] == 'enigmacerts') {
48e9c1 214             // This makes that section is not removed from the list
T 215             $p['blocks']['dummy']['options']['dummy'] = array();
216         }
217         else if ($p['section'] == 'enigmakeys') {
218             // This makes that section is not removed from the list
219             $p['blocks']['dummy']['options']['dummy'] = array();
220         }
221
222         return $p;
223     }
224
225     /**
226      * Handler for preferences_save hook.
227      * Executed on Enigma settings form submit.
228      *
229      * @param array Original parameters
230      *
231      * @return array Modified parameters
232      */
233     function preferences_save($p)
234     {
235         if ($p['section'] == 'enigmasettings') {
236             $a['prefs'] = array(
61be82 237 //                'dummy' => rcube_utils::get_input_value('_dummy', rcube_utils::INPUT_POST),
48e9c1 238             );
T 239         }
240
241         return $p;
242     }
243
244     /**
245      * Handler for keys/certs management UI template.
246      */
247     function preferences_ui()
248     {
249         $this->load_ui();
250         $this->ui->init();
251     }
252
253     /**
254      * Handler for message_body_prefix hook.
255      * Called for every displayed (content) part of the message.
256      * Adds infobox about signature verification and/or decryption
257      * status above the body.
258      *
259      * @param array Original parameters
260      *
261      * @return array Modified parameters
262      */
263     function status_message($p)
264     {
265         $part_id = $p['part']->mime_id;
266
267         // skip: not a message part
268         if ($p['part'] instanceof rcube_message)
269             return $p;
270
271         // skip: message has no signed/encoded content
272         if (!$this->engine)
273             return $p;
274
275         // Decryption status
276         if (isset($this->engine->decryptions[$part_id])) {
277
278             // get decryption status
279             $status = $this->engine->decryptions[$part_id];
280
281             // Load UI and add css script
282             $this->load_ui();
283             $this->ui->add_css();
284
285             // display status info
286             $attrib['id'] = 'enigma-message';
287
288             if ($status instanceof enigma_error) {
289                 $attrib['class'] = 'enigmaerror';
290                 $code = $status->getCode();
291                 if ($code == enigma_error::E_KEYNOTFOUND)
61be82 292                     $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($status->getData('id')),
48e9c1 293                         $this->gettext('decryptnokey')));
T 294                 else if ($code == enigma_error::E_BADPASS)
61be82 295                     $msg = rcube::Q($this->gettext('decryptbadpass'));
48e9c1 296                 else
61be82 297                     $msg = rcube::Q($this->gettext('decrypterror'));
48e9c1 298             }
T 299             else {
300                 $attrib['class'] = 'enigmanotice';
61be82 301                 $msg = rcube::Q($this->gettext('decryptok'));
48e9c1 302             }
T 303
304             $p['prefix'] .= html::div($attrib, $msg);
305         }
306
307         // Signature verification status
308         if (isset($this->engine->signed_parts[$part_id])
309             && ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
310         ) {
311             // add css script
312             $this->load_ui();
313             $this->ui->add_css();
314
315             // display status info
316             $attrib['id'] = 'enigma-message';
317
318             if ($sig instanceof enigma_signature) {
3e98f8 319                 $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
AM 320
321                 if ($sig->valid === enigma_error::E_UNVERIFIED) {
322                     $attrib['class'] = 'enigmawarning';
323                     $msg = str_replace('$sender', $sender, $this->gettext('sigunverified'));
324                     $msg = str_replace('$keyid', $sig->id, $msg);
325                     $msg = rcube::Q($msg);
326                 }
327                 else if ($sig->valid) {
48e9c1 328                     $attrib['class'] = 'enigmanotice';
61be82 329                     $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
48e9c1 330                 }
T 331                 else {
332                     $attrib['class'] = 'enigmawarning';
61be82 333                     $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
48e9c1 334                 }
T 335             }
3e98f8 336             else if ($sig && $sig->getCode() == enigma_error::E_KEYNOTFOUND) {
48e9c1 337                 $attrib['class'] = 'enigmawarning';
61be82 338                 $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')),
48e9c1 339                     $this->gettext('signokey')));
T 340             }
341             else {
342                 $attrib['class'] = 'enigmaerror';
61be82 343                 $msg = rcube::Q($this->gettext('sigerror'));
48e9c1 344             }
T 345 /*
346             $msg .= '&nbsp;' . html::a(array('href' => "#sigdetails",
61be82 347                 'onclick' => rcmail_output::JS_OBJECT_NAME.".command('enigma-sig-details')"),
AM 348                 rcube::Q($this->gettext('showdetails')));
48e9c1 349 */
T 350             // test
351 //            $msg .= '<br /><pre>'.$sig->body.'</pre>';
352
353             $p['prefix'] .= html::div($attrib, $msg);
354
355             // Display each signature message only once
356             unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
357         }
358
359         return $p;
360     }
361
362     /**
363      * Handler for plain/text message.
364      *
365      * @param array Reference to hook's parameters (see enigma::parse_structure())
366      */
367     private function parse_plain(&$p)
368     {
369         $this->load_engine();
370         $this->engine->parse_plain($p);
371     }
372     
373     /**
374      * Handler for multipart/signed message.
375      * Verifies signature.
376      *
377      * @param array Reference to hook's parameters (see enigma::parse_structure())
378      */
379     private function parse_signed(&$p)
380     {
381         $this->load_engine();
382         $this->engine->parse_signed($p);
383     }
384
385     /**
386      * Handler for multipart/encrypted and application/pkcs7-mime message.
387      *
388      * @param array Reference to hook's parameters (see enigma::parse_structure())
389      */
390     private function parse_encrypted(&$p)
391     {
392         $this->load_engine();
393         $this->engine->parse_encrypted($p);
394     }
395     
396     /**
397      * Handler for message_load hook.
398      * Check message bodies and attachments for keys/certs.
399      */
400     function message_load($p)
401     {
402         $this->message = $p['object'];
2193f6 403
48e9c1 404         // handle attachments vcard attachments
T 405         foreach ((array)$this->message->attachments as $attachment) {
406             if ($this->is_keys_part($attachment)) {
407                 $this->keys_parts[] = $attachment->mime_id;
408             }
409         }
410         // the same with message bodies
2193f6 411         foreach ((array)$this->message->parts as $part) {
48e9c1 412             if ($this->is_keys_part($part)) {
T 413                 $this->keys_parts[] = $part->mime_id;
414                 $this->keys_bodies[] = $part->mime_id;
415             }
416         }
417         // @TODO: inline PGP keys
418
419         if ($this->keys_parts) {
420             $this->add_texts('localization');
421         }
422     }
423
424     /**
425      * Handler for template_object_messagebody hook.
426      * This callback function adds a box below the message content
427      * if there is a key/cert attachment available
428      */
429     function message_output($p)
430     {
431         $attach_script = false;
432
433         foreach ($this->keys_parts as $part) {
434
435             // remove part's body
436             if (in_array($part, $this->keys_bodies))
437                 $p['content'] = '';
438
439             $style = "margin:0 1em; padding:0.2em 0.5em; border:1px solid #999; width: auto"
440                 ." border-radius:4px; -moz-border-radius:4px; -webkit-border-radius:4px";
441
654ac1 442             // add box below message body
48e9c1 443             $p['content'] .= html::p(array('style' => $style),
T 444                 html::a(array(
445                     'href' => "#",
61be82 446                     'onclick' => "return ".rcmail_output::JS_OBJECT_NAME.".enigma_import_attachment('".rcube::JQ($part)."')",
48e9c1 447                     'title' => $this->gettext('keyattimport')),
bc92ca 448                     html::img(array('src' => $this->url('skins/classic/key_add.png'), 'style' => "vertical-align:middle")))
48e9c1 449                 . ' ' . html::span(null, $this->gettext('keyattfound')));
T 450
451             $attach_script = true;
452         }
453
454         if ($attach_script) {
455             $this->include_script('enigma.js');
456         }
457
458         return $p;
459     }
460
461     /**
462      * Handler for attached keys/certs import
463      */
464     function import_file()
465     {
466         $this->load_engine();
467         $this->engine->import_file();
468     }
469
470     /**
471      * Checks if specified message part is a PGP-key or S/MIME cert data
472      *
473      * @param rcube_message_part Part object
474      *
475      * @return boolean True if part is a key/cert
476      */
477     private function is_keys_part($part)
478     {
479         // @TODO: S/MIME
480         return (
481             // Content-Type: application/pgp-keys
482             $part->mimetype == 'application/pgp-keys'
483         );
484     }
485 }