<?php
/**
*
* @author Benjamin Gillissen <hide@address.com>
*
* **************************************************************
Copyright (C) 2009 Benjamin Gillissen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details at:
http://www.gnu.org/copyleft/gpl.html
* **************************************************************
*/
class chal_digest extends realm {
private $chal, $ticket, $nonce;
public function __construct($realm, $chal){
parent::__construct($realm);
$this->chal = $chal;
unset($realm, $chal);
}
public function get_chaldata(){
$this->nonce = CORE::hash();
return $this->nonce;
}
public function seticket($ticket){ $this->ticket = $ticket; }
public function geticket(){
$info = $this->http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
if ( FALSE === $info ){ return FALSE; }
if ( !isset($info['opaque']) ){ return FALSE; }
return $info['opaque'];
}
public function sendform($err=NULL){
if ( NULL !== $err ){ die(langs::dico_translate($err, NULL, 'realm')); }
if ( headers_sent($file, $line)) {
errors::raise('Digest-HTTP Authentification failure : output already started at '.$file.':'.$line, CORE_LOG_ALERT, 'REALM');
}
header('WWW-Authenticate: Digest '.
'realm="'.$this->realm.'", '.
'domain="/'.browsing::basedir().'", '.
'qop="auth", '.
'algorithm=MD5, '.
'nonce="'.$this->nonce.'", '.
'opaque="'.$this->ticket.'"');
header('HTTP/1.1 401 Unauthorized');
exit;
}
public function istrying(){
if ( FALSE === isset($_SERVER['PHP_AUTH_DIGEST']) ){ return FALSE; }
$info = $this->http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
if ( !isset($info['opaque']) ){ return FALSE; }
return ( TRUE === $this->isticket($info['opaque']) AND FALSE === $this->ischallenged($info['opaque']) );
}
public function precheck($chaldata){
$info = $this->http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
if ( FALSE === $info ){ return 'realmsg_digestbadreply'; }
if ( $info['nonce'] !== $chaldata ){ return 'realmsg_digestnoncenomatch'; }
return TRUE;
}
public function authenticate(){
//FIXME i took some freedom from the RFC, need rework so it really follow digest RFC, with qop stuff
$data = $this->http_digest_parse($_SERVER['PHP_AUTH_DIGEST']);
$ruid = $this->uidbylogin($data['username']);
if ( FALSE === $ruid ){ return FALSE; }
$pass = $this->getpass($ruid);
if ( FALSE === $pass ){ return FALSE; }
$uid = realm_accounts::splitid($ruid);
$hash = configs::get('account', $uid['acc'], 'hash');
$scheme = configs::get('account', $uid['acc'], 'hashscheme');
if ($hash == 'clear'){
$a1 = md5($data['username'].':'.$this->realm.':'.$pass);
} elseif ( $hash == 'MD5' AND '%login:%realm:%pass' == $scheme ){
$a1 = $pass;
} else {
return FALSE;
}
$a2 = md5($_SERVER['REQUEST_METHOD'].':'.$_SERVER['REQUEST_URI']);
if ( isset($data['cnonce']) AND isset($data['nc']) AND isset($data['qop']) ){
$expected = md5($a1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$a2);
} else {
$expected = md5($a1.':'.$data['nonce'].':'.$a2);
}
if ($data['response'] === $expected ) { return $ruid; }
return FALSE;
}
private function http_digest_parse($txt){
$infos = Array('username', 'cnonce', 'nonce', 'response', 'opaque', 'uri', 'qop', 'nc');
foreach($infos as $k => &$info){
$buf = split($info.'=', $txt);
if ( isset($buf[1]) ){
$buf = split(',', $buf[1]);
if ( !empty($buf[0]) ){
$buf = ereg_replace("^\"", '', $buf[0]);
$o[$info] = ereg_replace('"$', '', $buf);
}
}
}
if (isset($o) ){ return $o; }
return FALSE;
}
}