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