code:
<?php

/**
 * OpenID server protocol and logic.
 *
 * Overview
 *
 * An OpenID server must perform three tasks:
 *
 *  1. Examine the incoming request to determine its nature and validity.
 *  2. Make a decision about how to respond to this request.
 *  3. Format the response according to the protocol.
 *
 * The first and last of these tasks may performed by the {@link
 * Auth_OpenID_Server::decodeRequest()} and {@link
 * Auth_OpenID_Server::encodeResponse} methods.  Who gets to do the
 * intermediate task -- deciding how to respond to the request -- will
 * depend on what type of request it is.
 *
 * If it's a request to authenticate a user (a 'checkid_setup' or
 * 'checkid_immediate' request), you need to decide if you will assert
 * that this user may claim the identity in question.  Exactly how you
 * do that is a matter of application policy, but it generally
 * involves making sure the user has an account with your system and
 * is logged in, checking to see if that identity is hers to claim,
 * and verifying with the user that she does consent to releasing that
 * information to the party making the request.
 *
 * Examine the properties of the {@link Auth_OpenID_CheckIDRequest}
 * object, and if and when you've come to a decision, form a response
 * by calling {@link Auth_OpenID_CheckIDRequest::answer()}.
 *
 * Other types of requests relate to establishing associations between
 * client and server and verifing the authenticity of previous
 * communications.  {@link Auth_OpenID_Server} contains all the logic
 * and data necessary to respond to such requests; just pass it to
 * {@link Auth_OpenID_Server::handleRequest()}.
 *
 * OpenID Extensions
 *
 * Do you want to provide other information for your users in addition
 * to authentication?  Version 1.2 of the OpenID protocol allows
 * consumers to add extensions to their requests.  For example, with
 * sites using the Simple Registration
 * Extension
 * (http://openid.net/specs/openid-simple-registration-extension-1_0.html),
 * a user can agree to have their nickname and e-mail address sent to
 * a site when they sign up.
 *
 * Since extensions do not change the way OpenID authentication works,
 * code to handle extension requests may be completely separate from
 * the {@link Auth_OpenID_Request} class here.  But you'll likely want
 * data sent back by your extension to be signed.  {@link
 * Auth_OpenID_ServerResponse} provides methods with which you can add
 * data to it which can be signed with the other data in the OpenID
 * signature.
 *
 * For example:
 *
 * <pre>  // when request is a checkid_* request
 *  $response = $request->answer(true);
 *  // this will a signed 'openid.sreg.timezone' parameter to the response
 *  response.addField('sreg', 'timezone', 'America/Los_Angeles')</pre>
 *
 * Stores
 *
 * The OpenID server needs to maintain state between requests in order
 * to function.  Its mechanism for doing this is called a store.  The
 * store interface is defined in Interface.php.  Additionally, several
 * concrete store implementations are provided, so that most sites
 * won't need to implement a custom store.  For a store backed by flat
 * files on disk, see {@link Auth_OpenID_FileStore}.  For stores based
 * on MySQL, SQLite, or PostgreSQL, see the {@link
 * Auth_OpenID_SQLStore} subclasses.
 *
 * Upgrading
 *
 * The keys by which a server looks up associations in its store have
 * changed in version 1.2 of this library.  If your store has entries
 * created from version 1.0 code, you should empty it.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * Required imports
 */
require_once "Auth/OpenID.php";
require_once "Auth/OpenID/Association.php";
require_once "Auth/OpenID/CryptUtil.php";
require_once "Auth/OpenID/BigMath.php";
require_once "Auth/OpenID/DiffieHellman.php";
require_once "Auth/OpenID/KVForm.php";
require_once "Auth/OpenID/TrustRoot.php";
require_once "Auth/OpenID/ServerRequest.php";
require_once "Auth/OpenID/Message.php";
require_once "Auth/OpenID/Nonce.php";

define('AUTH_OPENID_HTTP_OK', 200);
define('AUTH_OPENID_HTTP_REDIRECT', 302);
define('AUTH_OPENID_HTTP_ERROR', 400);

/**
 * @access private
 */
global $_Auth_OpenID_Request_Modes;
$_Auth_OpenID_Request_Modes = array('checkid_setup',
                                    'checkid_immediate');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_KVFORM', 'kfvorm');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_URL', 'URL/redirect');

/**
 * @access private
 */
define('Auth_OpenID_ENCODE_HTML_FORM', 'HTML form');

/**
 * @access private
 */
function Auth_OpenID_isError($obj, $cls = 'Auth_OpenID_ServerError')
{
    return is_a($obj, $cls);
}

/**
 * An error class which gets instantiated and returned whenever an
 * OpenID protocol error occurs.  Be prepared to use this in place of
 * an ordinary server response.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerError {
    /**
     * @access private
     */
    function Auth_OpenID_ServerError($message = null, $text = null,
                                     $reference = null, $contact = null)
    {
        $this->message = $message;
        $this->text = $text;
        $this->contact = $contact;
        $this->reference = $reference;
    }

    function getReturnTo()
    {
        if ($this->message &&
            $this->message->hasKey(Auth_OpenID_OPENID_NS, 'return_to')) {
            return $this->message->getArg(Auth_OpenID_OPENID_NS,
                                          'return_to');
        } else {
            return null;
        }
    }

    /**
     * Returns the return_to URL for the request which caused this
     * error.
     */
    function hasReturnTo()
    {
        return $this->getReturnTo() !== null;
    }

    /**
     * Encodes this error's response as a URL suitable for
     * redirection.  If the response has no return_to, another
     * Auth_OpenID_ServerError is returned.
     */
    function encodeToURL()
    {
        if (!$this->message) {
            return null;
        }

        $msg = $this->toMessage();
        return $msg->toURL($this->getReturnTo());
    }

    /**
     * Encodes the response to key-value form.  This is a
     * machine-readable format used to respond to messages which came
     * directly from the consumer and not through the user-agent.  See
     * the OpenID specification.
     */
    function encodeToKVForm()
    {
        return Auth_OpenID_KVForm::fromArray(
                                      array('mode' => 'error',
                                            'error' => $this->toString()));
    }

    function toFormMarkup($form_tag_attrs=null)
    {
        $msg = $this->toMessage();
        return $msg->toFormMarkup($this->getReturnTo(), $form_tag_attrs);
    }

    function toHTML($form_tag_attrs=null)
    {
        return Auth_OpenID::autoSubmitHTML(
                      $this->toFormMarkup($form_tag_attrs));
    }

    function toMessage()
    {
        // Generate a Message object for sending to the relying party,
        // after encoding.
        $namespace = $this->message->getOpenIDNamespace();
        $reply = new Auth_OpenID_Message($namespace);
        $reply->setArg(Auth_OpenID_OPENID_NS, 'mode', 'error');
        $reply->setArg(Auth_OpenID_OPENID_NS, 'error', $this->toString());

        if ($this->contact !== null) {
            $reply->setArg(Auth_OpenID_OPENID_NS, 'contact', $this->contact);
        }

        if ($this->reference !== null) {
            $reply->setArg(Auth_OpenID_OPENID_NS, 'reference',
                           $this->reference);
        }

        return $reply;
    }

    /**
     * Returns one of Auth_OpenID_ENCODE_URL,
     * Auth_OpenID_ENCODE_KVFORM, or null, depending on the type of
     * encoding expected for this error's payload.
     */
    function whichEncoding()
    {
        global $_Auth_OpenID_Request_Modes;

        if ($this->hasReturnTo()) {
            if ($this->message->isOpenID2() &&
                (strlen($this->encodeToURL()) >
                   Auth_OpenID_OPENID1_URL_LIMIT)) {
                return Auth_OpenID_ENCODE_HTML_FORM;
            } else {
                return Auth_OpenID_ENCODE_URL;
            }
        }

        if (!$this->message) {
            return null;
        }

        $mode = $this->message->getArg(Auth_OpenID_OPENID_NS,
                                       'mode');

        if ($mode) {
            if (!in_array($mode, $_Auth_OpenID_Request_Modes)) {
                return Auth_OpenID_ENCODE_KVFORM;
            }
        }
        return null;
    }

    /**
     * Returns this error message.
     */
    function toString()
    {
        if ($this->text) {
            return $this->text;
        } else {
            return get_class($this) . " error";
        }
    }
}

/**
 * Error returned by the server code when a return_to is absent from a
 * request.
 *
 * @package OpenID
 */
class Auth_OpenID_NoReturnToError extends Auth_OpenID_ServerError {
    function Auth_OpenID_NoReturnToError($message = null,
                                         $text = "No return_to URL available")
    {
        parent::Auth_OpenID_ServerError($message, $text);
    }

    function toString()
    {
        return "No return_to available";
    }
}

/**
 * An error indicating that the return_to URL is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedReturnURL extends Auth_OpenID_ServerError {
    function Auth_OpenID_MalformedReturnURL($message, $return_to)
    {
        $this->return_to = $return_to;
        parent::Auth_OpenID_ServerError($message, "malformed return_to URL");
    }
}

/**
 * This error is returned when the trust_root value is malformed.
 *
 * @package OpenID
 */
class Auth_OpenID_MalformedTrustRoot extends Auth_OpenID_ServerError {
    function Auth_OpenID_MalformedTrustRoot($message = null,
                                            $text = "Malformed trust root")
    {
        parent::Auth_OpenID_ServerError($message, $text);
    }

    function toString()
    {
        return "Malformed trust root";
    }
}

/**
 * The base class for all server request classes.
 *
 * @package OpenID
 */
class Auth_OpenID_Request {
    var $mode = null;
}

/**
 * A request to verify the validity of a previous response.
 *
 * @package OpenID
 */
class Auth_OpenID_CheckAuthRequest extends Auth_OpenID_Request {
    var $mode = "check_authentication";
    var $invalidate_handle = null;

    function Auth_OpenID_CheckAuthRequest($assoc_handle, $signed,
                                          $invalidate_handle = null)
    {
        $this->assoc_handle = $assoc_handle;
        $this->signed = $signed;
        if ($invalidate_handle !== null) {
            $this->invalidate_handle = $invalidate_handle;
        }
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->message = null;
    }

    static function fromMessage($message, $server=null)
    {
        $required_keys = array('assoc_handle', 'sig', 'signed');

        foreach ($required_keys as $k) {
            if (!$message->getArg(Auth_OpenID_OPENID_NS, $k)) {
                return new Auth_OpenID_ServerError($message,
                    sprintf("%s request missing required parameter %s from \
                            query", "check_authentication", $k));
            }
        }

        $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS, 'assoc_handle');
        $sig = $message->getArg(Auth_OpenID_OPENID_NS, 'sig');

        $signed_list = $message->getArg(Auth_OpenID_OPENID_NS, 'signed');
        $signed_list = explode(",", $signed_list);

        $signed = $message;
        if ($signed->hasKey(Auth_OpenID_OPENID_NS, 'mode')) {
            $signed->setArg(Auth_OpenID_OPENID_NS, 'mode', 'id_res');
        }

        $result = new Auth_OpenID_CheckAuthRequest($assoc_handle, $signed);
        $result->message = $message;
        $result->sig = $sig;
        $result->invalidate_handle = $message->getArg(Auth_OpenID_OPENID_NS,
                                                      'invalidate_handle');
        return $result;
    }

    function answer($signatory)
    {
        $is_valid = $signatory->verify($this->assoc_handle, $this->signed);

        // Now invalidate that assoc_handle so it this checkAuth
        // message cannot be replayed.
        $signatory->invalidate($this->assoc_handle, true);
        $response = new Auth_OpenID_ServerResponse($this);

        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'is_valid',
                                  ($is_valid ? "true" : "false"));

        if ($this->invalidate_handle) {
            $assoc = $signatory->getAssociation($this->invalidate_handle,
                                                false);
            if (!$assoc) {
                $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                          'invalidate_handle',
                                          $this->invalidate_handle);
            }
        }
        return $response;
    }
}

/**
 * A class implementing plaintext server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_PlainTextServerSession {
    /**
     * An object that knows how to handle association requests with no
     * session type.
     */
    var $session_type = 'no-encryption';
    var $needs_math = false;
    var $allowed_assoc_types = array('HMAC-SHA1', 'HMAC-SHA256');

    static function fromMessage($unused_request)
    {
        return new Auth_OpenID_PlainTextServerSession();
    }

    function answer($secret)
    {
        return array('mac_key' => base64_encode($secret));
    }
}

/**
 * A class implementing DH-SHA1 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA1ServerSession {
    /**
     * An object that knows how to handle association requests with
     * the Diffie-Hellman session type.
     */

    var $session_type = 'DH-SHA1';
    var $needs_math = true;
    var $allowed_assoc_types = array('HMAC-SHA1');
    var $hash_func = 'Auth_OpenID_SHA1';

    function Auth_OpenID_DiffieHellmanSHA1ServerSession($dh, $consumer_pubkey)
    {
        $this->dh = $dh;
        $this->consumer_pubkey = $consumer_pubkey;
    }

    static function getDH($message)
    {
        $dh_modulus = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_modulus');
        $dh_gen = $message->getArg(Auth_OpenID_OPENID_NS, 'dh_gen');

        if ((($dh_modulus === null) && ($dh_gen !== null)) ||
            (($dh_gen === null) && ($dh_modulus !== null))) {

            if ($dh_modulus === null) {
                $missing = 'modulus';
            } else {
                $missing = 'generator';
            }

            return new Auth_OpenID_ServerError($message,
                                'If non-default modulus or generator is '.
                                'supplied, both must be supplied.  Missing '.
                                $missing);
        }

        $lib = Auth_OpenID_getMathLib();

        if ($dh_modulus || $dh_gen) {
            $dh_modulus = $lib->base64ToLong($dh_modulus);
            $dh_gen = $lib->base64ToLong($dh_gen);
            if ($lib->cmp($dh_modulus, 0) == 0 ||
                $lib->cmp($dh_gen, 0) == 0) {
                return new Auth_OpenID_ServerError(
                  $message, "Failed to parse dh_mod or dh_gen");
            }
            $dh = new Auth_OpenID_DiffieHellman($dh_modulus, $dh_gen);
        } else {
            $dh = new Auth_OpenID_DiffieHellman();
        }

        $consumer_pubkey = $message->getArg(Auth_OpenID_OPENID_NS,
                                            'dh_consumer_public');
        if ($consumer_pubkey === null) {
            return new Auth_OpenID_ServerError($message,
                                  'Public key for DH-SHA1 session '.
                                  'not found in query');
        }

        $consumer_pubkey =
            $lib->base64ToLong($consumer_pubkey);

        if ($consumer_pubkey === false) {
            return new Auth_OpenID_ServerError($message,
                                       "dh_consumer_public is not base64");
        }

        return array($dh, $consumer_pubkey);
    }

    static function fromMessage($message)
    {
        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);

        if (is_a($result, 'Auth_OpenID_ServerError')) {
            return $result;
        } else {
            list($dh, $consumer_pubkey) = $result;
            return new Auth_OpenID_DiffieHellmanSHA1ServerSession($dh,
                                                    $consumer_pubkey);
        }
    }

    function answer($secret)
    {
        $lib = Auth_OpenID_getMathLib();
        $mac_key = $this->dh->xorSecret($this->consumer_pubkey, $secret,
                                        $this->hash_func);
        return array(
           'dh_server_public' =>
                $lib->longToBase64($this->dh->public),
           'enc_mac_key' => base64_encode($mac_key));
    }
}

/**
 * A class implementing DH-SHA256 server sessions.
 *
 * @package OpenID
 */
class Auth_OpenID_DiffieHellmanSHA256ServerSession
      extends Auth_OpenID_DiffieHellmanSHA1ServerSession {

    var $session_type = 'DH-SHA256';
    var $hash_func = 'Auth_OpenID_SHA256';
    var $allowed_assoc_types = array('HMAC-SHA256');

    static function fromMessage($message)
    {
        $result = Auth_OpenID_DiffieHellmanSHA1ServerSession::getDH($message);

        if (is_a($result, 'Auth_OpenID_ServerError')) {
            return $result;
        } else {
            list($dh, $consumer_pubkey) = $result;
            return new Auth_OpenID_DiffieHellmanSHA256ServerSession($dh,
                                                      $consumer_pubkey);
        }
    }
}

/**
 * A request to associate with the server.
 *
 * @package OpenID
 */
class Auth_OpenID_AssociateRequest extends Auth_OpenID_Request {
    var $mode = "associate";

    static function getSessionClasses()
    {
        return array(
          'no-encryption' => 'Auth_OpenID_PlainTextServerSession',
          'DH-SHA1' => 'Auth_OpenID_DiffieHellmanSHA1ServerSession',
          'DH-SHA256' => 'Auth_OpenID_DiffieHellmanSHA256ServerSession');
    }

    function Auth_OpenID_AssociateRequest($session, $assoc_type)
    {
        $this->session = $session;
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->assoc_type = $assoc_type;
    }

    static function fromMessage($message, $server=null)
    {
        if ($message->isOpenID1()) {
            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                             'session_type');

            if ($session_type == 'no-encryption') {
                // oidutil.log('Received OpenID 1 request with a no-encryption '
                //             'assocaition session type. Continuing anyway.')
            } else if (!$session_type) {
                $session_type = 'no-encryption';
            }
        } else {
            $session_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                             'session_type');
            if ($session_type === null) {
                return new Auth_OpenID_ServerError($message,
                  "session_type missing from request");
            }
        }

        $session_class = Auth_OpenID::arrayGet(
           Auth_OpenID_AssociateRequest::getSessionClasses(),
           $session_type);

        if ($session_class === null) {
            return new Auth_OpenID_ServerError($message,
                                               "Unknown session type " .
                                               $session_type);
        }

        $session = call_user_func(array($session_class, 'fromMessage'),
                                  $message);
        if (is_a($session, 'Auth_OpenID_ServerError')) {
            return $session;
        }

        $assoc_type = $message->getArg(Auth_OpenID_OPENID_NS,
                                       'assoc_type', 'HMAC-SHA1');

        if (!in_array($assoc_type, $session->allowed_assoc_types)) {
            $fmt = "Session type %s does not support association type %s";
            return new Auth_OpenID_ServerError($message,
              sprintf($fmt, $session_type, $assoc_type));
        }

        $obj = new Auth_OpenID_AssociateRequest($session, $assoc_type);
        $obj->message = $message;
        $obj->namespace = $message->getOpenIDNamespace();
        return $obj;
    }

    function answer($assoc)
    {
        $response = new Auth_OpenID_ServerResponse($this);
        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
           array(
                 'expires_in' => sprintf('%d', $assoc->getExpiresIn()),
                 'assoc_type' => $this->assoc_type,
                 'assoc_handle' => $assoc->handle));

        $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
           $this->session->answer($assoc->secret));

        if (! ($this->session->session_type == 'no-encryption'
               && $this->message->isOpenID1())) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'session_type',
                                      $this->session->session_type);
        }

        return $response;
    }

    function answerUnsupported($text_message,
                               $preferred_association_type=null,
                               $preferred_session_type=null)
    {
        if ($this->message->isOpenID1()) {
            return new Auth_OpenID_ServerError($this->message);
        }

        $response = new Auth_OpenID_ServerResponse($this);
        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'error_code', 'unsupported-type');
        $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                  'error', $text_message);

        if ($preferred_association_type) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'assoc_type',
                                      $preferred_association_type);
        }

        if ($preferred_session_type) {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'session_type',
                                      $preferred_session_type);
        }
        $response->code = AUTH_OPENID_HTTP_ERROR;
        return $response;
    }
}

/**
 * A request to confirm the identity of a user.
 *
 * @package OpenID
 */
class Auth_OpenID_CheckIDRequest extends Auth_OpenID_Request {
    /**
     * Return-to verification callback.  Default is
     * Auth_OpenID_verifyReturnTo from TrustRoot.php.
     */
    var $verifyReturnTo = 'Auth_OpenID_verifyReturnTo';

    /**
     * The mode of this request.
     */
    var $mode = "checkid_setup"; // or "checkid_immediate"

    /**
     * Whether this request is for immediate mode.
     */
    var $immediate = false;

    /**
     * The trust_root value for this request.
     */
    var $trust_root = null;

    /**
     * The OpenID namespace for this request.
     * deprecated since version 2.0.2
     */
    var $namespace;

    static function make($message, $identity, $return_to, $trust_root = null,
                  $immediate = false, $assoc_handle = null, $server = null)
    {
        if ($server === null) {
            return new Auth_OpenID_ServerError($message,
                                               "server must not be null");
        }

        if ($return_to &&
            !Auth_OpenID_TrustRoot::_parse($return_to)) {
            return new Auth_OpenID_MalformedReturnURL($message, $return_to);
        }

        $r = new Auth_OpenID_CheckIDRequest($identity, $return_to,
                                            $trust_root, $immediate,
                                            $assoc_handle, $server);

        $r->namespace = $message->getOpenIDNamespace();
        $r->message = $message;

        if (!$r->trustRootValid()) {
            return new Auth_OpenID_UntrustedReturnURL($message,
                                                      $return_to,
                                                      $trust_root);
        } else {
            return $r;
        }
    }

    function Auth_OpenID_CheckIDRequest($identity, $return_to,
                                        $trust_root = null, $immediate = false,
                                        $assoc_handle = null, $server = null,
                                        $claimed_id = null)
    {
        $this->namespace = Auth_OpenID_OPENID2_NS;
        $this->assoc_handle = $assoc_handle;
        $this->identity = $identity;
        if ($claimed_id === null) {
            $this->claimed_id = $identity;
        } else {
            $this->claimed_id = $claimed_id;
        }
        $this->return_to = $return_to;
        $this->trust_root = $trust_root;
        $this->server = $server;

        if ($immediate) {
            $this->immediate = true;
            $this->mode = "checkid_immediate";
        } else {
            $this->immediate = false;
            $this->mode = "checkid_setup";
        }
    }

    function equals($other)
    {
        return (
                (is_a($other, 'Auth_OpenID_CheckIDRequest')) &&
                ($this->namespace == $other->namespace) &&
                ($this->assoc_handle == $other->assoc_handle) &&
                ($this->identity == $other->identity) &&
                ($this->claimed_id == $other->claimed_id) &&
                ($this->return_to == $other->return_to) &&
                ($this->trust_root == $other->trust_root));
    }

    /*
     * Does the relying party publish the return_to URL for this
     * response under the realm? It is up to the provider to set a
     * policy for what kinds of realms should be allowed. This
     * return_to URL verification reduces vulnerability to data-theft
     * attacks based on open proxies, corss-site-scripting, or open
     * redirectors.
     *
     * This check should only be performed after making sure that the
     * return_to URL matches the realm.
     *
     * @return true if the realm publishes a document with the
     * return_to URL listed, false if not or if discovery fails
     */
    function returnToVerified()
    {
        $fetcher = Auth_Yadis_Yadis::getHTTPFetcher();
        return call_user_func_array($this->verifyReturnTo,
                                    array($this->trust_root, $this->return_to, $fetcher));
    }

    static function fromMessage($message, $server)
    {
        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
        $immediate = null;

        if ($mode == "checkid_immediate") {
            $immediate = true;
            $mode = "checkid_immediate";
        } else {
            $immediate = false;
            $mode = "checkid_setup";
        }

        $return_to = $message->getArg(Auth_OpenID_OPENID_NS,
                                      'return_to');

        if (($message->isOpenID1()) &&
            (!$return_to)) {
            $fmt = "Missing required field 'return_to' from checkid request";
            return new Auth_OpenID_ServerError($message, $fmt);
        }

        $identity = $message->getArg(Auth_OpenID_OPENID_NS,
                                     'identity');
        $claimed_id = $message->getArg(Auth_OpenID_OPENID_NS, 'claimed_id');
        if ($message->isOpenID1()) {
            if ($identity === null) {
                $s = "OpenID 1 message did not contain openid.identity";
                return new Auth_OpenID_ServerError($message, $s);
            }
        } else {
            if ($identity && !$claimed_id) {
                $s = "OpenID 2.0 message contained openid.identity but not " .
                  "claimed_id";
                return new Auth_OpenID_ServerError($message, $s);
            } else if ($claimed_id && !$identity) {
                $s = "OpenID 2.0 message contained openid.claimed_id " .
                  "but not identity";
                return new Auth_OpenID_ServerError($message, $s);
            }
        }

        // There's a case for making self.trust_root be a TrustRoot
        // here.  But if TrustRoot isn't currently part of the
        // "public" API, I'm not sure it's worth doing.
        if ($message->isOpenID1()) {
            $trust_root_param = 'trust_root';
        } else {
            $trust_root_param = 'realm';
        }
        $trust_root = $message->getArg(Auth_OpenID_OPENID_NS,
                                       $trust_root_param);
        if (! $trust_root) {
            $trust_root = $return_to;
        }

        if (! $message->isOpenID1() &&
            ($return_to === null) &&
            ($trust_root === null)) {
            return new Auth_OpenID_ServerError($message,
              "openid.realm required when openid.return_to absent");
        }

        $assoc_handle = $message->getArg(Auth_OpenID_OPENID_NS,
                                         'assoc_handle');

        $obj = Auth_OpenID_CheckIDRequest::make($message,
                                                $identity,
                                                $return_to,
                                                $trust_root,
                                                $immediate,
                                                $assoc_handle,
                                                $server);

        if (is_a($obj, 'Auth_OpenID_ServerError')) {
            return $obj;
        }

        $obj->claimed_id = $claimed_id;

        return $obj;
    }

    function idSelect()
    {
        // Is the identifier to be selected by the IDP?
        // So IDPs don't have to import the constant
        return $this->identity == Auth_OpenID_IDENTIFIER_SELECT;
    }

    function trustRootValid()
    {
        if (!$this->trust_root) {
            return true;
        }

        $tr = Auth_OpenID_TrustRoot::_parse($this->trust_root);
        if ($tr === false) {
            return new Auth_OpenID_MalformedTrustRoot($this->message,
                                                      $this->trust_root);
        }

        if ($this->return_to !== null) {
            return Auth_OpenID_TrustRoot::match($this->trust_root,
                                                $this->return_to);
        } else {
            return true;
        }
    }

    /**
     * Respond to this request.  Return either an
     * {@link Auth_OpenID_ServerResponse} or
     * {@link Auth_OpenID_ServerError}.
     *
     * @param bool $allow Allow this user to claim this identity, and
     * allow the consumer to have this information?
     *
     * @param string $server_url DEPRECATED.  Passing $op_endpoint to
     * the {@link Auth_OpenID_Server} constructor makes this optional.
     *
     * When an OpenID 1.x immediate mode request does not succeed, it
     * gets back a URL where the request may be carried out in a
     * not-so-immediate fashion.  Pass my URL in here (the fully
     * qualified address of this server's endpoint, i.e.
     * http://example.com/server), and I will use it as a base for the
     * URL for a new request.
     *
     * Optional for requests where {@link $immediate} is false or
     * $allow is true.
     *
     * @param string $identity The OP-local identifier to answer with.
     * Only for use when the relying party requested identifier
     * selection.
     *
     * @param string $claimed_id The claimed identifier to answer
     * with, for use with identifier selection in the case where the
     * claimed identifier and the OP-local identifier differ,
     * i.e. when the claimed_id uses delegation.
     *
     * If $identity is provided but this is not, $claimed_id will
     * default to the value of $identity.  When answering requests
     * that did not ask for identifier selection, the response
     * $claimed_id will default to that of the request.
     *
     * This parameter is new in OpenID 2.0.
     *
     * @return mixed
     */
    function answer($allow, $server_url = null, $identity = null,
                    $claimed_id = null)
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        if (!$server_url) {
            if ((!$this->message->isOpenID1()) &&
                (!$this->server->op_endpoint)) {
                return new Auth_OpenID_ServerError(null,
                  "server should be constructed with op_endpoint to " .
                  "respond to OpenID 2.0 messages.");
            }

            $server_url = $this->server->op_endpoint;
        }

        if ($allow) {
            $mode = 'id_res';
        } else if ($this->message->isOpenID1()) {
            if ($this->immediate) {
                $mode = 'id_res';
            } else {
                $mode = 'cancel';
            }
        } else {
            if ($this->immediate) {
                $mode = 'setup_needed';
            } else {
                $mode = 'cancel';
            }
        }

        if (!$this->trustRootValid()) {
            return new Auth_OpenID_UntrustedReturnURL(null,
                                                      $this->return_to,
                                                      $this->trust_root);
        }

        $response = new Auth_OpenID_ServerResponse($this);

        if ($claimed_id &&
            ($this->message->isOpenID1())) {
            return new Auth_OpenID_ServerError(null,
              "claimed_id is new in OpenID 2.0 and not " .
              "available for ".$this->namespace);
        }

        if ($identity && !$claimed_id) {
            $claimed_id = $identity;
        }

        if ($allow) {

            if ($this->identity == Auth_OpenID_IDENTIFIER_SELECT) {
                if (!$identity) {
                    return new Auth_OpenID_ServerError(null,
                      "This request uses IdP-driven identifier selection.  " .
                      "You must supply an identifier in the response.");
                }

                $response_identity = $identity;
                $response_claimed_id = $claimed_id;

            } else if ($this->identity) {
                if ($identity &&
                    ($this->identity != $identity)) {
                    $fmt = "Request was for %s, cannot reply with identity %s";
                    return new Auth_OpenID_ServerError(null,
                      sprintf($fmt, $this->identity, $identity));
                }

                $response_identity = $this->identity;
                $response_claimed_id = $this->claimed_id;
            } else {
                if ($identity) {
                    return new Auth_OpenID_ServerError(null,
                      "This request specified no identity and " .
                      "you supplied ".$identity);
                }

                $response_identity = null;
            }

            if (($this->message->isOpenID1()) &&
                ($response_identity === null)) {
                return new Auth_OpenID_ServerError(null,
                  "Request was an OpenID 1 request, so response must " .
                  "include an identifier.");
            }

            $response->fields->updateArgs(Auth_OpenID_OPENID_NS,
                   array('mode' => $mode,
                         'return_to' => $this->return_to,
                         'response_nonce' => Auth_OpenID_mkNonce()));

            if (!$this->message->isOpenID1()) {
                $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                          'op_endpoint', $server_url);
            }

            if ($response_identity !== null) {
                $response->fields->setArg(
                                          Auth_OpenID_OPENID_NS,
                                          'identity',
                                          $response_identity);
                if ($this->message->isOpenID2()) {
                    $response->fields->setArg(
                                              Auth_OpenID_OPENID_NS,
                                              'claimed_id',
                                              $response_claimed_id);
                }
            }

        } else {
            $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                      'mode', $mode);

            if ($this->immediate) {
                if (($this->message->isOpenID1()) &&
                    (!$server_url)) {
                    return new Auth_OpenID_ServerError(null,
                                 'setup_url is required for $allow=false \
                                  in OpenID 1.x immediate mode.');
                }

                $setup_request = new Auth_OpenID_CheckIDRequest(
                                                $this->identity,
                                                $this->return_to,
                                                $this->trust_root,
                                                false,
                                                $this->assoc_handle,
                                                $this->server,
                                                $this->claimed_id);
                $setup_request->message = $this->message;

                $setup_url = $setup_request->encodeToURL($server_url);

                if ($setup_url === null) {
                    return new Auth_OpenID_NoReturnToError();
                }

                $response->fields->setArg(Auth_OpenID_OPENID_NS,
                                          'user_setup_url',
                                          $setup_url);
            }
        }

        return $response;
    }

    function encodeToURL($server_url)
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        // Imported from the alternate reality where these classes are
        // used in both the client and server code, so Requests are
        // Encodable too.  That's right, code imported from alternate
        // realities all for the love of you, id_res/user_setup_url.

        $q = array('mode' => $this->mode,
                   'identity' => $this->identity,
                   'claimed_id' => $this->claimed_id,
                   'return_to' => $this->return_to);

        if ($this->trust_root) {
            if ($this->message->isOpenID1()) {
                $q['trust_root'] = $this->trust_root;
            } else {
                $q['realm'] = $this->trust_root;
            }
        }

        if ($this->assoc_handle) {
            $q['assoc_handle'] = $this->assoc_handle;
        }

        $response = new Auth_OpenID_Message(
            $this->message->getOpenIDNamespace());
        $response->updateArgs(Auth_OpenID_OPENID_NS, $q);
        return $response->toURL($server_url);
    }

    function getCancelURL()
    {
        if (!$this->return_to) {
            return new Auth_OpenID_NoReturnToError();
        }

        if ($this->immediate) {
            return new Auth_OpenID_ServerError(null,
                                               "Cancel is not an appropriate \
                                               response to immediate mode \
                                               requests.");
        }

        $response = new Auth_OpenID_Message(
            $this->message->getOpenIDNamespace());
        $response->setArg(Auth_OpenID_OPENID_NS, 'mode', 'cancel');
        return $response->toURL($this->return_to);
    }
}

/**
 * This class encapsulates the response to an OpenID server request.
 *
 * @package OpenID
 */
class Auth_OpenID_ServerResponse {

    function Auth_OpenID_ServerResponse($request)
    {
        $this->request = $request;
        $this->fields = new Auth_OpenID_Message($this->request->namespace);
    }

    function whichEncoding()
    {
      global $_Auth_OpenID_Request_Modes;

        if (in_array($this->request->mode, $_Auth_OpenID_Request_Modes)) {
            if ($this->fields->isOpenID2() &&
                (strlen($this->encodeToURL()) >
                   Auth_OpenID_OPENID1_URL_LIMIT)) {
                return Auth_OpenID_ENCODE_HTML_FORM;
            } else {
                return Auth_OpenID_ENCODE_URL;
            }
        } else {
            return Auth_OpenID_ENCODE_KVFORM;
        }
    }

    /*
     * Returns the form markup for this response.
     *
     * @return str
     */
    function toFormMarkup($form_tag_attrs=null)
    {
        return $this->fields->toFormMarkup($this->request->return_to,
                                           $form_tag_attrs);
    }

    /*
     * Returns an HTML document containing the form markup for this
     * response that autosubmits with javascript.
     */
    function toHTML()
    {
        return Auth_OpenID::autoSubmitHTML($this->toFormMarkup());
    }

    /*
     * Returns True if this response's encoding is ENCODE_HTML_FORM.
     * Convenience method for server authors.
     *
     * @return bool
     */
    function renderAsForm()
    {
        return $this->whichEncoding() == Auth_OpenID_ENCODE_HTML_FORM;
    }

    function encodeToURL()
    {
        return $this->fields->toURL($this->request->return_to);
    }

    function addExtension($extension_response)
    {
        $extension_response->toMessage($this->fields);
    }

    function needsSigning()
    {
        return $this->fields->getArg(Auth_OpenID_OPENID_NS,
                                     'mode') == 'id_res';
    }

    function encodeToKVForm()
    {
        return $this->fields->toKVForm();
    }
}

/**
 * A web-capable response object which you can use to generate a
 * user-agent response.
 *
 * @package OpenID
 */
class Auth_OpenID_WebResponse {
    var $code = AUTH_OPENID_HTTP_OK;
    var $body = "";

    function Auth_OpenID_WebResponse($code = null, $headers = null,
                                     $body = null)
    {
        if ($code) {
            $this->code = $code;
        }

        if ($headers !== null) {
            $this->headers = $headers;
        } else {
            $this->headers = array();
        }

        if ($body !== null) {
            $this->body = $body;
        }
    }
}

/**
 * Responsible for the signature of query data and the verification of
 * OpenID signature values.
 *
 * @package OpenID
 */
class Auth_OpenID_Signatory {

    // = 14 * 24 * 60 * 60; # 14 days, in seconds
    var $SECRET_LIFETIME = 1209600;

    // keys have a bogus server URL in them because the filestore
    // really does expect that key to be a URL.  This seems a little
    // silly for the server store, since I expect there to be only one
    // server URL.
    var $normal_key = 'http://localhost/|normal';
    var $dumb_key = 'http://localhost/|dumb';

    /**
     * Create a new signatory using a given store.
     */
    function Auth_OpenID_Signatory($store)
    {
        // assert store is not None
        $this->store = $store;
    }

    /**
     * Verify, using a given association handle, a signature with
     * signed key-value pairs from an HTTP request.
     */
    function verify($assoc_handle, $message)
    {
        $assoc = $this->getAssociation($assoc_handle, true);
        if (!$assoc) {
            // oidutil.log("failed to get assoc with handle %r to verify sig %r"
            //             % (assoc_handle, sig))
            return false;
        }

        return $assoc->checkMessageSignature($message);
    }

    /**
     * Given a response, sign the fields in the response's 'signed'
     * list, and insert the signature into the response.
     */
    function sign($response)
    {
        $signed_response = $response;
        $assoc_handle = $response->request->assoc_handle;

        if ($assoc_handle) {
            // normal mode
            $assoc = $this->getAssociation($assoc_handle, false, false);
            if (!$assoc || ($assoc->getExpiresIn() <= 0)) {
                // fall back to dumb mode
                $signed_response->fields->setArg(Auth_OpenID_OPENID_NS,
                             'invalidate_handle', $assoc_handle);
                $assoc_type = ($assoc ? $assoc->assoc_type : 'HMAC-SHA1');

                if ($assoc && ($assoc->getExpiresIn() <= 0)) {
                    $this->invalidate($assoc_handle, false);
                }

                $assoc = $this->createAssociation(true, $assoc_type);
            }
        } else {
            // dumb mode.
            $assoc = $this->createAssociation(true);
        }

        $signed_response->fields = $assoc->signMessage(
                                      $signed_response->fields);
        return $signed_response;
    }

    /**
     * Make a new association.
     */
    function createAssociation($dumb = true, $assoc_type = 'HMAC-SHA1')
    {
        $secret = Auth_OpenID_CryptUtil::getBytes(
                    Auth_OpenID_getSecretSize($assoc_type));

        $uniq = base64_encode(Auth_OpenID_CryptUtil::getBytes(4));
        $handle = sprintf('{%s}{%x}{%s}', $assoc_type, intval(time()), $uniq);

        $assoc = Auth_OpenID_Association::fromExpiresIn(
                      $this->SECRET_LIFETIME, $handle, $secret, $assoc_type);

        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }

        $this->store->storeAssociation($key, $assoc);
        return $assoc;
    }

    /**
     * Given an association handle, get the association from the
     * store, or return a ServerError or null if something goes wrong.
     */
    function getAssociation($assoc_handle, $dumb, $check_expiration=true)
    {
        if ($assoc_handle === null) {
            return new Auth_OpenID_ServerError(null,
                                     "assoc_handle must not be null");
        }

        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }

        $assoc = $this->store->getAssociation($key, $assoc_handle);

        if (($assoc !== null) && ($assoc->getExpiresIn() <= 0)) {
            if ($check_expiration) {
                $this->store->removeAssociation($key, $assoc_handle);
                $assoc = null;
            }
        }

        return $assoc;
    }

    /**
     * Invalidate a given association handle.
     */
    function invalidate($assoc_handle, $dumb)
    {
        if ($dumb) {
            $key = $this->dumb_key;
        } else {
            $key = $this->normal_key;
        }
        $this->store->removeAssociation($key, $assoc_handle);
    }
}

/**
 * Encode an {@link Auth_OpenID_ServerResponse} to an
 * {@link Auth_OpenID_WebResponse}.
 *
 * @package OpenID
 */
class Auth_OpenID_Encoder {

    var $responseFactory = 'Auth_OpenID_WebResponse';

    /**
     * Encode an {@link Auth_OpenID_ServerResponse} and return an
     * {@link Auth_OpenID_WebResponse}.
     */
    function encode($response)
    {
        $cls = $this->responseFactory;

        $encode_as = $response->whichEncoding();
        if ($encode_as == Auth_OpenID_ENCODE_KVFORM) {
            $wr = new $cls(null, null, $response->encodeToKVForm());
            if (is_a($response, 'Auth_OpenID_ServerError')) {
                $wr->code = AUTH_OPENID_HTTP_ERROR;
            }
        } else if ($encode_as == Auth_OpenID_ENCODE_URL) {
            $location = $response->encodeToURL();
            $wr = new $cls(AUTH_OPENID_HTTP_REDIRECT,
                           array('location' => $location));
        } else if ($encode_as == Auth_OpenID_ENCODE_HTML_FORM) {
          $wr = new $cls(AUTH_OPENID_HTTP_OK, array(),
                         $response->toHTML());
        } else {
            return new Auth_OpenID_EncodingError($response);
        }
        /* Allow the response to carry a custom error code (ex: for Association errors) */
        if(isset($response->code)) {
            $wr->code = $response->code;
        }
        return $wr;
    }
}

/**
 * An encoder which also takes care of signing fields when required.
 *
 * @package OpenID
 */
class Auth_OpenID_SigningEncoder extends Auth_OpenID_Encoder {

    function Auth_OpenID_SigningEncoder($signatory)
    {
        $this->signatory = $signatory;
    }

    /**
     * Sign an {@link Auth_OpenID_ServerResponse} and return an
     * {@link Auth_OpenID_WebResponse}.
     */
    function encode($response)
    {
        // the isinstance is a bit of a kludge... it means there isn't
        // really an adapter to make the interfaces quite match.
        if (!is_a($response, 'Auth_OpenID_ServerError') &&
            $response->needsSigning()) {

            if (!$this->signatory) {
                return new Auth_OpenID_ServerError(null,
                                       "Must have a store to sign request");
            }

            if ($response->fields->hasKey(Auth_OpenID_OPENID_NS, 'sig')) {
                return new Auth_OpenID_AlreadySigned($response);
            }
            $response = $this->signatory->sign($response);
        }

        return parent::encode($response);
    }
}

/**
 * Decode an incoming query into an Auth_OpenID_Request.
 *
 * @package OpenID
 */
class Auth_OpenID_Decoder {

    function Auth_OpenID_Decoder($server)
    {
        $this->server = $server;

        $this->handlers = array(
            'checkid_setup' => 'Auth_OpenID_CheckIDRequest',
            'checkid_immediate' => 'Auth_OpenID_CheckIDRequest',
            'check_authentication' => 'Auth_OpenID_CheckAuthRequest',
            'associate' => 'Auth_OpenID_AssociateRequest'
            );
    }

    /**
     * Given an HTTP query in an array (key-value pairs), decode it
     * into an Auth_OpenID_Request object.
     */
    function decode($query)
    {
        if (!$query) {
            return null;
        }

        $message = Auth_OpenID_Message::fromPostArgs($query);

        if ($message === null) {
            /*
             * It's useful to have a Message attached to a
             * ProtocolError, so we override the bad ns value to build
             * a Message out of it.  Kinda kludgy, since it's made of
             * lies, but the parts that aren't lies are more useful
             * than a 'None'.
             */
            $old_ns = $query['openid.ns'];

            $query['openid.ns'] = Auth_OpenID_OPENID2_NS;
            $message = Auth_OpenID_Message::fromPostArgs($query);
            return new Auth_OpenID_ServerError(
                  $message,
                  sprintf("Invalid OpenID namespace URI: %s", $old_ns));
        }

        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');
        if (!$mode) {
            return new Auth_OpenID_ServerError($message,
                                               "No mode value in message");
        }

        if (Auth_OpenID::isFailure($mode)) {
            return new Auth_OpenID_ServerError($message,
                                               $mode->message);
        }

        $handlerCls = Auth_OpenID::arrayGet($this->handlers, $mode,
                                            $this->defaultDecoder($message));

        if (!is_a($handlerCls, 'Auth_OpenID_ServerError')) {
            return call_user_func_array(array($handlerCls, 'fromMessage'),
                                        array($message, $this->server));
        } else {
            return $handlerCls;
        }
    }

    function defaultDecoder($message)
    {
        $mode = $message->getArg(Auth_OpenID_OPENID_NS, 'mode');

        if (Auth_OpenID::isFailure($mode)) {
            return new Auth_OpenID_ServerError($message,
                                               $mode->message);
        }

        return new Auth_OpenID_ServerError($message,
                       sprintf("Unrecognized OpenID mode %s", $mode));
    }
}

/**
 * An error that indicates an encoding problem occurred.
 *
 * @package OpenID
 */
class Auth_OpenID_EncodingError {
    function Auth_OpenID_EncodingError($response)
    {
        $this->response = $response;
    }
}

/**
 * An error that indicates that a response was already signed.
 *
 * @package OpenID
 */
class Auth_OpenID_AlreadySigned extends Auth_OpenID_EncodingError {
    // This response is already signed.
}

/**
 * An error that indicates that the given return_to is not under the
 * given trust_root.
 *
 * @package OpenID
 */
class Auth_OpenID_UntrustedReturnURL extends Auth_OpenID_ServerError {
    function Auth_OpenID_UntrustedReturnURL($message, $return_to,
                                            $trust_root)
    {
        parent::Auth_OpenID_ServerError($message, "Untrusted return_to URL");
        $this->return_to = $return_to;
        $this->trust_root = $trust_root;
    }

    function toString()
    {
        return sprintf("return_to %s not under trust_root %s",
                       $this->return_to, $this->trust_root);
    }
}

/**
 * I handle requests for an OpenID server.
 *
 * Some types of requests (those which are not checkid requests) may
 * be handed to my {@link handleRequest} method, and I will take care
 * of it and return a response.
 *
 * For your convenience, I also provide an interface to {@link
 * Auth_OpenID_Decoder::decode()} and {@link
 * Auth_OpenID_SigningEncoder::encode()} through my methods {@link
 * decodeRequest} and {@link encodeResponse}.
 *
 * All my state is encapsulated in an {@link Auth_OpenID_OpenIDStore}.
 *
 * Example:
 *
 * <pre> $oserver = new Auth_OpenID_Server(Auth_OpenID_FileStore($data_path),
 *                                   "http://example.com/op");
 * $request = $oserver->decodeRequest();
 * if (in_array($request->mode, array('checkid_immediate',
 *                                    'checkid_setup'))) {
 *     if ($app->isAuthorized($request->identity, $request->trust_root)) {
 *         $response = $request->answer(true);
 *     } else if ($request->immediate) {
 *         $response = $request->answer(false);
 *     } else {
 *         $app->showDecidePage($request);
 *         return;
 *     }
 * } else {
 *     $response = $oserver->handleRequest($request);
 * }
 *
 * $webresponse = $oserver->encode($response);</pre>
 *
 * @package OpenID
 */
class Auth_OpenID_Server {
    function Auth_OpenID_Server($store, $op_endpoint=null)
    {
        $this->store = $store;
        $this->signatory = new Auth_OpenID_Signatory($this->store);
        $this->encoder = new Auth_OpenID_SigningEncoder($this->signatory);
        $this->decoder = new Auth_OpenID_Decoder($this);
        $this->op_endpoint = $op_endpoint;
        $this->negotiator = Auth_OpenID_getDefaultNegotiator();
    }

    /**
     * Handle a request.  Given an {@link Auth_OpenID_Request} object,
     * call the appropriate {@link Auth_OpenID_Server} method to
     * process the request and generate a response.
     *
     * @param Auth_OpenID_Request $request An {@link Auth_OpenID_Request}
     * returned by {@link Auth_OpenID_Server::decodeRequest()}.
     *
     * @return Auth_OpenID_ServerResponse $response A response object
     * capable of generating a user-agent reply.
     */
    function handleRequest($request)
    {
        if (method_exists($this, "openid_" . $request->mode)) {
            $handler = array($this, "openid_" . $request->mode);
            return call_user_func_array($handler, array($request));
        }
        return null;
    }

    /**
     * The callback for 'check_authentication' messages.
     */
    function openid_check_authentication($request)
    {
        return $request->answer($this->signatory);
    }

    /**
     * The callback for 'associate' messages.
     */
    function openid_associate($request)
    {
        $assoc_type = $request->assoc_type;
        $session_type = $request->session->session_type;
        if ($this->negotiator->isAllowed($assoc_type, $session_type)) {
            $assoc = $this->signatory->createAssociation(false,
                                                         $assoc_type);
            return $request->answer($assoc);
        } else {
            $message = sprintf('Association type %s is not supported with '.
                               'session type %s', $assoc_type, $session_type);
            list($preferred_assoc_type, $preferred_session_type) =
                $this->negotiator->getAllowedType();
            return $request->answerUnsupported($message,
                                               $preferred_assoc_type,
                                               $preferred_session_type);
        }
    }

    /**
     * Encodes as response in the appropriate format suitable for
     * sending to the user agent.
     */
    function encodeResponse($response)
    {
        return $this->encoder->encode($response);
    }

    /**
     * Decodes a query args array into the appropriate
     * {@link Auth_OpenID_Request} object.
     */
    function decodeRequest($query=null)
    {
        if ($query === null) {
            $query = Auth_OpenID::getQuery();
        }

        return $this->decoder->decode($query);
    }
}


code:
<?php
/**
 * OpenID Server Request
 *
 * @see Auth_OpenID_Server
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

/**
 * Imports
 */
require_once "Auth/OpenID.php";

/**
 * Object that holds the state of a request to the OpenID server
 *
 * With accessor functions to get at the internal request data.
 *
 * @see Auth_OpenID_Server
 * @package OpenID
 */
class Auth_OpenID_ServerRequest {
    function Auth_OpenID_ServerRequest()
    {
        $this->mode = null;
    }
}

license: mit


code:
<?php
/**
 * Functions for dealing with OpenID trust roots
 *
 * PHP versions 4 and 5
 *
 * LICENSE: See the COPYING file included in this distribution.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

require_once 'Auth/OpenID/Discover.php';

/**
 * A regular expression that matches a domain ending in a top-level domains.
 * Used in checking trust roots for sanity.
 *
 * @access private
 */
define('Auth_OpenID___TLDs',
       '/\.(ac|ad|ae|aero|af|ag|ai|al|am|an|ao|aq|ar|arpa|as|asia' .
       '|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|biz|bj|bm|bn|bo|br' .
       '|bs|bt|bv|bw|by|bz|ca|cat|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co' .
       '|com|coop|cr|cu|cv|cx|cy|cz|de|dj|dk|dm|do|dz|ec|edu|ee|eg' .
       '|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl' .
       '|gm|gn|gov|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie' .
       '|il|im|in|info|int|io|iq|ir|is|it|je|jm|jo|jobs|jp|ke|kg|kh' .
       '|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly' .
       '|ma|mc|md|me|mg|mh|mil|mk|ml|mm|mn|mo|mobi|mp|mq|mr|ms|mt' .
       '|mu|museum|mv|mw|mx|my|mz|na|name|nc|ne|net|nf|ng|ni|nl|no' .
       '|np|nr|nu|nz|om|org|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|pro|ps|pt' .
       '|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl' .
       '|sm|sn|so|sr|st|su|sv|sy|sz|tc|td|tel|tf|tg|th|tj|tk|tl|tm' .
       '|tn|to|tp|tr|travel|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve' .
       '|vg|vi|vn|vu|wf|ws|xn--0zwm56d|xn--11b5bs3a9aj6g' .
       '|xn--80akhbyknj4f|xn--9t4b11yi5a|xn--deba0ad|xn--g6w251d' .
       '|xn--hgbk6aj7f53bba|xn--hlcj6aya9esc7a|xn--jxalpdlp' .
       '|xn--kgbechtv|xn--zckzah|ye|yt|yu|za|zm|zw)\.?$/');

define('Auth_OpenID___HostSegmentRe',
       "/^(?:[-a-zA-Z0-9!$&'\\(\\)\\*+,;=._~]|%[a-zA-Z0-9]{2})*$/");

/**
 * A wrapper for trust-root related functions
 */
class Auth_OpenID_TrustRoot {
    /*
     * Return a discovery URL for this realm.
     *
     * Return null if the realm could not be parsed or was not valid.
     *
     * @param return_to The relying party return URL of the OpenID
     * authentication request
     *
     * @return The URL upon which relying party discovery should be
     * run in order to verify the return_to URL
     */
    static function buildDiscoveryURL($realm)
    {
        $parsed = Auth_OpenID_TrustRoot::_parse($realm);

        if ($parsed === false) {
            return false;
        }

        if ($parsed['wildcard']) {
            // Use "www." in place of the star
            if ($parsed['host'][0] != '.') {
                return false;
            }

            $www_domain = 'www' . $parsed['host'];

            return sprintf('%s://%s%s', $parsed['scheme'],
                           $www_domain, $parsed['path']);
        } else {
            return $parsed['unparsed'];
        }
    }

    /**
     * Parse a URL into its trust_root parts.
     *
     * @static
     *
     * @access private
     *
     * @param string $trust_root The url to parse
     *
     * @return mixed $parsed Either an associative array of trust root
     * parts or false if parsing failed.
     */
    static function _parse($trust_root)
    {
        $trust_root = Auth_OpenID_urinorm($trust_root);
        if ($trust_root === null) {
            return false;
        }

        if (preg_match("/:\/\/[^:]+(:\d+){2,}(\/|$)/", $trust_root)) {
            return false;
        }

        $parts = @parse_url($trust_root);
        if ($parts === false) {
            return false;
        }

        $required_parts = array('scheme', 'host');
        $forbidden_parts = array('user', 'pass', 'fragment');
        $keys = array_keys($parts);
        if (array_intersect($keys, $required_parts) != $required_parts) {
            return false;
        }

        if (array_intersect($keys, $forbidden_parts) != array()) {
            return false;
        }

        if (!preg_match(Auth_OpenID___HostSegmentRe, $parts['host'])) {
            return false;
        }

        $scheme = strtolower($parts['scheme']);
        $allowed_schemes = array('http', 'https');
        if (!in_array($scheme, $allowed_schemes)) {
            return false;
        }
        $parts['scheme'] = $scheme;

        $host = strtolower($parts['host']);
        $hostparts = explode('*', $host);
        switch (count($hostparts)) {
        case 1:
            $parts['wildcard'] = false;
            break;
        case 2:
            if ($hostparts[0] ||
                ($hostparts[1] && substr($hostparts[1], 0, 1) != '.')) {
                return false;
            }
            $host = $hostparts[1];
            $parts['wildcard'] = true;
            break;
        default:
            return false;
        }
        if (strpos($host, ':') !== false) {
            return false;
        }

        $parts['host'] = $host;

        if (isset($parts['path'])) {
            $path = strtolower($parts['path']);
            if (substr($path, 0, 1) != '/') {
                return false;
            }
        } else {
            $path = '/';
        }

        $parts['path'] = $path;
        if (!isset($parts['port'])) {
            $parts['port'] = false;
        }

        $parts['unparsed'] = $trust_root;

        return $parts;
    }

    /**
     * Is this trust root sane?
     *
     * A trust root is sane if it is syntactically valid and it has a
     * reasonable domain name. Specifically, the domain name must be
     * more than one level below a standard TLD or more than two
     * levels below a two-letter tld.
     *
     * For example, '*.com' is not a sane trust root, but '*.foo.com'
     * is.  '*.co.uk' is not sane, but '*.bbc.co.uk' is.
     *
     * This check is not always correct, but it attempts to err on the
     * side of marking sane trust roots insane instead of marking
     * insane trust roots sane. For example, 'kink.fm' is marked as
     * insane even though it "should" (for some meaning of should) be
     * marked sane.
     *
     * This function should be used when creating OpenID servers to
     * alert the users of the server when a consumer attempts to get
     * the user to accept a suspicious trust root.
     *
     * @static
     * @param string $trust_root The trust root to check
     * @return bool $sanity Whether the trust root looks OK
     */
    static function isSane($trust_root)
    {
        $parts = Auth_OpenID_TrustRoot::_parse($trust_root);
        if ($parts === false) {
            return false;
        }

        // Localhost is a special case
        if ($parts['host'] == 'localhost') {
            return true;
        }

        $host_parts = explode('.', $parts['host']);
        if ($parts['wildcard']) {
            // Remove the empty string from the beginning of the array
            array_shift($host_parts);
        }

        if ($host_parts && !$host_parts[count($host_parts) - 1]) {
            array_pop($host_parts);
        }

        if (!$host_parts) {
            return false;
        }

        // Don't allow adjacent dots
        if (in_array('', $host_parts, true)) {
            return false;
        }

        // Get the top-level domain of the host. If it is not a valid TLD,
        // it's not sane.
        preg_match(Auth_OpenID___TLDs, $parts['host'], $matches);
        if (!$matches) {
            return false;
        }
        $tld = $matches[1];

        if (count($host_parts) == 1) {
            return false;
        }

        if ($parts['wildcard']) {
            // It's a 2-letter tld with a short second to last segment
            // so there needs to be more than two segments specified
            // (e.g. *.co.uk is insane)
            $second_level = $host_parts[count($host_parts) - 2];
            if (strlen($tld) == 2 && strlen($second_level) <= 3) {
                return count($host_parts) > 2;
            }
        }

        return true;
    }

    /**
     * Does this URL match the given trust root?
     *
     * Return whether the URL falls under the given trust root. This
     * does not check whether the trust root is sane. If the URL or
     * trust root do not parse, this function will return false.
     *
     * @param string $trust_root The trust root to match against
     *
     * @param string $url The URL to check
     *
     * @return bool $matches Whether the URL matches against the
     * trust root
     */
    static function match($trust_root, $url)
    {
        $trust_root_parsed = Auth_OpenID_TrustRoot::_parse($trust_root);
        $url_parsed = Auth_OpenID_TrustRoot::_parse($url);
        if (!$trust_root_parsed || !$url_parsed) {
            return false;
        }

        // Check hosts matching
        if ($url_parsed['wildcard']) {
            return false;
        }
        if ($trust_root_parsed['wildcard']) {
            $host_tail = $trust_root_parsed['host'];
            $host = $url_parsed['host'];
            if ($host_tail &&
                substr($host, -(strlen($host_tail))) != $host_tail &&
                substr($host_tail, 1) != $host) {
                return false;
            }
        } else {
            if ($trust_root_parsed['host'] != $url_parsed['host']) {
                return false;
            }
        }

        // Check path and query matching
        $base_path = $trust_root_parsed['path'];
        $path = $url_parsed['path'];
        if (!isset($trust_root_parsed['query'])) {
            if ($base_path != $path) {
                if (substr($path, 0, strlen($base_path)) != $base_path) {
                    return false;
                }
                if (substr($base_path, strlen($base_path) - 1, 1) != '/' &&
                    substr($path, strlen($base_path), 1) != '/') {
                    return false;
                }
            }
        } else {
            $base_query = $trust_root_parsed['query'];
            $query = @$url_parsed['query'];
            $qplus = substr($query, 0, strlen($base_query) + 1);
            $bqplus = $base_query . '&';
            if ($base_path != $path ||
                ($base_query != $query && $qplus != $bqplus)) {
                return false;
            }
        }

        // The port and scheme need to match exactly
        return ($trust_root_parsed['scheme'] == $url_parsed['scheme'] &&
                $url_parsed['port'] === $trust_root_parsed['port']);
    }
}

/*
 * If the endpoint is a relying party OpenID return_to endpoint,
 * return the endpoint URL. Otherwise, return None.
 *
 * This function is intended to be used as a filter for the Yadis
 * filtering interface.
 *
 * @see: C{L{openid.yadis.services}}
 * @see: C{L{openid.yadis.filters}}
 *
 * @param endpoint: An XRDS BasicServiceEndpoint, as returned by
 * performing Yadis dicovery.
 *
 * @returns: The endpoint URL or None if the endpoint is not a
 * relying party endpoint.
 */
function filter_extractReturnURL($endpoint)
{
    if ($endpoint->matchTypes(array(Auth_OpenID_RP_RETURN_TO_URL_TYPE))) {
        return $endpoint;
    } else {
        return null;
    }
}

function &Auth_OpenID_extractReturnURL(&$endpoint_list)
{
    $result = array();

    foreach ($endpoint_list as $endpoint) {
        if (filter_extractReturnURL($endpoint)) {
            $result[] = $endpoint;
        }
    }

    return $result;
}

/*
 * Is the return_to URL under one of the supplied allowed return_to
 * URLs?
 */
function Auth_OpenID_returnToMatches($allowed_return_to_urls, $return_to)
{
    foreach ($allowed_return_to_urls as $allowed_return_to) {
        // A return_to pattern works the same as a realm, except that
        // it's not allowed to use a wildcard. We'll model this by
        // parsing it as a realm, and not trying to match it if it has
        // a wildcard.

        $return_realm = Auth_OpenID_TrustRoot::_parse($allowed_return_to);
        if (// Parses as a trust root
            ($return_realm !== false) &&
            // Does not have a wildcard
            (!$return_realm['wildcard']) &&
            // Matches the return_to that we passed in with it
            (Auth_OpenID_TrustRoot::match($allowed_return_to, $return_to))) {
            return true;
        }
    }

    // No URL in the list matched
    return false;
}

/*
 * Given a relying party discovery URL return a list of return_to
 * URLs.
 */
function Auth_OpenID_getAllowedReturnURLs($relying_party_url, $fetcher,
              $discover_function=null)
{
    if ($discover_function === null) {
        $discover_function = array('Auth_Yadis_Yadis', 'discover');
    }

    $xrds_parse_cb = array('Auth_OpenID_ServiceEndpoint', 'consumerFromXRDS');

    list($rp_url_after_redirects, $endpoints) =
        Auth_Yadis_getServiceEndpoints($relying_party_url, $xrds_parse_cb,
                                       $discover_function, $fetcher);

    if ($rp_url_after_redirects != $relying_party_url) {
        // Verification caused a redirect
        return false;
    }

    call_user_func_array($discover_function,
                         array($relying_party_url, &$fetcher));

    $return_to_urls = array();
    $matching_endpoints = Auth_OpenID_extractReturnURL($endpoints);

    foreach ($matching_endpoints as $e) {
        $return_to_urls[] = $e->server_url;
    }

    return $return_to_urls;
}

/*
 * Verify that a return_to URL is valid for the given realm.
 *
 * This function builds a discovery URL, performs Yadis discovery on
 * it, makes sure that the URL does not redirect, parses out the
 * return_to URLs, and finally checks to see if the current return_to
 * URL matches the return_to.
 *
 * @return true if the return_to URL is valid for the realm
 */
function Auth_OpenID_verifyReturnTo($realm_str, $return_to, $fetcher,
              $_vrfy='Auth_OpenID_getAllowedReturnURLs')
{
    $disco_url = Auth_OpenID_TrustRoot::buildDiscoveryURL($realm_str);

    if ($disco_url === false) {
        return false;
    }

    $allowable_urls = call_user_func_array($_vrfy,
                           array($disco_url, $fetcher));

    // The realm_str could not be parsed.
    if ($allowable_urls === false) {
        return false;
    }

    if (Auth_OpenID_returnToMatches($allowable_urls, $return_to)) {
        return true;
    } else {
        return false;
    }
}


code:
<?php

/**
 * URI normalization routines.
 *
 * @package OpenID
 * @author JanRain, Inc. <openid@janrain.com>
 * @copyright 2005-2008 Janrain, Inc.
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
 */

require_once 'Auth/Yadis/Misc.php';

// from appendix B of rfc 3986 (http://www.ietf.org/rfc/rfc3986.txt)
function Auth_OpenID_getURIPattern()
{
    return '&^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?&';
}

function Auth_OpenID_getAuthorityPattern()
{
    return '/^([^@]*@)?([^:]*)(:.*)?/';
}

function Auth_OpenID_getEncodedPattern()
{
    return '/%([0-9A-Fa-f]{2})/';
}

# gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"
#
# sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
#                  / "*" / "+" / "," / ";" / "="
#
# unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
function Auth_OpenID_getURLIllegalCharRE()
{
    return "/([^-A-Za-z0-9:\/\?#\[\]@\!\$&'\(\)\*\+,;=\._~\%])/";
}

function Auth_OpenID_getUnreserved()
{
    $_unreserved = array();
    for ($i = 0; $i < 256; $i++) {
        $_unreserved[$i] = false;
    }

    for ($i = ord('A'); $i <= ord('Z'); $i++) {
        $_unreserved[$i] = true;
    }

    for ($i = ord('0'); $i <= ord('9'); $i++) {
        $_unreserved[$i] = true;
    }

    for ($i = ord('a'); $i <= ord('z'); $i++) {
        $_unreserved[$i] = true;
    }

    $_unreserved[ord('-')] = true;
    $_unreserved[ord('.')] = true;
    $_unreserved[ord('_')] = true;
    $_unreserved[ord('~')] = true;

    return $_unreserved;
}

function Auth_OpenID_getEscapeRE()
{
    $parts = array();
    foreach (array_merge(Auth_Yadis_getUCSChars(),
                         Auth_Yadis_getIPrivateChars()) as $pair) {
        list($m, $n) = $pair;
        $parts[] = sprintf("%s-%s", chr($m), chr($n));
    }

    return sprintf('[%s]', implode('', $parts));
}

function Auth_OpenID_pct_encoded_replace_unreserved($mo)
{
    $_unreserved = Auth_OpenID_getUnreserved();

    $i = intval($mo[1], 16);
    if ($_unreserved[$i]) {
        return chr($i);
    } else {
        return strtoupper($mo[0]);
    }

    return $mo[0];
}

function Auth_OpenID_pct_encoded_replace($mo)
{
    return chr(intval($mo[1], 16));
}

function Auth_OpenID_remove_dot_segments($path)
{
    $result_segments = array();

    while ($path) {
        if (Auth_Yadis_startswith($path, '../')) {
            $path = substr($path, 3);
        } else if (Auth_Yadis_startswith($path, './')) {
            $path = substr($path, 2);
        } else if (Auth_Yadis_startswith($path, '/./')) {
            $path = substr($path, 2);
        } else if ($path == '/.') {
            $path = '/';
        } else if (Auth_Yadis_startswith($path, '/../')) {
            $path = substr($path, 3);
            if ($result_segments) {
                array_pop($result_segments);
            }
        } else if ($path == '/..') {
            $path = '/';
            if ($result_segments) {
                array_pop($result_segments);
            }
        } else if (($path == '..') ||
                   ($path == '.')) {
            $path = '';
        } else {
            $i = 0;
            if ($path[0] == '/') {
                $i = 1;
            }
            $i = strpos($path, '/', $i);
            if ($i === false) {
                $i = strlen($path);
            }
            $result_segments[] = substr($path, 0, $i);
            $path = substr($path, $i);
        }
    }

    return implode('', $result_segments);
}

function Auth_OpenID_urinorm($uri)
{
    $uri_matches = array();
    preg_match(Auth_OpenID_getURIPattern(), $uri, $uri_matches);

    if (count($uri_matches) < 9) {
        for ($i = count($uri_matches); $i <= 9; $i++) {
            $uri_matches[] = '';
        }
    }

    $illegal_matches = array();
    preg_match(Auth_OpenID_getURLIllegalCharRE(),
               $uri, $illegal_matches);
    if ($illegal_matches) {
        return null;
    }

    $scheme = $uri_matches[2];
    if ($scheme) {
        $scheme = strtolower($scheme);
    }

    $scheme = $uri_matches[2];
    if ($scheme === '') {
        // No scheme specified
        return null;
    }

    $scheme = strtolower($scheme);
    if (!in_array($scheme, array('http', 'https'))) {
        // Not an absolute HTTP or HTTPS URI
        return null;
    }

    $authority = $uri_matches[4];
    if ($authority === '') {
        // Not an absolute URI
        return null;
    }

    $authority_matches = array();
    preg_match(Auth_OpenID_getAuthorityPattern(),
               $authority, $authority_matches);
    if (count($authority_matches) === 0) {
        // URI does not have a valid authority
        return null;
    }

    if (count($authority_matches) < 4) {
        for ($i = count($authority_matches); $i <= 4; $i++) {
            $authority_matches[] = '';
        }
    }

    list($_whole, $userinfo, $host, $port) = $authority_matches;

    if ($userinfo === null) {
        $userinfo = '';
    }

    if (strpos($host, '%') !== -1) {
        $host = strtolower($host);
        $host = preg_replace_callback(
                  Auth_OpenID_getEncodedPattern(),
                  'Auth_OpenID_pct_encoded_replace', $host);
        // NO IDNA.
        // $host = unicode($host, 'utf-8').encode('idna');
    } else {
        $host = strtolower($host);
    }

    if ($port) {
        if (($port == ':') ||
            ($scheme == 'http' && $port == ':80') ||
            ($scheme == 'https' && $port == ':443')) {
            $port = '';
        }
    } else {
        $port = '';
    }

    $authority = $userinfo . $host . $port;

    $path = $uri_matches[5];
    $path = preg_replace_callback(
               Auth_OpenID_getEncodedPattern(),
               'Auth_OpenID_pct_encoded_replace_unreserved', $path);

    $path = Auth_OpenID_remove_dot_segments($path);
    if (!$path) {
        $path = '/';
    }

    $query = $uri_matches[6];
    if ($query === null) {
        $query = '';
    }

    $fragment = $uri_matches[8];
    if ($fragment === null) {
        $fragment = '';
    }

    return $scheme . '://' . $authority . $path . $query . $fragment;
}


code:
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | 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.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | 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.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id: SASL.php 286825 2009-08-05 06:23:42Z cweiske $

/**
* Client implementation of various SASL mechanisms
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('PEAR.php');

class Auth_SASL
{
    /**
    * Factory class. Returns an object of the request
    * type.
    *
    * @param string $type One of: Anonymous
    *                             Plain
    *                             CramMD5
    *                             DigestMD5
    *                     Types are not case sensitive
    */
    function &factory($type)
    {
        switch (strtolower($type)) {
            case 'anonymous':
                $filename  = 'Auth/SASL/Anonymous.php';
                $classname = 'Auth_SASL_Anonymous';
                break;

            case 'login':
                $filename  = 'Auth/SASL/Login.php';
                $classname = 'Auth_SASL_Login';
                break;

            case 'plain':
                $filename  = 'Auth/SASL/Plain.php';
                $classname = 'Auth_SASL_Plain';
                break;

            case 'external':
                $filename  = 'Auth/SASL/External.php';
                $classname = 'Auth_SASL_External';
                break;

            case 'crammd5':
                $filename  = 'Auth/SASL/CramMD5.php';
                $classname = 'Auth_SASL_CramMD5';
                break;

            case 'digestmd5':
                $filename  = 'Auth/SASL/DigestMD5.php';
                $classname = 'Auth_SASL_DigestMD5';
                break;

            default:
                return PEAR::raiseError('Invalid SASL mechanism type');
                break;
        }

        require_once($filename);
        $obj = new $classname();
        return $obj;
    }
}

?>


code:
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | 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.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | 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.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id: Anonymous.php 286825 2009-08-05 06:23:42Z cweiske $

/**
* Implmentation of ANONYMOUS SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_Anonymous extends Auth_SASL_Common
{
    /**
    * Not much to do here except return the token supplied.
    * No encoding, hashing or encryption takes place for this
    * mechanism, simply one of:
    *  o An email address
    *  o An opaque string not containing "@" that can be interpreted
    *    by the sysadmin
    *  o Nothing
    *
    * We could have some logic here for the second option, but this
    * would by no means create something interpretable.
    *
    * @param  string $token Optional email address or string to provide
    *                       as trace information.
    * @return string        The unaltered input token
    */
    function getResponse($token = '')
    {
        return $token;
    }
}
?>


code:
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | 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.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | 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.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id: Common.php 286825 2009-08-05 06:23:42Z cweiske $

/**
* Common functionality to SASL mechanisms
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

class Auth_SASL_Common
{
    /**
    * Function which implements HMAC MD5 digest
    *
    * @param  string $key  The secret key
    * @param  string $data The data to protect
    * @return string       The HMAC MD5 digest
    */
    function _HMAC_MD5($key, $data)
    {
        if (strlen($key) > 64) {
            $key = pack('H32', md5($key));
        }

        if (strlen($key) < 64) {
            $key = str_pad($key, 64, chr(0));
        }

        $k_ipad = substr($key, 0, 64) ^ str_repeat(chr(0x36), 64);
        $k_opad = substr($key, 0, 64) ^ str_repeat(chr(0x5C), 64);

        $inner  = pack('H32', md5($k_ipad . $data));
        $digest = md5($k_opad . $inner);

        return $digest;
    }
}
?>


code:
<?php
// +-----------------------------------------------------------------------+
// | Copyright (c) 2002-2003 Richard Heyes                                 |
// | 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.|
// | o The names of the authors may not be used to endorse or promote      |
// |   products derived from this software without specific prior written  |
// |   permission.                                                         |
// |                                                                       |
// | 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.  |
// |                                                                       |
// +-----------------------------------------------------------------------+
// | Author: Richard Heyes <richard@php.net>                               |
// +-----------------------------------------------------------------------+
//
// $Id: CramMD5.php 286825 2009-08-05 06:23:42Z cweiske $

/**
* Implmentation of CRAM-MD5 SASL mechanism
*
* @author  Richard Heyes <richard@php.net>
* @access  public
* @version 1.0
* @package Auth_SASL
*/

require_once('Auth/SASL/Common.php');

class Auth_SASL_CramMD5 extends Auth_SASL_Common
{
    /**
    * Implements the CRAM-MD5 SASL mechanism
    * This DOES NOT base64 encode the return value,
    * you will need to do that yourself.
    *
    * @param string $user      Username
    * @param string $pass      Password
    * @param string $challenge The challenge supplied by the server.
    *                          this should be already base64_decoded.
    *
    * @return string The string to pass back to the server, of the form
    *                "<user> <digest>". This is NOT base64_encoded.
    */
    function getResponse($user, $pass, $challenge)
    {
        return $user . ' ' . $this->_HMAC_MD5($pass, $challenge);
    }
}
?>