server/apps/user_openid/phpmyid.php
Robin Appelman bd4aec2c4c Experimental OpenID user backend and server.
Currently all users can set one OpenID identity that allows access to the account.

The identity url's for the server is owncloud/apps/user_openid/user.php/$username
2011-06-23 17:08:04 +02:00

1723 lines
43 KiB
PHP

<?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;
global $USERNAME;
// this is a 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.');
if (isset($_SERVER['PHP_AUTH_USER']) && $profile['authorized'] === false && $_SERVER['PHP_AUTH_USER']==$USERNAME) {
if (OC_USER::checkPassword($USERNAME, $_SERVER['PHP_AUTH_PW'])) {// successful login!
// return to the refresh url if they get in
$_SESSION['openid_auth']=true;
$_SESSION['openid_user']=$USERNAME;
wrap_redirect($_SESSION['post_auth_url']);
// failed login
} else {
$_SESSION['failures']++;
debug('Login failed');
debug('Fail count: ' . $_SESSION['failures']);
}
}
// 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'));
header('WWW-Authenticate: Basic realm="ownCloud"');
$q = strpos($_SESSION['cancel_auth_url'], '?') ? '&' : '?';
wrap_refresh($_SESSION['cancel_auth_url'] . $q . 'openid.mode=cancel');
// die('401 Unauthorized');
}
/**
* 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 ) {
global $known, $profile, $sreg;
global $USERNAME;
// 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 return1_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 ($_SESSION['openid_auth'] === false || $USERNAME != $_SESSION['openid_user']) {
// 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);
$html= '<!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>
';
error_log($html);
echo $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'];
error_log($_SERVER['HTTP_HOST']);
/**
* 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",
$proto,
$_SERVER['HTTP_HOST'],
// $port,//host already includes the path
$_SERVER["REQUEST_URI"]);
error_log($profile['req_url']);
// 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');
?>