<?php

require_once HORDE_BASE . '/lib/Crypt.php';
require_once HORDE_BASE . '/lib/Crypt/smime.php';

/** @const IMP_SMIME_PUBKEY_FIELD Name of S/MIME public key field in addressbook. */
define('IMP_SMIME_PUBKEY_FIELD', 'smimePublicKey');

/** @const IMP_SMIME_CERT_PREFS Name of S/MIME additional cert field in preferences. */
define('IMP_SMIME_CERT_PREFS', 'smime_additional_cert');

/** @const IMP_SMIME_PRIVKEY_PREFS Name of S/MIME private key field in preferences. */
define('IMP_SMIME_PRIVKEY_PREFS', 'smime_private_key');

/** @const IMP_SMIME_PUBKEY_PREFS Name of S/MIME public key field in preferences. */
define('IMP_SMIME_PUBKEY_PREFS', 'smime_public_key');

/* @const IMP_SMIME_PASSPHRASE_CACHE Name of S/MIME cache entry containing the user's passphrase. */
define('IMP_SMIME_PASSPHRASE_CACHE', 'smime_passphrase');

/**
 * The IMP_SMIME:: class contains all functions related to handling
 * S/MIME messages within IMP.
 *
 * $Horde: imp/lib/SMIME.php,v 1.17 2003/07/25 08:17:14 slusarz Exp $
 *
 * Copyright 2002-2003 Mike Cochrane <mike@graftonhall.co.nz>
 *
 * 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  Mike Cochrane <mike@graftonhall.co.nz>
 * @version $Revision: 1.17 $
 * @since   IMP 4.0
 * @package imp
 */
class IMP_SMIME extends Horde_Crypt_smime {

    /**
     * The list of available sources to search for keys.
     *
     * @var array $_sources
     */
    var $_sources = array();

    /**
     * Constructor.
     *
     * @access public
     */
    function IMP_SMIME()
    {
        global $conf, $prefs;

        /* Get the listing of all sources we search for public keys. */
        if (($sources = $prefs->getValue('search_sources'))) {
            $this->_sources = explode("\t", $sources);
            if ((count($this->_sources) == 1) && empty($this->_sources[0])) {
                $this->_sources = array();
            }
        }

        parent::Horde_Crypt_smime();
    }
    
    /**
     * Add the personal public key to the prefs.
     *
     * @access public
     *
     * @param mixed $key  The public key to add (either string or array).
     */
    function addPersonalPublicKey($key)
    {
        global $prefs;

        if (is_array($key)) {
            $prefs->setValue(IMP_SMIME_PUBKEY_PREFS, implode('', $key));
        } else {
            $prefs->setValue(IMP_SMIME_PUBKEY_PREFS, $key);
        }
    }

    /**
     * Add the personal private key to the prefs.
     *
     * @access public
     *
     * @param mixed $key  The private key to add (either string or array).
     */
    function addPersonalPrivateKey($key)
    {
        global $prefs;

        if (is_array($key)) {
            $prefs->setValue(IMP_SMIME_PRIVKEY_PREFS, implode('', $key));
        } else {
            $prefs->setValue(IMP_SMIME_PRIVKEY_PREFS, $key);
        }
    }
     
    /**
     * Get the personal public key from the prefs.
     *
     * @access public
     *
     * @return string  The personal S/MIME public key.
     */
    function getPersonalPublicKey()
    {
        global $prefs;

        return $prefs->getValue(IMP_SMIME_PUBKEY_PREFS);
    }

    /**
     * Get the personal private key from the prefs.
     *
     * @access public
     *
     * @return string  The personal S/MIME private key.
     */
    function getPersonalPrivateKey()
    {
        global $prefs;

        return $prefs->getValue(IMP_SMIME_PRIVKEY_PREFS);
    }

    /**
     * Get any additional certificates from the prefs.
     *
     * @access public
     *
     * @return string  Additional signing certs for inclusion.
     */
    function getAdditionalCert()
    {
        global $prefs;

        return $prefs->getValue('import_additional_cert');
    }

    /**
     * Deletes the specified personal keys from the prefs.
     *
     * @access public
     */
    function deletePersonalKeys()
    {
        global $prefs;

        $prefs->setValue(IMP_SMIME_PUBKEY_PREFS, '');
        $prefs->setValue(IMP_SMIME_PRIVKEY_PREFS, '');

        $this->unsetPassphrase();
    }

    /**
     * Add a public key to an address book.
     *
     * @access public
     *
     * @param string $cert  A public certificate to add.
     *
     * @return Boolean True on success full add.
     *                Returns PEAR_Error or error.
     */
    function addPublicKey($cert, $from)
    {
        global $prefs, $registry;

        /* Make sure the certificate is valid. */
        $key_info = openssl_x509_parse($cert);

        if (!is_array($key_info) || !array_key_exists('subject', $key_info)) {
            return PEAR::raiseError(_("Not a valid public key."), 'horde.error');
        }

        /* Add key to the user's address book. */
        if (array_key_exists('Email', $key_info['subject'])) {
            $key = 'Email';
        } elseif (array_key_exists('emailAddress', $key_info['subject'])) {
            $key = 'emailAddress';
        } else {
            return PEAR::raiseError(_("Not a valid public key."), 'horde.error');
        }

        $result = $registry->call('contacts/addField', array($key_info['subject'][$key], $from, IMP_SMIME_PUBKEY_FIELD, $cert, $prefs->getValue('add_source')));

        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $key_info;
    }

    /**
     * Returns the params needed to encrypt a message being sent to the
     * specified email address.
     *
     * @access public
     *
     * @param string $address  The e-mail address of the recipient.
     *
     * @return array   The Params to encrypt a message to this person.
     *                 Returns PEAR_Error object on error.
     */
    function encryptMessageParams($address)
    {
        /* We can only encrypt if we are sending to a single person. */
        $addrOb = IMP::bareAddress($address, true);
        if (count($addrOb) > 1) {
            return PEAR::raiseError(_("For message encryption, there can only be one recipient in the To: field."));
        }
        $key_addr = array_pop($addrOb);

        $public_key = $this->getPublicKey($key_addr);
        if (is_a($public_key, 'PEAR_Error')) {
            return $public_key;
        }

        return array('type'   => 'message', 
                     'pubkey' => $public_key, 
                     'email'  => $address);
    }

    /**
     * Retrieves a public key by e-mail.
     * First, the key will be attempted to be retrieved from a user's
     * address book(s).
     * Second, if unsuccessful, the key is attempted to be retrieved via
     * a public PGP keyserver.
     *
     * @access public
     *
     * @param string $address               The e-mail address to search by.
     * @param optional string $fingerprint  The fingerprint of the user's key.
     *
     * @return string  The PGP public key requested.
     *                 Returns PEAR_Error object on error.
     */
    function getPublicKey($address)
    {
        global $registry;

        /* Try retrieving by e-mail only first. */
        $result = $registry->call('contacts/getField', array($address, IMP_SMIME_PUBKEY_FIELD, $this->_sources));

        return $result;
    }

    /**
     * Returns the params needed to sign a message.
     *
     * @access public
     *
     * @return array   The params needed.
     */
    function signMessageParams()
    {
        $params = array(
            'type'        =>  'signature', 
            'pubkey'      =>  $this->getPersonalPublicKey(), 
            'privkey'     =>  $this->getPersonalPrivateKey(), 
            'passphrase'  =>  $this->getPassphrase(), 
            'sigtype'     =>  'detach',
            'certs'       =>  $this->getAdditionalCert()
        );

        return $params;
    }

    /**
     * Verifies a signed message with a given public key.
     *
     * @access public
     *
     * @param string $text  The text to verify.
     *
     * @return boolean  See Horde_Crypt_smime::verify().
     *                  Returns PEAR_Error object on error.
     */
    function verifySignature($text)
    {
        global $conf;

        /* verify() returns a PEAR_Error object on error. */
        return $this->verify($text, $conf['utils']['openssl_cafile']);
    }


    /**
     * Decrypt a message with user's public/private keypair.
     *
     * @access public
     *
     * @param string $text  The text to decrypt.
     *
     * @return string  See Horde_Crypt_smime::decrypt().
     *                 Returns PEAR_Error object on error.
     */
    function decryptMessage($text)
    {
        /* decrypt() returns a PEAR_Error object on error. */
        return $this->decrypt($text, array('type' => 'message', 
                                           'pubkey' => $this->getPersonalPublicKey(), 
                                           'privkey' => $this->getPersonalPrivateKey(), 
                                           'passphrase' => $this->getPassphrase()));
    }

    /**
     * Gets the user's passphrase from the session cache.
     *
     * @access public
     *
     * @return string  The passphrase, if set.
     */
    function getPassphrase()
    {
        global $imp;

        if (array_key_exists(IMP_SMIME_PASSPHRASE_CACHE, $imp)) {
            return Secret::read(Secret::getKey('imp'), $imp[IMP_SMIME_PASSPHRASE_CACHE]);
        }
        
        return false;
    }

    /**
     * Store's the user's passphrase in the session cache.
     *
     * @access public
     *
     * @param string $passphrase  The user's passphrase.
     *
     * @return boolean  Returns true if correct passphrase, false if incorrect.
     */
    function storePassphrase($passphrase)
    {
        global $imp;

        if ($this->verifyPassphrase($this->getPersonalPublicKey(), $this->getPersonalPrivateKey(), $passphrase) === false) {
            return false; 
        }

        $imp[IMP_SMIME_PASSPHRASE_CACHE] = Secret::write(Secret::getKey('imp'), $passphrase);

        return true;
    }

    /**
     * Clear the passphrase from the session cache.
     *
     * @access public
     */
    function unsetPassphrase()
    {
        global $imp;

        unset($imp[IMP_SMIME_PASSPHRASE_CACHE]);
    }
    
    /**
     * Generates the javascript code for saving public keys.
     *
     * @access public
     *
     * @param object MIME_Part &$mime_part  The MIME_Part containing the 
     *                                      public key.
     * @param optional string $cache        The MIME_Part identifier.
     *
     * @return string  The URL for saving public keys.
     */
    function savePublicKeyURL($cert, $from)
    {
        if (empty($cache)) {
            require_once HORDE_BASE . '/lib/SessionObjects.php';
            $cacheSess = &Horde_SessionObjects::singleton();
            $oid = $cacheSess->storeOid($cert);
        }

        return $this->getJSOpenWinCode('save_attachment_public_key', false, array('from=' . $from, 'cert=' . $oid));
    }
    
    /**
     * Return certificate of a message signer if it was included in the message.
     *
     */
    function getSignerCert($text) 
    {
        global $conf;

        /* Check for availability of OpenSSL PHP extension. */
        $openssl = IMP_SMIME::checkForOpenSSL();
        if (is_a($openssl, 'PEAR_Error')) {
            return $openssl;
        }

        /* Create temp files for input/output. */
        $input = IMP_SMIME::_createTempFile('horde-smime');
        $certs = IMP_SMIME::_createTempFile('horde-smime');

        /* Write text to file */
        $fp = fopen($input, 'w+');
        fwrite($fp, $text);
        fclose($fp);

        /* Try again without verfying the signer's cert */
        $result = openssl_pkcs7_verify($input, PKCS7_DETACHED | PKCS7_NOVERIFY, $certs);

        if ($result === false) {   
            return false;
        } else {
            $fp = @fopen($certs, 'r');
            $result = @fread($fp, filesize($certs));
            @fclose($fp);
            return $result;
        }
    }
    
    /**
     * Print out the link for the open_smime_win javascript function.
     *
     * @access public
     *
     * @param integer $actionid       The ActionID to perform.
     * @param optional 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 optional array $params  Additional parameters needed for the
     *                                reload page.
     *
     * @return string  The javascript link.
     */
    function getJSOpenWinCode($actionid, $reload = true, $params = null)
    {
        $popup_url = Horde::applicationUrl('smime.php');
        $popup_url = Horde::addParameter($popup_url, 'smimeActionID=' . $actionid);
        if (!empty($reload)) {
            if (is_bool($reload)) {
                $popup_url = Horde::addParameter($popup_url, 'reload=' . Horde::selfUrl(true));
            } else {
                require_once HORDE_BASE . '/lib/SessionObjects.php';
                $cacheSess = &Horde_SessionObjects::singleton();
                $popup_url = Horde::addParameter($popup_url, 'passphrase_action=' . $cacheSess->storeOid($reload, false));
            }
        }

        if (is_array($params)) {
            foreach ($params as $val) {
                $popup_url = Horde::addParameter($popup_url, $val);
            }
        }

        return "open_smime_win('" . $popup_url . "');";
    }

    /**
     * Sign a MIME_Part using S/MIME.
     *
     * @access public
     *
     * @param object MIME_Part $mime_part  The MIME_Part object to sign.
     *
     * @return object MIME_Part  See Horde_Crypt_smime::signMIMEPart().
     *                           Returns PEAR_Error object on error.
     */
    function signMIMEPart($mime_part)
    {
        return parent::signMIMEPart($mime_part, $this->signMessageParams());
    }


}
