From: Michael M Slusarz Date: Fri, 5 Dec 2008 06:08:30 +0000 (-0700) Subject: Ticket #5388: PGP functions now work in DIMP X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=30c381998a1b46fbb2b7059fea0fba8335b75367;p=horde.git Ticket #5388: PGP functions now work in DIMP Passphrase dialogs now use redbox.js to display. --- diff --git a/imp/compose.php b/imp/compose.php index 01084f028..a3588d60c 100644 --- a/imp/compose.php +++ b/imp/compose.php @@ -713,9 +713,9 @@ if ($pgp_passphrase_dialog || $pgp_symmetric_passphrase_dialog) { $imp_pgp = &Horde_Crypt::singleton(array('imp', 'pgp')); Horde::addScriptFile('popup.js', 'imp', true); if ($pgp_passphrase_dialog) { - $notification->push($imp_pgp->getJSOpenWinCode('open_passphrase_dialog', "opener.focus();opener.uniqSubmit('send_message');"), 'javascript'); + $notification->push(IMP::passphraseDialogJS('PGPPersonal', 'uniqSubmit(\'send_message\')'), 'javascript'); } else { - $notification->push($imp_pgp->getJSOpenWinCode('open_symmetric_passphrase_dialog', "opener.focus();opener.uniqSubmit('send_message');", array('symmetricid' => 'imp_compose_' . $composeCacheID)), 'javascript'); + $notification->push(IMP::passphraseDialogJS('PGPSymmetric', 'uniqSubmit(\'send_message\')', array('symmetricid' => 'imp_compose_' . $composeCacheID)), 'javascript'); } } elseif ($smime_passphrase_dialog) { $imp_smime = &Horde_Crypt::singleton(array('imp', 'smime')); diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index dadb52891..3bb68de56 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -2,6 +2,7 @@ v5.0-cvs -------- +[mms] PGP functions now work in DIMP (Bug #5388). [mms] Encrypted bodytext now appears when replying/forwarding a message (Request #1345). [mms] When deleting/emptying a folder, display message count (Request #7424). diff --git a/imp/imp-dimp.php b/imp/imp-dimp.php index 77307d262..7b19f5381 100644 --- a/imp/imp-dimp.php +++ b/imp/imp-dimp.php @@ -154,6 +154,7 @@ $cacheid = Util::getPost('cacheid'); // encoding. ob_start(); +$notify = true; $result = false; /* We know we are going to be exclusively dealing with this mailbox, so @@ -672,6 +673,37 @@ case 'SendMDN': $imp_ui = new IMP_UI_Message(); $imp_ui->MDNCheck(reset($fetch_ret[$index]['headertext']), true); break; + +case 'PGPSymmetric': +case 'PGPPersonal': + $imp_pgp = &Horde_Crypt::singleton(array('imp', 'pgp')); + $secure_check = $imp_pgp->requireSecureConnection(); + + $symmetricid = Util::getFormData('symmetricid'); + $passphrase = Util::getFormData('passphrase'); + + $result = new stdClass; + $result->success = false; + + if (is_a($secure_check, 'PEAR_Error')) { + $result->error = $secure_check->getMessage(); + } elseif ($passphrase) { + if ($imp_pgp->storePassphrase(($action == 'PGPSymmetric') ? 'symmetric' : 'personal', $passphrase, $symmetricid)) { + $result->success = true; + } else { + $result->error = _("Invalid passphrase entered."); + } + } else { + $result->error = _("No passphrase entered."); + } + + /* TODO - This code will eventually be moved to the API. But this function + * may be called by IMP so explicitly include DIMP.php. */ + require_once IMP_BASE . '/lib/DIMP.php'; + $notify = false; + + print_r($_SESSION['imp']['cache']); + break; } // Clear the output buffer that we started above, and log any unexpected @@ -683,4 +715,4 @@ if ($errors) { } // Send the final result. -IMP::sendHTTPResponse(DIMP::prepareResponse($result), 'json'); +IMP::sendHTTPResponse(DIMP::prepareResponse($result, $notify), 'json'); diff --git a/imp/index-dimp.php b/imp/index-dimp.php index 5cfd2794c..28fb0eaee 100644 --- a/imp/index-dimp.php +++ b/imp/index-dimp.php @@ -24,6 +24,14 @@ $scripts = array( array('popup.js', 'imp', true) ); +/* Load necessary files for PGP or S/MIME passphrase input. */ +if ((!empty($GLOBALS['conf']['utils']['gnupg']) && + $GLOBALS['prefs']->getValue('use_pgp')) || + ($GLOBALS['prefs']->getValue('use_smime') && + Util::extensionExists('openssl'))) { + $scripts[] = array('encrypt.js', 'imp', true); +} + /* Get site specific menu items. */ $js_code = $site_menu = array(); if (is_readable(IMP_BASE . '/config/menu.php')) { diff --git a/imp/js/encrypt.js b/imp/js/encrypt.js new file mode 100644 index 000000000..f96a341f2 --- /dev/null +++ b/imp/js/encrypt.js @@ -0,0 +1 @@ +var IMPEncrypt={display:function(A){A=decodeURIComponent(A).evalJSON(true);this.action=A.action;this.params=A.params;this.uri=A.uri;var B=new Element("FORM",{action:"#",id:"RB_confirm"}).insert(new Element("P").insert(A.text)).insert(new Element("INPUT",{type:"text",size:15})).insert(new Element("INPUT",{type:"button",className:"button",value:A.ok_text}).observe("click",this._onClick.bind(this))).insert(new Element("INPUT",{type:"button",className:"button",value:A.cancel_text}).observe("click",this._close.bind(this))).observe("keydown",function(C){if((C.keyCode||C.charCode)==Event.KEY_RETURN){C.stop();this._onClick(C)}}.bind(this));RedBox.overlay=true;RedBox.onDisplay=Form.focusFirstElement.curry(B);RedBox.showHtml(B)},_close:function(){var A=RedBox.getWindowContents();[A,A.descendants()].flatten().compact().invoke("stopObserving");RedBox.close()},_onClick:function(A){params=this.params||{};params.passphrase=$F(A.findElement("form").down("input"));new Ajax.Request(this.uri,{parameters:params,onSuccess:this._onSuccess.bind(this),onFailure:this._onFailure.bind(this)})},_onSuccess:function(A){try{A=A.responseText.evalJSON(true)}catch(B){}if(A.response.success){this._close();if(this.action){this.action()}else{location.reload()}}else{if(A.response.error){alert(A.response.error)}}},_onFailure:function(A){}}; \ No newline at end of file diff --git a/imp/js/src/encrypt.js b/imp/js/src/encrypt.js new file mode 100644 index 000000000..ab7ce68af --- /dev/null +++ b/imp/js/src/encrypt.js @@ -0,0 +1,73 @@ +/** + * Javascript code used to display a RedBox pasphrase dialog. + * + * Copyright 2008 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (GPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/gpl.html. + * + * @author Michael Slusarz + */ + +var IMPEncrypt = { + + display: function(data) + { + data = decodeURIComponent(data).evalJSON(true); + this.action = data.action; + this.params = data.params; + this.uri = data.uri; + + var n = new Element('FORM', { action: '#', id: 'RB_confirm' }).insert( + new Element('P').insert(data.text) + ).insert( + new Element('INPUT', { type: 'text', size: 15 }) + ).insert( + new Element('INPUT', { type: 'button', className: 'button', value: data.ok_text }).observe('click', this._onClick.bind(this)) + ).insert( + new Element('INPUT', { type: 'button', className: 'button', value: data.cancel_text }).observe('click', this._close.bind(this)) + ).observe('keydown', function(e) { if ((e.keyCode || e.charCode) == Event.KEY_RETURN) { e.stop(); this._onClick(e); } }.bind(this)); + + RedBox.overlay = true; + RedBox.onDisplay = Form.focusFirstElement.curry(n); + RedBox.showHtml(n); + }, + + _close: function() + { + var c = RedBox.getWindowContents(); + [ c, c.descendants()].flatten().compact().invoke('stopObserving'); + RedBox.close(); + }, + + _onClick: function(e) + { + params = this.params || {}; + params.passphrase = $F(e.findElement('form').down('input')); + + new Ajax.Request(this.uri, { parameters: params, onSuccess: this._onSuccess.bind(this), onFailure: this._onFailure.bind(this) }); + }, + + _onSuccess: function(r) + { + try { + r = r.responseText.evalJSON(true); + } catch (e) {} + + if (r.response.success) { + this._close(); + if (this.action) { + this.action(); + } else { + location.reload(); + } + } else if (r.response.error) { + alert(r.response.error); + } + }, + + _onFailure: function(r) + { + } + +}; diff --git a/imp/lib/Crypt/pgp.php b/imp/lib/Crypt/pgp.php index b368e5e94..279bfb330 100644 --- a/imp/lib/Crypt/pgp.php +++ b/imp/lib/Crypt/pgp.php @@ -410,10 +410,20 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp /** * Clear the passphrase from the session cache. + * + * @param integer $type The type of passphrase. Either 'personal' or + * 'symmetric'. + * @param string $id If $type is 'symmetric', the ID of the + * stored passphrase. Else, all passphrases + * are deleted. */ - public function unsetPassphrase($type) + public function unsetPassphrase($type, $id = null) { - unset($_SESSION['imp']['cache']['pgp'][$type]); + if (($type == 'symmetric') && !is_null($id)) { + unset($_SESSION['imp']['cache']['pgp']['symmetric'][$id]); + } else { + unset($_SESSION['imp']['cache']['pgp'][$type]); + } } /** @@ -441,35 +451,11 @@ class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp */ public function savePublicKeyURL($mailbox, $uid, $id) { - return $this->getJSOpenWinCode('save_attachment_public_key', false, array('mailbox' => $mailbox, 'uid' => $uid, 'mime_id' => $id)); - } - - /** - * Print out the link for the javascript PGP popup. - * - * @param string $actionid The ActionID to perform. - * @param mixed $reload If true, reload base window on close. If text, - * run this JS on close. If false, don't do - * anything on close. - * @param array $params Additional parameters needed for the popup - * page. - * - * @return string The javascript link. - */ - public function getJSOpenWinCode($actionid, $reload = true, - $params = array()) - { - $params['actionID'] = $actionid; - if (!empty($reload)) { - if (is_bool($reload)) { - $params['reload'] = html_entity_decode(Util::removeParameter(Horde::selfUrl(true), array('actionID'))); - } else { - require_once 'Horde/SessionObjects.php'; - $cacheSess = &Horde_SessionObjects::singleton(); - $params['passphrase_action'] = $cacheSess->storeOid($reload, false); - } - } - + $params = array( + 'actionID' => 'save_attachment_public_key', + 'uid' => $uid, + 'mime_id' => $id + ); return IMP::popupIMPString('pgp.php', $params, 450, 200); } diff --git a/imp/lib/IMP.php b/imp/lib/IMP.php index 3640d84b0..74bee351f 100644 --- a/imp/lib/IMP.php +++ b/imp/lib/IMP.php @@ -1337,6 +1337,18 @@ class IMP } /** + * Do necessary escaping to output JSON in a HTML parameter. + * + * @param mixed $json The data to JSON-ify. + * + * @return string The escaped string. + */ + static public function escapeJSON($json) + { + return '/*-secure-' . rawurlencode(Horde_Serialize::serialize($json, SERIALIZE_JSON, NLS::getCharset())) . '*/'; + } + + /** * Log login related message. * * @param string $status Either 'login', 'logout', or 'failed'. @@ -1919,4 +1931,45 @@ class IMP return $cache; } + + /** + * Generate the JS code necessary to open a passphrase dialog. Adds the + * necessary JS files to open the dialog. + * + * @param string $type The dialog type. + * @param string $action The JS code to run after success. Defaults to + * reloading the current window. + * @param array $params Any additiona parameters to pass. + * + * @return string The generated JS code. + */ + static public function passphraseDialogJS($type, $action = null, + $params = array()) + { + Horde::addScriptFile('prototype.js', 'horde', true); + Horde::addScriptFile('effects.js', 'horde', true); + Horde::addScriptFile('encrypt.js', 'imp', true); + Horde::addScriptFile('redbox.js', 'imp', true); + + switch ($type) { + case 'PGPPersonal': + $text = _("Enter your personal PGP passphrase."); + break; + + case 'PGPSymmetric': + $text = _("Enter the passphrase used to encrypt this message."); + break; + } + + $js_params = array( + 'action' => $action ? 'function() {' . $action . '}' : '', + 'uri' => Horde::applicationUrl('imp-dimp.php', true, -1) . '/' . $type, + 'params' => $params, + 'text' => $text, + 'ok_text' => _("OK"), + 'cancel_text' => _("Cancel") + ); + + return 'IMPEncrypt.display(\'' . IMP::escapeJSON($js_params) . '\')'; + } } diff --git a/imp/lib/Mime/Viewer/pgp.php b/imp/lib/Mime/Viewer/pgp.php index 669805dfe..cd94364c3 100644 --- a/imp/lib/Mime/Viewer/pgp.php +++ b/imp/lib/Mime/Viewer/pgp.php @@ -84,12 +84,6 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver $this->_address = Horde_Mime_Address::bareAddress($headers->getValue('from')); } - /* We need to insert JS code if PGP support is active. */ - if (!empty($this->_imppgp) && $GLOBALS['prefs']->getValue('use_pgp')) { - Horde::addScriptFile('prototype.js', 'horde', true); - Horde::addScriptFile('popup.js', 'imp', true); - } - switch ($this->_mimepart->getType()) { case 'application/pgp-keys': return $this->_outputPGPKey(); @@ -127,13 +121,14 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver $data_id = Horde_Mime::mimeIdArithmetic($version_id, 'next'); /* Initialize inline data. */ + $resymmetric = isset(self::$_inlinecache[$base_id]); self::$_inlinecache[$base_id] = array( $base_id => array( 'data' => '', 'status' => array( array( 'icon' => Horde::img('mime/encryption.png', 'PGP'), - 'text' => array() + 'text' => $resymmetric ? self::$_inlinecache[$base_id][$base_id]['status'][0]['text'] : array() ) ), 'type' => 'text/html; charset=' . NLS::getCharset() @@ -165,15 +160,29 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver /* Check if this a symmetrically encrypted message. */ $symmetric = $this->_imppgp->encryptedSymmetrically($encrypted_data); if ($symmetric) { - $symmetric_id = $this->_imppgp->getSymmetricID($this->_params['contents']->getMailbox(), $this->_params['contents']->getIndex(), $this->_mimepart->getMimeId()); + $symmetric_id = $this->_getSymmetricID(); $symmetric_pass = $this->_imppgp->getPassphrase('symmetric', $symmetric_id); if (is_null($symmetric_pass)) { - if ($_SESSION['imp']['view'] == 'imp') { - // TODO: Fix to work with DIMP + $js_action = ''; + + switch ($_SESSION['imp']['view']) { + case 'dimp': + $js_action = 'DimpCore.reloadMessage({});'; + // Fall through + + case 'imp': /* Ask for the correct passphrase if this is encrypted * symmetrically. */ - $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase that was used to encrypt this message."), null, null, $this->_imppgp->getJSOpenWinCode('open_symmetric_passphrase_dialog', true, array('symmetricid' => $symmetric_id)) . ' return false;') . _("You must enter the passphrase that was used to encrypt this message.") . ''; + if (!$resymmetric) { + $status[] = _("The message below has been encrypted with PGP."); + } + $status[] = Horde::link('#', '', null, null, IMP::passphraseDialogJS('PGPSymmetric', $js_action, array('symmetricid' => $symmetric_id)) . ';return false;') . _("You must enter the passphrase used to encrypt this message to view it.") . ''; + break; + + case 'mimp': + $status[] = _("The message has been encrypted with PGP."); + break; } return null; } @@ -197,11 +206,23 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver $personal_pass = $this->_imppgp->getPassphrase('personal'); if (is_null($personal_pass)) { - if ($_SESSION['imp']['view'] == 'imp') { - // TODO: Fix to work with DIMP + $js_action = ''; + + switch ($_SESSION['imp']['view']) { + case 'dimp': + $js_action = 'DimpCore.reloadMessage({});'; + // Fall through + + case 'imp': /* Ask for the private key's passphrase if this is * encrypted asymmetrically. */ - $status[] = Horde::link('#', _("The message below has been encrypted with PGP. You must enter the passphrase for your PGP private key before it can be decrypted."), null, null, $this->_imppgp->getJSOpenWinCode('open_passphrase_dialog') . ' return false;') . _("You must enter the passphrase for your PGP private key to view this message.") . ''; + $status[] = _("The message below has been encrypted with PGP."); + $status[] = Horde::link('#', '', null, null, IMP::passphraseDialogJS('PGPPersonal', $js_action) . ';return false;') . _("You must enter the passphrase for your PGP private key to view this message.") . ''; + break; + + case 'mimp': + $status[] = _("The message has been encrypted with PGP."); + break; } return null; } @@ -219,6 +240,10 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver if (is_a($decrypted_data, 'PEAR_Error')) { $status[] = _("The message below does not appear to be a valid PGP encrypted message. Error: ") . $decrypted_data->getMessage(); + if (!is_null($symmetric_pass)) { + $this->_imppgp->unsetPassphrase('symmetric', $this->_getSymmetricID()); + return $this->_getEmbeddedMimeParts(); + } return null; } @@ -359,4 +384,14 @@ class IMP_Horde_Mime_Viewer_pgp extends Horde_Mime_Viewer_Driver ? self::$_inlinecache[$id] : array(); } + + /** + * Generates the symmetric ID for this message. + * + * @return string Symmetric ID. + */ + protected function _getSymmetricID() + { + return $this->_imppgp->getSymmetricID($this->_params['contents']->getMailbox(), $this->_params['contents']->getIndex(), $this->_mimepart->getMimeId()); + } } diff --git a/imp/pgp.php b/imp/pgp.php index 37d62c1b0..32b5a8f8e 100644 --- a/imp/pgp.php +++ b/imp/pgp.php @@ -14,30 +14,6 @@ function _printKeyInfo($key = '') _textWindowOutput('PGP Key Information', empty($key_info) ? _("Invalid key") : $key_info); } -function _outputPassphraseDialog($secure_check, $symmetricid = null) -{ - if (is_a($secure_check, 'PEAR_Error')) { - $GLOBALS['notification']->push($secure_check, 'horde.warning'); - } - - $title = _("PGP Passphrase Input"); - require IMP_TEMPLATES . '/common-header.inc'; - IMP::status(); - - if (is_a($secure_check, 'PEAR_Error')) { - return; - } - - $t = new IMP_Template(); - $t->setOption('gettext', true); - $t->set('symmetricid', $symmetricid); - $t->set('submit_url', Util::addParameter(Horde::applicationUrl('pgp.php'), 'actionID', $symmetricid ? 'process_symmetric_passphrase_dialog' : 'process_passphrase_dialog')); - $t->set('reload', htmlspecialchars(Util::getFormData('reload'))); - $t->set('action', Util::getFormData('passphrase_action')); - $t->set('locked_img', Horde::img('locked.png', _("PGP"), null, $GLOBALS['registry']->getImageDir('horde'))); - echo $t->fetch(IMP_TEMPLATES . '/pgp/passphrase.html'); -} - function _importKeyDialog($target) { $title = _("Import PGP Key"); @@ -272,54 +248,6 @@ case 'save_attachment_public_key': } exit; -case 'open_passphrase_dialog': - if ($imp_pgp->getPassphrase('personal')) { - Util::closeWindowJS(); - } else { - _outputPassphraseDialog($secure_check); - } - exit; - -case 'open_symmetric_passphrase_dialog': - $symmetricid = Util::getFormData('symmetricid'); - if ($imp_pgp->getPassphrase('symmetric', $symmetricid)) { - Util::closeWindowJS(); - } else { - _outputPassphraseDialog($secure_check, $symmetricid); - } - exit; - -case 'process_passphrase_dialog': -case 'process_symmetric_passphrase_dialog': - $symmetric = $actionID == 'process_symmetric_passphrase_dialog'; - $symmetricid = Util::getFormData('symmetricid'); - $passphrase = Util::getFormData('passphrase'); - if (is_a($secure_check, 'PEAR_Error')) { - _outputPassphraseDialog($secure_check, $symmetricid); - } elseif ($passphrase) { - $success = $imp_pgp->storePassphrase($symmetric ? 'symmetric' : 'personal', $passphrase, $symmetricid); - - if ($success) { - if (Util::getFormData('passphrase_action')) { - $oid = Util::getFormData('passphrase_action'); - $cacheSess = &Horde_SessionObjects::singleton(); - $cacheSess->setPruneFlag($oid, true); - Util::closeWindowJS($cacheSess->query($oid)); - } elseif (Util::getFormData('reload')) { - Util::closeWindowJS('opener.focus();opener.location.href="' . Util::getFormData('reload') . '";'); - } else { - Util::closeWindowJS(); - } - } else { - $notification->push("Invalid passphrase entered.", 'horde.error'); - _outputPassphraseDialog($secure_check, $symmetricid); - } - } else { - $notification->push("No passphrase entered.", 'horde.error'); - _outputPassphraseDialog($secure_check, $symmetricid); - } - exit; - case 'unset_passphrase': $imp_pgp->unsetPassphrase('personal'); $notification->push(_("Passphrase successfully unloaded."), 'horde.success'); @@ -417,7 +345,7 @@ if ($prefs->getValue('use_pgp')) { $t->set('sendkey', Horde::link(Util::addParameter($selfURL, 'actionID', 'send_public_key'), _("Send Key to Public Keyserver"))); $t->set('personalkey-public-help', Help::link('imp', 'pgp-personalkey-public')); $passphrase = $imp_pgp->getPassphrase('personal'); - $t->set('passphrase', (empty($passphrase)) ? Horde::link('#', _("Enter Passphrase"), null, null, htmlspecialchars($imp_pgp->getJSOpenWinCode('open_passphrase_dialog')) . ' return false;') . _("Enter Passphrase") : Horde::link(Util::addParameter($selfURL, 'actionID', 'unset_passphrase'), _("Unload Passphrase")) . _("Unload Passphrase")); + $t->set('passphrase', (empty($passphrase)) ? Horde::link('#', _("Enter Passphrase"), null, null, IMP::passphraseDialogJS('PGPPersonal') . ';return false;') . _("Enter Passphrase") : Horde::link(Util::addParameter($selfURL, 'actionID', 'unset_passphrase'), _("Unload Passphrase")) . _("Unload Passphrase")); $t->set('viewprivate', Horde::link(Util::addParameter($selfURL, 'actionID', 'view_personal_private_key'), _("View Personal Private Key"), null, 'view_key')); $t->set('infoprivate', Horde::link(Util::addParameter($selfURL, 'actionID', 'info_personal_private_key'), _("Information on Personal Private Key"), null, 'info_key')); $t->set('personalkey-private-help', Help::link('imp', 'pgp-personalkey-private')); diff --git a/imp/templates/pgp/passphrase.html b/imp/templates/pgp/passphrase.html deleted file mode 100644 index 153afb573..000000000 --- a/imp/templates/pgp/passphrase.html +++ /dev/null @@ -1,32 +0,0 @@ -
- - - - - - - - - - -
- - - Please enter the passphrase for this message. You will only need to enter this passphrase once per session. - - Please enter the passphrase for your PGP private key. You will only need to enter this passphrase once per session. - -
- -
-   -   - - -
- -
- -