PNG  IHDRxsBIT|d pHYs+tEXtSoftwarewww.inkscape.org<,tEXtComment File Manager

File Manager

Path: /proc/self/root/opt/alt/php56/usr/share/pear/Net/

Viewing File: Sieve.php

<?php
/**
 * This file contains the Net_Sieve class.
 *
 * PHP version 5
 *
 * +-----------------------------------------------------------------------+
 * | All rights reserved.                                                  |
 * |                                                                       |
 * | Redistribution and use in source and binary forms, with or without    |
 * | modification, are permitted provided that the following conditions    |
 * | are met:                                                              |
 * |                                                                       |
 * | o Redistributions of source code must retain the above copyright      |
 * |   notice, this list of conditions and the following disclaimer.       |
 * | o Redistributions in binary form must reproduce the above copyright   |
 * |   notice, this list of conditions and the following disclaimer in the |
 * |   documentation and/or other materials provided with the distribution.|
 * |                                                                       |
 * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
 * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
 * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
 * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
 * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
 * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
 * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
 * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
 * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
 * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
 * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
 * +-----------------------------------------------------------------------+
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD
 * @link      http://pear.php.net/package/Net_Sieve
 */

require_once 'PEAR.php';
require_once 'Net/Socket.php';

/**
 * Disconnected state
 *
 * @const NET_SIEVE_STATE_DISCONNECTED
 */
define('NET_SIEVE_STATE_DISCONNECTED', 1);

/**
 * Authorisation state
 *
 * @const NET_SIEVE_STATE_AUTHORISATION
 */
define('NET_SIEVE_STATE_AUTHORISATION', 2);

/**
 * Transaction state
 *
 * @const NET_SIEVE_STATE_TRANSACTION
 */
define('NET_SIEVE_STATE_TRANSACTION', 3);


/**
 * A class for talking to the timsieved server which comes with Cyrus IMAP.
 *
 * @category  Networking
 * @package   Net_Sieve
 * @author    Richard Heyes <richard@phpguru.org>
 * @author    Damian Fernandez Sosa <damlists@cnba.uba.ar>
 * @author    Anish Mistry <amistry@am-productions.biz>
 * @author    Jan Schneider <jan@horde.org>
 * @author    Neil Munday <neil@mundayweb.com>
 * @copyright 2002-2003 Richard Heyes
 * @copyright 2006-2008 Anish Mistry
 * @license   http://www.opensource.org/licenses/bsd-license.php BSD
 * @version   Release: 1.4.5
 * @link      http://pear.php.net/package/Net_Sieve
 * @link      http://tools.ietf.org/html/rfc5228 RFC 5228 (Sieve: An Email
 *            Filtering Language)
 * @link      http://tools.ietf.org/html/rfc5804 RFC 5804 A Protocol for
 *            Remotely Managing Sieve Scripts
 */
class Net_Sieve
{
    /**
     * The authentication methods this class supports.
     *
     * Can be overwritten if having problems with certain methods.
     *
     * @var array
     */
    var $supportedAuthMethods = array(
        'DIGEST-MD5',
        'CRAM-MD5',
        'EXTERNAL',
        'PLAIN' ,
        'LOGIN',
        'GSSAPI',
        'XOAUTH2'
    );

    /**
     * SASL authentication methods that require Auth_SASL.
     *
     * @var array
     */
    var $supportedSASLAuthMethods = array('DIGEST-MD5', 'CRAM-MD5');

    /**
     * The socket handle.
     *
     * @var resource
     */
    var $_sock;

    /**
     * Parameters and connection information.
     *
     * @var array
     */
    var $_data;

    /**
     * Current state of the connection.
     *
     * One of the NET_SIEVE_STATE_* constants.
     *
     * @var integer
     */
    var $_state;

    /**
     * PEAR object to avoid strict warnings.
     *
     * @var PEAR_Error
     */
    var $_pear;

    /**
     * Constructor error.
     *
     * @var PEAR_Error
     */
    var $_error;

    /**
     * Whether to enable debugging.
     *
     * @var boolean
     */
    var $_debug = false;

    /**
     * Debug output handler.
     *
     * This has to be a valid callback.
     *
     * @var string|array
     */
    var $_debug_handler = null;

    /**
     * Whether to pick up an already established connection.
     *
     * @var boolean
     */
    var $_bypassAuth = false;

    /**
     * Whether to use TLS if available.
     *
     * @var boolean
     */
    var $_useTLS = true;

    /**
     * Additional options for stream_context_create().
     *
     * @var array
     */
    var $_options = null;

    /**
     * Maximum number of referral loops
     *
     * @var array
     */
    var $_maxReferralCount = 15;

    /**
     * Kerberos service principal to use for GSSAPI authentication.
     *
     * @var string
     */
    var $_gssapiPrincipal = null;

    /**
     * Kerberos service cname to use for GSSAPI authentication.
     *
     * @var string
     */
    var $_gssapiCN = null;

    /**
     * Constructor.
     *
     * Sets up the object, connects to the server and logs in. Stores any
     * generated error in $this->_error, which can be retrieved using the
     * getError() method.
     *
     * @param string  $user       Login username.
     * @param string  $pass       Login password.
     * @param string  $host       Hostname of server.
     * @param string  $port       Port of server.
     * @param string  $logintype  Type of login to perform (see
     *                            $supportedAuthMethods).
     * @param string  $euser      Effective user. If authenticating as an
     *                            administrator, login as this user.
     * @param boolean $debug      Whether to enable debugging (@see setDebug()).
     * @param string  $bypassAuth Skip the authentication phase. Useful if the
     *                            socket is already open.
     * @param boolean $useTLS     Use TLS if available.
     * @param array   $options    Additional options for
     *                            stream_context_create().
     * @param mixed   $handler    A callback handler for the debug output.
     * @param string  $principal  Kerberos service principal to use
     *                            with GSSAPI authentication.
     * @param string  $cname      Kerberos service cname to use
     *                            with GSSAPI authentication.
     */
    function __construct($user = null, $pass  = null, $host = 'localhost',
        $port = 2000, $logintype = '', $euser = '',
        $debug = false, $bypassAuth = false, $useTLS = true,
        $options = null, $handler = null, $principal = null, $cname = null
    ) {
        $this->_pear = new PEAR();
        $this->_state             = NET_SIEVE_STATE_DISCONNECTED;
        $this->_data['user']      = $user;
        $this->_data['pass']      = $pass;
        $this->_data['host']      = $host;
        $this->_data['port']      = $port;
        $this->_data['logintype'] = $logintype;
        $this->_data['euser']     = $euser;
        $this->_sock              = new Net_Socket();
        $this->_bypassAuth        = $bypassAuth;
        $this->_useTLS            = $useTLS;
        $this->_options           = (array) $options;
        $this->_gssapiPrincipal   = $principal;
        $this->_gssapiCN          = $cname;

        $this->setDebug($debug, $handler);

        /* Try to include the Auth_SASL package.  If the package is not
         * available, we disable the authentication methods that depend upon
         * it. */
        if ((@include_once 'Auth/SASL.php') === false) {
            $this->_debug('Auth_SASL not present');
            $this->supportedAuthMethods = array_diff(
                $this->supportedAuthMethods,
                $this->supportedSASLAuthMethods
            );
        }

        if (strlen($user) && strlen($pass)) {
            $this->_error = $this->_handleConnectAndLogin();
        }
    }

    /**
     * Returns any error that may have been generated in the constructor.
     *
     * @return boolean|PEAR_Error  False if no error, PEAR_Error otherwise.
     */
    function getError()
    {
        return is_a($this->_error, 'PEAR_Error') ? $this->_error : false;
    }

    /**
     * Sets the debug state and handler function.
     *
     * @param boolean $debug   Whether to enable debugging.
     * @param string  $handler A custom debug handler. Must be a valid callback.
     *
     * @return void
     */
    function setDebug($debug = true, $handler = null)
    {
        $this->_debug = $debug;
        $this->_debug_handler = $handler;
    }

    /**
     * Sets the Kerberos service principal for use with GSSAPI
     * authentication.
     *
     * @param string $principal The Kerberos service principal
     *
     * @return void
     */
    function setServicePrincipal($principal)
    {
        $this->_gssapiPrincipal = $principal;
    }

    /**
     * Sets the Kerberos service CName for use with GSSAPI
     * authentication.
     *
     * @param string $cname The Kerberos service principal
     *
     * @return void
     */
    function setServiceCN($cname)
    {
        $this->_gssapiCN = $cname;
    }

    /**
     * Connects to the server and logs in.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function _handleConnectAndLogin()
    {
        $res = $this->connect($this->_data['host'], $this->_data['port'], $this->_options, $this->_useTLS);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_bypassAuth === false) {
            $res = $this->login($this->_data['user'], $this->_data['pass'], $this->_data['logintype'], $this->_data['euser'], $this->_bypassAuth);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        return true;
    }

    /**
     * Handles connecting to the server and checks the response validity.
     *
     * @param string  $host    Hostname of server.
     * @param string  $port    Port of server.
     * @param array   $options List of options to pass to
     *                         stream_context_create().
     * @param boolean $useTLS  Use TLS if available.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function connect($host, $port, $options = null, $useTLS = true)
    {
        $this->_data['host'] = $host;
        $this->_data['port'] = $port;
        $this->_useTLS       = $useTLS;

        if (is_array($options)) {
            $this->_options = array_merge($this->_options, $options);
        }

        if (NET_SIEVE_STATE_DISCONNECTED != $this->_state) {
            return $this->_pear->raiseError('Not currently in DISCONNECTED state', 1);
        }

        $res = $this->_sock->connect($host, $port, false, 5, $options);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_bypassAuth) {
            $this->_state = NET_SIEVE_STATE_TRANSACTION;

            // Reset capabilities
            $this->_parseCapability('');
        } else {
            $this->_state = NET_SIEVE_STATE_AUTHORISATION;

            $res = $this->_doCmd();
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }

            // Reset capabilities (use unattended capabilities)
            $this->_parseCapability($res);
        }

        // Explicitly ask for the capabilities if needed
        if (empty($this->_capability['implementation'])) {
            $res = $this->_cmdCapability();
            if (is_a($res, 'PEAR_Error')) {
                return $this->_pear->raiseError(
                    'Failed to connect, server said: ' . $res->getMessage(), 2
                );
            }
        }

        // Check if we can enable TLS via STARTTLS.
        if ($useTLS && !empty($this->_capability['starttls'])
            && function_exists('stream_socket_enable_crypto')
        ) {
            $res = $this->_startTLS();
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        return true;
    }

    /**
     * Disconnect from the Sieve server.
     *
     * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
     *                               disconnecting.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function disconnect($sendLogoutCMD = true)
    {
        return $this->_cmdLogout($sendLogoutCMD);
    }

    /**
     * Logs into server.
     *
     * @param string  $user       Login username.
     * @param string  $pass       Login password.
     * @param string  $logintype  Type of login method to use.
     * @param string  $euser      Effective UID (perform on behalf of $euser).
     * @param boolean $bypassAuth Do not perform authentication.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function login($user, $pass, $logintype = null, $euser = '', $bypassAuth = false)
    {
        $this->_data['user']      = $user;
        $this->_data['pass']      = $pass;
        $this->_data['logintype'] = $logintype;
        $this->_data['euser']     = $euser;
        $this->_bypassAuth        = $bypassAuth;

        if (NET_SIEVE_STATE_AUTHORISATION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        if (!$bypassAuth ) {
            $res = $this->_cmdAuthenticate($user, $pass, $logintype, $euser);
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        $this->_state = NET_SIEVE_STATE_TRANSACTION;

        return true;
    }

    /**
     * Returns an indexed array of scripts currently on the server.
     *
     * @param string $active Will be set to the name of the active script
     *
     * @return array  Indexed array of scriptnames, PEAR_Error on failure
     */
    function listScripts(&$active = null)
    {
        if (is_array($scripts = $this->_cmdListScripts())) {
            if (isset($scripts[1])) {
                $active = $scripts[1];
            }

            return $scripts[0];
        }

        return $scripts;
    }

    /**
     * Returns the active script.
     *
     * @return string  The active scriptname.
     */
    function getActive()
    {
        if (is_array($scripts = $this->_cmdListScripts())) {
            return $scripts[1];
        }
    }

    /**
     * Sets the active script.
     *
     * @param string $scriptname The name of the script to be set as active.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function setActive($scriptname)
    {
        return $this->_cmdSetActive($scriptname);
    }

    /**
     * Retrieves a script.
     *
     * @param string $scriptname The name of the script to be retrieved.
     *
     * @return string  The script on success, PEAR_Error on failure.
     */
    function getScript($scriptname)
    {
        return $this->_cmdGetScript($scriptname);
    }

    /**
     * Adds a script to the server.
     *
     * @param string  $scriptname Name of the script.
     * @param string  $script     The script content.
     * @param boolean $makeactive Whether to make this the active script.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function installScript($scriptname, $script, $makeactive = false)
    {
        $res = $this->_cmdPutScript($scriptname, $script);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($makeactive) {
            return $this->_cmdSetActive($scriptname);
        }

        return true;
    }

    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function removeScript($scriptname)
    {
        return $this->_cmdDeleteScript($scriptname);
    }

    /**
     * Checks if the server has space to store the script by the server.
     *
     * @param string  $scriptname The name of the script to mark as active.
     * @param integer $size       The size of the script.
     *
     * @return boolean|PEAR_Error  True if there is space, PEAR_Error otherwise.
     *
     * @todo Rename to hasSpace()
     */
    function haveSpace($scriptname, $size)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in TRANSACTION state', 1);
        }

        $res = $this->_doCmd(sprintf('HAVESPACE %s %d', $this->_escape($scriptname), $size));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Returns the list of extensions the server supports.
     *
     * @return array  List of extensions or PEAR_Error on failure.
     */
    function getExtensions()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        return $this->_capability['extensions'];
    }

    /**
     * Returns whether the server supports an extension.
     *
     * @param string $extension The extension to check.
     *
     * @return boolean  Whether the extension is supported or PEAR_Error on
     *                  failure.
     */
    function hasExtension($extension)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        $extension = trim($this->_toUpper($extension));
        if (is_array($this->_capability['extensions'])) {
            foreach ($this->_capability['extensions'] as $ext) {
                if ($ext == $extension) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns the list of authentication methods the server supports.
     *
     * @return array  List of authentication methods or PEAR_Error on failure.
     */
    function getAuthMechs()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        return $this->_capability['sasl'];
    }

    /**
     * Returns whether the server supports an authentication method.
     *
     * @param string $method The method to check.
     *
     * @return boolean  Whether the method is supported or PEAR_Error on
     *                  failure.
     */
    function hasAuthMech($method)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 7);
        }

        $method = trim($this->_toUpper($method));

        if (is_array($this->_capability['sasl'])) {
            foreach ($this->_capability['sasl'] as $sasl) {
                if ($sasl == $method) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Handles the authentication using any known method.
     *
     * @param string $uid        The userid to authenticate as.
     * @param string $pwd        The password to authenticate with.
     * @param string $userMethod The method to use. If empty, the class chooses
     *                           the best (strongest) available method.
     * @param string $euser      The effective uid to authenticate as.
     *
     * @return void
     */
    function _cmdAuthenticate($uid, $pwd, $userMethod = null, $euser = '')
    {
        $method = $this->_getBestAuthMethod($userMethod);
        if (is_a($method, 'PEAR_Error')) {
            return $method;
        }

        switch ($method) {
        case 'DIGEST-MD5':
            return $this->_authDigestMD5($uid, $pwd, $euser);
        case 'CRAM-MD5':
            $result = $this->_authCRAMMD5($uid, $pwd, $euser);
            break;
        case 'LOGIN':
            $result = $this->_authLOGIN($uid, $pwd, $euser);
            break;
        case 'PLAIN':
            $result = $this->_authPLAIN($uid, $pwd, $euser);
            break;
        case 'EXTERNAL':
            $result = $this->_authEXTERNAL($uid, $pwd, $euser);
            break;
        case 'GSSAPI':
            $result = $this->_authGSSAPI($pwd);
            break;
        case 'XOAUTH2':
            $result = $this->_authXOAUTH2($uid, $pwd, $euser);
            break;
        default :
            $result = $this->_pear->raiseError(
                $method . ' is not a supported authentication method'
            );
            break;
        }

        $res = $this->_doCmd();
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if ($this->_pear->isError($res = $this->_cmdCapability())) {
            return $this->_pear->raiseError(
                'Failed to connect, server said: ' . $res->getMessage(), 2
            );
        }

        return $result;
    }

    /**
     * Authenticates the user using the PLAIN method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authPLAIN($user, $pass, $euser)
    {
        return $this->_sendCmd(
            sprintf(
                'AUTHENTICATE "PLAIN" "%s"',
                base64_encode($euser . chr(0) . $user . chr(0) . $pass)
            )
        );
    }

    /**
     * Authenticates the user using the GSSAPI method.
     *
     * @note the PHP krb5 extension is required and the service principal and cname
     *       must have been set.
     * @see  setServicePrincipal()
     *
     * @return void
     */
    function _authGSSAPI()
    {
        if (!extension_loaded('krb5')) {
            return $this->_pear->raiseError('The krb5 extension is required for GSSAPI authentication', 2);
        }

        if (!$this->_gssapiPrincipal) {
            return $this->_pear->raiseError('No Kerberos service principal set', 2);
        }

        if (!$this->_gssapiCN) {
            return $this->_pear->raiseError('No Kerberos service CName set', 2);
        }

        putenv('KRB5CCNAME=' . $this->_gssapiCN);

        try {
            $ccache = new KRB5CCache();
            $ccache->open($this->_gssapiCN);

            $gssapicontext = new GSSAPIContext();
            $gssapicontext->acquireCredentials($ccache);

            $token   = '';
            $success = $gssapicontext->initSecContext($this->_gssapiPrincipal, null, null, null, $token);
            $token   = base64_encode($token);
        }
        catch (Exception $e) {
            return $this->_pear->raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        $this->_sendCmd("AUTHENTICATE \"GSSAPI\" {" . strlen($token) . "+}");

        $response = $this->_doCmd($token, true);

        try {
            $challenge = base64_decode(substr($response, 1, -1));
            $gssapicontext->unwrap($challenge, $challenge);
            $gssapicontext->wrap($challenge, $challenge, true);
        }
        catch (Exception $e) {
            return $this->_pear->raiseError('GSSAPI authentication failed: ' . $e->getMessage());
        }

        $response = base64_encode($challenge);

        $this->_sendCmd("{" . strlen($response) . "+}");

        return $this->_sendCmd($response);
    }

    /**
     * Authenticates the user using the LOGIN method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as. Not used.
     *
     * @return void
     */
    function _authLOGIN($user, $pass, $euser)
    {
        $result = $this->_sendCmd('AUTHENTICATE "LOGIN"');
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->_doCmd('"' . base64_encode($user) . '"', true);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $this->_doCmd('"' . base64_encode($pass) . '"', true);
    }

    /**
     * Authenticates the user using the CRAM-MD5 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as. Not used.
     *
     * @return void
     */
    function _authCRAMMD5($user, $pass, $euser)
    {
        $challenge = $this->_doCmd('AUTHENTICATE "CRAM-MD5"', true);
        if (is_a($challenge, 'PEAR_Error')) {
            return $challenge;
        }

        $auth_sasl = new Auth_SASL;
        $cram      = $auth_sasl->factory('crammd5');
        $challenge = base64_decode(trim($challenge));
        $response  = $cram->getResponse($user, $pass, $challenge);

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

        return $this->_sendStringResponse(base64_encode($response));
    }

    /**
     * Authenticates the user using the DIGEST-MD5 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authDigestMD5($user, $pass, $euser)
    {
        $challenge = $this->_doCmd('AUTHENTICATE "DIGEST-MD5"', true);
        if (is_a($challenge, 'PEAR_Error')) {
            return $challenge;
        }

        $auth_sasl = new Auth_SASL;
        $digest    = $auth_sasl->factory('digestmd5');
        $challenge = base64_decode(trim($challenge));

        // @todo Really 'localhost'?
        $response = $digest->getResponse($user, $pass, $challenge, 'localhost', 'sieve', $euser);
        if (is_a($response, 'PEAR_Error')) {
            return $response;
        }

        $result = $this->_sendStringResponse(base64_encode($response));
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        $result = $this->_doCmd('', true);
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        if ($this->_toUpper(substr($result, 0, 2)) == 'OK') {
            return;
        }

        /* We don't use the protocol's third step because SIEVE doesn't allow
         * subsequent authentication, so we just silently ignore it. */
        $result = $this->_sendStringResponse('');
        if (is_a($result, 'PEAR_Error')) {
            return $result;
        }

        return $this->_doCmd();
    }

    /**
     * Authenticates the user using the EXTERNAL method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $pass  The password to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     *
     * @since 1.1.7
     */
    function _authEXTERNAL($user, $pass, $euser)
    {
        $cmd = sprintf(
            'AUTHENTICATE "EXTERNAL" "%s"',
            base64_encode(strlen($euser) ? $euser : $user)
        );

        return $this->_sendCmd($cmd);
    }

    /**
     * Authenticates the user using the XOAUTH2 method.
     *
     * @param string $user  The userid to authenticate as.
     * @param string $token The token to authenticate with.
     * @param string $euser The effective uid to authenticate as.
     *
     * @return void
     */
    function _authXOAUTH2($user, $token, $euser)
    {
        // default to $user if $euser is not set
        if (! $euser) {
            $euser = $user;
        }

        $auth = base64_encode("user=$euser\001auth=$token\001\001");
        return $this->_sendCmd("AUTHENTICATE \"XOAUTH2\" \"$auth\"");
    }

    /**
     * Removes a script from the server.
     *
     * @param string $scriptname Name of the script to delete.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdDeleteScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('DELETESCRIPT %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Retrieves the contents of the named script.
     *
     * @param string $scriptname Name of the script to retrieve.
     *
     * @return string  The script if successful, PEAR_Error otherwise.
     */
    function _cmdGetScript($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('GETSCRIPT %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return preg_replace('/^{[0-9]+}\r\n/', '', $res);
    }

    /**
     * Sets the active script, i.e. the one that gets run on new mail by the
     * server.
     *
     * @param string $scriptname The name of the script to mark as active.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdSetActive($scriptname)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd(sprintf('SETACTIVE %s', $this->_escape($scriptname)));
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Returns the list of scripts on the server.
     *
     * @return array  An array with the list of scripts in the first element
     *                and the active script in the second element on success,
     *                PEAR_Error otherwise.
     */
    function _cmdListScripts()
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $res = $this->_doCmd('LISTSCRIPTS');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $scripts = array();
        $activescript = null;
        $res = explode("\r\n", $res);
        foreach ($res as $value) {
            if (preg_match('/^"(.*)"( ACTIVE)?$/i', $value, $matches)) {
                $script_name = stripslashes($matches[1]);
                $scripts[] = $script_name;
                if (!empty($matches[2])) {
                    $activescript = $script_name;
                }
            }
        }

        return array($scripts, $activescript);
    }

    /**
     * Adds a script to the server.
     *
     * @param string $scriptname Name of the new script.
     * @param string $scriptdata The new script.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdPutScript($scriptname, $scriptdata)
    {
        if (NET_SIEVE_STATE_TRANSACTION != $this->_state) {
            return $this->_pear->raiseError('Not currently in AUTHORISATION state', 1);
        }

        $stringLength = $this->_getLineLength($scriptdata);
        $command      = sprintf(
            "PUTSCRIPT %s {%d+}\r\n%s",
            $this->_escape($scriptname),
            $stringLength,
            $scriptdata
        );

        $res = $this->_doCmd($command);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        return true;
    }

    /**
     * Logs out of the server and terminates the connection.
     *
     * @param boolean $sendLogoutCMD Whether to send LOGOUT command before
     *                               disconnecting.
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdLogout($sendLogoutCMD = true)
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 1);
        }

        if ($sendLogoutCMD) {
            $res = $this->_doCmd('LOGOUT');
            if (is_a($res, 'PEAR_Error')) {
                return $res;
            }
        }

        $this->_sock->disconnect();
        $this->_state = NET_SIEVE_STATE_DISCONNECTED;

        return true;
    }

    /**
     * Sends the CAPABILITY command
     *
     * @return boolean  True on success, PEAR_Error otherwise.
     */
    function _cmdCapability()
    {
        if (NET_SIEVE_STATE_DISCONNECTED == $this->_state) {
            return $this->_pear->raiseError('Not currently connected', 1);
        }
        $res = $this->_doCmd('CAPABILITY');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }
        $this->_parseCapability($res);
        return true;
    }

    /**
     * Parses the response from the CAPABILITY command and stores the result
     * in $_capability.
     *
     * @param string $data The response from the capability command.
     *
     * @return void
     */
    function _parseCapability($data)
    {
        // Clear the cached capabilities.
        $this->_capability = array('sasl' => array(),
                                   'extensions' => array());

        $data = preg_split('/\r?\n/', $this->_toUpper($data), -1, PREG_SPLIT_NO_EMPTY);

        for ($i = 0; $i < count($data); $i++) {
            if (!preg_match('/^"([A-Z]+)"( "(.*)")?$/', $data[$i], $matches)) {
                continue;
            }
            switch ($matches[1]) {
            case 'IMPLEMENTATION':
                $this->_capability['implementation'] = $matches[3];
                break;

            case 'SASL':
                if (!empty($matches[3])) {
                    $this->_capability['sasl'] = preg_split('/\s+/', $matches[3]);
                }
                break;

            case 'SIEVE':
                if (!empty($matches[3])) {
                    $this->_capability['extensions'] = preg_split('/\s+/', $matches[3]);
                }
                break;

            case 'STARTTLS':
                $this->_capability['starttls'] = true;
                break;
            }
        }
    }

    /**
     * Sends a command to the server
     *
     * @param string $cmd The command to send.
     *
     * @return void
     */
    function _sendCmd($cmd)
    {
        $status = $this->_sock->getStatus();
        if (is_a($status, 'PEAR_Error') || $status['eof']) {
            return $this->_pear->raiseError('Failed to write to socket: connection lost');
        }
        $error = $this->_sock->write($cmd . "\r\n");
        if (is_a($error, 'PEAR_Error')) {
            return $this->_pear->raiseError(
                'Failed to write to socket: ' . $error->getMessage()
            );
        }
        $this->_debug("C: $cmd");
    }

    /**
     * Sends a string response to the server.
     *
     * @param string $str The string to send.
     *
     * @return void
     */
    function _sendStringResponse($str)
    {
        return $this->_sendCmd('{' . $this->_getLineLength($str) . "+}\r\n" . $str);
    }

    /**
     * Receives a single line from the server.
     *
     * @return string  The server response line.
     */
    function _recvLn()
    {
        $lastline = $this->_sock->gets(8192);
        if (is_a($lastline, 'PEAR_Error')) {
            return $this->_pear->raiseError(
                'Failed to read from socket: ' . $lastline->getMessage()
            );
        }

        $lastline = rtrim($lastline);
        $this->_debug("S: $lastline");

        if ($lastline === '') {
            return $this->_pear->raiseError('Failed to read from socket');
        }

        return $lastline;
    }

    /**
     * Receives a number of bytes from the server.
     *
     * @param integer $length Number of bytes to read.
     *
     * @return string The server response.
     */
    function _recvBytes($length)
    {
        $response = '';
        $response_length = 0;
        while ($response_length < $length) {
            $response .= $this->_sock->read($length - $response_length);
            $response_length = $this->_getLineLength($response);
        }
        $this->_debug('S: ' . rtrim($response));
        return $response;
    }

    /**
     * Send a command and retrieves a response from the server.
     *
     * @param string  $cmd  The command to send.
     * @param boolean $auth Whether this is an authentication command.
     *
     * @return string|PEAR_Error Reponse string if an OK response, PEAR_Error
     *                           if a NO response.
     */
    function _doCmd($cmd = '', $auth = false)
    {
        $referralCount = 0;
        while ($referralCount < $this->_maxReferralCount) {
            if (strlen($cmd)) {
                $error = $this->_sendCmd($cmd);
                if (is_a($error, 'PEAR_Error')) {
                    return $error;
                }
            }

            $response = '';
            while (true) {
                $line = $this->_recvLn();
                if (is_a($line, 'PEAR_Error')) {
                    return $line;
                }

                if (preg_match('/^(OK|NO)/i', $line, $tag)) {
                    // Check for string literal message.
                    if (preg_match('/{([0-9]+)}$/', $line, $matches)) {
                        $line = substr($line, 0, -(strlen($matches[1]) + 2))
                            . str_replace(
                                "\r\n", ' ', $this->_recvBytes($matches[1] + 2)
                            );
                    }

                    if ('OK' == $this->_toUpper($tag[1])) {
                        $response .= $line;
                        return rtrim($response);
                    }

                    return $this->_pear->raiseError(trim($response . substr($line, 2)), 3);
                }

                if (preg_match('/^BYE/i', $line)) {
                    $error = $this->disconnect(false);
                    if (is_a($error, 'PEAR_Error')) {
                        return $this->_pear->raiseError(
                            'Cannot handle BYE, the error was: '
                            . $error->getMessage(),
                            4
                        );
                    }
                    // Check for referral, then follow it.  Otherwise, carp an
                    // error.
                    if (preg_match('/^bye \(referral "(sieve:\/\/)?([^"]+)/i', $line, $matches)) {
                        // Replace the old host with the referral host
                        // preserving any protocol prefix.
                        $this->_data['host'] = preg_replace(
                            '/\w+(?!(\w|\:\/\/)).*/', $matches[2],
                            $this->_data['host']
                        );
                        $error = $this->_handleConnectAndLogin();
                        if (is_a($error, 'PEAR_Error')) {
                            return $this->_pear->raiseError(
                                'Cannot follow referral to '
                                . $this->_data['host'] . ', the error was: '
                                . $error->getMessage(),
                                5
                            );
                        }
                        break;
                    }
                    return $this->_pear->raiseError(trim($response . $line), 6);
                }

                if (preg_match('/^{([0-9]+)}/', $line, $matches)) {
                    // Matches literal string responses.
                    $line = $this->_recvBytes($matches[1] + 2);
                    if (!$auth) {
                        // Receive the pending OK only if we aren't
                        // authenticating since string responses during
                        // authentication don't need an OK.
                        $this->_recvLn();
                    }
                    return $line;
                }

                if ($auth) {
                    // String responses during authentication don't need an
                    // OK.
                    $response .= $line;
                    return rtrim($response);
                }

                $response .= $line . "\r\n";
                $referralCount++;
            }
        }

        return $this->_pear->raiseError('Max referral count (' . $referralCount . ') reached. Cyrus murder loop error?', 7);
    }

    /**
     * Returns the name of the best authentication method that the server
     * has advertised.
     *
     * @param string $userMethod Only consider this method as available.
     *
     * @return string  The name of the best supported authentication method or
     *                 a PEAR_Error object on failure.
     */
    function _getBestAuthMethod($userMethod = null)
    {
        if (!isset($this->_capability['sasl'])) {
            return $this->_pear->raiseError('This server doesn\'t support any authentication methods. SASL problem?');
        }
        if (!$this->_capability['sasl']) {
            return $this->_pear->raiseError('This server doesn\'t support any authentication methods.');
        }

        if ($userMethod) {
            if (in_array($userMethod, $this->_capability['sasl'])) {
                return $userMethod;
            }

            $msg = 'No supported authentication method found. The server supports these methods: %s, but we want to use: %s';
            return $this->_pear->raiseError(
                sprintf($msg, implode(', ', $this->_capability['sasl']), $userMethod)
            );
        }

        foreach ($this->supportedAuthMethods as $method) {
            if (in_array($method, $this->_capability['sasl'])) {
                return $method;
            }
        }

        $msg = 'No supported authentication method found. The server supports these methods: %s, but we only support: %s';
        return $this->_pear->raiseError(
            sprintf($msg, implode(', ', $this->_capability['sasl']), implode(', ', $this->supportedAuthMethods))
        );
    }

    /**
     * Starts a TLS connection.
     *
     * @return boolean  True on success, PEAR_Error on failure.
     */
    function _startTLS()
    {
        $res = $this->_doCmd('STARTTLS');
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        if (isset($this->_options['ssl']['crypto_method'])) {
            $crypto_method = $this->_options['ssl']['crypto_method'];
        } else {
            // There is no flag to enable all TLS methods. Net_SMTP
            // handles enabling TLS similarly.
            $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
                | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
                | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
        }

        if (!stream_socket_enable_crypto($this->_sock->fp, true, $crypto_method)) {
            return $this->_pear->raiseError('Failed to establish TLS connection', 2);
        }

        $this->_debug('STARTTLS negotiation successful');

        // The server should be sending a CAPABILITY response after
        // negotiating TLS. Read it, and ignore if it doesn't.
        // Unfortunately old Cyrus versions are broken and don't send a
        // CAPABILITY response, thus we would wait here forever. Parse the
        // Cyrus version and work around this broken behavior.
        if (!preg_match('/^CYRUS TIMSIEVED V([0-9.]+)/', $this->_capability['implementation'], $matches)
            || version_compare($matches[1], '2.3.10', '>=')
        ) {
            $res = $this->_doCmd();
        }

        // Reset capabilities (use unattended capabilities)
        $this->_parseCapability(is_string($res) ? $res : '');

        // Query the server capabilities again now that we are under encryption.
        if (empty($this->_capability['implementation'])) {
            $res = $this->_cmdCapability();
            if (is_a($res, 'PEAR_Error')) {
                return $this->_pear->raiseError(
                    'Failed to connect, server said: ' . $res->getMessage(), 2
                );
            }
        }

        return true;
    }

    /**
     * Returns the length of a string.
     *
     * @param string $string A string.
     *
     * @return integer  The length of the string.
     */
    function _getLineLength($string)
    {
        if (extension_loaded('mbstring')) {
            return mb_strlen($string, '8bit');
        } else {
            return strlen($string);
        }
    }

    /**
     * Locale independant strtoupper() implementation.
     *
     * @param string $string The string to convert to lowercase.
     *
     * @return string  The lowercased string, based on ASCII encoding.
     */
    function _toUpper($string)
    {
        $language = setlocale(LC_CTYPE, 0);
        setlocale(LC_CTYPE, 'C');
        $string = strtoupper($string);
        setlocale(LC_CTYPE, $language);
        return $string;
    }

    /**
     * Converts strings into RFC's quoted-string or literal-c2s form.
     *
     * @param string $string The string to convert.
     *
     * @return string Result string.
     */
    function _escape($string)
    {
        // Some implementations don't allow UTF-8 characters in quoted-string,
        // use literal-c2s.
        if (preg_match('/[^\x01-\x09\x0B-\x0C\x0E-\x7F]/', $string)) {
            return sprintf("{%d+}\r\n%s", $this->_getLineLength($string), $string);
        }

        return '"' . addcslashes($string, '\\"') . '"';
    }

    /**
     * Write debug text to the current debug output handler.
     *
     * @param string $message Debug message text.
     *
     * @return void
     */
    function _debug($message)
    {
        if ($this->_debug) {
            if ($this->_debug_handler) {
                call_user_func_array($this->_debug_handler, array(&$this, $message));
            } else {
                echo "$message\n";
            }
        }
    }
}
b IDATxytVսϓ22 A@IR :hCiZ[v*E:WũZA ^dQeQ @ !jZ'>gsV仿$|?g)&x-EIENT ;@xT.i%-X}SvS5.r/UHz^_$-W"w)Ɗ/@Z &IoX P$K}JzX:;` &, ŋui,e6mX ԵrKb1ԗ)DADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADADA݀!I*]R;I2$eZ#ORZSrr6mteffu*((Pu'v{DIߔ4^pIm'77WEEE;vƎ4-$]'RI{\I&G :IHJ DWBB=\WR޽m o$K(V9ABB.}jѢv`^?IOȅ} ڶmG}T#FJ`56$-ھ}FI&v;0(h;Б38CӧOWf!;A i:F_m9s&|q%=#wZprrrla A &P\\СC[A#! {olF} `E2}MK/vV)i{4BffV\|ۭX`b@kɶ@%i$K z5zhmX[IXZ` 'b%$r5M4º/l ԃߖxhʔ)[@=} K6IM}^5k㏷݆z ΗÿO:gdGBmyT/@+Vɶ纽z񕏵l.y޴it뭷zV0[Y^>Wsqs}\/@$(T7f.InݺiR$푔n.~?H))\ZRW'Mo~v Ov6oԃxz! S,&xm/yɞԟ?'uaSѽb,8GלKboi&3t7Y,)JJ c[nzӳdE&KsZLӄ I?@&%ӟ۶mSMMњ0iؐSZ,|J+N ~,0A0!5%Q-YQQa3}$_vVrf9f?S8`zDADADADADADADADADAdqP,تmMmg1V?rSI꒟]u|l RCyEf٢9 jURbztѰ!m5~tGj2DhG*{H9)꒟ר3:(+3\?/;TUݭʴ~S6lڧUJ*i$d(#=Yݺd{,p|3B))q:vN0Y.jkק6;SɶVzHJJЀ-utѹսk>QUU\޲~]fFnK?&ߡ5b=z9)^|u_k-[y%ZNU6 7Mi:]ۦtk[n X(e6Bb."8cۭ|~teuuw|ήI-5"~Uk;ZicEmN/:]M> cQ^uiƞ??Ңpc#TUU3UakNwA`:Y_V-8.KKfRitv޲* 9S6ֿj,ՃNOMߤ]z^fOh|<>@Å5 _/Iu?{SY4hK/2]4%it5q]GGe2%iR| W&f*^]??vq[LgE_3f}Fxu~}qd-ږFxu~I N>\;͗O֊:̗WJ@BhW=y|GgwܷH_NY?)Tdi'?խwhlmQi !SUUsw4kӺe4rfxu-[nHtMFj}H_u~w>)oV}(T'ebʒv3_[+vn@Ȭ\S}ot}w=kHFnxg S 0eޢm~l}uqZfFoZuuEg `zt~? b;t%>WTkķh[2eG8LIWx,^\thrl^Ϊ{=dž<}qV@ ⠨Wy^LF_>0UkDuʫuCs$)Iv:IK;6ֲ4{^6եm+l3>݆uM 9u?>Zc }g~qhKwڭeFMM~pМuqǿz6Tb@8@Y|jx](^]gf}M"tG -w.@vOqh~/HII`S[l.6nØXL9vUcOoB\xoǤ'T&IǍQw_wpv[kmO{w~>#=P1Pɞa-we:iǏlHo׈꒟f9SzH?+shk%Fs:qVhqY`jvO'ρ?PyX3lх]˾uV{ݞ]1,MzYNW~̈́ joYn}ȚF߾׮mS]F z+EDxm/d{F{-W-4wY듏:??_gPf ^3ecg ҵs8R2מz@TANGj)}CNi/R~}c:5{!ZHӋӾ6}T]G]7W6^n 9*,YqOZj:P?Q DFL|?-^.Ɵ7}fFh׶xe2Pscz1&5\cn[=Vn[ĶE鎀uˌd3GII k;lNmشOuuRVfBE]ۣeӶu :X-[(er4~LHi6:Ѻ@ԅrST0trk%$Č0ez" *z"T/X9|8.C5Feg}CQ%͞ˣJvL/?j^h&9xF`њZ(&yF&Iݻfg#W;3^{Wo^4'vV[[K';+mӍִ]AC@W?1^{එyh +^]fm~iԵ]AB@WTk̏t uR?l.OIHiYyԶ]Aˀ7c:q}ힽaf6Z~қm(+sK4{^6}T*UUu]n.:kx{:2 _m=sAߤU@?Z-Vކеz왍Nэ{|5 pڶn b p-@sPg]0G7fy-M{GCF'%{4`=$-Ge\ eU:m+Zt'WjO!OAF@ik&t݆ϥ_ e}=]"Wz_.͜E3leWFih|t-wZۍ-uw=6YN{6|} |*={Ѽn.S.z1zjۻTH]흾 DuDvmvK.`V]yY~sI@t?/ϓ. m&["+P?MzovVЫG3-GRR[(!!\_,^%?v@ҵő m`Y)tem8GMx.))A]Y i`ViW`?^~!S#^+ѽGZj?Vģ0.))A꨷lzL*]OXrY`DBBLOj{-MH'ii-ϰ ok7^ )쭡b]UXSְmռY|5*cֽk0B7镹%ڽP#8nȎq}mJr23_>lE5$iwui+ H~F`IjƵ@q \ @#qG0".0" l`„.0! ,AQHN6qzkKJ#o;`Xv2>,tێJJ7Z/*A .@fفjMzkg @TvZH3Zxu6Ra'%O?/dQ5xYkU]Rֽkق@DaS^RSּ5|BeHNN͘p HvcYcC5:y #`οb;z2.!kr}gUWkyZn=f Pvsn3p~;4p˚=ē~NmI] ¾ 0lH[_L hsh_ғߤc_њec)g7VIZ5yrgk̞W#IjӪv>՞y睝M8[|]\շ8M6%|@PZڨI-m>=k='aiRo-x?>Q.}`Ȏ:Wsmu u > .@,&;+!!˱tﭧDQwRW\vF\~Q7>spYw$%A~;~}6¾ g&if_=j,v+UL1(tWake:@Ș>j$Gq2t7S?vL|]u/ .(0E6Mk6hiۺzښOrifޱxm/Gx> Lal%%~{lBsR4*}{0Z/tNIɚpV^#Lf:u@k#RSu =S^ZyuR/.@n&΃z~B=0eg뺆#,Þ[B/?H uUf7y Wy}Bwegל`Wh(||`l`.;Ws?V@"c:iɍL֯PGv6zctM̠':wuW;d=;EveD}9J@B(0iհ bvP1{\P&G7D޴Iy_$-Qjm~Yrr&]CDv%bh|Yzni_ˆR;kg}nJOIIwyuL}{ЌNj}:+3Y?:WJ/N+Rzd=hb;dj͒suݔ@NKMԄ jqzC5@y°hL m;*5ezᕏ=ep XL n?מ:r`۵tŤZ|1v`V뽧_csج'ߤ%oTuumk%%%h)uy]Nk[n 'b2 l.=͜E%gf$[c;s:V-͞WߤWh-j7]4=F-X]>ZLSi[Y*We;Zan(ӇW|e(HNNP5[= r4tP &0<pc#`vTNV GFqvTi*Tyam$ߏWyE*VJKMTfFw>'$-ؽ.Ho.8c"@DADADADADADADADADA~j*֘,N;Pi3599h=goضLgiJ5փy~}&Zd9p֚ e:|hL``b/d9p? fgg+%%hMgXosج, ΩOl0Zh=xdjLmhݻoO[g_l,8a]٭+ӧ0$I]c]:粹:Teꢢ"5a^Kgh,&= =՟^߶“ߢE ܹS J}I%:8 IDAT~,9/ʃPW'Mo}zNƍ쨓zPbNZ~^z=4mswg;5 Y~SVMRXUյڱRf?s:w ;6H:ºi5-maM&O3;1IKeamZh͛7+##v+c ~u~ca]GnF'ټL~PPPbn voC4R,ӟgg %hq}@#M4IÇ Oy^xMZx ) yOw@HkN˖-Sǎmb]X@n+i͖!++K3gd\$mt$^YfJ\8PRF)77Wא!Cl$i:@@_oG I{$# 8磌ŋ91A (Im7֭>}ߴJq7ޗt^ -[ԩSj*}%]&' -ɓ'ꫯVzzvB#;a 7@GxI{j޼ƌ.LÇWBB7`O"I$/@R @eee@۷>}0,ɒ2$53Xs|cS~rpTYYY} kHc %&k.], @ADADADADADADADADA@lT<%''*Lo^={رc5h %$+CnܸQ3fҥK}vUVVs9G R,_{xˇ3o߾;TTTd}馛]uuuG~iԩ@4bnvmvfϞ /Peeeq}}za I~,誫{UWW뮻}_~YƍSMMMYχ֝waw\ďcxꩧtEƍկ_?۷5@u?1kNׯWzz/wy>}zj3 k(ٺuq_Zvf̘:~ ABQ&r|!%KҥKgԞ={<_X-z !CyFUUz~ ABQIIIjݺW$UXXDٳZ~ ABQƍecW$<(~<RSSvZujjjԧOZQu@4 8m&&&jԩg$ď1h ͟?_{768@g =@`)))5o6m3)ѣƌJ;wҿUTT /KZR{~a=@0o<*狔iFɶ[ˎ;T]]OX@?K.ۈxN pppppppppppppppppPfl߾] ,{ァk۶mڿo5BTӦMӴiӴ|r DB2e|An!Dy'tkΝ[A $***t5' "!駟oaDnΝ:t֭[gDШQ06qD;@ x M6v(PiizmZ4ew"@̴ixf [~-Fٱc&IZ2|n!?$@{[HTɏ#@hȎI# _m(F /6Z3z'\r,r!;w2Z3j=~GY7"I$iI.p_"?pN`y DD?: _  Gÿab7J !Bx@0 Bo cG@`1C[@0G @`0C_u V1 aCX>W ` | `!<S `"<. `#c`?cAC4 ?c p#~@0?:08&_MQ1J h#?/`7;I  q 7a wQ A 1 Hp !#<8/#@1Ul7=S=K.4Z?E_$i@!1!E4?`P_  @Bă10#: "aU,xbFY1 [n|n #'vEH:`xb #vD4Y hi.i&EΖv#O H4IŶ}:Ikh @tZRF#(tXҙzZ ?I3l7q@õ|ۍ1,GpuY Ꮿ@hJv#xxk$ v#9 5 }_$c S#=+"K{F*m7`#%H:NRSp6I?sIՖ{Ap$I$I:QRv2$Z @UJ*$]<FO4IENDB`