<?php
// PLEASE DO NOT EDIT THIS FILE UNLESS YOU KNOW WHAT YOU ARE DOING!

/**
 * phpMyID - A standalone, single user, OpenID Identity Provider
 *
 * @package phpMyID
 * @author CJ Niemira <siege (at) siege (dot) org>
 * @copyright 2006-2008
 * @license http://www.gnu.org/licenses/gpl.html GNU Public License
 * @url http://siege.org/projects/phpMyID
 * @version 0.9
 */


/**
 * Set a constant to indicate that phpMyID is running
 */
define('PHPMYID_STARTED', true);

/**
 * List the known types and modes
 * @name $known
 * @global array $GLOBALS['known']
 */
$GLOBALS['known'] = array(
	'assoc_types'	=> array('HMAC-SHA1'),

	'openid_modes'	=> array('accept',
				 'associate',
				 'authorize',
				 'cancel',
				 'checkid_immediate',
				 'checkid_setup',
				 'check_authentication',
				 'error',
				 'id_res',
				 'login',
				 'logout',
			 	 'test'),

	'session_types'	=> array('',
				 'DH-SHA1'),

	'bigmath_types' => array('DH-SHA1'),
);

/**
 * Defined by OpenID spec
 * @name $g
 * @global integer $GLOBALS['g']
 */
$GLOBALS['g'] = 2;

/**
 * Defined by OpenID spec
 * @name $p
 * @global integer $GLOBALS['p']
 */
$GLOBALS['p'] = '155172898181473697471232257763715539915724801966915404479707' .
'7953140576293785419175806512274236981889937278161526466314385615958256881888' .
'8995127215884267541995034125870655654980358010487053768147672651325574704076' .
'5857479291291572334510643245094715007229621094194349783925984760375594985848' .
'253359305585439638443';


// Runmode functions

/**
 * Allow the user to accept trust on a URL
 * @global array $profile
 */
function accept_mode () {
	global $profile;

	// this is a user session
	user_session();

	// the user needs refresh urls in their session to access this mode
	if (! isset($_SESSION['post_accept_url']) || ! isset($_SESSION['cancel_accept_url']) || ! isset($_SESSION['unaccepted_url']))
		error_500('You may not access this mode directly.');

	// has the user accepted the trust_root?
	$accepted = @strlen($_REQUEST['accepted'])
			? $_REQUEST['accepted']
			: null;

	// if so, refresh back to post_accept_url
	if ($accepted === 'yes') {
		$_SESSION['accepted_url'] = $_SESSION['unaccepted_url'];
		wrap_redirect($_SESSION['post_accept_url']);

	// if they rejected it, return to the client
	} elseif ($accepted === 'no') {
		wrap_redirect($_SESSION['cancel_accept_url']);
	}

	// if neither, offer the trust request
	$q = strpos($profile['req_url'], '?') ? '&' : '?';
	$yes = $profile['req_url'] . $q . 'accepted=yes';
	$no  = $profile['req_url'] . $q . 'accepted=no';

	wrap_html('The client site you are attempting to log into has requested that you trust the following URL:<br/><b>' . $_SESSION['unaccepted_url'] . '</b><br/><br/>Do you wish to continue?<br/><a href="' . $yes . '">Yes</a> | <a href="' . $no . '">No</a>');
}

/** * Perform an association with a consumer
 * @global array $known
 * @global array $profile
 * @global integer $g
 * @global integer $p
 */
function associate_mode () {
	global $g, $known, $p, $profile;

	// Validate the request
	if (! isset($_REQUEST['openid_mode']) || $_REQUEST['openid_mode'] != 'associate')
		error_400();

	// Get the request options, using defaults as necessary
	$assoc_type = (@strlen($_REQUEST['openid_assoc_type'])
		    && in_array($_REQUEST['openid_assoc_type'], $known['assoc_types']))
			? $_REQUEST['openid_assoc_type']
			: 'HMAC-SHA1';

	$session_type = (@strlen($_REQUEST['openid_session_type'])
		      && in_array($_REQUEST['openid_session_type'], $known['session_types']))
			? $_REQUEST['openid_session_type']
			: '';

	$dh_modulus = (@strlen($_REQUEST['openid_dh_modulus']))
		? long(base64_decode($_REQUEST['openid_dh_modulus']))
		: ($session_type == 'DH-SHA1'
			? $p
			: null);

	$dh_gen = (@strlen($_REQUEST['openid_dh_gen']))
		? long(base64_decode($_REQUEST['openid_dh_gen']))
		: ($session_type == 'DH-SHA1'
			? $g
			: null);

	$dh_consumer_public = (@strlen($_REQUEST['openid_dh_consumer_public']))
		? $_REQUEST['openid_dh_consumer_public']
		: ($session_type == 'DH-SHA1'
			? error_post('dh_consumer_public was not specified')
			: null);

	$lifetime = time() + $profile['lifetime'];

	// Create standard keys
	$keys = array(
		'assoc_type' => $assoc_type,
		'expires_in' => $profile['lifetime']
	);

	// If I can't handle bigmath, default to plaintext sessions
	if (in_array($session_type, $known['bigmath_types']) && $profile['use_bigmath'] === false)
		$session_type = null;

	// Add response keys based on the session type
	switch ($session_type) {
		case 'DH-SHA1':
			// Create the associate id and shared secret now
			list ($assoc_handle, $shared_secret) = new_assoc($lifetime);

			// Compute the Diffie-Hellman stuff
			$private_key = random($dh_modulus);
			$public_key = bmpowmod($dh_gen, $private_key, $dh_modulus);
			$remote_key = long(base64_decode($dh_consumer_public));
			$ss = bmpowmod($remote_key, $private_key, $dh_modulus);

			$keys['assoc_handle'] = $assoc_handle;
			$keys['session_type'] = $session_type;
			$keys['dh_server_public'] = base64_encode(bin($public_key));
			$keys['enc_mac_key'] = base64_encode(x_or(sha1_20(bin($ss)), $shared_secret));

			break;

		default:
			// Create the associate id and shared secret now
			list ($assoc_handle, $shared_secret) = new_assoc($lifetime);

			$keys['assoc_handle'] = $assoc_handle;
			$keys['mac_key'] = base64_encode($shared_secret);
	}

	// Return the keys
	wrap_kv($keys);
}


/**
 * Perform a user authorization
 * @global array $profile
 */
function authorize_mode () {
	global $profile;

	// this is a user session
	user_session();

	// the user needs refresh urls in their session to access this mode
	if (! isset($_SESSION['post_auth_url']) || ! isset($_SESSION['cancel_auth_url']))
		error_500('You may not access this mode directly.');

	// try to get the digest headers - what a PITA!
	if (function_exists('apache_request_headers') && ini_get('safe_mode') == false) {
		$arh = apache_request_headers();
		$hdr = isset($arh['Authorization']) ? $arh['Authorization'] : null;

	} elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
		$hdr = $_SERVER['PHP_AUTH_DIGEST'];

	} elseif (isset($_SERVER['HTTP_AUTHORIZATION'])) {
		$hdr = $_SERVER['HTTP_AUTHORIZATION'];

	} elseif (isset($_ENV['PHP_AUTH_DIGEST'])) {
		$hdr = $_ENV['PHP_AUTH_DIGEST'];

	} elseif (isset($_SERVER['Authorization'])) {
		$hdr = $_SERVER['Authorization'];

	} elseif (isset($_REQUEST['auth'])) {
		$hdr = stripslashes(urldecode($_REQUEST['auth']));

	} else {
		$hdr = null;
	}

	debug('Authorization header: ' . $hdr);
	$digest = substr($hdr,0,7) == 'Digest '
		?  substr($hdr, strpos($hdr, ' ') + 1)
		: $hdr;

	$stale = false;

	// is the user trying to log in?
	if (! is_null($digest) && $profile['authorized'] === false) {
		debug('Digest headers: ' . $digest);
		$hdr = array();

		// decode the Digest authorization headers
		preg_match_all('/(\w+)=(?:"([^"]+)"|([^\s,]+))/', $digest, $mtx, PREG_SET_ORDER);

		foreach ($mtx as $m)
			$hdr[$m[1]] = $m[2] ? $m[2] : $m[3];
		debug($hdr, 'Parsed digest headers:');

		if (! isset($_SESSION['failures']))
			$_SESSION['failures'] = 0;

		if (isset($_SESSION['uniqid']) && $hdr['nonce'] != $_SESSION['uniqid'])
			$stale = true;

		if (isset($_SESSION['uniqid']))
			unset($_SESSION['uniqid']);

		if ($profile['auth_username'] == $hdr['username'] && ! $stale) {

			// the entity body should always be null in this case
			$entity_body = '';
			$a1 = strtolower($profile['auth_password']);
			$a2 = $hdr['qop'] == 'auth-int'
				? md5(implode(':', array($_SERVER['REQUEST_METHOD'], $hdr['uri'], md5($entity_body))))
				: md5(implode(':', array($_SERVER['REQUEST_METHOD'], $hdr['uri'])));
			$ok = md5(implode(':', array($a1, $hdr['nonce'], $hdr['nc'], $hdr['cnonce'], $hdr['qop'], $a2)));

			// successful login!
			if ($hdr['response'] == $ok) {
				debug('Authentication successful');
				debug('User session is: ' . session_id());
				$_SESSION['auth_username'] = $hdr['username'];
				$_SESSION['auth_url'] = $profile['idp_url'];
				$profile['authorized'] = true;

				// return to the refresh url if they get in
				wrap_redirect($_SESSION['post_auth_url']);

			// failed login
			} else {
				$_SESSION['failures']++;
				debug('Login failed: ' . $hdr['response'] . ' != ' . $ok);
				debug('Fail count: ' . $_SESSION['failures']);
			}

		} elseif ($profile['auth_username'] != $hdr['username']) {
			$_SESSION['failures']++;
			debug('Bad username: ' . $hdr['username']);
			debug('Fail count: ' . $_SESSION['failures']);
		}

		// does this make too many failures?
		if (strcmp(hexdec($hdr['nc']), 4) > 0 || $_SESSION['failures'] > 4) {
			debug('Too many password failures');
			error_get($_SESSION['cancel_auth_url'], 'Too many password failures. Double check your authorization realm. You must restart your browser to try again.');
		}

	} elseif (isset($_SESSION['uniqid']) && is_null($digest) && $profile['authorized'] === false) {
		unset($_SESSION['uniqid']);
		error_500('Missing expected authorization header.');
	}

	// if we get this far the user is not authorized, so send the headers
	$uid = uniqid(mt_rand(1,9));
	$_SESSION['uniqid'] = $uid;

	debug('Prompting user to log in. Stale? ' . $stale);
	header('HTTP/1.0 401 Unauthorized');
	header(sprintf('WWW-Authenticate: Digest qop="auth-int, auth", realm="%s", domain="%s", nonce="%s", opaque="%s", stale="%s", algorithm="MD5"', $profile['auth_realm'], $profile['auth_domain'], $uid, md5($profile['auth_realm']), $stale ? 'true' : 'false'));
	$q = strpos($_SESSION['cancel_auth_url'], '?') ? '&' : '?';
	wrap_refresh($_SESSION['cancel_auth_url'] . $q . 'openid.mode=cancel');
}


/**
 *  Handle a consumer's request for cancellation.
 */
function cancel_mode () {
	wrap_html('Request cancelled.');
}


/**
 * Handle a consumer's request to see if the user is authenticated
 */
function check_authentication_mode () {
	// Validate the request
	if (! isset($_REQUEST['openid_mode']) || $_REQUEST['openid_mode'] != 'check_authentication')
		error_400();

	$assoc_handle = @strlen($_REQUEST['openid_assoc_handle'])
		? $_REQUEST['openid_assoc_handle']
		: error_post('Missing assoc_handle');

	$sig = @strlen($_REQUEST['openid_sig'])
		? $_REQUEST['openid_sig']
		: error_post('Missing sig');

	$signed = @strlen($_REQUEST['openid_signed'])
		? $_REQUEST['openid_signed']
		: error_post('Missing signed');

	// Prepare the return keys
	$keys = array(
		'openid.mode' => 'id_res'
	);

	// Invalidate the assoc handle if we need to
	if (@strlen($_REQUEST['openid_invalidate_handle'])) {
		destroy_assoc_handle($_REQUEST['openid_invalidate_handle']);

		$keys['invalidate_handle'] = $_REQUEST['openid_invalidate_handle'];
	}

	// Validate the sig by recreating the kv pair and signing
	$_REQUEST['openid_mode'] = 'id_res';
	$tokens = '';
	foreach (explode(',', $signed) as $param) {
		$post = preg_replace('/\./', '_', $param);
		$tokens .= sprintf("%s:%s\n", $param, $_REQUEST['openid_' . $post]);
	}

	// Add the sreg stuff, if we've got it
	if (isset($sreg_required)) {
		foreach (explode(',', $sreg_required) as $key) {
			if (! isset($sreg[$key]))
				continue;
			$skey = 'sreg.' . $key;

			$tokens .= sprintf("%s:%s\n", $skey, $sreg[$key]);
			$keys[$skey] = $sreg[$key];
			$fields[] = $skey;
		}
	}

	// Look up the consumer's shared_secret and timeout
	list ($shared_secret, $expires) = secret($assoc_handle);

	// if I can't verify the assoc_handle, or if it's expired
	if ($shared_secret == false || (is_numeric($expires) && $expires < time())) {
		$keys['is_valid'] = 'false';

	} else {
		$ok = base64_encode(hmac($shared_secret, $tokens));
		$keys['is_valid'] = ($sig == $ok) ? 'true' : 'false';
	}

	// Return the keys
	wrap_kv($keys);
}


/**
 * Handle a consumer's request to see if the end user is logged in
 * @global array $known
 * @global array $profile
 * @global array $sreg
 */
function checkid ( $wait ) {
	debug("checkid: wait? $wait");
	global $known, $profile, $sreg;

	// This is a user session
	user_session();

	// Get the options, use defaults as necessary
	$return_to = @strlen($_REQUEST['openid_return_to'])
		? $_REQUEST['openid_return_to']
		: error_400('Missing return_to');

	$identity = @strlen($_REQUEST['openid_identity'])
			? $_REQUEST['openid_identity']
			: error_get($return_to, 'Missing identity');

	$assoc_handle = @strlen($_REQUEST['openid_assoc_handle'])
			? $_REQUEST['openid_assoc_handle']
			: null;

	$trust_root = @strlen($_REQUEST['openid_trust_root'])
			? $_REQUEST['openid_trust_root']
			: $return_to;

	$sreg_required = @strlen($_REQUEST['openid_sreg_required'])
			? $_REQUEST['openid_sreg_required']
			: '';

	$sreg_optional = @strlen($_REQUEST['openid_sreg_optional'])
			? $_REQUEST['openid_sreg_optional']
			: '';

	// determine the cancel url
	$q = strpos($return_to, '?') ? '&' : '?';
	$cancel_url = $return_to . $q . 'openid.mode=cancel';

	// required and optional make no difference to us
	$sreg_required .= ',' . $sreg_optional;

	// do the trust_root analysis
	if ($trust_root != $return_to) {
		// the urls are not the same, be sure return decends from trust
		if (! url_descends($return_to, $trust_root))
			error_500('Invalid trust_root: "' . $trust_root . '"');

	}

	// transfer the user to the url accept mode if they're paranoid
	if ($wait == 1 && isset($profile['paranoid']) && $profile['paranoid'] === true && (! session_is_registered('accepted_url') || $_SESSION['accepted_url'] != $trust_root)) {
		$_SESSION['cancel_accept_url'] = $cancel_url;
		$_SESSION['post_accept_url'] = $profile['req_url'];
		$_SESSION['unaccepted_url'] = $trust_root;

		debug('Transferring to acceptance mode.');
		debug('Cancel URL: ' . $_SESSION['cancel_accept_url']);
		debug('Post URL: ' . $_SESSION['post_accept_url']);

		$q = strpos($profile['idp_url'], '?') ? '&' : '?';
		wrap_redirect($profile['idp_url'] . $q . 'openid.mode=accept');
	}

	// make sure i am this identifier
	if ($identity != $profile['idp_url']) {
		debug("Invalid identity: $identity");
		debug("IdP URL: " . $profile['idp_url']);
		error_get($return_to, "Invalid identity: '$identity'");
	}

	// begin setting up return keys
	$keys = array(
		'mode' => 'id_res'
	);

	// if the user is not logged in, transfer to the authorization mode
	if ($profile['authorized'] === false || $identity != $_SESSION['auth_url']) {
		// users can only be logged in to one url at a time
		$_SESSION['auth_username'] = null;
		$_SESSION['auth_url'] = null;

		if ($wait) {
			unset($_SESSION['uniqid']);

			$_SESSION['cancel_auth_url'] = $cancel_url;
			$_SESSION['post_auth_url'] = $profile['req_url'];

			debug('Transferring to authorization mode.');
			debug('Cancel URL: ' . $_SESSION['cancel_auth_url']);
			debug('Post URL: ' . $_SESSION['post_auth_url']);

			$q = strpos($profile['idp_url'], '?') ? '&' : '?';
			wrap_redirect($profile['idp_url'] . $q . 'openid.mode=authorize');
		} else {
			$keys['user_setup_url'] = $profile['idp_url'];
		}

	// the user is logged in
	} else {
		// remove the refresh URLs if set
		unset($_SESSION['cancel_auth_url']);
		unset($_SESSION['post_auth_url']);

		// check the assoc handle
		list($shared_secret, $expires) = secret($assoc_handle);

		// if I can't verify the assoc_handle, or if it's expired
		if ($shared_secret == false || (is_numeric($expires) && $expires < time())) {
			debug("Session expired or missing key: $expires < " . time());
			if ($assoc_handle != null) {
				$keys['invalidate_handle'] = $assoc_handle;
				destroy_assoc_handle($assoc_handle);
			}

			$lifetime = time() + $profile['lifetime'];
			list ($assoc_handle, $shared_secret) = new_assoc($lifetime);
		}

		$keys['identity'] = $profile['idp_url'];
		$keys['assoc_handle'] = $assoc_handle;
		$keys['return_to'] = $return_to;

		$fields = array_keys($keys);
		$tokens = '';
		foreach ($fields as $key)
			$tokens .= sprintf("%s:%s\n", $key, $keys[$key]);

		// add sreg keys
		foreach (explode(',', $sreg_required) as $key) {
			if (! isset($sreg[$key]))
				continue;
			$skey = 'sreg.' . $key;

			$tokens .= sprintf("%s:%s\n", $skey, $sreg[$key]);
			$keys[$skey] = $sreg[$key];
			$fields[] = $skey;
		}

		$keys['signed'] = implode(',', $fields);
		$keys['sig'] = base64_encode(hmac($shared_secret, $tokens));
	}

	wrap_keyed_redirect($return_to, $keys);
}


/**
 * Handle a consumer's request to see if the user is already logged in
 */
function checkid_immediate_mode () {
	if (! isset($_REQUEST['openid_mode']) || $_REQUEST['openid_mode'] != 'checkid_immediate')
		error_500();

	checkid(false);
}


/**
 * Handle a consumer's request to see if the user is logged in, but be willing
 * to wait for them to perform a login if they're not
 */
function checkid_setup_mode () {
	if (! isset($_REQUEST['openid_mode']) || $_REQUEST['openid_mode'] != 'checkid_setup')
		error_500();

	checkid(true);
}


/**
 * Handle errors
 */
function error_mode () {
	isset($_REQUEST['openid_error']) 
		? wrap_html($_REQUEST['openid_error'])
		: error_500();
}


/**
 * Show a user if they are logged in or not
 * @global array $profile
 */
function id_res_mode () {
	global $profile;

	user_session();

	if ($profile['authorized'])
		wrap_html('You are logged in as ' . $_SESSION['auth_username']);

	wrap_html('You are not logged in');
}


/**
 * Allow a user to perform a static login
 * @global array $profile
 */
function login_mode () {
	global $profile;

	user_session();

	if ($profile['authorized'])
		id_res_mode();

	$keys = array(
		'mode' => 'checkid_setup',
		'identity' => $profile['idp_url'],
		'return_to' => $profile['idp_url']
	);

	wrap_keyed_redirect($profile['idp_url'], $keys);
}


/**
 * Allow a user to perform a static logout
 * @global array $profile
 */
function logout_mode () {
	global $profile;

	user_session();

	if (! $profile['authorized'])
		wrap_html('You were not logged in');

	$_SESSION = array();
	session_destroy();
	debug('User session destroyed.');

	header('HTTP/1.0 401 Unauthorized');
	wrap_redirect($profile['idp_url']);
}


/**
 * The default information screen
 * @global array $profile
 */
function no_mode () {
	global $profile;

	wrap_html('This is an OpenID server endpoint. For more information, see http://openid.net/<br/>Server: <b>' . $profile['idp_url'] . '</b><br/>Realm: <b>' . $profile['php_realm'] . '</b><br/><a href="' . $profile['idp_url'] . '?openid.mode=login">Login</a>' . ($profile['allow_test'] === true ? ' | <a href="' . $profile['idp_url'] . '?openid.mode=test">Test</a>' : null));
}


/**
 * Testing for setup
 * @global array $profile
 */
function test_mode () {
	global $profile, $p, $g;

	if ($profile['allow_test'] != true)
		error_403();

	@ini_set('max_execution_time', 180);

	$test_expire = time() + 120;
	$test_ss_enc = 'W7hvmld2yEYdDb0fHfSkKhQX+PM=';
	$test_ss = base64_decode($test_ss_enc);
	$test_token = "alpha:bravo\ncharlie:delta\necho:foxtrot";
	$test_server_private = '11263846781670293092494395517924811173145217135753406847875706165886322533899689335716152496005807017390233667003995430954419468996805220211293016296351031812246187748601293733816011832462964410766956326501185504714561648498549481477143603650090931135412673422192550825523386522507656442905243832471167330268';
	$test_client_public = base64_decode('AL63zqI5a5p8HdXZF5hFu8p+P9GOb816HcHuvNOhqrgkKdA3fO4XEzmldlb37nv3+xqMBgWj6gxT7vfuFerEZLBvuWyVvR7IOGZmx0BAByoq3fxYd3Fpe2Coxngs015vK37otmH8e83YyyGo5Qua/NAf13yz1PVuJ5Ctk7E+YdVc');

	$res = array();

	// bcmath
	$res['bcmath'] = extension_loaded('bcmath')
		? 'pass' : 'warn - not loaded';

	// gmp
	if ($profile['allow_gmp']) {
		$res['gmp'] = extension_loaded('gmp')
		? 'pass' : 'warn - not loaded';
	} else {
		$res['gmp'] = 'pass - n/a';
	}

	// sys_get_temp_dir
	$res['logfile'] = is_writable($profile['logfile'])
		? 'pass' : "warn - log is not writable";

	// session & new_assoc
	user_session();
	list($test_assoc, $test_new_ss) = new_assoc($test_expire);
	$res['session'] = ($test_assoc != session_id())
		? 'pass' : 'fail';

	// secret
	@session_unregister('shared_secret');
	list($check, $check2) = secret($test_assoc);
	$res['secret'] = ($check == $test_new_ss)
		? 'pass' : 'fail';

	// expire
	$res['expire'] = ($check2 <= $test_expire)
		? 'pass' : 'fail';

	// base64
	$res['base64'] = (base64_encode($test_ss) == $test_ss_enc)
		? 'pass' : 'fail';

	// hmac
	$test_sig = base64_decode('/VXgHvZAOdoz/OTa5+XJXzSGhjs=');
	$check = hmac($test_ss, $test_token);
	$res['hmac'] = ($check == $test_sig)
		? 'pass' : sprintf("fail - '%s'", base64_encode($check));

	if ($profile['use_bigmath']) {
		// bigmath powmod
		$test_server_public = '102773334773637418574009974502372885384288396853657336911033649141556441102566075470916498748591002884433213640712303846640842555822818660704173387461364443541327856226098159843042567251113889701110175072389560896826887426539315893475252988846151505416694218615764823146765717947374855806613410142231092856731';
		$check = bmpowmod($g, $test_server_private, $p);
		$res['bmpowmod-1'] = ($check == $test_server_public)
			? 'pass' : sprintf("fail - '%s'", $check);

		// long
		$test_client_long = '133926731803116519408547886573524294471756220428015419404483437186057383311250738749035616354107518232016420809434801736658109316293127101479053449990587221774635063166689561125137927607200322073086097478667514042144489248048756916881344442393090205172004842481037581607299263456852036730858519133859409417564';
		$res['long'] = (long($test_client_public) == $test_client_long)
			? 'pass' : 'fail';

		// bigmath powmod 2
		$test_client_share = '19333275433742428703546496981182797556056709274486796259858099992516081822015362253491867310832140733686713353304595602619444380387600756677924791671971324290032515367930532292542300647858206600215875069588627551090223949962823532134061941805446571307168890255137575975911397744471376862555181588554632928402';
		$check = bmpowmod($test_client_long, $test_server_private, $p);
		$res['bmpowmod-2'] = ($check == $test_client_share)
			? 'pass' : sprintf("fail - '%s'", $check);

		// bin
		$test_client_mac_s1 = base64_decode('G4gQQkYM6QmAzhKbVKSBahFesPL0nL3F2MREVwEtnVRRYI0ifl9zmPklwTcvURt3QTiGBd+9Dn3ESLk5qka6IO5xnILcIoBT8nnGVPiOZvTygfuzKp4tQ2mXuIATJoa7oXRGmBWtlSdFapH5Zt6NJj4B83XF/jzZiRwdYuK4HJI=');
		$check = bin($test_client_share);
		$res['bin'] = ($check == $test_client_mac_s1)
			? 'pass' : sprintf("fail - '%s'", base64_encode($check));

	} else {
		$res['bigmath'] = 'fail - big math functions are not available.';
	}

	// sha1_20
	$test_client_mac_s1 = base64_decode('G4gQQkYM6QmAzhKbVKSBahFesPL0nL3F2MREVwEtnVRRYI0ifl9zmPklwTcvURt3QTiGBd+9Dn3ESLk5qka6IO5xnILcIoBT8nnGVPiOZvTygfuzKp4tQ2mXuIATJoa7oXRGmBWtlSdFapH5Zt6NJj4B83XF/jzZiRwdYuK4HJI=');
	$test_client_mac_s2 = base64_decode('0Mb2t9d/HvAZyuhbARJPYdx3+v4=');
	$check = sha1_20($test_client_mac_s1);
	$res['sha1_20'] = ($check == $test_client_mac_s2)
		? 'pass' : sprintf("fail - '%s'", base64_encode($check));

	// x_or
	$test_client_mac_s3 = base64_decode('i36ZLYAJ1rYEx1VEHObrS8hgAg0=');
	$check = x_or($test_client_mac_s2, $test_ss);
	$res['x_or'] = ($check == $test_client_mac_s3)
		? 'pass' : sprintf("fail - '%s'", base64_encode($check));

	$out = "<table border=1 cellpadding=4>\n";
	foreach ($res as $test => $stat) {
		$code = substr($stat, 0, 4);
		$color = ($code == 'pass') ? '#9f9'
			: (($code == 'warn') ? '#ff9' : '#f99');
		$out .= sprintf("<tr><th>%s</th><td style='background:%s'>%s</td></tr>\n", $test, $color, $stat);
	}
	$out .= "</table>";

	wrap_html( $out );
}


// Support functions

/**
 * Prefix the keys of an array with  'openid.'
 * @param array $array
 * @return array
 */
function append_openid ($array) {
	$keys = array_keys($array);
	$vals = array_values($array);

	$r = array();
	for ($i=0; $i<sizeof($keys); $i++)
		$r['openid.' . $keys[$i]] = $vals[$i];
	return $r;
}

/**
 * Create a big math addition function
 * @param string $l
 * @param string $r
 * @return string
 * @url http://www.icosaedro.it/bigint Inspired by
 */
function bmadd($l, $r) {
	if (function_exists('bcadd'))
		return bcadd($l, $r);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_add($l, $r));

	$l = strval($l); $r = strval($r);
	$ll = strlen($l); $rl = strlen($r);
	if ($ll < $rl) {
		$l = str_repeat("0", $rl-$ll) . $l;
		$o = $rl;

	} elseif ( $ll > $rl ) {
		$r = str_repeat("0", $ll-$rl) . $r;
		$o = $ll;

	} else {
		$o = $ll;
	}

	$v = '';
	$carry = 0;

	for ($i = $o-1; $i >= 0; $i--) {
		$d = (int)$l[$i] + (int)$r[$i] + $carry;
		if ($d <= 9) {
			$carry = 0;

		} else {
			$carry = 1;
			$d -= 10;
		}
		$v = (string) $d . $v;
	}

	if ($carry > 0)
		$v = "1" . $v;

	return $v;
}

/**
 * Create a big math comparison function
 * @param string $l
 * @param string $r
 * @return string
 */
function bmcomp($l, $r) {
	if (function_exists('bccomp'))
		return bccomp($l, $r);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_cmp($l, $r));

	$l = strval($l); $r = strval($r);
	$ll = strlen($l); $lr = strlen($r);
	if ($ll != $lr)
		return ($ll > $lr) ? 1 : -1;

	return strcmp($l, $r);
}

/**
 * Create a big math division function
 * @param string $l
 * @param string $r
 * @param int $z
 * @return string
 * @url http://www.icosaedro.it/bigint Inspired by
 */
function bmdiv($l, $r, $z = 0) {
	if (function_exists('bcdiv'))
		return ($z == 0) ? bcdiv($l, $r) : bcmod($l, $r);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(($z == 0) ? gmp_div_q($l, $r) : gmp_mod($l, $r));

	$l = strval($l); $r = strval($r);
	$v = '0';

	while (true) {
		if( bmcomp($l, $r) < 0 )
			break;

		$delta = strlen($l) - strlen($r);
		if ($delta >= 1) {
			$zeroes = str_repeat("0", $delta);
			$r2 = $r . $zeroes;

			if (strcmp($l, $r2) >= 0) {
				$v = bmadd($v, "1" . $zeroes);
				$l = bmsub($l, $r2);

			} else {
				$zeroes = str_repeat("0", $delta - 1);
				$v = bmadd($v, "1" . $zeroes);
				$l = bmsub($l, $r . $zeroes);
			}

		} else {
			$l = bmsub($l, $r);
			$v = bmadd($v, "1");
		}
	}

	return ($z == 0) ? $v : $l;
}

/**
 * Create a big math multiplication function
 * @param string $l
 * @param string $r
 * @return string
 * @url http://www.icosaedro.it/bigint Inspired by
 */
function bmmul($l, $r) {
	if (function_exists('bcmul'))
		return bcmul($l, $r);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_mul($l, $r));

	$l = strval($l); $r = strval($r);

	$v = '0';
	$z = '';

	for( $i = strlen($r)-1; $i >= 0; $i-- ){
		$bd = (int) $r[$i];
		$carry = 0;
		$p = "";
		for( $j = strlen($l)-1; $j >= 0; $j-- ){
			$ad = (int) $l[$j];
			$pd = $ad * $bd + $carry;
			if( $pd <= 9 ){
				$carry = 0;
			} else {
				$carry = (int) ($pd / 10);
				$pd = $pd % 10;
			}
			$p = (string) $pd . $p;
		}
		if( $carry > 0 )
			$p = (string) $carry . $p;
		$p = $p . $z;
		$z .= "0";
		$v = bmadd($v, $p);
	}

	return $v;
}

/**
 * Create a big math modulus function
 * @param string $value
 * @param string $mod 
 * @return string
 */
function bmmod( $value, $mod ) {
	if (function_exists('bcmod'))
		return bcmod($value, $mod);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_mod($value, $mod));

	$r = bmdiv($value, $mod, 1);
	return $r;
}

/**
 * Create a big math power function
 * @param string $value
 * @param string $exponent
 * @return string
 */
function bmpow ($value, $exponent) {
	if (function_exists('bcpow'))
		return bcpow($value, $exponent);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_pow($value, $exponent));

	$r = '1';
	while ($exponent) {
		$r = bmmul($r, $value, 100);
		$exponent--;
	}
	return (string)rtrim($r, '0.');
}

/**
 * Create a big math 'powmod' function
 * @param string $value
 * @param string $exponent
 * @param string $mod 
 * @return string
 * @url http://php.net/manual/en/function.bcpowmod.php#72704 Borrowed from
 */
function bmpowmod ($value, $exponent, $mod) {
	if (function_exists('bcpowmod'))
		return bcpowmod($value, $exponent, $mod);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_powm($value, $exponent, $mod));

	$r = '';
	while ($exponent != '0') {
		$t = bmmod($exponent, '4096');
		$r = substr("000000000000" . decbin(intval($t)), -12) . $r;
		$exponent = bmdiv($exponent, '4096');
	}

	$r = preg_replace("!^0+!","",$r);

	if ($r == '')
		$r = '0';
	$value = bmmod($value, $mod);
	$erb = strrev($r);
	$q = '1';
	$a[0] = $value;

	for ($i = 1; $i < strlen($erb); $i++) {
		$a[$i] = bmmod( bmmul($a[$i-1], $a[$i-1]), $mod );
	}

	for ($i = 0; $i < strlen($erb); $i++) {
		if ($erb[$i] == "1") {
			$q = bmmod( bmmul($q, $a[$i]), $mod );
		}
	}

	return($q);
}

/**
 * Create a big math subtraction function
 * @param string $l
 * @param string $r
 * @return string
 * @url http://www.icosaedro.it/bigint Inspired by
 */
function bmsub($l, $r) {
	if (function_exists('bcsub'))
		return bcsub($l, $r);

	global $profile;
	if ($profile['use_gmp'])
		return gmp_strval(gmp_sub($l, $r));


	$l = strval($l); $r = strval($r);
	$ll = strlen($l); $rl = strlen($r);

	if ($ll < $rl) {
		$l = str_repeat("0", $rl-$ll) . $l;
		$o = $rl;
	} elseif ( $ll > $rl ) {
		$r = str_repeat("0", $ll-$rl) . (string)$r;
		$o = $ll;
	} else {
		$o = $ll;
	}

	if (strcmp($l, $r) >= 0) {
		$sign = '';
	} else {
		$x = $l; $l = $r; $r = $x;
		$sign = '-';
	}

	$v = '';
	$carry = 0;

	for ($i = $o-1; $i >= 0; $i--) {
		$d = ($l[$i] - $r[$i]) - $carry;
		if ($d < 0) {
			$carry = 1;
			$d += 10;
		} else {
			$carry = 0;
		}
		$v = (string) $d . $v;
	}

	return $sign . ltrim($v, '0');
}


/**
 * Get a binary value
 * @param integer $n
 * @return string
 * @url http://openidenabled.com Borrowed from PHP-OpenID
 */
function bin ($n) {
	$bytes = array();
	while (bmcomp($n, 0) > 0) {
		array_unshift($bytes, bmmod($n, 256));
		$n = bmdiv($n, bmpow(2,8));
	}

	if ($bytes && ($bytes[0] > 127))
		array_unshift($bytes, 0);

	$b = '';
	foreach ($bytes as $byte)
		$b .= pack('C', $byte);

	return $b;
}


/**
 * Debug logging
 * @param mixed $x
 * @param string $m 
 */
function debug ($x, $m = null) {
	global $profile;

	if (! isset($profile['debug']) || $profile['debug'] === false)
		return true;

	if (! is_writable(dirname($profile['logfile'])) &! is_writable($profile['logfile']))
		error_500('Cannot write to debug log: ' . $profile['logfile']);

	if (is_array($x)) {
		ob_start();
		print_r($x);
		$x = $m . ($m != null ? "\n" : '') . ob_get_clean();

	} else {
		$x .= "\n";
	}

	error_log($x . "\n", 3, $profile['logfile']);
}


/**
 * Destroy a consumer's assoc handle
 * @param string $id
 */
function destroy_assoc_handle ( $id ) {
	debug("Destroying session: $id");

	$sid = session_id();
	session_write_close();

	session_id($id);
	session_start();
	session_destroy();

	session_id($sid);
	session_start();
}


/**
 * Return an error message to the user
 * @param string $message
 */
function error_400 ( $message = 'Bad Request' ) {
	header("HTTP/1.1 400 Bad Request");
	wrap_html($message);
}


/**
 * Return an error message to the user
 * @param string $message
 */
function error_403 ( $message = 'Forbidden' ) {
	header("HTTP/1.1 403 Forbidden");
	wrap_html($message);
}


/**
 * Return an error message to the user
 * @param string $message
 */
function error_500 ( $message = 'Internal Server Error' ) {
	header("HTTP/1.1 500 Internal Server Error");
	wrap_html($message);
}


/**
 * Return an error message to the consumer
 * @param string $message
 */
function error_get ( $url, $message = 'Bad Request') {
	wrap_keyed_redirect($url, array('mode' => 'error', 'error' => $message));
}


/**
 * Return an error message to the consumer
 * @param string $message
 */
function error_post ( $message = 'Bad Request' ) {
	header("HTTP/1.1 400 Bad Request");
	echo ('error:' . $message);
	exit(0);
}


/**
 * Do an HMAC
 * @param string $key
 * @param string $data
 * @param string $hash
 * @return string
 * @url http://php.net/manual/en/function.sha1.php#39492 Borrowed from
 */
function hmac($key, $data, $hash = 'sha1_20') {
	$blocksize=64;

	if (strlen($key) > $blocksize)
		$key = $hash($key);

	$key = str_pad($key, $blocksize,chr(0x00));
	$ipad = str_repeat(chr(0x36),$blocksize);
	$opad = str_repeat(chr(0x5c),$blocksize);

	$h1 = $hash(($key ^ $ipad) . $data);
	$hmac = $hash(($key ^ $opad) . $h1);
	return $hmac;
}


if (! function_exists('http_build_query')) {
/**
 * Create function if missing
 * @param array $array
 * @return string
 */
function http_build_query ($array) {
	$r = array();
	foreach ($array as $key => $val)
		$r[] = sprintf('%s=%s', urlencode($key), urlencode($val));
	return implode('&', $r);
}}


/**
 * Turn a binary back into a long
 * @param string $b
 * @return integer
 * @url http://openidenabled.com Borrowed from PHP-OpenID
 */
function long($b) {
	$bytes = array_merge(unpack('C*', $b));
	$n = 0;
	foreach ($bytes as $byte) {
		$n = bmmul($n, bmpow(2,8));
		$n = bmadd($n, $byte);
	}
	return $n;
}


/**
 * Create a new consumer association
 * @param integer $expiration
 * @return array
 */
function new_assoc ( $expiration ) {
	if (isset($_SESSION) && is_array($_SESSION)) {
		$sid = session_id();
		$dat = session_encode();
		session_write_close();
	}

	session_start();
	session_regenerate_id('false');

	$id = session_id();
	$shared_secret = new_secret();
	debug('Started new assoc session: ' . $id);

	$_SESSION = array();
	$_SESSION['expiration'] = $expiration;
	$_SESSION['shared_secret'] = base64_encode($shared_secret);

	session_write_close();

	if (isset($sid)) {
		session_id($sid);
		session_start();
		$_SESSION = array();
		session_decode($dat);
	}

	return array($id, $shared_secret);
}


/**
 * Create a new shared secret
 * @return string
 */
function new_secret () {
	$r = '';
	for($i=0; $i<20; $i++)
		$r .= chr(mt_rand(0, 255));

	debug("Generated new key: hash = '" . md5($r) . "', length = '" . strlen($r) . "'");
	return $r;
}


/**
 * Random number generation
 * @param integer max
 * @return integer
 */
function random ( $max ) {
	if (strlen($max) < 4)
		return mt_rand(1, $max - 1);

	$r = '';
	for($i=1; $i<strlen($max) - 1; $i++)
		$r .= mt_rand(0,9);
	$r .= mt_rand(1,9);

	return $r;
}

/**
 * Get the shared secret and expiration time for the specified assoc_handle
 * @param string $handle assoc_handle to look up
 * @return array (shared_secret, expiration_time)
 */
function secret ( $handle ) {
	if (! preg_match('/^\w+$/', $handle))
		return array(false, 0);

	if (isset($_SESSION) && is_array($_SESSION)) {
		$sid = session_id();
		$dat = session_encode();
		session_write_close();
	}

	session_id($handle);
	session_start();
	debug('Started session to acquire key: ' . session_id());

	$secret = session_is_registered('shared_secret')
		? base64_decode($_SESSION['shared_secret'])
		: false;

	$expiration = session_is_registered('expiration')
		? $_SESSION['expiration']
		: null;

	session_write_close();

	if (isset($sid)) {
		session_id($sid);
		session_start();
		$_SESSION = array();
		session_decode($dat);
	}

	debug("Found key: hash = '" . md5($secret) . "', length = '" . strlen($secret) . "', expiration = '$expiration'");
	return array($secret, $expiration);
}


/**
 * Do an internal self check
 * @global array $profile
 * @global array $sreg
 */
function self_check () {
	global $profile, $sreg;

	if (! isset($profile) || ! is_array($profile))
		error_500('No configuration found, you shouldn\'t access this file directly.');

	if (version_compare(phpversion(), '4.2.0', 'lt'))
		error_500('The required minimum version of PHP is 4.2.0, you are running ' . phpversion());

	$extension_r = array('session', 'pcre');
	foreach ($extension_r as $x) {
		if (! extension_loaded($x))
			@dl($x);
		if (! extension_loaded($x))
			error_500("Required extension '$x' is missing.");
	}

	$extension_b = array('suhosin');
	foreach ($extension_b as $x) {
		if (extension_loaded($x) &! $profile["allow_$x"])
			error_500("phpMyID is not compatible with '$x'");
	}

	$keys = array('auth_username', 'auth_password');
	foreach ($keys as $key) {
		if (! array_key_exists($key, $profile))
			error_500("'$key' is missing from your profile.");
	}

	if (! isset($sreg) || ! is_array($sreg))
		$sreg = array();
}


/**
 * Do SHA1 20 byte encryption
 * @param string $v
 * @return string
 * @url http://openidenabled.com Borrowed from PHP-OpenID
 */
function sha1_20 ($v) {
	if (version_compare(phpversion(), '5.0.0', 'ge'))
		return sha1($v, true);

	$hex = sha1($v);
	$r = '';
	for ($i = 0; $i < 40; $i += 2) {
		$hexcode = substr($hex, $i, 2);
		$charcode = base_convert($hexcode, 16, 10);
		$r .= chr($charcode);
	}
	return $r;
}


/**
 * Look for the point of differentiation in two strings
 * @param string $a
 * @param string $b
 * @return int
 */
function str_diff_at ($a, $b) {
	if ($a == $b)
		return -1;
	$n = min(strlen($a), strlen($b));
	for ($i = 0; $i < $n; $i++)
		if ($a[$i] != $b[$i])
			return $i;
	return $n;
}


if (! function_exists('sys_get_temp_dir') && ini_get('open_basedir') == false) {
/**
 * Create function if missing
 * @return string
 */
function sys_get_temp_dir () {
	$keys = array('TMP', 'TMPDIR', 'TEMP');
	foreach ($keys as $key) {
		if (isset($_ENV[$key]) && is_dir($_ENV[$key]) && is_writable($_ENV[$key]))
			return realpath($_ENV[$key]);
	}

	$tmp = tempnam(false, null);
	if (file_exists($tmp)) {
		$dir = realpath(dirname($tmp));
		unlink($tmp);
		return realpath($dir);
	}

	return realpath(dirname(__FILE__));
}} elseif (! function_exists('sys_get_temp_dir')) {
function sys_get_temp_dir () {
	return realpath(dirname(__FILE__));
}}


/**
 * Determine if a child URL actually decends from the parent, and that the
 * parent is a good URL.
 * THIS IS EXPERIMENTAL
 * @param string $parent
 * @param string $child
 * @return bool
 */
function url_descends ( $child, $parent ) {
	if ($child == $parent)
		return true;

	$keys = array();
	$parts = array();

	$req = array('scheme', 'host');
	$bad = array('fragment', 'pass', 'user');

	foreach (array('parent', 'child') as $name) {
		$parts[$name] = @parse_url($$name);
		if ($parts[$name] === false)
			return false;

		$keys[$name] = array_keys($parts[$name]);

		if (array_intersect($keys[$name], $req) != $req)
			return false;

		if (array_intersect($keys[$name], $bad) != array())
			return false;

		if (! preg_match('/^https?$/i', strtolower($parts[$name]['scheme'])))
			return false;

		if (! array_key_exists('port', $parts[$name]))
			$parts[$name]['port'] = (strtolower($parts[$name]['scheme']) == 'https') ? 443 : 80;

		if (! array_key_exists('path', $parts[$name]))
			$parts[$name]['path'] = '/';
	}

	// port and scheme must match
	if ($parts['parent']['scheme'] != $parts['child']['scheme'] ||
	    $parts['parent']['port'] != $parts['child']['port'])
		return false;

	// compare the hosts by reversing the strings
	$cr_host = strtolower(strrev($parts['child']['host']));
	$pr_host = strtolower(strrev($parts['parent']['host']));

	$break = str_diff_at($cr_host, $pr_host);
	if ($break >= 0 && ($pr_host[$break] != '*' || substr_count(substr($pr_host, 0, $break), '.') < 2))
		return false;

	// now compare the paths
	$break = str_diff_at($parts['child']['path'], $parts['parent']['path']);
	if ($break >= 0
	   && ($break < strlen($parts['parent']['path']) && $parts['parent']['path'][$break] != '*')
	   || ($break > strlen($parts['child']['path'])))
		return false;

	return true;
}


/**
 * Create a user session
 * @global array $profile
 * @global array $proto
 */
function user_session () {
	global $proto, $profile;

	session_name('phpMyID_Server');
	@session_start();

	$profile['authorized'] = (isset($_SESSION['auth_username'])
			    && $_SESSION['auth_username'] == $profile['auth_username'])
			? true
			: false;

	debug('Started user session: ' . session_id() . ' Auth? ' . $profile['authorized']);
}


/**
 * Return HTML
 * @global string $charset
 * @param string $message
 */
function wrap_html ( $message ) {
	global $charset, $profile;

	header('Content-Type: text/html; charset=' . $charset);
	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>phpMyID</title>
<link rel="openid.server" href="' . $profile['req_url'] . '" />
<link rel="openid.delegate" href="' . $profile['idp_url'] . '" />
' . implode("\n", $profile['opt_headers']) . '
<meta name="charset" content="' . $charset . '" />
<meta name="robots" content="noindex,nofollow" />
</head>
<body>
<p>' . $message . '</p>
</body>
</html>
';

	exit(0);
}


/**
 * Return a key-value pair in plain text
 * @global string $charset
 * @param array $keys
 */
function wrap_kv ( $keys ) {
	global $charset;

	debug($keys, 'Wrapped key/vals');
	header('Content-Type: text/plain; charset=' . $charset);
	foreach ($keys as $key => $value)
		printf("%s:%s\n", $key, $value);

	exit(0);
}


/**
 * Redirect, with OpenID keys
 * @param string $url
 * @param array @keys
 */
function wrap_keyed_redirect ($url, $keys) {
	$keys = append_openid($keys);
	debug($keys, 'Location keys');

	$q = strpos($url, '?') ? '&' : '?';
	wrap_redirect($url . $q . http_build_query($keys));
}


/**
 * Redirect the browser
 * @global string $charset
 * @param string $url
 */
function wrap_redirect ($url) {
	header('HTTP/1.1 302 Found');
	header('Location: ' . $url);
	debug('Location: ' . $url);
	exit(0);
}

/**
 * Return an HTML refresh
 * @global string $charset
 * @param string $url
 */
function wrap_refresh ($url) {
	global $charset;

	header('Content-Type: text/html; charset=' . $charset);
	echo '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>phpMyID</title>
<meta http-equiv="refresh" content="0;url=' . $url . '">
</head>
<body>
<p>Redirecting to <a href="' . $url . '">' . $url . '</a></p>
</body>
</html>
';

	debug('Refresh: ' . $url);
	exit(0);
}


/**
 * Implement binary x_or
 * @param string $a
 * @param string $b
 * @return string
 */
function x_or ($a, $b) {
	$r = "";

	for ($i = 0; $i < strlen($b); $i++)
		$r .= $a[$i] ^ $b[$i];
	debug("Xor size: " . strlen($r));
	return $r;
}



/*
 * App Initialization
 */
// Determine the charset to use
$GLOBALS['charset'] = 'iso-8859-1';

// Set the internal encoding
if (function_exists('mb_internal_encoding'))
	mb_internal_encoding($charset);

// Avoid problems with non-default arg_separator.output settings
// Credit for this goes to user 'prelog' on the forums
ini_set('arg_separator.output', '&');

// Do a check to be sure everything is set up correctly
self_check();


/**
 * Determine the HTTP request port
 * @name $port
 * @global integer $GLOBALS['port']
 */
$GLOBALS['port'] = ((isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on' && $_SERVER['SERVER_PORT'] == 443)
	  || $_SERVER['SERVER_PORT'] == 80)
		? ''
		: ':' . $_SERVER['SERVER_PORT'];

/**
 * Determine the HTTP request protocol
 * @name $proto
 * @global string $GLOBALS['proto']
 */
$GLOBALS['proto'] = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == 'on') ? 'https' : 'http';

// Set the authorization state - DO NOT OVERRIDE
$profile['authorized'] = false;

// Set a default IDP URL
if (! array_key_exists('idp_url', $profile))
	$profile['idp_url'] = sprintf("%s://%s%s%s",
			      $proto,
			      $_SERVER['SERVER_NAME'],
			      $port,
			      $_SERVER['PHP_SELF']);

// Determine the requested URL - DO NOT OVERRIDE
$profile['req_url'] = sprintf("%s://%s%s%s",
		      $proto,
		      $_SERVER['HTTP_HOST'],
		      $port,
		      $_SERVER["REQUEST_URI"]);

// Set the default allowance for testing
if (! array_key_exists('allow_test', $profile))
	$profile['allow_test'] = false;

// Set the default allowance for gmp
if (! array_key_exists('allow_gmp', $profile))
	$profile['allow_gmp'] = false;

// Set the default force bigmath - BAD IDEA to override this
if (! array_key_exists('force_bigmath', $profile))
	$profile['force_bigmath'] = false;

// Determine if GMP is usable
$profile['use_gmp'] = (extension_loaded('gmp') && $profile['allow_gmp']) ? true : false;

// Determine if I can perform big math functions
$profile['use_bigmath'] = (extension_loaded('bcmath') || $profile['use_gmp'] || $profile['force_bigmath']) ? true : false;

// Set a default authentication domain
if (! array_key_exists('auth_domain', $profile))
	$profile['auth_domain'] = $profile['req_url'] . ' ' . $profile['idp_url'];

// Set a default authentication realm
if (! array_key_exists('auth_realm', $profile))
	$profile['auth_realm'] = 'phpMyID';

// Determine the realm for digest authentication - DO NOT OVERRIDE
$profile['php_realm'] = $profile['auth_realm'] . (ini_get('safe_mode') ? '-' . getmyuid() : '');

// Set a default lifetime - the lesser of GC and cache time
if (! array_key_exists('lifetime', $profile)) {
	$sce = session_cache_expire() * 60;
	$gcm = ini_get('session.gc_maxlifetime');
	$profile['lifetime'] = $sce < $gcm ? $sce : $gcm;
}

// Set a default log file
if (! array_key_exists('logfile', $profile))
	$profile['logfile'] = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $profile['auth_realm'] . '.debug.log';


/*
 * Optional Initialization
 */
// Setup optional headers
$profile['opt_headers'] = array();

// Determine if I should add microid stuff
if (array_key_exists('microid', $profile)) {
	$hash = sha1($profile['idp_url']);
	$values = is_array($profile['microid']) ? $profile['microid'] : array($profile['microid']);

	foreach ($values as $microid) {
		preg_match('/^([a-z]+)/i', $microid, $mtx);
		$profile['opt_headers'][] = sprintf('<meta name="microid" content="%s+%s:sha1:%s" />', $mtx[1], $proto, sha1(sha1($microid) . $hash));
	}
}

// Determine if I should add pavatar stuff
if (array_key_exists('pavatar', $profile))
	$profile['opt_headers'][] = sprintf('<link rel="pavatar" href="%s" />', $profile['pavatar']);


/*
 * Do it
 */
// Decide which runmode, based on user request or default
$run_mode = (isset($_REQUEST['openid_mode'])
	  && in_array($_REQUEST['openid_mode'], $known['openid_modes']))
	? $_REQUEST['openid_mode']
	: 'no';

// Run in the determined runmode
debug("Run mode: $run_mode at: " . time());
debug($_REQUEST, 'Request params');
call_user_func($run_mode . '_mode');
?>
