1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12:
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31:
32: class IMP_Mime_Viewer_Pgp extends Horde_Mime_Viewer_Base
33: {
34:
35: const PGP_SIGN_ENC = 'imp-pgp-signed-encrypted';
36:
37: 38: 39: 40: 41:
42: protected $_capability = array(
43: 'full' => false,
44: 'info' => false,
45: 'inline' => true,
46: 47: 48: 49:
50: 'raw' => false
51: );
52:
53: 54: 55: 56: 57:
58: protected $_metadata = array(
59: 'compressed' => false,
60: 'embedded' => true,
61: 'forceinline' => true
62: );
63:
64: 65: 66: 67: 68:
69: protected $_sender = null;
70:
71: 72: 73: 74: 75:
76: protected function _render()
77: {
78: switch ($this->_mimepart->getType()) {
79: case 'application/pgp-keys':
80: $vars = $GLOBALS['injector']->getInstance('Horde_Variables');
81: if ($vars->pgp_view_key) {
82:
83: return array(
84: $this->_mimepart->getMimeId() => array(
85: 'data' => '<html><body><tt>' . nl2br(str_replace(' ', ' ', $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp')->pgpPrettyKey($this->_mimepart->getContents()))) . '</tt></body></html>',
86: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
87: )
88: );
89: }
90:
91: return array(
92: $this->_mimepart->getMimeId() => array(
93: 'data' => $this->_mimepart->getContents(),
94: 'type' => 'text/plain; charset=' . $this->_mimepart->getCharset()
95: )
96: );
97: }
98: }
99:
100: 101: 102: 103: 104:
105: protected function _renderRaw()
106: {
107: $ret = array(
108: 'data' => '',
109: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset')
110: );
111:
112: switch ($this->_mimepart->getType()) {
113: case 'application/pgp-signature':
114: $parts = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp_Parse')->parse($this->_mimepart->getContents());
115: foreach (array_keys($parts) as $key) {
116: if ($parts[$key]['type'] == Horde_Crypt_Pgp::ARMOR_SIGNATURE) {
117: $ret['data'] = implode("\r\n", $parts[$key]['data']);
118: break;
119: }
120: }
121: break;
122: }
123:
124: return array(
125: $this->_mimepart->getMimeId() => $ret
126: );
127: }
128:
129: 130: 131: 132: 133:
134: protected function _renderInline()
135: {
136: $id = $this->_mimepart->getMimeId();
137:
138: switch ($this->_mimepart->getType()) {
139: case 'application/pgp-keys':
140: return $this->_outputPGPKey();
141:
142: case 'multipart/signed':
143: return $this->_outputPGPSigned();
144:
145: case 'multipart/encrypted':
146: $cache = $this->getConfigParam('imp_contents')->getViewCache();
147:
148: if (isset($cache->pgp[$id])) {
149: return array_merge(array(
150: $id => array(
151: 'data' => null,
152: 'status' => $cache->pgp[$id]['status'],
153: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset'),
154: 'wrap' => $cache->pgp[$id]['wrap']
155: )
156: ), $cache->pgp[$id]['other']);
157: }
158:
159:
160: case 'application/pgp-encrypted':
161: case 'application/pgp-signature':
162: default:
163: return array();
164: }
165: }
166:
167: 168: 169: 170: 171: 172: 173:
174: protected function _getEmbeddedMimeParts()
175: {
176: if ($this->_mimepart->getType() != 'multipart/encrypted') {
177: return null;
178: }
179:
180: $partlist = array_keys($this->_mimepart->contentTypeMap());
181: $base_id = reset($partlist);
182: $version_id = next($partlist);
183:
184: $id_ob = new Horde_Mime_Id($version_id);
185: $data_id = $id_ob->idArithmetic($id_ob::ID_NEXT);
186:
187: $status = new IMP_Mime_Status();
188: $status->icon('mime/encryption.png', 'PGP');
189:
190: $cache = $this->getConfigParam('imp_contents')->getViewCache();
191: $cache->pgp[$base_id] = array(
192: 'status' => array($status),
193: 'other' => array(
194: $version_id => null,
195: $data_id => null
196: ),
197: 'wrap' => ''
198: );
199:
200:
201: if (empty($GLOBALS['conf']['gnupg']['path']) ||
202: !$GLOBALS['prefs']->getValue('use_pgp')) {
203: $status->addText(_("The data in this part has been encrypted via PGP, however, PGP support is disabled so the message cannot be decrypted."));
204: return null;
205: }
206:
207: 208: 209:
210: $encrypted_part = $this->getConfigParam('imp_contents')->getMIMEPart($data_id);
211: $encrypted_data = $encrypted_part->getContents();
212:
213: $symmetric_pass = $personal_pass = null;
214:
215:
216: try {
217: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
218: $symmetric = $imp_pgp->encryptedSymmetrically($encrypted_data);
219: if ($symmetric) {
220: $symmetric_id = $this->_getSymmetricID();
221: $symmetric_pass = $imp_pgp->getPassphrase('symmetric', $symmetric_id);
222:
223: if (is_null($symmetric_pass)) {
224: $status->addText(_("The data in this part has been encrypted via PGP."));
225:
226: 227:
228: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_PassphraseDialog', array(
229: 'params' => array(
230: 'symmetricid' => $symmetric_id
231: ),
232: 'type' => 'pgpSymmetric'
233: ));
234: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("You must enter the passphrase used to encrypt this message to view it.") . '</a>');
235: return null;
236: }
237: }
238: } catch (Horde_Exception $e) {
239: Horde::log($e, 'INFO');
240: return null;
241: }
242:
243:
244: try {
245: $info = $imp_pgp->pgpPacketInformation($encrypted_data);
246: } catch (Horde_Exception $e) {
247: Horde::log($e, 'INFO');
248: return null;
249: }
250:
251: $literal = !empty($info['literal']);
252: if ($literal) {
253: $status->addText(_("The data in this part has been compressed via PGP."));
254: } else {
255: $status->addText(_("The data in this part has been encrypted via PGP."));
256:
257: if (!$symmetric) {
258: if ($imp_pgp->getPersonalPrivateKey()) {
259: $personal_pass = $imp_pgp->getPassphrase('personal');
260: if (is_null($personal_pass)) {
261: 262:
263: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_PassphraseDialog', array(
264: 'type' => 'pgpPersonal'
265: ));
266: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("You must enter the passphrase for your PGP private key to view this message.") . '</a>');
267: return null;
268: }
269: } else {
270: 271:
272: $status->addText(_("However, no personal private key exists so the message cannot be decrypted."));
273: return null;
274: }
275: }
276: }
277:
278: try {
279: if (!is_null($symmetric_pass)) {
280: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'symmetric', array(
281: 'passphrase' => $symmetric_pass,
282: 'sender' => $this->_getSender()->bare_address
283: ));
284: } elseif (!is_null($personal_pass)) {
285: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'personal', array(
286: 'passphrase' => $personal_pass,
287: 'sender' => $this->_getSender()->bare_address
288: ));
289: } else {
290: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'literal');
291: }
292: } catch (Horde_Exception $e) {
293: $status->addText(_("The data in this part does not appear to be a valid PGP encrypted message. Error: ") . $e->getMessage());
294: if (!is_null($symmetric_pass)) {
295: $imp_pgp->unsetPassphrase('symmetric', $this->_getSymmetricID());
296: return $this->_getEmbeddedMimeParts();
297: }
298: return null;
299: }
300:
301: $cache->pgp[$base_id]['wrap'] = 'mimePartWrapValid';
302:
303:
304: if ($decrypted_data->result) {
305: $sig_text = is_bool($decrypted_data->result)
306: ? _("The data in this part has been digitally signed via PGP.")
307: : $this->_textFilter($decrypted_data->result, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::NOHTML));
308:
309: $status2 = new IMP_Mime_Status($sig_text);
310: $status2->action(IMP_Mime_Status::SUCCESS);
311:
312: $cache->pgp[$base_id]['status'][] = $status2;
313: }
314:
315:
316: if ($this->_mimepart->getMetadata(Horde_Crypt_Pgp_Parse::PGP_ARMOR)) {
317: $decrypted_data->message = "Content-Type: text/plain\n\n" .
318: $decrypted_data->message;
319: }
320:
321: $new_part = Horde_Mime_Part::parseMessage($decrypted_data->message, array(
322: 'forcemime' => true
323: ));
324:
325: if ($new_part->getType() == 'multipart/signed') {
326: $data = new Horde_Stream_Temp();
327: try {
328: $data->add(Horde_Mime_Part::getRawPartText($decrypted_data->message, 'header', '1'));
329: $data->add("\n\n");
330: $data->add(Horde_Mime_Part::getRawPartText($decrypted_data->message, 'body', '1'));
331: } catch (Horde_Mime_Exception $e) {}
332:
333: $new_part->setMetadata(self::PGP_SIGN_ENC, $data->stream);
334: $new_part->setContents($decrypted_data->message, array(
335: 'encoding' => 'binary'
336: ));
337: }
338:
339: return $new_part;
340: }
341:
342: 343: 344: 345: 346:
347: protected function _outputPGPKey()
348: {
349:
350: if (empty($GLOBALS['conf']['gnupg']['path']) ||
351: !$GLOBALS['prefs']->getValue('use_pgp')) {
352: return array();
353: }
354:
355:
356: $status = new IMP_Mime_Status(_("A PGP Public Key is attached to the message."));
357: $status->icon('mime/encryption.png', 'PGP');
358:
359: $imp_contents = $this->getConfigParam('imp_contents');
360: $mime_id = $this->_mimepart->getMimeId();
361:
362: if ($GLOBALS['prefs']->getValue('use_pgp') &&
363: $GLOBALS['prefs']->getValue('add_source') &&
364: $GLOBALS['registry']->hasMethod('contacts/addField')) {
365:
366: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create('IMP_Ajax_Imple_ImportEncryptKey', array(
367: 'mime_id' => $mime_id,
368: 'muid' => strval($imp_contents->getIndicesOb()),
369: 'type' => 'pgp'
370: ));
371: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getDomId())) . _("Save the key to your address book.") . '</a>');
372: }
373: $status->addText($imp_contents->linkViewJS($this->_mimepart, 'view_attach', _("View key details."), array('params' => array('mode' => IMP_Contents::RENDER_FULL, 'pgp_view_key' => 1))));
374:
375: return array(
376: $mime_id => array(
377: 'data' => '',
378: 'status' => $status,
379: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
380: )
381: );
382: }
383:
384: 385: 386: 387: 388:
389: protected function _outputPGPSigned()
390: {
391: global $conf, $injector, $prefs, $registry, $session;
392:
393: $partlist = array_keys($this->_mimepart->contentTypeMap());
394: $base_id = reset($partlist);
395: $signed_id = next($partlist);
396:
397: $id_ob = new Horde_Mime_Id($signed_id);
398: $sig_id = $id_ob->idArithmetic($id_ob::ID_NEXT);
399:
400: if (!$prefs->getValue('use_pgp') || empty($conf['gnupg']['path'])) {
401: return array(
402: $sig_id => null
403: );
404: }
405:
406: $status = new IMP_Mime_Status();
407: $status->addText(_("The data in this part has been digitally signed via PGP."));
408: $status->icon('mime/encryption.png', 'PGP');
409:
410: $ret = array(
411: $base_id => array(
412: 'data' => '',
413: 'nosummary' => true,
414: 'status' => array($status),
415: 'type' => 'text/html; charset=' . $this->getConfigParam('charset'),
416: 'wrap' => 'mimePartWrap'
417: ),
418: $sig_id => null
419: );
420:
421: if ($prefs->getValue('pgp_verify') ||
422: $injector->getInstance('Horde_Variables')->pgp_verify_msg) {
423: $imp_contents = $this->getConfigParam('imp_contents');
424: $sig_part = $imp_contents->getMIMEPart($sig_id);
425:
426: $status2 = new IMP_Mime_Status();
427:
428: if (!$sig_part) {
429: $status2->action(IMP_Mime_Status::ERROR);
430: $sig_text = _("This digitally signed message is broken.");
431: $ret[$base_id]['wrap'] = 'mimePartWrapInvalid';
432: } else {
433: 434:
435: $session->close();
436:
437: try {
438: $imp_pgp = $injector->getInstance('IMP_Crypt_Pgp');
439: if ($sig_raw = $sig_part->getMetadata(Horde_Crypt_Pgp_Parse::SIG_RAW)) {
440: $sig_result = $imp_pgp->verifySignature($sig_raw, $this->_getSender()->bare_address, null, $sig_part->getMetadata(Horde_Crypt_Pgp_Parse::SIG_CHARSET));
441: } else {
442: $stream = $imp_contents->isEmbedded($signed_id)
443: ? $this->_mimepart->getMetadata(self::PGP_SIGN_ENC)
444: : $imp_contents->getBodyPart($signed_id, array('mimeheaders' => true, 'stream' => true))->data;
445:
446: rewind($stream);
447: stream_filter_register('horde_eol', 'Horde_Stream_Filter_Eol');
448: stream_filter_append($stream, 'horde_eol', STREAM_FILTER_READ, array(
449: 'eol' => Horde_Mime_Part::RFC_EOL
450: ));
451:
452: $sig_result = $imp_pgp->verifySignature(stream_get_contents($stream), $this->_getSender()->bare_address, $sig_part->getContents());
453: }
454:
455: $status2->action(IMP_Mime_Status::SUCCESS);
456: $sig_text = $sig_result->message;
457: $ret[$base_id]['wrap'] = 'mimePartWrapValid';
458: } catch (Horde_Exception $e) {
459: $status2->action(IMP_Mime_Status::ERROR);
460: $sig_text = $e->getMessage();
461: $ret[$base_id]['wrap'] = 'mimePartWrapInvalid';
462: }
463: }
464:
465: $status2->addText($this->_textFilter($sig_text, 'text2html', array(
466: 'parselevel' => Horde_Text_Filter_Text2html::NOHTML
467: )));
468: $ret[$base_id]['status'][] = $status2;
469: } else {
470: switch ($registry->getView()) {
471: case Horde_Registry::VIEW_BASIC:
472: $status->addText(Horde::link(Horde::selfUrlParams()->add(array('pgp_verify_msg' => 1))) . _("Click HERE to verify the message.") . '</a>');
473: break;
474:
475: case Horde_Registry::VIEW_DYNAMIC:
476: $status->addText(Horde::link('#', '', 'pgpVerifyMsg') . _("Click HERE to verify the message.") . '</a>');
477: break;
478: }
479: }
480:
481: return $ret;
482: }
483:
484: 485: 486: 487: 488:
489: protected function _getSymmetricID()
490: {
491: return $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp')->getSymmetricID($this->getConfigParam('imp_contents')->getMailbox(), $this->getConfigParam('imp_contents')->getUid(), $this->_mimepart->getMimeId());
492: }
493:
494: 495: 496: 497: 498:
499: protected function _getSender()
500: {
501: if (is_null($this->_sender)) {
502: $headers = $this->getConfigParam('imp_contents')->getHeader();
503: $tmp = $headers->getOb('from');
504: $this->_sender = $tmp[0];
505: }
506:
507: return $this->_sender;
508: }
509:
510: 511: 512: 513: 514: 515: 516:
517: public function canRender($mode)
518: {
519: switch ($mode) {
520: case 'full':
521: if ($this->_mimepart->getType() == 'application/pgp-keys') {
522: return true;
523: }
524: break;
525:
526: case 'raw':
527: if (($this->_mimepart->getType() == 'application/pgp-signature') &&
528: $this->_mimepart->getMetadata(Horde_Crypt_Pgp_Parse::SIG_RAW)) {
529: return true;
530: }
531: break;
532: }
533:
534: return parent::canRender($mode);
535: }
536:
537: }
538: