Mercurial > hg > rc1
comparison plugins/enigma/lib/enigma_engine.php @ 0:1e000243b222
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:50:29 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:1e000243b222 |
|---|---|
| 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 } |
