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: class IMP_Mime_Viewer_Smime extends Horde_Mime_Viewer_Base
28: {
29: 30: 31: 32: 33:
34: protected $_capability = array(
35: 'full' => false,
36: 'info' => false,
37: 'inline' => true,
38: 'raw' => false
39: );
40:
41: 42: 43: 44: 45:
46: protected $_metadata = array(
47: 'compressed' => false,
48: 'embedded' => true,
49: 'forceinline' => true
50: );
51:
52: 53: 54: 55: 56:
57: protected $_impsmime = null;
58:
59: 60: 61: 62: 63:
64: static protected $_cache = array();
65:
66: 67: 68:
69: protected function _initSmime()
70: {
71: if (is_null($this->_impsmime) &&
72: $GLOBALS['prefs']->getValue('use_smime')) {
73: try {
74: $this->_impsmime = $GLOBALS['injector']->getInstance('IMP_Crypt_Smime');
75: $this->_impsmime->checkForOpenSSL();
76: } catch (Horde_Exception $e) {
77: $this->_impsmime = null;
78: }
79: }
80: }
81:
82: 83: 84: 85: 86:
87: protected function _renderInline()
88: {
89:
90: $this->_initSmime();
91:
92: if (Horde_Util::getFormData('view_smime_key')) {
93: return $this->_outputSmimeKey();
94: }
95:
96: if (is_null($this->_impsmime)) {
97: $this->_impsmime = false;
98: } else {
99: 100:
101: Horde::addScriptFile('imp.js', 'imp');
102: }
103:
104: $id = $this->_mimepart->getMimeId();
105:
106: switch ($this->_mimepart->getType()) {
107: case 'multipart/signed';
108: if (!in_array($this->_mimepart->getContentTypeParameter('protocol'), array('application/pkcs7-signature', 'application/x-pkcs7-signature'))) {
109: return array();
110: }
111: $this->_parseSignedData(true);
112:
113:
114: case 'application/pkcs7-mime':
115: case 'application/x-pkcs7-mime':
116: if (isset(self::$_cache[$id])) {
117: $ret = array(
118: $id => array(
119: 'data' => null,
120: 'status' => self::$_cache[$id]['status'],
121: 'type' => 'text/plain; charset=' . $this->getConfigParam('charset'),
122: 'wrap' => self::$_cache[$id]['wrap']
123: )
124: );
125: if (isset(self::$_cache[$id]['sig'])) {
126: $ret[self::$_cache[$id]['sig']] = null;
127: }
128: return $ret;
129: }
130:
131:
132: default:
133: return array();
134: }
135: }
136:
137: 138: 139: 140: 141: 142: 143: 144:
145: protected function _getEmbeddedMimeParts()
146: {
147: if (!in_array($this->_mimepart->getType(), array('application/pkcs7-mime', 'application/x-pkcs7-mime'))) {
148: return null;
149: }
150:
151:
152: switch ($this->_mimepart->getContentTypeParameter('smime-type')) {
153: case 'signed-data':
154: return $this->_parseSignedData();
155:
156: case 'enveloped-data':
157: default:
158: 159: 160:
161: return $this->_parseEnvelopedData();
162: }
163: }
164:
165: 166: 167: 168: 169:
170: protected function _parseEnvelopedData()
171: {
172: $base_id = $this->_mimepart->getMimeId();
173:
174:
175: $status = new IMP_Mime_Status(_("The data in this part has been encrypted via S/MIME."));
176: $status->icon('mime/encryption.png', 'S/MIME');
177: self::$_cache[$base_id] = array(
178: 'status' => $status,
179: 'wrap' => ''
180: );
181:
182:
183: $this->_initSmime();
184: if (empty($this->_impsmime)) {
185: $status->addText(_("S/MIME support is not currently enabled so the data is unable to be decrypted."));
186: return null;
187: }
188:
189: if (!$this->_impsmime->getPersonalPrivateKey()) {
190: $status->addText(_("No personal private key exists so the data is unable to be decrypted."));
191: return null;
192: }
193:
194:
195: $passphrase = $this->_impsmime->getPassphrase();
196: if ($passphrase === false) {
197: $imple = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Imple')->create(array('imp', 'PassphraseDialog'), array(
198: 'type' => 'smimePersonal'
199: ));
200: $status->addText(Horde::link('#', '', '', '', '', '', '', array('id' => $imple->getPassphraseId())) . _("You must enter the passphrase for your S/MIME private key to view this data.") . '</a>');
201: return null;
202: }
203:
204: $raw_text = $this->_getPartStream($this->_mimepart->getMimeId());
205:
206: try {
207: $decrypted_data = $this->_impsmime->decryptMessage($this->_mimepart->replaceEOL($raw_text, Horde_Mime_Part::RFC_EOL));
208: } catch (Horde_Exception $e) {
209: $status->addText($e->getMessage());
210: return null;
211: }
212:
213: self::$_cache[$base_id]['wrap'] = 'mimePartWrapValid';
214:
215: $new_part = Horde_Mime_Part::parseMessage($decrypted_data, array(
216: 'forcemime' => true
217: ));
218:
219: $hdrs = $this->getConfigParam('imp_contents')->getHeader();
220: $new_part->setMetadata('imp-smime-from', $hdrs->getValue('from'));
221:
222: return $new_part;
223: }
224:
225: 226: 227: 228: 229: 230: 231:
232: protected function _parseSignedData($sig_only = false)
233: {
234: $partlist = array_keys($this->_mimepart->contentTypeMap());
235: $base_id = reset($partlist);
236: $sig_id = Horde_Mime::mimeIdArithmetic(next($partlist), 'next');
237:
238:
239: $status = new IMP_Mime_Status(_("The data in this part has been digitally signed via S/MIME."));
240: $status->icon('mime/encryption.png', 'S/MIME');
241:
242: self::$_cache[$base_id] = array(
243: 'sig' => $sig_id,
244: 'status' => $status,
245: 'wrap' => 'mimePartWrap'
246: );
247:
248: if (!$GLOBALS['prefs']->getValue('use_smime')) {
249: $status->addText(_("S/MIME support is not enabled so the digital signature is unable to be verified."));
250: return null;
251: }
252:
253: if ($this->getConfigParam('imp_contents')->isEmbedded($base_id)) {
254: $hdrs = new Horde_Mime_Headers();
255: $hdrs->addHeader('From', $this->_mimepart->getMetadata('imp-smime-from'));
256:
257: $stream = $this->_mimepart->toString(array(
258: 'headers' => $hdrs,
259: 'stream' => true
260: ));
261: } else {
262: $stream = $this->_getPartStream($base_id);
263: }
264:
265: $raw_text = $this->_mimepart->replaceEOL($stream, Horde_Mime_Part::RFC_EOL);
266:
267: $this->_initSmime();
268: $sig_result = null;
269:
270: if ($GLOBALS['prefs']->getValue('smime_verify') ||
271: Horde_Util::getFormData('smime_verify_msg')) {
272: try {
273: $sig_result = $this->_impsmime->verifySignature($raw_text);
274: if ($sig_result->verify) {
275: $status->action(IMP_Mime_Status::SUCCESS);
276: } else {
277: $status->action(IMP_Mime_Status::WARNING);
278: }
279: self::$_cache[$base_id]['wrap'] = 'mimePartWrapValid';
280:
281: $email = is_array($sig_result->email)
282: ? implode(', ', $sig_result->email)
283: : $sig_result->email;
284:
285: $status->addText($sig_result->msg);
286:
287: if (!empty($sig_result->cert)) {
288: $cert = $this->_impsmime->parseCert($sig_result->cert);
289: if (isset($cert['certificate']['subject']['CommonName'])) {
290: $email = $cert['certificate']['subject']['CommonName'] . ' (' . trim($email) . ')';
291: }
292: }
293:
294: if (strlen($email)) {
295: $status->addText(sprintf(_("Sender: %s"), htmlspecialchars($email)));
296: }
297:
298: if (!empty($sig_result->cert) &&
299: isset($sig_result->email) &&
300: $GLOBALS['registry']->hasMethod('contacts/addField') &&
301: $GLOBALS['prefs']->getValue('add_source')) {
302: $status_out = '[' . $this->getConfigParam('imp_contents')->linkViewJS($this->_mimepart, 'view_attach', _("View Certificate"), array('params' => array('mode' => IMP_Contents::RENDER_INLINE, 'view_smime_key' => 1))) . ']';
303: try {
304: $this->_impsmime->getPublicKey($sig_result->email);
305: } catch (Horde_Exception $e) {
306: $status_out .= ' [' . Horde::link('#', '', null, null, $this->_impsmime->savePublicKeyURL($this->getConfigParam('imp_contents')->getMailbox(), $this->getConfigParam('imp_contents')->getUid(), $base_id) . ' return false;') . _("Save Certificate in your Address Book") . '</a>]';
307: }
308: $status->addText($status_out);
309: }
310: } catch (Horde_Exception $e) {
311: $status->action(IMP_Mime_Status::ERROR);
312: self::$_cache[$base_id]['wrap'] = 'mimePartWrapInvalid';
313: $status->addText($e->getMessage());
314: }
315: } else {
316: switch (IMP::getViewMode()) {
317: case 'imp':
318: $status->addText(Horde::link(IMP::selfUrl()->add('smime_verify_msg', 1)) . _("Click HERE to verify the data.") . '</a>');
319: break;
320:
321: case 'dimp':
322: $status->addText(Horde::link('#', '', 'smimeVerifyMsg') . _("Click HERE to verify the data.") . '</a>');
323: break;
324: }
325: }
326:
327: if ($sig_only) {
328: return;
329: }
330:
331: $subpart = $this->getConfigParam('imp_contents')->getMIMEPart($sig_id);
332: if (empty($subpart)) {
333: try {
334: $msg_data = $this->_impsmime->extractSignedContents($raw_text);
335: $subpart = Horde_Mime_Part::parseMessage($msg_data, array('forcemime' => true));
336: } catch (Horde_Exception $e) {
337: $status->addText($e->getMessage());
338: return null;
339: }
340: }
341:
342: return $subpart;
343: }
344:
345: 346: 347: 348: 349:
350: protected function _outputSmimeKey()
351: {
352: if (empty($this->_impsmime)) {
353: return array();
354: }
355:
356: $raw_text = $this->_getPartStream($this->_mimepart->getMimeId());
357:
358: try {
359: $sig_result = $this->_impsmime->verifySignature($this->_mimepart->replaceEOL($raw_text, Horde_Mime_Part::RFC_EOL));
360: } catch (Horde_Exception $e) {
361: return array();
362: }
363:
364: return array(
365: $this->_mimepart->getMimeId() => array(
366: 'data' => $this->_impsmime->certToHTML($sig_result->cert),
367: 'type' => 'text/html; charset=' . $this->getConfigParam('charset')
368: )
369: );
370: }
371:
372: 373:
374: protected function _getPartStream($id)
375: {
376: return $id
377: ? $this->getConfigParam('imp_contents')->getBodyPart($id, array('mimeheaders' => true, 'stream' => true))
378: : $this->getConfigParam('imp_contents')->fullMessageText();
379: }
380:
381: }
382: