source: subversion/trunk/plugins/enigma/enigma.php @ 4087

Last change on this file since 4087 was 4087, checked in by alec, 3 years ago
  • Fix set_busy() usage, add encryption menu in compose
File size: 15.2 KB
Line 
1<?php
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
30class 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
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'));
54
55            // message displaying
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'));
60            }
61            // message composing
62            else if ($rcmail->action == 'compose') {
63                $this->load_ui();
64                $this->ui->init($section);
65            }
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'));
70            }
71        }
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'));
77
78            // register handler for keys/certs management
79            $this->register_action('plugin.enigma', array($this, 'preferences_ui'));
80
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)) {
84                $this->load_ui();
85                $this->ui->init($section);
86            }
87        }
88    }
89
90    /**
91     * Plugin environment initialization.
92     */
93    function load_env()
94    {
95        if ($this->env_loaded)
96            return;
97
98        $this->env_loaded = true;
99
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);
104
105        // load the Enigma plugin configuration
106        $this->load_config();
107
108        // include localization (if wasn't included before)
109        $this->add_texts('localization/');
110    }
111
112    /**
113     * Plugin UI initialization.
114     */
115    function load_ui()
116    {
117        if ($this->ui)
118            return;
119
120        // load config/localization
121        $this->load_env();
122
123        // Load UI
124        $this->ui = new enigma_ui($this, $this->home);
125    }
126
127    /**
128     * Plugin engine initialization.
129     */
130    function load_engine()
131    {
132        if ($this->engine)
133            return;
134
135        // load config/localization
136        $this->load_env();
137
138        $this->engine = new enigma_engine($this);
139    }
140
141    /**
142     * Handler for message_part_structure hook.
143     * Called for every part of the message.
144     *
145     * @param array Original parameters
146     *
147     * @return array Modified parameters
148     */
149    function parse_structure($p)
150    {
151        $struct = $p['structure'];
152
153        if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
154            $this->parse_plain($p);
155        }
156        else if ($p['mimetype'] == 'multipart/signed') {
157            $this->parse_signed($p);
158        }
159        else if ($p['mimetype'] == 'multipart/encrypted') {
160            $this->parse_encrypted($p);
161        }
162        else if ($p['mimetype'] == 'application/pkcs7-mime') {
163            $this->parse_encrypted($p);
164        }
165
166        return $p;
167    }
168
169    /**
170     * Handler for preferences_sections_list hook.
171     * Adds Enigma settings sections into preferences sections list.
172     *
173     * @param array Original parameters
174     *
175     * @return array Modified parameters
176     */
177    function preferences_section($p)
178    {
179        // add labels
180        $this->add_texts('localization/');
181
182        $p['list']['enigmasettings'] = array(
183            'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'),
184        );
185        $p['list']['enigmacerts'] = array(
186            'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'),
187        );
188        $p['list']['enigmakeys'] = array(
189            'id' => 'enigmakeys', 'section' => $this->gettext('enigmakeys'),
190        );
191
192        return $p;
193    }
194
195    /**
196     * Handler for preferences_list hook.
197     * Adds options blocks into Enigma settings sections in Preferences.
198     *
199     * @param array Original parameters
200     *
201     * @return array Modified parameters
202     */
203    function preferences_list($p)
204    {
205        if ($p['section'] == 'enigmasettings') {
206            // This makes that section is not removed from the list
207            $p['blocks']['dummy']['options']['dummy'] = array();
208        }
209        else if ($p['section'] == 'enigmacerts') {
210            // This makes that section is not removed from the list
211            $p['blocks']['dummy']['options']['dummy'] = array();
212        }
213        else if ($p['section'] == 'enigmakeys') {
214            // This makes that section is not removed from the list
215            $p['blocks']['dummy']['options']['dummy'] = array();
216        }
217
218        return $p;
219    }
220
221    /**
222     * Handler for preferences_save hook.
223     * Executed on Enigma settings form submit.
224     *
225     * @param array Original parameters
226     *
227     * @return array Modified parameters
228     */
229    function preferences_save($p)
230    {
231        if ($p['section'] == 'enigmasettings') {
232            $a['prefs'] = array(
233//                'dummy' => get_input_value('_dummy', RCUBE_INPUT_POST),
234            );
235        }
236
237        return $p;
238    }
239
240    /**
241     * Handler for keys/certs management UI template.
242     */
243    function preferences_ui()
244    {
245        $this->load_ui();
246        $this->ui->init();
247    }
248
249    /**
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.
254     *
255     * @param array Original parameters
256     *
257     * @return array Modified parameters
258     */
259    function status_message($p)
260    {
261        $part_id = $p['part']->mime_id;
262
263        // skip: not a message part
264        if ($p['part'] instanceof rcube_message)
265            return $p;
266
267        // skip: message has no signed/encoded content
268        if (!$this->engine)
269            return $p;
270
271        // Decryption status
272        if (isset($this->engine->decryptions[$part_id])) {
273
274            // get decryption status
275            $status = $this->engine->decryptions[$part_id];
276
277            // Load UI and add css script
278            $this->load_ui();
279            $this->ui->add_css();
280
281            // display status info
282            $attrib['id'] = 'enigma-message';
283
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'));
292                else
293                    $msg = Q($this->gettext('decrypterror'));
294            }
295            else {
296                $attrib['class'] = 'enigmanotice';
297                $msg = Q($this->gettext('decryptok'));
298            }
299
300            $p['prefix'] .= html::div($attrib, $msg);
301        }
302
303        // Signature verification status
304        if (isset($this->engine->signed_parts[$part_id])
305            && ($sig = $this->engine->signatures[$this->engine->signed_parts[$part_id]])
306        ) {
307            // add css script
308            $this->load_ui();
309            $this->ui->add_css();
310
311            // display status info
312            $attrib['id'] = 'enigma-message';
313
314            if ($sig instanceof enigma_signature) {
315                if ($sig->valid) {
316                    $attrib['class'] = 'enigmanotice';
317                    $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
318                    $msg = Q(str_replace('$sender', $sender, $this->gettext('sigvalid')));
319                }
320                else {
321                    $attrib['class'] = 'enigmawarning';
322                    $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>';
323                    $msg = Q(str_replace('$sender', $sender, $this->gettext('siginvalid')));
324                }
325            }
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')));
330            }
331            else {
332                $attrib['class'] = 'enigmaerror';
333                $msg = Q($this->gettext('sigerror'));
334            }
335/*
336            $msg .= '&nbsp;' . html::a(array('href' => "#sigdetails",
337                'onclick' => JS_OBJECT_NAME.".command('enigma-sig-details')"),
338                Q($this->gettext('showdetails')));
339*/
340            // test
341//            $msg .= '<br /><pre>'.$sig->body.'</pre>';
342
343            $p['prefix'] .= html::div($attrib, $msg);
344
345            // Display each signature message only once
346            unset($this->engine->signatures[$this->engine->signed_parts[$part_id]]);
347        }
348
349        return $p;
350    }
351
352    /**
353     * Handler for plain/text message.
354     *
355     * @param array Reference to hook's parameters (see enigma::parse_structure())
356     */
357    private function parse_plain(&$p)
358    {
359        $this->load_engine();
360        $this->engine->parse_plain($p);
361    }
362   
363    /**
364     * Handler for multipart/signed message.
365     * Verifies signature.
366     *
367     * @param array Reference to hook's parameters (see enigma::parse_structure())
368     */
369    private function parse_signed(&$p)
370    {
371        $this->load_engine();
372        $this->engine->parse_signed($p);
373    }
374
375    /**
376     * Handler for multipart/encrypted and application/pkcs7-mime message.
377     *
378     * @param array Reference to hook's parameters (see enigma::parse_structure())
379     */
380    private function parse_encrypted(&$p)
381    {
382        $this->load_engine();
383        $this->engine->parse_encrypted($p);
384    }
385   
386    /**
387     * Handler for message_load hook.
388     * Check message bodies and attachments for keys/certs.
389     */
390    function message_load($p)
391    {
392        $this->message = $p['object'];
393   
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;
398            }
399        }
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;
405            }
406        }
407        // @TODO: inline PGP keys
408
409        if ($this->keys_parts) {
410            $this->add_texts('localization');
411        }
412    }
413
414    /**
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
418     */
419    function message_output($p)
420    {
421        $attach_script = false;
422
423        foreach ($this->keys_parts as $part) {
424
425            // remove part's body
426            if (in_array($part, $this->keys_bodies))
427                $p['content'] = '';
428
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";
431
432            // add box below messsage body
433            $p['content'] .= html::p(array('style' => $style),
434                html::a(array(
435                    'href' => "#",
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')));
440
441            $attach_script = true;
442        }
443
444        if ($attach_script) {
445            $this->include_script('enigma.js');
446        }
447
448        return $p;
449    }
450
451    /**
452     * Handler for attached keys/certs import
453     */
454    function import_file()
455    {
456        $this->load_engine();
457        $this->engine->import_file();
458    }
459
460    /**
461     * Checks if specified message part is a PGP-key or S/MIME cert data
462     *
463     * @param rcube_message_part Part object
464     *
465     * @return boolean True if part is a key/cert
466     */
467    private function is_keys_part($part)
468    {
469        // @TODO: S/MIME
470        return (
471            // Content-Type: application/pgp-keys
472            $part->mimetype == 'application/pgp-keys'
473        );
474    }
475}
Note: See TracBrowser for help on using the repository browser.