nextcloud/apps/user_openid/phpmyid.php

1725 lines
42 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
*/
require( 'template.php' );
/**
* 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 () {
$tmpl = new OC_TEMPLATE( 'user_openid', 'nomode', 'guest' );
$tmpl->printPage();
}
/**
* 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');
?>