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: class IMP_Mime_Viewer_Pgp extends Horde_Mime_Viewer_Base
26: {
27:
28: const PGP_ARMOR = 'imp-pgp-armor';
29: const PGP_SIG = 'imp-pgp-signature';
30: const PGP_CHARSET = 'imp-pgp-charset';
31:
32: 33: 34: 35: 36:
37: protected $_capability = array(
38: 'full' => false,
39: 'info' => false,
40: 'inline' => true,
41: 42: 43:
44: 'raw' => false
45: );
46:
47: 48: 49: 50: 51:
52: protected $_metadata = array(
53: 'compressed' => false,
54: 'embedded' => true,
55: 'forceinline' => true
56: );
57:
58: 59: 60: 61: 62:
63: protected $_address = null;
64:
65: 66: 67: 68: 69:
70: static protected $_cache = array();
71:
72: 73: 74: 75: 76:
77: protected function _renderRaw()
78: {
79: $id = $this->_mimepart->getMimeId();
80:
81: $ret = array(
82: $id => array(
83: 'data' => '',
84: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset')
85: )
86: );
87:
88: $parts = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp')->parsePGPData($this->_mimepart->getContents());
89: foreach (array_keys($parts) as $key) {
90: if ($parts[$key]['type'] == Horde_Crypt_Pgp::ARMOR_SIGNATURE) {
91: $ret[$id]['data'] = implode("\r\n", $parts[$key]['data']);
92: break;
93: }
94: }
95:
96: return $ret;
97: }
98:
99: 100: 101: 102: 103:
104: protected function _renderInline()
105: {
106: $id = $this->_mimepart->getMimeId();
107:
108: if (Horde_Util::getFormData('rawpgpkey')) {
109: return array(
110: $id => array(
111: 'data' => $this->_mimepart->getContents(),
112: 'type' => 'text/plain; charset=' . $this->_mimepart->getCharset()
113: )
114: );
115: }
116:
117:
118: if (is_null($this->_address)) {
119: $headers = $this->getConfigParam('imp_contents')->getHeader();
120: $this->_address = Horde_Mime_Address::bareAddress($headers->getValue('from'));
121: }
122:
123: switch ($this->_mimepart->getType()) {
124: case 'application/pgp-keys':
125: return $this->_outputPGPKey();
126:
127: case 'multipart/signed':
128: return $this->_outputPGPSigned();
129:
130: case 'multipart/encrypted':
131: if (!isset($headers)) {
132: $headers = $this->getConfigParam('imp_contents')->getHeader();
133: }
134:
135: $mid = $headers->getValue('message-id');
136: if (isset(self::$_cache[$mid][$id])) {
137: return array_merge(array(
138: $id => array(
139: 'data' => null,
140: 'status' => self::$_cache[$mid][$id]['status'],
141: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset'),
142: 'wrap' => self::$_cache[$mid][$id]['wrap']
143: )
144: ), self::$_cache[$mid][$id]['other']);
145: }
146:
147:
148: case 'application/pgp-encrypted':
149: case 'application/pgp-signature':
150: default:
151: return array();
152: }
153: }
154:
155: 156: 157: 158: 159: 160: 161:
162: protected function _getEmbeddedMimeParts()
163: {
164: if ($this->_mimepart->getType() != 'multipart/encrypted') {
165: return null;
166: }
167:
168: $mid = $this->getConfigParam('imp_contents')->getHeader()->getValue('message-id');
169:
170: $partlist = array_keys($this->_mimepart->contentTypeMap());
171: $base_id = reset($partlist);
172: $version_id = next($partlist);
173: $data_id = Horde_Mime::mimeIdArithmetic($version_id, 'next');
174:
175: $status = new IMP_Mime_Status();
176: $status->icon('mime/encryption.png', 'PGP');
177:
178: self::$_cache[$mid][$base_id] = array(
179: 'status' => array($status),
180: 'other' => array(
181: $version_id => null,
182: $data_id => null
183: ),
184: 'wrap' => ''
185: );
186:
187:
188: if (empty($GLOBALS['conf']['gnupg']['path']) ||
189: !$GLOBALS['prefs']->getValue('use_pgp')) {
190: $status->addText(_("The data in this part has been encrypted via PGP, however, PGP support is disabled so the message cannot be decrypted."));
191: return null;
192: }
193:
194: 195: 196:
197: $encrypted_part = $this->getConfigParam('imp_contents')->getMIMEPart($data_id);
198: $encrypted_data = $encrypted_part->getContents();
199:
200: $symmetric_pass = $personal_pass = null;
201:
202:
203: try {
204: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
205: $symmetric = $imp_pgp->encryptedSymmetrically($encrypted_data);
206: if ($symmetric) {
207: $symmetric_id = $this->_getSymmetricID();
208: $symmetric_pass = $imp_pgp->getPassphrase('symmetric', $symmetric_id);
209:
210: if (is_null($symmetric_pass)) {
211: $status->addText(_("The data in this part has been encrypted via PGP."));
212:
213: 214:
215: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create(array('imp', 'PassphraseDialog'), array(
216: 'params' => array(
217: 'symmetricid' => $symmetric_id
218: ),
219: 'type' => 'pgpSymmetric'
220: ));
221: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getPassphraseId())) . _("You must enter the passphrase used to encrypt this message to view it.") . '</a>');
222: return null;
223: }
224: }
225: } catch (Horde_Exception $e) {
226: Horde::logMessage($e, 'INFO');
227: return null;
228: }
229:
230:
231: try {
232: $info = $imp_pgp->pgpPacketInformation($encrypted_data);
233: } catch (Horde_Exception $e) {
234: Horde::logMessage($e, 'INFO');
235: return null;
236: }
237:
238: $literal = !empty($info['literal']);
239: if ($literal) {
240: $status->addText(_("The data in this part has been compressed via PGP."));
241: } else {
242: $status->addText(_("The data in this part has been encrypted via PGP."));
243:
244: if (!$symmetric) {
245: if ($imp_pgp->getPersonalPrivateKey()) {
246: $personal_pass = $imp_pgp->getPassphrase('personal');
247: if (is_null($personal_pass)) {
248: 249:
250: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create(array('imp', 'PassphraseDialog'), array(
251: 'type' => 'pgpPersonal'
252: ));
253: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getPassphraseId())) . _("You must enter the passphrase for your PGP private key to view this message.") . '</a>');
254: return null;
255: }
256: } else {
257: 258:
259: $status->addText(_("However, no personal private key exists so the message cannot be decrypted."));
260: return null;
261: }
262: }
263: }
264:
265: try {
266: if (!is_null($symmetric_pass)) {
267: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'symmetric', $symmetric_pass);
268: } elseif (!is_null($personal_pass)) {
269: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'personal', $personal_pass);
270: } else {
271: $decrypted_data = $imp_pgp->decryptMessage($encrypted_data, 'literal');
272: }
273: } catch (Horde_Exception $e) {
274: $status->addText(_("The data in this part does not appear to be a valid PGP encrypted message. Error: ") . $e->getMessage());
275: if (!is_null($symmetric_pass)) {
276: $imp_pgp->unsetPassphrase('symmetric', $this->_getSymmetricID());
277: return $this->_getEmbeddedMimeParts();
278: }
279: return null;
280: }
281:
282: self::$_cache[$mid][$base_id]['wrap'] = 'mimePartWrapValid';
283:
284:
285: if ($decrypted_data->result) {
286: $sig_text = is_bool($decrypted_data->result)
287: ? _("The data in this part has been digitally signed via PGP.")
288: : $this->_textFilter($decrypted_data->result, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::NOHTML));
289:
290: $status2 = new IMP_Mime_Status($sig_text);
291: $status2->action(IMP_Mime_Status::SUCCESS);
292:
293: self::$_cache[$mid][$base_id]['status'][] = $status2;
294: }
295:
296:
297: if ($this->_mimepart->getMetadata(self::PGP_ARMOR)) {
298: $decrypted_data->message = "Content-Type: text/plain\n\n" .
299: $decrypted_data->message;
300: }
301:
302: return Horde_Mime_Part::parseMessage($decrypted_data->message, array(
303: 'forcemime' => true
304: ));
305: }
306:
307: 308: 309: 310: 311:
312: protected function _outputPGPKey()
313: {
314:
315: if (empty($GLOBALS['conf']['gnupg']['path']) ||
316: !$GLOBALS['prefs']->getValue('use_pgp')) {
317: return array();
318: }
319:
320:
321: $status = new IMP_Mime_Status(_("A PGP Public Key is attached to the message."));
322: $status->icon('mime/encryption.png', 'PGP');
323:
324: $mime_id = $this->_mimepart->getMimeId();
325: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
326:
327: if ($GLOBALS['prefs']->getValue('use_pgp') &&
328: $GLOBALS['prefs']->getValue('add_source') &&
329: $GLOBALS['registry']->hasMethod('contacts/addField')) {
330: $status->addText(Horde::link('#', '', '', '', $imp_pgp->savePublicKeyURL($this->getConfigParam('imp_contents')->getMailbox(), $this->getConfigParam('imp_contents')->getUid(), $mime_id) . 'return false;') . _("Save the key to your address book.") . '</a>');
331: }
332: $status->addText($this->getConfigParam('imp_contents')->linkViewJS($this->_mimepart, 'view_attach', _("View the raw text of the Public Key."), array('jstext' => _("View Public Key"), 'params' => array('mode' => IMP_Contents::RENDER_INLINE, 'rawpgpkey' => 1))));
333:
334: try {
335: $data = '<span class="fixed">' . nl2br(str_replace(' ', ' ', $imp_pgp->pgpPrettyKey($this->_mimepart->getContents()))) . '</span>';
336: } catch (Horde_Exception $e) {
337: $data = $e->getMessage();
338: }
339:
340: return array(
341: $mime_id => array(
342: 'data' => $data,
343: 'status' => $status,
344: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
345: )
346: );
347: }
348:
349: 350: 351: 352: 353:
354: protected function _outputPGPSigned()
355: {
356: $partlist = array_keys($this->_mimepart->contentTypeMap());
357: $base_id = reset($partlist);
358: $signed_id = next($partlist);
359: $sig_id = Horde_Mime::mimeIdArithmetic($signed_id, 'next');
360:
361: $status = new IMP_Mime_Status();
362: $status->icon('mime/encryption.png', 'PGP');
363:
364: $ret = array(
365: $base_id => array(
366: 'data' => '',
367: 'nosummary' => true,
368: 'status' => array($status),
369: 'type' => 'text/html; charset=' . $this->getConfigParam('charset'),
370: 'wrap' => 'mimePartWrap'
371: ),
372: $sig_id => null
373: );
374:
375: if (!$GLOBALS['prefs']->getValue('use_pgp') ||
376: empty($GLOBALS['conf']['gnupg']['path'])) {
377: 378:
379: $status->addText(_("The data in this part has been digitally signed via PGP, but the signature cannot be verified."));
380: return $ret;
381: }
382:
383: $status->addText(_("The data in this part has been digitally signed via PGP."));
384:
385: if ($GLOBALS['prefs']->getValue('pgp_verify') ||
386: Horde_Util::getFormData('pgp_verify_msg')) {
387: $sig_part = $this->getConfigParam('imp_contents')->getMIMEPart($sig_id);
388:
389: $status2 = new IMP_Mime_Status();
390:
391: try {
392: $imp_pgp = $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp');
393: $sig_result = $sig_part->getMetadata(self::PGP_SIG)
394: ? $imp_pgp->verifySignature($sig_part->getContents(array('canonical' => true)), $this->_address, null, $sig_part->getMetadata(self::PGP_CHARSET))
395: : $imp_pgp->verifySignature($sig_part->replaceEOL($this->getConfigParam('imp_contents')->getBodyPart($signed_id, array('mimeheaders' => true)), Horde_Mime_Part::RFC_EOL), $this->_address, $sig_part->getContents());
396:
397: $status2->action(IMP_Mime_Status::SUCCESS);
398: $sig_text = $sig_result->message;
399: $ret[$base_id]['wrap'] = 'mimePartWrapValid';
400: } catch (Horde_Exception $e) {
401: $status2->action(IMP_Mime_Status::ERROR);
402: $sig_text = $e->getMessage();
403: $ret[$base_id]['wrap'] = 'mimePartWrapInvalid';
404: }
405:
406: $status2->addText($this->_textFilter($sig_text, 'text2html', array(
407: 'parselevel' => Horde_Text_Filter_Text2html::NOHTML
408: )));
409: $ret[$base_id]['status'][] = $status2;
410: } else {
411: switch (IMP::getViewMode()) {
412: case 'imp':
413: $status->addText(Horde::link(IMP::selfUrl()->add(array('pgp_verify_msg' => 1))) . _("Click HERE to verify the message.") . '</a>');
414: break;
415:
416: case 'dimp':
417: $status->addText(Horde::link('#', '', 'pgpVerifyMsg') . _("Click HERE to verify the message.") . '</a>');
418: break;
419: }
420: }
421:
422: return $ret;
423: }
424:
425: 426: 427: 428: 429:
430: protected function _getSymmetricID()
431: {
432: return $GLOBALS['injector']->getInstance('IMP_Crypt_Pgp')->getSymmetricID($this->getConfigParam('imp_contents')->getMailbox(), $this->getConfigParam('imp_contents')->getUid(), $this->_mimepart->getMimeId());
433: }
434:
435: 436: 437: 438: 439: 440: 441:
442: public function canRender($mode)
443: {
444: return (($mode == 'raw') &&
445: ($this->_mimepart->getType() == 'application/pgp-signature') &&
446: $this->_mimepart->getMetadata(self::PGP_SIG))
447: ? true
448: : parent::canRender($mode);
449: }
450:
451: }
452: