0
|
1 <?php
|
|
2
|
|
3 /**
|
|
4 +-------------------------------------------------------------------------+
|
|
5 | Engine of the Enigma Plugin |
|
|
6 | |
|
|
7 | Copyright (C) 2010-2016 The Roundcube Dev Team |
|
|
8 | |
|
|
9 | Licensed under the GNU General Public License version 3 or |
|
|
10 | any later version with exceptions for skins & plugins. |
|
|
11 | See the README file for a full license statement. |
|
|
12 | |
|
|
13 +-------------------------------------------------------------------------+
|
|
14 | Author: Aleksander Machniak <alec@alec.pl> |
|
|
15 +-------------------------------------------------------------------------+
|
|
16 */
|
|
17
|
|
18 /**
|
|
19 * Enigma plugin engine.
|
|
20 *
|
|
21 * RFC2440: OpenPGP Message Format
|
|
22 * RFC3156: MIME Security with OpenPGP
|
|
23 * RFC3851: S/MIME
|
|
24 */
|
|
25 class enigma_engine
|
|
26 {
|
|
27 private $rc;
|
|
28 private $enigma;
|
|
29 private $pgp_driver;
|
|
30 private $smime_driver;
|
|
31 private $password_time;
|
|
32
|
|
33 public $decryptions = array();
|
|
34 public $signatures = array();
|
|
35 public $encrypted_parts = array();
|
|
36
|
|
37 const ENCRYPTED_PARTIALLY = 100;
|
|
38
|
|
39 const SIGN_MODE_BODY = 1;
|
|
40 const SIGN_MODE_SEPARATE = 2;
|
|
41 const SIGN_MODE_MIME = 4;
|
|
42
|
|
43 const ENCRYPT_MODE_BODY = 1;
|
|
44 const ENCRYPT_MODE_MIME = 2;
|
|
45 const ENCRYPT_MODE_SIGN = 4;
|
|
46
|
|
47
|
|
48 /**
|
|
49 * Plugin initialization.
|
|
50 */
|
|
51 function __construct($enigma)
|
|
52 {
|
|
53 $this->rc = rcmail::get_instance();
|
|
54 $this->enigma = $enigma;
|
|
55
|
|
56 $this->password_time = $this->rc->config->get('enigma_password_time') * 60;
|
|
57
|
|
58 // this will remove passwords from session after some time
|
|
59 if ($this->password_time) {
|
|
60 $this->get_passwords();
|
|
61 }
|
|
62 }
|
|
63
|
|
64 /**
|
|
65 * PGP driver initialization.
|
|
66 */
|
|
67 function load_pgp_driver()
|
|
68 {
|
|
69 if ($this->pgp_driver) {
|
|
70 return;
|
|
71 }
|
|
72
|
|
73 $driver = 'enigma_driver_' . $this->rc->config->get('enigma_pgp_driver', 'gnupg');
|
|
74 $username = $this->rc->user->get_username();
|
|
75
|
|
76 // Load driver
|
|
77 $this->pgp_driver = new $driver($username);
|
|
78
|
|
79 if (!$this->pgp_driver) {
|
|
80 rcube::raise_error(array(
|
|
81 'code' => 600, 'type' => 'php',
|
|
82 'file' => __FILE__, 'line' => __LINE__,
|
|
83 'message' => "Enigma plugin: Unable to load PGP driver: $driver"
|
|
84 ), true, true);
|
|
85 }
|
|
86
|
|
87 // Initialise driver
|
|
88 $result = $this->pgp_driver->init();
|
|
89
|
|
90 if ($result instanceof enigma_error) {
|
|
91 self::raise_error($result, __LINE__, true);
|
|
92 }
|
|
93 }
|
|
94
|
|
95 /**
|
|
96 * S/MIME driver initialization.
|
|
97 */
|
|
98 function load_smime_driver()
|
|
99 {
|
|
100 if ($this->smime_driver) {
|
|
101 return;
|
|
102 }
|
|
103
|
|
104 $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl');
|
|
105 $username = $this->rc->user->get_username();
|
|
106
|
|
107 // Load driver
|
|
108 $this->smime_driver = new $driver($username);
|
|
109
|
|
110 if (!$this->smime_driver) {
|
|
111 rcube::raise_error(array(
|
|
112 'code' => 600, 'type' => 'php',
|
|
113 'file' => __FILE__, 'line' => __LINE__,
|
|
114 'message' => "Enigma plugin: Unable to load S/MIME driver: $driver"
|
|
115 ), true, true);
|
|
116 }
|
|
117
|
|
118 // Initialise driver
|
|
119 $result = $this->smime_driver->init();
|
|
120
|
|
121 if ($result instanceof enigma_error) {
|
|
122 self::raise_error($result, __LINE__, true);
|
|
123 }
|
|
124 }
|
|
125
|
|
126 /**
|
|
127 * Handler for message signing
|
|
128 *
|
|
129 * @param Mail_mime Original message
|
|
130 * @param int Encryption mode
|
|
131 *
|
|
132 * @return enigma_error On error returns error object
|
|
133 */
|
|
134 function sign_message(&$message, $mode = null)
|
|
135 {
|
|
136 $mime = new enigma_mime_message($message, enigma_mime_message::PGP_SIGNED);
|
|
137 $from = $mime->getFromAddress();
|
|
138
|
|
139 // find private key
|
|
140 $key = $this->find_key($from, true);
|
|
141
|
|
142 if (empty($key)) {
|
|
143 return new enigma_error(enigma_error::KEYNOTFOUND);
|
|
144 }
|
|
145
|
|
146 // check if we have password for this key
|
|
147 $passwords = $this->get_passwords();
|
|
148 $pass = $passwords[$key->id];
|
|
149
|
|
150 if ($pass === null) {
|
|
151 // ask for password
|
|
152 $error = array('missing' => array($key->id => $key->name));
|
|
153 return new enigma_error(enigma_error::BADPASS, '', $error);
|
|
154 }
|
|
155
|
|
156 $key->password = $pass;
|
|
157
|
|
158 // select mode
|
|
159 switch ($mode) {
|
|
160 case self::SIGN_MODE_BODY:
|
|
161 $pgp_mode = Crypt_GPG::SIGN_MODE_CLEAR;
|
|
162 break;
|
|
163
|
|
164 case self::SIGN_MODE_MIME:
|
|
165 $pgp_mode = Crypt_GPG::SIGN_MODE_DETACHED;
|
|
166 break;
|
|
167
|
|
168 default:
|
|
169 if ($mime->isMultipart()) {
|
|
170 $pgp_mode = Crypt_GPG::SIGN_MODE_DETACHED;
|
|
171 }
|
|
172 else {
|
|
173 $pgp_mode = Crypt_GPG::SIGN_MODE_CLEAR;
|
|
174 }
|
|
175 }
|
|
176
|
|
177 // get message body
|
|
178 if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
|
|
179 // in this mode we'll replace text part
|
|
180 // with the one containing signature
|
|
181 $body = $message->getTXTBody();
|
|
182
|
|
183 $text_charset = $message->getParam('text_charset');
|
|
184 $line_length = $this->rc->config->get('line_length', 72);
|
|
185
|
|
186 // We can't use format=flowed for signed messages
|
|
187 if (strpos($text_charset, 'format=flowed')) {
|
|
188 list($charset, $params) = explode(';', $text_charset);
|
|
189 $body = rcube_mime::unfold_flowed($body);
|
|
190 $body = rcube_mime::wordwrap($body, $line_length, "\r\n", false, $charset);
|
|
191
|
|
192 $text_charset = str_replace(";\r\n format=flowed", '', $text_charset);
|
|
193 }
|
|
194 }
|
|
195 else {
|
|
196 // here we'll build PGP/MIME message
|
|
197 $body = $mime->getOrigBody();
|
|
198 }
|
|
199
|
|
200 // sign the body
|
|
201 $result = $this->pgp_sign($body, $key, $pgp_mode);
|
|
202
|
|
203 if ($result !== true) {
|
|
204 if ($result->getCode() == enigma_error::BADPASS) {
|
|
205 // ask for password
|
|
206 $error = array('bad' => array($key->id => $key->name));
|
|
207 return new enigma_error(enigma_error::BADPASS, '', $error);
|
|
208 }
|
|
209
|
|
210 return $result;
|
|
211 }
|
|
212
|
|
213 // replace message body
|
|
214 if ($pgp_mode == Crypt_GPG::SIGN_MODE_CLEAR) {
|
|
215 $message->setTXTBody($body);
|
|
216 $message->setParam('text_charset', $text_charset);
|
|
217 }
|
|
218 else {
|
|
219 $mime->addPGPSignature($body, $this->pgp_driver->signature_algorithm());
|
|
220 $message = $mime;
|
|
221 }
|
|
222 }
|
|
223
|
|
224 /**
|
|
225 * Handler for message encryption
|
|
226 *
|
|
227 * @param Mail_mime Original message
|
|
228 * @param int Encryption mode
|
|
229 * @param bool Is draft-save action - use only sender's key for encryption
|
|
230 *
|
|
231 * @return enigma_error On error returns error object
|
|
232 */
|
|
233 function encrypt_message(&$message, $mode = null, $is_draft = false)
|
|
234 {
|
|
235 $mime = new enigma_mime_message($message, enigma_mime_message::PGP_ENCRYPTED);
|
|
236
|
|
237 // always use sender's key
|
|
238 $from = $mime->getFromAddress();
|
|
239
|
|
240 // check senders key for signing
|
|
241 if ($mode & self::ENCRYPT_MODE_SIGN) {
|
|
242 $sign_key = $this->find_key($from, true);
|
|
243
|
|
244 if (empty($sign_key)) {
|
|
245 return new enigma_error(enigma_error::KEYNOTFOUND);
|
|
246 }
|
|
247
|
|
248 // check if we have password for this key
|
|
249 $passwords = $this->get_passwords();
|
|
250 $sign_pass = $passwords[$sign_key->id];
|
|
251
|
|
252 if ($sign_pass === null) {
|
|
253 // ask for password
|
|
254 $error = array('missing' => array($sign_key->id => $sign_key->name));
|
|
255 return new enigma_error(enigma_error::BADPASS, '', $error);
|
|
256 }
|
|
257
|
|
258 $sign_key->password = $sign_pass;
|
|
259 }
|
|
260
|
|
261 $recipients = array($from);
|
|
262
|
|
263 // if it's not a draft we add all recipients' keys
|
|
264 if (!$is_draft) {
|
|
265 $recipients = array_merge($recipients, $mime->getRecipients());
|
|
266 }
|
|
267
|
|
268 if (empty($recipients)) {
|
|
269 return new enigma_error(enigma_error::KEYNOTFOUND);
|
|
270 }
|
|
271
|
|
272 $recipients = array_unique($recipients);
|
|
273
|
|
274 // find recipient public keys
|
|
275 foreach ((array) $recipients as $email) {
|
|
276 if ($email == $from && $sign_key) {
|
|
277 $key = $sign_key;
|
|
278 }
|
|
279 else {
|
|
280 $key = $this->find_key($email);
|
|
281 }
|
|
282
|
|
283 if (empty($key)) {
|
|
284 return new enigma_error(enigma_error::KEYNOTFOUND, '', array(
|
|
285 'missing' => $email
|
|
286 ));
|
|
287 }
|
|
288
|
|
289 $keys[] = $key;
|
|
290 }
|
|
291
|
|
292 // select mode
|
|
293 if ($mode & self::ENCRYPT_MODE_BODY) {
|
|
294 $encrypt_mode = $mode;
|
|
295 }
|
|
296 else if ($mode & self::ENCRYPT_MODE_MIME) {
|
|
297 $encrypt_mode = $mode;
|
|
298 }
|
|
299 else {
|
|
300 $encrypt_mode = $mime->isMultipart() ? self::ENCRYPT_MODE_MIME : self::ENCRYPT_MODE_BODY;
|
|
301 }
|
|
302
|
|
303 // get message body
|
|
304 if ($encrypt_mode == self::ENCRYPT_MODE_BODY) {
|
|
305 // in this mode we'll replace text part
|
|
306 // with the one containing encrypted message
|
|
307 $body = $message->getTXTBody();
|
|
308 }
|
|
309 else {
|
|
310 // here we'll build PGP/MIME message
|
|
311 $body = $mime->getOrigBody();
|
|
312 }
|
|
313
|
|
314 // sign the body
|
|
315 $result = $this->pgp_encrypt($body, $keys, $sign_key);
|
|
316
|
|
317 if ($result !== true) {
|
|
318 if ($result->getCode() == enigma_error::BADPASS) {
|
|
319 // ask for password
|
|
320 $error = array('bad' => array($sign_key->id => $sign_key->name));
|
|
321 return new enigma_error(enigma_error::BADPASS, '', $error);
|
|
322 }
|
|
323
|
|
324 return $result;
|
|
325 }
|
|
326
|
|
327 // replace message body
|
|
328 if ($encrypt_mode == self::ENCRYPT_MODE_BODY) {
|
|
329 $message->setTXTBody($body);
|
|
330 }
|
|
331 else {
|
|
332 $mime->setPGPEncryptedBody($body);
|
|
333 $message = $mime;
|
|
334 }
|
|
335 }
|
|
336
|
|
337 /**
|
|
338 * Handler for attaching public key to a message
|
|
339 *
|
|
340 * @param Mail_mime Original message
|
|
341 *
|
|
342 * @return bool True on success, False on failure
|
|
343 */
|
|
344 function attach_public_key(&$message)
|
|
345 {
|
|
346 $headers = $message->headers();
|
|
347 $from = rcube_mime::decode_address_list($headers['From'], 1, false, null, true);
|
|
348 $from = $from[1];
|
|
349
|
|
350 // find my key
|
|
351 if ($from && ($key = $this->find_key($from))) {
|
|
352 $pubkey_armor = $this->export_key($key->id);
|
|
353
|
|
354 if (!$pubkey_armor instanceof enigma_error) {
|
|
355 $pubkey_name = '0x' . enigma_key::format_id($key->id) . '.asc';
|
|
356 $message->addAttachment($pubkey_armor, 'application/pgp-keys', $pubkey_name, false, '7bit');
|
|
357 return true;
|
|
358 }
|
|
359 }
|
|
360
|
|
361 return false;
|
|
362 }
|
|
363
|
|
364 /**
|
|
365 * Handler for message_part_structure hook.
|
|
366 * Called for every part of the message.
|
|
367 *
|
|
368 * @param array Original parameters
|
|
369 * @param string Part body (will be set if used internally)
|
|
370 *
|
|
371 * @return array Modified parameters
|
|
372 */
|
|
373 function part_structure($p, $body = null)
|
|
374 {
|
|
375 if ($p['mimetype'] == 'text/plain' || $p['mimetype'] == 'application/pgp') {
|
|
376 $this->parse_plain($p, $body);
|
|
377 }
|
|
378 else if ($p['mimetype'] == 'multipart/signed') {
|
|
379 $this->parse_signed($p, $body);
|
|
380 }
|
|
381 else if ($p['mimetype'] == 'multipart/encrypted') {
|
|
382 $this->parse_encrypted($p);
|
|
383 }
|
|
384 else if ($p['mimetype'] == 'application/pkcs7-mime') {
|
|
385 $this->parse_encrypted($p);
|
|
386 }
|
|
387
|
|
388 return $p;
|
|
389 }
|
|
390
|
|
391 /**
|
|
392 * Handler for message_part_body hook.
|
|
393 *
|
|
394 * @param array Original parameters
|
|
395 *
|
|
396 * @return array Modified parameters
|
|
397 */
|
|
398 function part_body($p)
|
|
399 {
|
|
400 // encrypted attachment, see parse_plain_encrypted()
|
|
401 if ($p['part']->need_decryption && $p['part']->body === null) {
|
|
402 $this->load_pgp_driver();
|
|
403
|
|
404 $storage = $this->rc->get_storage();
|
|
405 $body = $storage->get_message_part($p['object']->uid, $p['part']->mime_id, $p['part'], null, null, true, 0, false);
|
|
406 $result = $this->pgp_decrypt($body);
|
|
407
|
|
408 // @TODO: what to do on error?
|
|
409 if ($result === true) {
|
|
410 $p['part']->body = $body;
|
|
411 $p['part']->size = strlen($body);
|
|
412 $p['part']->body_modified = true;
|
|
413 }
|
|
414 }
|
|
415
|
|
416 return $p;
|
|
417 }
|
|
418
|
|
419 /**
|
|
420 * Handler for plain/text message.
|
|
421 *
|
|
422 * @param array Reference to hook's parameters
|
|
423 * @param string Part body (will be set if used internally)
|
|
424 */
|
|
425 function parse_plain(&$p, $body = null)
|
|
426 {
|
|
427 $part = $p['structure'];
|
|
428
|
|
429 // Get message body from IMAP server
|
|
430 if ($body === null) {
|
|
431 $body = $this->get_part_body($p['object'], $part);
|
|
432 }
|
|
433
|
|
434 // In this way we can use fgets on string as on file handle
|
|
435 // Don't use php://temp for security (body may come from an encrypted part)
|
|
436 $fd = fopen('php://memory', 'r+');
|
|
437 if (!$fd) {
|
|
438 return;
|
|
439 }
|
|
440
|
|
441 fwrite($fd, $body);
|
|
442 rewind($fd);
|
|
443
|
|
444 $body = '';
|
|
445 $prefix = '';
|
|
446 $mode = '';
|
|
447 $tokens = array(
|
|
448 'BEGIN PGP SIGNED MESSAGE' => 'signed-start',
|
|
449 'END PGP SIGNATURE' => 'signed-end',
|
|
450 'BEGIN PGP MESSAGE' => 'encrypted-start',
|
|
451 'END PGP MESSAGE' => 'encrypted-end',
|
|
452 );
|
|
453 $regexp = '/^-----(' . implode('|', array_keys($tokens)) . ')-----[\r\n]*/';
|
|
454
|
|
455 while (($line = fgets($fd)) !== false) {
|
|
456 if ($line[0] === '-' && $line[4] === '-' && preg_match($regexp, $line, $m)) {
|
|
457 switch ($tokens[$m[1]]) {
|
|
458 case 'signed-start':
|
|
459 $body = $line;
|
|
460 $mode = 'signed';
|
|
461 break;
|
|
462
|
|
463 case 'signed-end':
|
|
464 if ($mode === 'signed') {
|
|
465 $body .= $line;
|
|
466 }
|
|
467 break 2; // ignore anything after this line
|
|
468
|
|
469 case 'encrypted-start':
|
|
470 $body = $line;
|
|
471 $mode = 'encrypted';
|
|
472 break;
|
|
473
|
|
474 case 'encrypted-end':
|
|
475 if ($mode === 'encrypted') {
|
|
476 $body .= $line;
|
|
477 }
|
|
478 break 2; // ignore anything after this line
|
|
479 }
|
|
480
|
|
481 continue;
|
|
482 }
|
|
483
|
|
484 if ($mode === 'signed') {
|
|
485 $body .= $line;
|
|
486 }
|
|
487 else if ($mode === 'encrypted') {
|
|
488 $body .= $line;
|
|
489 }
|
|
490 else {
|
|
491 $prefix .= $line;
|
|
492 }
|
|
493 }
|
|
494
|
|
495 fclose($fd);
|
|
496
|
|
497 if ($mode === 'signed') {
|
|
498 $this->parse_plain_signed($p, $body, $prefix);
|
|
499 }
|
|
500 else if ($mode === 'encrypted') {
|
|
501 $this->parse_plain_encrypted($p, $body, $prefix);
|
|
502 }
|
|
503 }
|
|
504
|
|
505 /**
|
|
506 * Handler for multipart/signed message.
|
|
507 *
|
|
508 * @param array Reference to hook's parameters
|
|
509 * @param string Part body (will be set if used internally)
|
|
510 */
|
|
511 function parse_signed(&$p, $body = null)
|
|
512 {
|
|
513 $struct = $p['structure'];
|
|
514
|
|
515 // S/MIME
|
|
516 if ($struct->parts[1] && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
|
|
517 $this->parse_smime_signed($p, $body);
|
|
518 }
|
|
519 // PGP/MIME: RFC3156
|
|
520 // The multipart/signed body MUST consist of exactly two parts.
|
|
521 // The first part contains the signed data in MIME canonical format,
|
|
522 // including a set of appropriate content headers describing the data.
|
|
523 // The second body MUST contain the PGP digital signature. It MUST be
|
|
524 // labeled with a content type of "application/pgp-signature".
|
|
525 else if (count($struct->parts) == 2
|
|
526 && $struct->parts[1] && $struct->parts[1]->mimetype == 'application/pgp-signature'
|
|
527 ) {
|
|
528 $this->parse_pgp_signed($p, $body);
|
|
529 }
|
|
530 }
|
|
531
|
|
532 /**
|
|
533 * Handler for multipart/encrypted message.
|
|
534 *
|
|
535 * @param array Reference to hook's parameters
|
|
536 */
|
|
537 function parse_encrypted(&$p)
|
|
538 {
|
|
539 $struct = $p['structure'];
|
|
540
|
|
541 // S/MIME
|
|
542 if ($p['mimetype'] == 'application/pkcs7-mime') {
|
|
543 $this->parse_smime_encrypted($p);
|
|
544 }
|
|
545 // PGP/MIME: RFC3156
|
|
546 // The multipart/encrypted MUST consist of exactly two parts. The first
|
|
547 // MIME body part must have a content type of "application/pgp-encrypted".
|
|
548 // This body contains the control information.
|
|
549 // The second MIME body part MUST contain the actual encrypted data. It
|
|
550 // must be labeled with a content type of "application/octet-stream".
|
|
551 else if (count($struct->parts) == 2
|
|
552 && $struct->parts[0] && $struct->parts[0]->mimetype == 'application/pgp-encrypted'
|
|
553 && $struct->parts[1] && $struct->parts[1]->mimetype == 'application/octet-stream'
|
|
554 ) {
|
|
555 $this->parse_pgp_encrypted($p);
|
|
556 }
|
|
557 }
|
|
558
|
|
559 /**
|
|
560 * Handler for plain signed message.
|
|
561 * Excludes message and signature bodies and verifies signature.
|
|
562 *
|
|
563 * @param array Reference to hook's parameters
|
|
564 * @param string Message (part) body
|
|
565 * @param string Body prefix (additional text before the encrypted block)
|
|
566 */
|
|
567 private function parse_plain_signed(&$p, $body, $prefix = '')
|
|
568 {
|
|
569 if (!$this->rc->config->get('enigma_signatures', true)) {
|
|
570 return;
|
|
571 }
|
|
572
|
|
573 $this->load_pgp_driver();
|
|
574 $part = $p['structure'];
|
|
575
|
|
576 // Verify signature
|
|
577 if ($this->rc->action == 'show' || $this->rc->action == 'preview' || $this->rc->action == 'print') {
|
|
578 $sig = $this->pgp_verify($body);
|
|
579 }
|
|
580
|
|
581 // In this way we can use fgets on string as on file handle
|
|
582 // Don't use php://temp for security (body may come from an encrypted part)
|
|
583 $fd = fopen('php://memory', 'r+');
|
|
584 if (!$fd) {
|
|
585 return;
|
|
586 }
|
|
587
|
|
588 fwrite($fd, $body);
|
|
589 rewind($fd);
|
|
590
|
|
591 $body = $part->body = null;
|
|
592 $part->body_modified = true;
|
|
593
|
|
594 // Extract body (and signature?)
|
|
595 while (($line = fgets($fd, 1024)) !== false) {
|
|
596 if ($part->body === null)
|
|
597 $part->body = '';
|
|
598 else if (preg_match('/^-----BEGIN PGP SIGNATURE-----/', $line))
|
|
599 break;
|
|
600 else
|
|
601 $part->body .= $line;
|
|
602 }
|
|
603
|
|
604 fclose($fd);
|
|
605
|
|
606 // Remove "Hash" Armor Headers
|
|
607 $part->body = preg_replace('/^.*\r*\n\r*\n/', '', $part->body);
|
|
608 // de-Dash-Escape (RFC2440)
|
|
609 $part->body = preg_replace('/(^|\n)- -/', '\\1-', $part->body);
|
|
610
|
|
611 if ($prefix) {
|
|
612 $part->body = $prefix . $part->body;
|
|
613 }
|
|
614
|
|
615 // Store signature data for display
|
|
616 if (!empty($sig)) {
|
|
617 $sig->partial = !empty($prefix);
|
|
618 $this->signatures[$part->mime_id] = $sig;
|
|
619 }
|
|
620 }
|
|
621
|
|
622 /**
|
|
623 * Handler for PGP/MIME signed message.
|
|
624 * Verifies signature.
|
|
625 *
|
|
626 * @param array Reference to hook's parameters
|
|
627 * @param string Part body (will be set if used internally)
|
|
628 */
|
|
629 private function parse_pgp_signed(&$p, $body = null)
|
|
630 {
|
|
631 if (!$this->rc->config->get('enigma_signatures', true)) {
|
|
632 return;
|
|
633 }
|
|
634
|
|
635 if ($this->rc->action != 'show' && $this->rc->action != 'preview' && $this->rc->action != 'print') {
|
|
636 return;
|
|
637 }
|
|
638
|
|
639 $this->load_pgp_driver();
|
|
640 $struct = $p['structure'];
|
|
641
|
|
642 $msg_part = $struct->parts[0];
|
|
643 $sig_part = $struct->parts[1];
|
|
644
|
|
645 // Get bodies
|
|
646 if ($body === null) {
|
|
647 if (!$struct->body_modified) {
|
|
648 $body = $this->get_part_body($p['object'], $struct);
|
|
649 }
|
|
650 }
|
|
651
|
|
652 $boundary = $struct->ctype_parameters['boundary'];
|
|
653
|
|
654 // when it is a signed message forwarded as attachment
|
|
655 // ctype_parameters property will not be set
|
|
656 if (!$boundary && $struct->headers['content-type']
|
|
657 && preg_match('/boundary="?([a-zA-Z0-9\'()+_,-.\/:=?]+)"?/', $struct->headers['content-type'], $m)
|
|
658 ) {
|
|
659 $boundary = $m[1];
|
|
660 }
|
|
661
|
|
662 // set signed part body
|
|
663 list($msg_body, $sig_body) = $this->explode_signed_body($body, $boundary);
|
|
664
|
|
665 // Verify
|
|
666 if ($sig_body && $msg_body) {
|
|
667 $sig = $this->pgp_verify($msg_body, $sig_body);
|
|
668
|
|
669 // Store signature data for display
|
|
670 $this->signatures[$struct->mime_id] = $sig;
|
|
671 $this->signatures[$msg_part->mime_id] = $sig;
|
|
672 }
|
|
673 }
|
|
674
|
|
675 /**
|
|
676 * Handler for S/MIME signed message.
|
|
677 * Verifies signature.
|
|
678 *
|
|
679 * @param array Reference to hook's parameters
|
|
680 * @param string Part body (will be set if used internally)
|
|
681 */
|
|
682 private function parse_smime_signed(&$p, $body = null)
|
|
683 {
|
|
684 if (!$this->rc->config->get('enigma_signatures', true)) {
|
|
685 return;
|
|
686 }
|
|
687
|
|
688 // @TODO
|
|
689 }
|
|
690
|
|
691 /**
|
|
692 * Handler for plain encrypted message.
|
|
693 *
|
|
694 * @param array Reference to hook's parameters
|
|
695 * @param string Message (part) body
|
|
696 * @param string Body prefix (additional text before the encrypted block)
|
|
697 */
|
|
698 private function parse_plain_encrypted(&$p, $body, $prefix = '')
|
|
699 {
|
|
700 if (!$this->rc->config->get('enigma_decryption', true)) {
|
|
701 return;
|
|
702 }
|
|
703
|
|
704 $this->load_pgp_driver();
|
|
705 $part = $p['structure'];
|
|
706
|
|
707 // Decrypt
|
|
708 $result = $this->pgp_decrypt($body, $signature);
|
|
709
|
|
710 // Store decryption status
|
|
711 $this->decryptions[$part->mime_id] = $result;
|
|
712
|
|
713 // Store signature data for display
|
|
714 if ($signature) {
|
|
715 $this->signatures[$part->mime_id] = $signature;
|
|
716 }
|
|
717
|
|
718 // find parent part ID
|
|
719 if (strpos($part->mime_id, '.')) {
|
|
720 $items = explode('.', $part->mime_id);
|
|
721 array_pop($items);
|
|
722 $parent = implode('.', $items);
|
|
723 }
|
|
724 else {
|
|
725 $parent = 0;
|
|
726 }
|
|
727
|
|
728 // Parse decrypted message
|
|
729 if ($result === true) {
|
|
730 $part->body = $prefix . $body;
|
|
731 $part->body_modified = true;
|
|
732
|
|
733 // it maybe PGP signed inside, verify signature
|
|
734 $this->parse_plain($p, $body);
|
|
735
|
|
736 // Remember it was decrypted
|
|
737 $this->encrypted_parts[] = $part->mime_id;
|
|
738
|
|
739 // Inform the user that only a part of the body was encrypted
|
|
740 if ($prefix) {
|
|
741 $this->decryptions[$part->mime_id] = self::ENCRYPTED_PARTIALLY;
|
|
742 }
|
|
743
|
|
744 // Encrypted plain message may contain encrypted attachments
|
|
745 // in such case attachments have .pgp extension and type application/octet-stream.
|
|
746 // This is what happens when you select "Encrypt each attachment separately
|
|
747 // and send the message using inline PGP" in Thunderbird's Enigmail.
|
|
748
|
|
749 if ($p['object']->mime_parts[$parent]) {
|
|
750 foreach ((array)$p['object']->mime_parts[$parent]->parts as $p) {
|
|
751 if ($p->disposition == 'attachment' && $p->mimetype == 'application/octet-stream'
|
|
752 && preg_match('/^(.*)\.pgp$/i', $p->filename, $m)
|
|
753 ) {
|
|
754 // modify filename
|
|
755 $p->filename = $m[1];
|
|
756 // flag the part, it will be decrypted when needed
|
|
757 $p->need_decryption = true;
|
|
758 // disable caching
|
|
759 $p->body_modified = true;
|
|
760 }
|
|
761 }
|
|
762 }
|
|
763 }
|
|
764 // decryption failed, but the message may have already
|
|
765 // been cached with the modified parts (see above),
|
|
766 // let's bring the original state back
|
|
767 else if ($p['object']->mime_parts[$parent]) {
|
|
768 foreach ((array)$p['object']->mime_parts[$parent]->parts as $p) {
|
|
769 if ($p->need_decryption && !preg_match('/^(.*)\.pgp$/i', $p->filename, $m)) {
|
|
770 // modify filename
|
|
771 $p->filename .= '.pgp';
|
|
772 // flag the part, it will be decrypted when needed
|
|
773 unset($p->need_decryption);
|
|
774 }
|
|
775 }
|
|
776 }
|
|
777 }
|
|
778
|
|
779 /**
|
|
780 * Handler for PGP/MIME encrypted message.
|
|
781 *
|
|
782 * @param array Reference to hook's parameters
|
|
783 */
|
|
784 private function parse_pgp_encrypted(&$p)
|
|
785 {
|
|
786 if (!$this->rc->config->get('enigma_decryption', true)) {
|
|
787 return;
|
|
788 }
|
|
789
|
|
790 $this->load_pgp_driver();
|
|
791
|
|
792 $struct = $p['structure'];
|
|
793 $part = $struct->parts[1];
|
|
794
|
|
795 // Get body
|
|
796 $body = $this->get_part_body($p['object'], $part);
|
|
797
|
|
798 // Decrypt
|
|
799 $result = $this->pgp_decrypt($body, $signature);
|
|
800
|
|
801 if ($result === true) {
|
|
802 // Parse decrypted message
|
|
803 $struct = $this->parse_body($body);
|
|
804
|
|
805 // Modify original message structure
|
|
806 $this->modify_structure($p, $struct, strlen($body));
|
|
807
|
|
808 // Parse the structure (there may be encrypted/signed parts inside
|
|
809 $this->part_structure(array(
|
|
810 'object' => $p['object'],
|
|
811 'structure' => $struct,
|
|
812 'mimetype' => $struct->mimetype
|
|
813 ), $body);
|
|
814
|
|
815 // Attach the decryption message to all parts
|
|
816 $this->decryptions[$struct->mime_id] = $result;
|
|
817 foreach ((array) $struct->parts as $sp) {
|
|
818 $this->decryptions[$sp->mime_id] = $result;
|
|
819 if ($signature) {
|
|
820 $this->signatures[$sp->mime_id] = $signature;
|
|
821 }
|
|
822 }
|
|
823 }
|
|
824 else {
|
|
825 $this->decryptions[$part->mime_id] = $result;
|
|
826
|
|
827 // Make sure decryption status message will be displayed
|
|
828 $part->type = 'content';
|
|
829 $p['object']->parts[] = $part;
|
|
830
|
|
831 // don't show encrypted part on attachments list
|
|
832 // don't show "cannot display encrypted message" text
|
|
833 $p['abort'] = true;
|
|
834 }
|
|
835 }
|
|
836
|
|
837 /**
|
|
838 * Handler for S/MIME encrypted message.
|
|
839 *
|
|
840 * @param array Reference to hook's parameters
|
|
841 */
|
|
842 private function parse_smime_encrypted(&$p)
|
|
843 {
|
|
844 if (!$this->rc->config->get('enigma_decryption', true)) {
|
|
845 return;
|
|
846 }
|
|
847
|
|
848 // @TODO
|
|
849 }
|
|
850
|
|
851 /**
|
|
852 * PGP signature verification.
|
|
853 *
|
|
854 * @param mixed Message body
|
|
855 * @param mixed Signature body (for MIME messages)
|
|
856 *
|
|
857 * @return mixed enigma_signature or enigma_error
|
|
858 */
|
|
859 private function pgp_verify(&$msg_body, $sig_body = null)
|
|
860 {
|
|
861 // @TODO: Handle big bodies using (temp) files
|
|
862 $sig = $this->pgp_driver->verify($msg_body, $sig_body);
|
|
863
|
|
864 if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::KEYNOTFOUND) {
|
|
865 self::raise_error($sig, __LINE__);
|
|
866 }
|
|
867
|
|
868 return $sig;
|
|
869 }
|
|
870
|
|
871 /**
|
|
872 * PGP message decryption.
|
|
873 *
|
|
874 * @param mixed &$msg_body Message body
|
|
875 * @param enigma_signature &$signature Signature verification result
|
|
876 *
|
|
877 * @return mixed True or enigma_error
|
|
878 */
|
|
879 private function pgp_decrypt(&$msg_body, &$signature = null)
|
|
880 {
|
|
881 // @TODO: Handle big bodies using (temp) files
|
|
882 $keys = $this->get_passwords();
|
|
883 $result = $this->pgp_driver->decrypt($msg_body, $keys, $signature);
|
|
884
|
|
885 if ($result instanceof enigma_error) {
|
|
886 if ($result->getCode() != enigma_error::KEYNOTFOUND) {
|
|
887 self::raise_error($result, __LINE__);
|
|
888 }
|
|
889
|
|
890 return $result;
|
|
891 }
|
|
892
|
|
893 $msg_body = $result;
|
|
894
|
|
895 return true;
|
|
896 }
|
|
897
|
|
898 /**
|
|
899 * PGP message signing
|
|
900 *
|
|
901 * @param mixed Message body
|
|
902 * @param enigma_key The key (with passphrase)
|
|
903 * @param int Signing mode
|
|
904 *
|
|
905 * @return mixed True or enigma_error
|
|
906 */
|
|
907 private function pgp_sign(&$msg_body, $key, $mode = null)
|
|
908 {
|
|
909 // @TODO: Handle big bodies using (temp) files
|
|
910 $result = $this->pgp_driver->sign($msg_body, $key, $mode);
|
|
911
|
|
912 if ($result instanceof enigma_error) {
|
|
913 if ($result->getCode() != enigma_error::KEYNOTFOUND) {
|
|
914 self::raise_error($result, __LINE__);
|
|
915 }
|
|
916
|
|
917 return $result;
|
|
918 }
|
|
919
|
|
920 $msg_body = $result;
|
|
921
|
|
922 return true;
|
|
923 }
|
|
924
|
|
925 /**
|
|
926 * PGP message encrypting
|
|
927 *
|
|
928 * @param mixed Message body
|
|
929 * @param array Keys (array of enigma_key objects)
|
|
930 * @param string Optional signing Key ID
|
|
931 * @param string Optional signing Key password
|
|
932 *
|
|
933 * @return mixed True or enigma_error
|
|
934 */
|
|
935 private function pgp_encrypt(&$msg_body, $keys, $sign_key = null, $sign_pass = null)
|
|
936 {
|
|
937 // @TODO: Handle big bodies using (temp) files
|
|
938 $result = $this->pgp_driver->encrypt($msg_body, $keys, $sign_key, $sign_pass);
|
|
939
|
|
940 if ($result instanceof enigma_error) {
|
|
941 if ($result->getCode() != enigma_error::KEYNOTFOUND) {
|
|
942 self::raise_error($result, __LINE__);
|
|
943 }
|
|
944
|
|
945 return $result;
|
|
946 }
|
|
947
|
|
948 $msg_body = $result;
|
|
949
|
|
950 return true;
|
|
951 }
|
|
952
|
|
953 /**
|
|
954 * PGP keys listing.
|
|
955 *
|
|
956 * @param mixed Key ID/Name pattern
|
|
957 *
|
|
958 * @return mixed Array of keys or enigma_error
|
|
959 */
|
|
960 function list_keys($pattern = '')
|
|
961 {
|
|
962 $this->load_pgp_driver();
|
|
963 $result = $this->pgp_driver->list_keys($pattern);
|
|
964
|
|
965 if ($result instanceof enigma_error) {
|
|
966 self::raise_error($result, __LINE__);
|
|
967 }
|
|
968
|
|
969 return $result;
|
|
970 }
|
|
971
|
|
972 /**
|
|
973 * Find PGP private/public key
|
|
974 *
|
|
975 * @param string E-mail address
|
|
976 * @param bool Need a key for signing?
|
|
977 *
|
|
978 * @return enigma_key The key
|
|
979 */
|
|
980 function find_key($email, $can_sign = false)
|
|
981 {
|
|
982 $this->load_pgp_driver();
|
|
983 $result = $this->pgp_driver->list_keys($email);
|
|
984
|
|
985 if ($result instanceof enigma_error) {
|
|
986 self::raise_error($result, __LINE__);
|
|
987 return;
|
|
988 }
|
|
989
|
|
990 $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT;
|
|
991
|
|
992 // check key validity and type
|
|
993 foreach ($result as $key) {
|
|
994 if ($subkey = $key->find_subkey($email, $mode)) {
|
|
995 return $key;
|
|
996 }
|
|
997 }
|
|
998 }
|
|
999
|
|
1000 /**
|
|
1001 * PGP key details.
|
|
1002 *
|
|
1003 * @param mixed Key ID
|
|
1004 *
|
|
1005 * @return mixed enigma_key or enigma_error
|
|
1006 */
|
|
1007 function get_key($keyid)
|
|
1008 {
|
|
1009 $this->load_pgp_driver();
|
|
1010 $result = $this->pgp_driver->get_key($keyid);
|
|
1011
|
|
1012 if ($result instanceof enigma_error) {
|
|
1013 self::raise_error($result, __LINE__);
|
|
1014 }
|
|
1015
|
|
1016 return $result;
|
|
1017 }
|
|
1018
|
|
1019 /**
|
|
1020 * PGP key delete.
|
|
1021 *
|
|
1022 * @param string Key ID
|
|
1023 *
|
|
1024 * @return enigma_error|bool True on success
|
|
1025 */
|
|
1026 function delete_key($keyid)
|
|
1027 {
|
|
1028 $this->load_pgp_driver();
|
|
1029 $result = $this->pgp_driver->delete_key($keyid);
|
|
1030
|
|
1031 if ($result instanceof enigma_error) {
|
|
1032 self::raise_error($result, __LINE__);
|
|
1033 }
|
|
1034
|
|
1035 return $result;
|
|
1036 }
|
|
1037
|
|
1038 /**
|
|
1039 * PGP keys pair generation.
|
|
1040 *
|
|
1041 * @param array Key pair parameters
|
|
1042 *
|
|
1043 * @return mixed enigma_key or enigma_error
|
|
1044 */
|
|
1045 function generate_key($data)
|
|
1046 {
|
|
1047 $this->load_pgp_driver();
|
|
1048 $result = $this->pgp_driver->gen_key($data);
|
|
1049
|
|
1050 if ($result instanceof enigma_error) {
|
|
1051 self::raise_error($result, __LINE__);
|
|
1052 }
|
|
1053
|
|
1054 return $result;
|
|
1055 }
|
|
1056
|
|
1057 /**
|
|
1058 * PGP keys/certs import.
|
|
1059 *
|
|
1060 * @param mixed Import file name or content
|
|
1061 * @param boolean True if first argument is a filename
|
|
1062 *
|
|
1063 * @return mixed Import status data array or enigma_error
|
|
1064 */
|
|
1065 function import_key($content, $isfile = false)
|
|
1066 {
|
|
1067 $this->load_pgp_driver();
|
|
1068 $result = $this->pgp_driver->import($content, $isfile, $this->get_passwords());
|
|
1069
|
|
1070 if ($result instanceof enigma_error) {
|
|
1071 self::raise_error($result, __LINE__);
|
|
1072 }
|
|
1073 else {
|
|
1074 $result['imported'] = $result['public_imported'] + $result['private_imported'];
|
|
1075 $result['unchanged'] = $result['public_unchanged'] + $result['private_unchanged'];
|
|
1076 }
|
|
1077
|
|
1078 return $result;
|
|
1079 }
|
|
1080
|
|
1081 /**
|
|
1082 * PGP keys/certs export.
|
|
1083 *
|
|
1084 * @param string Key ID
|
|
1085 * @param resource Optional output stream
|
|
1086 * @param bool Include private key
|
|
1087 *
|
|
1088 * @return mixed Key content or enigma_error
|
|
1089 */
|
|
1090 function export_key($key, $fp = null, $include_private = false)
|
|
1091 {
|
|
1092 $this->load_pgp_driver();
|
|
1093 $result = $this->pgp_driver->export($key, $include_private, $this->get_passwords());
|
|
1094
|
|
1095 if ($result instanceof enigma_error) {
|
|
1096 self::raise_error($result, __LINE__);
|
|
1097 return $result;
|
|
1098 }
|
|
1099
|
|
1100 if ($fp) {
|
|
1101 fwrite($fp, $result);
|
|
1102 }
|
|
1103 else {
|
|
1104 return $result;
|
|
1105 }
|
|
1106 }
|
|
1107
|
|
1108 /**
|
|
1109 * Registers password for specified key/cert sent by the password prompt.
|
|
1110 */
|
|
1111 function password_handler()
|
|
1112 {
|
|
1113 $keyid = rcube_utils::get_input_value('_keyid', rcube_utils::INPUT_POST);
|
|
1114 $passwd = rcube_utils::get_input_value('_passwd', rcube_utils::INPUT_POST, true);
|
|
1115
|
|
1116 if ($keyid && $passwd !== null && strlen($passwd)) {
|
|
1117 $this->save_password(strtoupper($keyid), $passwd);
|
|
1118 }
|
|
1119 }
|
|
1120
|
|
1121 /**
|
|
1122 * Saves key/cert password in user session
|
|
1123 */
|
|
1124 function save_password($keyid, $password)
|
|
1125 {
|
|
1126 // we store passwords in session for specified time
|
|
1127 if ($config = $_SESSION['enigma_pass']) {
|
|
1128 $config = $this->rc->decrypt($config);
|
|
1129 $config = @unserialize($config);
|
|
1130 }
|
|
1131
|
|
1132 $config[$keyid] = array($password, time());
|
|
1133
|
|
1134 $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
|
|
1135 }
|
|
1136
|
|
1137 /**
|
|
1138 * Returns currently stored passwords
|
|
1139 */
|
|
1140 function get_passwords()
|
|
1141 {
|
|
1142 if ($config = $_SESSION['enigma_pass']) {
|
|
1143 $config = $this->rc->decrypt($config);
|
|
1144 $config = @unserialize($config);
|
|
1145 }
|
|
1146
|
|
1147 $threshold = $this->password_time ? time() - $this->password_time : 0;
|
|
1148 $keys = array();
|
|
1149
|
|
1150 // delete expired passwords
|
|
1151 foreach ((array) $config as $key => $value) {
|
|
1152 if ($threshold && $value[1] < $threshold) {
|
|
1153 unset($config[$key]);
|
|
1154 $modified = true;
|
|
1155 }
|
|
1156 else {
|
|
1157 $keys[$key] = $value[0];
|
|
1158 }
|
|
1159 }
|
|
1160
|
|
1161 if ($modified) {
|
|
1162 $_SESSION['enigma_pass'] = $this->rc->encrypt(serialize($config));
|
|
1163 }
|
|
1164
|
|
1165 return $keys;
|
|
1166 }
|
|
1167
|
|
1168 /**
|
|
1169 * Get message part body.
|
|
1170 *
|
|
1171 * @param rcube_message Message object
|
|
1172 * @param rcube_message_part Message part
|
|
1173 */
|
|
1174 private function get_part_body($msg, $part)
|
|
1175 {
|
|
1176 // @TODO: Handle big bodies using file handles
|
|
1177
|
|
1178 // This is a special case when we want to get the whole body
|
|
1179 // using direct IMAP access, in other cases we prefer
|
|
1180 // rcube_message::get_part_body() as the body may be already in memory
|
|
1181 if (!$part->mime_id) {
|
|
1182 // fake the size which may be empty for multipart/* parts
|
|
1183 // otherwise get_message_part() below will fail
|
|
1184 if (!$part->size) {
|
|
1185 $reset = true;
|
|
1186 $part->size = 1;
|
|
1187 }
|
|
1188
|
|
1189 $storage = $this->rc->get_storage();
|
|
1190 $body = $storage->get_message_part($msg->uid, $part->mime_id, $part,
|
|
1191 null, null, true, 0, false);
|
|
1192
|
|
1193 if ($reset) {
|
|
1194 $part->size = 0;
|
|
1195 }
|
|
1196 }
|
|
1197 else {
|
|
1198 $body = $msg->get_part_body($part->mime_id, false);
|
|
1199
|
|
1200 // Convert charset to get rid of possible non-ascii characters (#5962)
|
|
1201 if ($part->charset && stripos($part->charset, 'ASCII') === false) {
|
|
1202 $body = rcube_charset::convert($body, $part->charset, 'US-ASCII');
|
|
1203 }
|
|
1204 }
|
|
1205
|
|
1206 return $body;
|
|
1207 }
|
|
1208
|
|
1209 /**
|
|
1210 * Parse decrypted message body into structure
|
|
1211 *
|
|
1212 * @param string Message body
|
|
1213 *
|
|
1214 * @return array Message structure
|
|
1215 */
|
|
1216 private function parse_body(&$body)
|
|
1217 {
|
|
1218 // Mail_mimeDecode need \r\n end-line, but gpg may return \n
|
|
1219 $body = preg_replace('/\r?\n/', "\r\n", $body);
|
|
1220
|
|
1221 // parse the body into structure
|
|
1222 $struct = rcube_mime::parse_message($body);
|
|
1223
|
|
1224 return $struct;
|
|
1225 }
|
|
1226
|
|
1227 /**
|
|
1228 * Replace message encrypted structure with decrypted message structure
|
|
1229 *
|
|
1230 * @param array Hook arguments
|
|
1231 * @param rcube_message_part Part structure
|
|
1232 * @param int Part size
|
|
1233 */
|
|
1234 private function modify_structure(&$p, $struct, $size = 0)
|
|
1235 {
|
|
1236 // modify mime_parts property of the message object
|
|
1237 $old_id = $p['structure']->mime_id;
|
|
1238
|
|
1239 foreach (array_keys($p['object']->mime_parts) as $idx) {
|
|
1240 if (!$old_id || $idx == $old_id || strpos($idx, $old_id . '.') === 0) {
|
|
1241 unset($p['object']->mime_parts[$idx]);
|
|
1242 }
|
|
1243 }
|
|
1244
|
|
1245 // set some part params used by Roundcube core
|
|
1246 $struct->headers = array_merge($p['structure']->headers, $struct->headers);
|
|
1247 $struct->size = $size;
|
|
1248 $struct->filename = $p['structure']->filename;
|
|
1249
|
|
1250 // modify the new structure to be correctly handled by Roundcube
|
|
1251 $this->modify_structure_part($struct, $p['object'], $old_id);
|
|
1252
|
|
1253 // replace old structure with the new one
|
|
1254 $p['structure'] = $struct;
|
|
1255 $p['mimetype'] = $struct->mimetype;
|
|
1256 }
|
|
1257
|
|
1258 /**
|
|
1259 * Modify decrypted message part
|
|
1260 *
|
|
1261 * @param rcube_message_part
|
|
1262 * @param rcube_message
|
|
1263 */
|
|
1264 private function modify_structure_part($part, $msg, $old_id)
|
|
1265 {
|
|
1266 // never cache the body
|
|
1267 $part->body_modified = true;
|
|
1268 $part->encoding = 'stream';
|
|
1269
|
|
1270 // modify part identifier
|
|
1271 if ($old_id) {
|
|
1272 $part->mime_id = !$part->mime_id ? $old_id : ($old_id . '.' . $part->mime_id);
|
|
1273 }
|
|
1274
|
|
1275 // Cache the fact it was decrypted
|
|
1276 $this->encrypted_parts[] = $part->mime_id;
|
|
1277 $msg->mime_parts[$part->mime_id] = $part;
|
|
1278
|
|
1279 // modify sub-parts
|
|
1280 foreach ((array) $part->parts as $p) {
|
|
1281 $this->modify_structure_part($p, $msg, $old_id);
|
|
1282 }
|
|
1283 }
|
|
1284
|
|
1285 /**
|
|
1286 * Extracts body and signature of multipart/signed message body
|
|
1287 */
|
|
1288 private function explode_signed_body($body, $boundary)
|
|
1289 {
|
|
1290 if (!$body) {
|
|
1291 return array();
|
|
1292 }
|
|
1293
|
|
1294 $boundary = '--' . $boundary;
|
|
1295 $boundary_len = strlen($boundary) + 2;
|
|
1296
|
|
1297 // Find boundaries
|
|
1298 $start = strpos($body, $boundary) + $boundary_len;
|
|
1299 $end = strpos($body, $boundary, $start);
|
|
1300
|
|
1301 // Get signed body and signature
|
|
1302 $sig = substr($body, $end + $boundary_len);
|
|
1303 $body = substr($body, $start, $end - $start - 2);
|
|
1304
|
|
1305 // Cleanup signature
|
|
1306 $sig = substr($sig, strpos($sig, "\r\n\r\n") + 4);
|
|
1307 $sig = substr($sig, 0, strpos($sig, $boundary));
|
|
1308
|
|
1309 return array($body, $sig);
|
|
1310 }
|
|
1311
|
|
1312 /**
|
|
1313 * Checks if specified message part is a PGP-key or S/MIME cert data
|
|
1314 *
|
|
1315 * @param rcube_message_part Part object
|
|
1316 *
|
|
1317 * @return boolean True if part is a key/cert
|
|
1318 */
|
|
1319 public function is_keys_part($part)
|
|
1320 {
|
|
1321 // @TODO: S/MIME
|
|
1322 return (
|
|
1323 // Content-Type: application/pgp-keys
|
|
1324 $part->mimetype == 'application/pgp-keys'
|
|
1325 );
|
|
1326 }
|
|
1327
|
|
1328 /**
|
|
1329 * Removes all user keys and assigned data
|
|
1330 *
|
|
1331 * @param string Username
|
|
1332 *
|
|
1333 * @return bool True on success, False on failure
|
|
1334 */
|
|
1335 public function delete_user_data($username)
|
|
1336 {
|
|
1337 $homedir = $this->rc->config->get('enigma_pgp_homedir', INSTALL_PATH . 'plugins/enigma/home');
|
|
1338 $homedir .= DIRECTORY_SEPARATOR . $username;
|
|
1339
|
|
1340 return file_exists($homedir) ? self::delete_dir($homedir) : true;
|
|
1341 }
|
|
1342
|
|
1343 /**
|
|
1344 * Recursive method to remove directory with its content
|
|
1345 *
|
|
1346 * @param string Directory
|
|
1347 */
|
|
1348 public static function delete_dir($dir)
|
|
1349 {
|
|
1350 // This code can be executed from command line, make sure
|
|
1351 // we have permissions to delete keys directory
|
|
1352 if (!is_writable($dir)) {
|
|
1353 rcube::raise_error("Unable to delete $dir", false, true);
|
|
1354 return false;
|
|
1355 }
|
|
1356
|
|
1357 if ($content = scandir($dir)) {
|
|
1358 foreach ($content as $filename) {
|
|
1359 if ($filename != '.' && $filename != '..') {
|
|
1360 $filename = $dir . DIRECTORY_SEPARATOR . $filename;
|
|
1361
|
|
1362 if (is_dir($filename)) {
|
|
1363 self::delete_dir($filename);
|
|
1364 }
|
|
1365 else {
|
|
1366 unlink($filename);
|
|
1367 }
|
|
1368 }
|
|
1369 }
|
|
1370
|
|
1371 rmdir($dir);
|
|
1372 }
|
|
1373
|
|
1374 return true;
|
|
1375 }
|
|
1376
|
|
1377 /**
|
|
1378 * Raise/log (relevant) errors
|
|
1379 */
|
|
1380 protected static function raise_error($result, $line, $abort = false)
|
|
1381 {
|
|
1382 if ($result->getCode() != enigma_error::BADPASS) {
|
|
1383 rcube::raise_error(array(
|
|
1384 'code' => 600,
|
|
1385 'file' => __FILE__,
|
|
1386 'line' => $line,
|
|
1387 'message' => "Enigma plugin: " . $result->getMessage()
|
|
1388 ), true, $abort);
|
|
1389 }
|
|
1390 }
|
|
1391 }
|