<?php
/*******************************************************
 *  Author:    Paweł Skup <pawel.skup@wp.pl>           *
 *  Version:   1.0.1-2013.11.01                        *
 *  Project:   www.programistrz.pl/projekty/ntlm       *
 *  License:   Creative Commons Attribution (CC BY)    *
 *  www.creativecommons.org/licenses/by/3.0/legalcode  *
 *******************************************************/

//require_once('curl.php');  // własna wersja biblioteki cURL (przydatna w przypadku braku takowej w systemie)

define('DEF_NTLM_LOGIN_EXPIRE_TIME',15*60);      // jak długo uwierzytelnienie jest pamiętane (15 minut)
define('DEF_NTLM_MAX_EXECUTION_TIME_LIMIT',60);  // maksymalny czas oczekiwania na uwierzytelnienie (1 minuta)
define('DEF_NTLM_MAX_ITERATIONS',6);             // ile razy można maksymalnie próbować przesyłać nagłówki w celu uwierzytelnienia (2 próby po 3 nagłówki)
define('DEF_NTLM_COMMUNICATION_DELAY',100);      // opóźnienie w mikrosekundach podczas komunikacji z użyciem plików
define('DEF_NTLM_POST_ID_NAME','_NTLM_ID_');     // nazwa zmiennej przekazywanej w POST zawierającej id sesji

//define('DEF_NTLM_THIS_SCRIPT_URL','http://intranet/this/script/url/auth.php');  // należy podać właściwy adres URL skryptu, jeśli ten wyliczany przez funkcję ntlm_computeThisScriptUrl() jest nieprawidłowy

define('DEF_NTLM_AUTHENTICATE_URL','http://intranet/path/verify.html');  // tutaj należy podać właściwy adres URL!


// rozpoznanie uruchomienia skryptu z poziomu przeglądarki (przez cURL) od uruchomienia go poprzez include/require
if (isset($_SERVER['REQUEST_URI']) && preg_match('/\b'.basename(__FILE__).'$/i',$_SERVER['REQUEST_URI']))
{
  global 
$sessid;

  function 
ntlm_proxyRequest($ch,$url,$headers)
  {
    
curl_setopt($ch,CURLOPT_URL,$url);
    
curl_setopt($ch,CURLOPT_HEADER,1);
    
curl_setopt($ch,CURLOPT_NOBODY,1);
    
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
    
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
    
$page=curl_exec($ch);
    return 
str_replace("\r",'',trim($page));
  }

  function 
ntlm_onExit()
  {
    static 
$cleaned=false;
    if (
$cleaned) return;
    global 
$sessid;
    
ntlm_closeHandler($sessid);
    
$cleaned=true;
  }

  if (isset(
$_POST[DEF_NTLM_POST_ID_NAME]))
  {
    
set_time_limit(DEF_NTLM_MAX_EXECUTION_TIME_LIMIT);
    
register_shutdown_function('ntlm_onExit');
    
$ch=curl_init();
    
$sessid=$_POST[DEF_NTLM_POST_ID_NAME];
    while (
true)
    {
      
$data=ntlm_receiveData($sessid,1);
      if (!
$data) break;
      
$data=explode("\n",$data);
      
$data=ntlm_proxyRequest($ch,DEF_NTLM_AUTHENTICATE_URL,$data);
      
ntlm_sendData($sessid,2,$data);
    }
    
curl_close($ch);
  }
  else
    echo 
basename(__FILE__);
  exit;
}

function 
checkLogin()
{
  
$sessid=session_id();
  if (!
$sessid) die('Require session_start();');
  
$user=@$_SESSION['NTLM.USER_NAME'];
  
$last=@$_SESSION['NTLM.LAST_CHECK_LOGIN'];

  if (!isset(
$user) || !isset($last) || $last+DEF_NTLM_LOGIN_EXPIRE_TIME<ntlm_getmicrotime())  // must authenticate
  
{
    if (!isset(
$_SESSION['NTLM.AuthIterations']))
    {
      
ntlm_openHandler($sessid);
      
$ch=curl_init();
      
curl_setopt($ch,CURLOPT_URL,ntlm_computeThisScriptUrl());
      
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
      
curl_setopt($ch,CURLOPT_FRESH_CONNECT,1);
      
curl_setopt($ch,CURLOPT_HEADER,0);
      
curl_setopt($ch,CURLOPT_HTTPHEADER,array('Connection: close'));
      
curl_setopt($ch,CURLOPT_POST,1);
      
curl_setopt($ch,CURLOPT_POSTFIELDS,DEF_NTLM_POST_ID_NAME.'='.$sessid);
      
curl_setopt($ch,CURLOPT_CONNECTTIMEOUT,2);
      if (
defined('CURLOPT_TIMEOUT_MS'))
        
curl_setopt($ch,CURLOPT_TIMEOUT_MS,250);
      else
        
curl_setopt($ch,CURLOPT_TIMEOUT,1);
      
curl_exec($ch);
      
curl_close($ch);
      
$_SESSION['NTLM.AuthIterations']=DEF_NTLM_MAX_ITERATIONS;
    }

    
$authIterations=$_SESSION['NTLM.AuthIterations'];
    
$_SESSION['NTLM.AuthIterations']=$authIterations-1;
    if (!
$authIterations) return false;

    
$data=ntlm_getRequestHeaders();
    
ntlm_sendData($sessid,0,$data);
debug($data);  // zapisanie nagłówków w celu późniejszej analizy procesu uwierzytelnienia

    
$messageType3=false;
    if (
preg_match('/^Authorization: (?:Negotiate|NTLM) (.*?)$/mis',$data,$matches))
    {
      
$message=base64_decode($matches[1]);
      
$i=strpos($message,"NTLMSSP\0");
      if (
$i!==false)
      {
        if (
$i>0$message=substr($message,$i);
        
$messageType3=(ord(substr($message,8,1))==3);
      }
    }

    
$data=ntlm_receiveData($sessid,3);
debug($data);  // zapisanie nagłówków w celu późniejszej analizy procesu uwierzytelnienia

    
if ($messageType3)
    {
      
ntlm_sendData($sessid,0,'');
      unset(
$_SESSION['NTLM.AuthIterations']);
      if (
preg_match('/^HTTP\/1\.\d [23]\d\d /mis',$data,$matches))
      {
        
$domain=ntlm_utf16le2utf8(substr($message,ord(substr($message,0x20,1)),ord(substr($message,0x1c,1))));
        
$login=ntlm_utf16le2utf8(substr($message,ord(substr($message,0x28,1)),ord(substr($message,0x24,1))));
        
$host=ntlm_utf16le2utf8(substr($message,ord(substr($message,0x30,1)),ord(substr($message,0x2c,1))));
        
$_SESSION['NTLM.USER_NAME']=$login;
        
$_SESSION['NTLM.DOMAIN']=$domain;
        
$_SESSION['NTLM.HOST']=$host;
        
$_SESSION['NTLM.LAST_CHECK_LOGIN']=ntlm_getmicrotime();
        return 
true;
      }
      else
        return 
false;
    }

    
$data=explode("\n",$data);
    for (
$i=0;$i<count($data);$i++)
      
header($data[$i],false);
    exit;
  }
  return 
true;
}

function 
ntlm_getmicrotime()
{
  
$time=explode(' ',microtime(),2);
  return (float)
$time[1]+(float)$time[0];
}

function 
ntlm_computeThisScriptUrl()
{
  if (
defined('DEF_NTLM_THIS_SCRIPT_URL') && DEF_NTLM_THIS_SCRIPT_URL)
    return 
DEF_NTLM_THIS_SCRIPT_URL;
  
$dir=str_replace('\\','/',dirname($_SERVER['SCRIPT_FILENAME']));
  
$cwd=str_replace('\\','/',getcwd());
  
$auth=str_replace('\\','/',__FILE__);
  
$offset=strlen($_SERVER['SCRIPT_FILENAME'])-strlen($_SERVER['SCRIPT_NAME']);
  return 
"http".(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS']!='off'?'s':'')."://".$_SERVER['HTTP_HOST'].substr(ntlm_exchangeCommonSufix($cwd,$dir,$auth),$offset);
}

function 
ntlm_exchangeCommonSufix($str1,$str2,$str)
{
  
$len1=strlen($str1);
  
$len2=strlen($str2);
  while (
$len1 && $len2 && $str1[$len1-1]==$str2[$len2-1])
  {
    
$len1--; $len2--;
  }
  
$str1=substr($str1,0,$len1);
  
$str2=substr($str2,0,$len2);
  return 
str_replace($str1,$str2,$str);
}

function 
ntlm_utf16le2utf8($str)
{
  
$output='';
  
$len=strlen($str);
  for (
$i=0;$i<$len;)
  {
    
$word=ord(substr($str,$i++,1))|(ord(substr($str,$i++,1))<<8);
    if     (
$word<0x80)     $ch=chr($word);
    elseif (
$word<0x800)    $ch=chr($word>>6|0xc0).chr($word&0x3f|0x80);
    elseif (
$word<0x10000)  $ch=chr($word>>12|0xe0).chr($word>>6&0x3f|0x80).chr($word&0x3f|0x80);
    elseif (
$word<0x200000$ch=chr($word>>18|0xf0).chr($word>>12&0x3f|0x80).chr($word>>6&0x3f|0x80).chr($word&0x3f|0x80);
    
$output.=$ch;
  }
  return 
$output;
}

function 
ntlm_getRequestHeaders()
{
  
$str='';
  if (
function_exists('apache_request_headers'))
  {
    
$headers=apache_request_headers();
    foreach (
$headers as $key => $value)
      if (!
preg_match('/^(Host|Referer|Cookie)$/i',$key))
        
$str.="$key$value\n";
  }
  else
  {
    foreach (
$_SERVER as $key => $value)
      if (
substr($key,0,5)=='HTTP_')
      {
        
$key=str_replace(' ','-',ucwords(strtolower(str_replace('_',' ',substr($key,5)))));
        if (!
preg_match('/^(Host|Referer|Cookie)$/i',$key))
          
$str.="$key$value\n";
      }
  }
  return 
$str;
}

function 
ntlm_file_get_contents($file)
{
  if (!
file_exists($file)) return false;
  
$fp=fopen($file,'r');
  
$data=fread($fp,filesize($file));
  
fclose($fp);
  return 
$data;
}

function 
ntlm_file_put_contents($file,$data)
{
  
$fp=fopen($file,'w');
  
fwrite($fp,$data);
  
fclose($fp);
  return 
true;
}

function 
ntlm_openHandler($id)
{
  @
ntlm_file_put_contents(dirname(__FILE__).DIRECTORY_SEPARATOR.$id.'.lock',0);
}

function 
ntlm_closeHandler($id)
{
  @
unlink(dirname(__FILE__).DIRECTORY_SEPARATOR.$id.'.lock');
}

function 
ntlm_readAndDeleteFile($file)
{
  while (!
file_exists($file)) usleep(DEF_NTLM_COMMUNICATION_DELAY);
  
$data=@ntlm_file_get_contents($file);
  @
unlink($file);
  return 
$data;
}

function 
ntlm_sendData($id,$round,$data)
{
  
$id=dirname(__FILE__).DIRECTORY_SEPARATOR.$id;
  
$sem=$id.'.lock';
  while (
true)
  {  
    
$counter=@ntlm_file_get_contents($sem);
    if (
$counter==$round) break;
    
usleep(DEF_NTLM_COMMUNICATION_DELAY);
  }
  @
ntlm_file_put_contents($id.'.data',$data);
  @
ntlm_file_put_contents($sem,($round+1)&3);
}

function 
ntlm_receiveData($id,$round)
{
  
$id=dirname(__FILE__).DIRECTORY_SEPARATOR.$id;
  
$sem=$id.'.lock';
  while (
true)
  {  
    
$counter=@ntlm_file_get_contents($sem);
    if (
$counter==$round) break;
    
usleep(DEF_NTLM_COMMUNICATION_DELAY);
  }
  
$data=ntlm_readAndDeleteFile($id.'.data');
  @
ntlm_file_put_contents($sem,($round+1)&3);
  return 
$data;
}


// funkcja zapisująca nagłówki HTTP na potrzeby późniejszej analizy procesu uwierzytelnienia
function debug($msg)
{
  if (
defined('_DEBUG_'))
  {
    
$fp=fopen(dirname(__FILE__).DIRECTORY_SEPARATOR.'debug.txt','a');
    
$msg=ntlm_getmicrotime()."\t".$msg."|";
    
fwrite($fp,$msg);
    
fclose($fp);
  }
}
?>