Location: PHPKode > projects > crVCL PHP Framework > sasl.lib.php
<?php

/*

The contents of this file are subject to the Mozilla Public License
Version 1.1 (the "License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/MPL-1.1.html or see MPL-1.1.txt in directory "license"

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

The Initial Developers of the Original Code are: 
Copyright (c) 2003-2012, CR-Solutions (http://www.cr-solutions.net), Ricardo Cescon
All Rights Reserved.

Contributor(s): Ricardo Cescon

crVCL PHP Framework Version 2.4
*/




############################################################
if(!defined("SASL_LIB")){
   define ("SASL_LIB", 1);
############################################################

class sasl
{
	var $m_error='';
	var $m_mechanism='';

	var $m_encode_response=1;


	var $m_driver;
	var $m_drivers=array(
		"Digest"   => "sasl_digest",
		"CRAM-MD5" => "sasl_cram_md5",
		"LOGIN"    => "sasl_login",
		"NTLM"     => "sasl_ntlm",
		"PLAIN"    => "sasl_plain",
		"Basic"    => "sasl_basic"
	);
	var $m_credentials=array();

//-------------------------------------------------------------------------------------------------------------------------------------	

   function setCredential($key,$value)
	{
		$this->m_credentials[$key]=$value;
	}
//-------------------------------------------------------------------------------------------------------------------------------------
	function getCredentials(&$credentials,$defaults,&$interactions)
	{
		reset($credentials);
		$end=(gettype($key=key($credentials))!="string");
		for(;!$end;)
		{
			if(!isset($this->m_credentials[$key]))
			{
				if(isset($defaults[$key]))
					$credentials[$key]=$defaults[$key];
				else
				{
					$this->m_error="the requested credential ".$key." is not defined";
					return(SASL_NOMECH);
				}
			}
			else
				$credentials[$key]=$this->m_credentials[$key];
			next($credentials);
			$end=(gettype($key=key($credentials))!="string");
		}
		return(SASL_CONTINUE);
	}
//-------------------------------------------------------------------------------------------------------------------------------------
	function start($mechanisms, &$message, &$interactions)
	{
		if(strlen($this->m_error))
			return(SASL_FAIL);
		if(isset($this->m_driver))
			return($this->m_driver->start($this,$message,$interactions));
		$no_mechanism_error="";
		for($m=0;$m<count($mechanisms);$m++)
		{
			$mechanism=$mechanisms[$m];
			if(isset($this->m_drivers[$mechanism]))
			{				
				$this->m_driver=new $this->m_drivers[$mechanism];
				if($this->m_driver->initialize($this))
				{
					$this->m_encode_response=1;
					$status=$this->m_driver->start($this,$message,$interactions);
					switch($status)
					{
						case SASL_NOMECH:
							unset($this->m_driver);
							if(strlen($no_mechanism_error)==0)
								$no_mechanism_error=$this->m_error;
							$this->m_error="";
							break;
						case SASL_CONTINUE:
							$this->m_mechanism=$mechanism;
							return($status);
						default:
							unset($this->m_driver);
							$this->m_error="";
							return($status);
					}
				}
				else
				{
					unset($this->m_driver);
					if(strlen($no_mechanism_error)==0)
						$no_mechanism_error=$this->m_error;
					$this->m_error="";
				}
			}
		}
		$this->m_error=(strlen($no_mechanism_error) ? $no_mechanism_error : "it was not requested any of the authentication mechanisms that are supported");
		return(SASL_NOMECH);
	}
//-------------------------------------------------------------------------------------------------------------------------------------
	function step($response, &$message, &$interactions)
	{
		if(strlen($this->m_error))
			return(SASL_FAIL);
		return($this->m_driver->step($this,$response,$message,$interactions));
	}
}
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
class sasl_interact
{
	var $m_id;
	var $m_challenge;
	var $m_prompt;
	var $m_default_result;
	var $m_result;
};
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
define("SASL_PLAIN_STATE_START",    0);
define("SASL_PLAIN_STATE_IDENTIFY", 1);
define("SASL_PLAIN_STATE_DONE",     2);

define("SASL_PLAIN_DEFAULT_MODE",            0);
define("SASL_PLAIN_EXIM_MODE",               1);
define("SASL_PLAIN_EXIM_DOCUMENTATION_MODE", 2);

class sasl_plain
{
	var $m_credentials=array();
	var $m_state=SASL_PLAIN_STATE_START;

	function initialize(&$client)
	{
		return(1);
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_PLAIN_STATE_START)
		{
			$client->m_error="PLAIN authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>"",
			"mode"=>""
		);
		$defaults=array(
			"realm"=>"",
			"mode"=>""
		);
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
		{
			switch($this->m_credentials["mode"])
			{
				case SASL_PLAIN_EXIM_MODE:
					$message=$this->m_credentials["user"]."\0".$this->m_credentials["password"]."\0";
					break;
				case SASL_PLAIN_EXIM_DOCUMENTATION_MODE:
					$message="\0".$this->m_credentials["user"]."\0".$this->m_credentials["password"];
					break;
				default:
					$message=$this->m_credentials["user"]."\0".$this->m_credentials["user"].(strlen($this->m_credentials["realm"]) ? "@".$this->m_credentials["realm"] : "")."\0".$this->m_credentials["password"];
					break;
			}
			$this->m_state=SASL_PLAIN_STATE_DONE;
		}
		else
			unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
/*
			case SASL_PLAIN_STATE_IDENTIFY:
				switch($this->m_credentials["mode"])
				{
					case SASL_PLAIN_EXIM_MODE:
						$message=$this->m_credentials["user"]."\0".$this->m_credentials["password"]."\0";
						break;
					case SASL_PLAIN_EXIM_DOCUMENTATION_MODE:
						$message="\0".$this->m_credentials["user"]."\0".$this->m_credentials["password"];
						break;
					default:
						$message=$this->m_credentials["user"]."\0".$this->m_credentials["user"].(strlen($this->m_credentials["realm"]) ? "@".$this->m_credentials["realm"] : "")."\0".$this->m_credentials["password"];
						break;
				}
				var_dump($message);
				$this->m_state=SASL_PLAIN_STATE_DONE;
				break;
*/
			case SASL_PLAIN_STATE_DONE:
				$client->m_error="PLAIN authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->m_error="invalid PLAIN authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------

define("SASL_BASIC_STATE_START",    0);
define("SASL_BASIC_STATE_DONE",     1);

class sasl_basic
{
	var $m_credentials=array();
	var $m_state=SASL_BASIC_STATE_START;

	function initialize(&$client)
	{
		return(1);
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_BASIC_STATE_START)
		{
			$client->m_error="Basic authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			"user"=>"",
			"password"=>""
		);
		$defaults=array(
		);
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
		{
			$message=$this->m_credentials["user"].":".$this->m_credentials["password"];
			$this->m_state=SASL_BASIC_STATE_DONE;
		}
		else
			unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
			case SASL_BASIC_STATE_DONE:
				$client->m_error="Basic authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->m_error="invalid Basic authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
define("SASL_CRAM_MD5_STATE_START",             0);
define("SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE", 1);
define("SASL_CRAM_MD5_STATE_DONE",              2);

class sasl_cram_md5
{
	var $m_credentials=array();
	var $m_state=SASL_CRAM_MD5_STATE_START;

	function initialize(&$client)
	{
		return(1);
	}

	function HMACMD5($key,$text)
	{
		$key=(strlen($key)<64 ? str_pad($key,64,"\0") : substr($key,0,64));
		return(md5((str_repeat("\x5c", 64)^$key).pack("H32", md5((str_repeat("\x36", 64)^$key).$text))));
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_CRAM_MD5_STATE_START)
		{
			$client->m_error="CRAM-MD5 authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			"user"=>"",
			"password"=>""
		);
		$defaults=array();
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->m_state=SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE;
		unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
			case SASL_CRAM_MD5_STATE_RESPOND_CHALLENGE:
				$message=$this->m_credentials["user"]." ".$this->HMACMD5($this->m_credentials["password"], $response);
				$this->m_state=SASL_CRAM_MD5_STATE_DONE;
				break;
			case SASL_CRAM_MD5_STATE_DONE:
				$client->m_error="CRAM-MD5 authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->m_error="invalid CRAM-MD5 authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
};


//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------

define('SASL_DIGEST_STATE_START',             0);
define('SASL_DIGEST_STATE_RESPOND_CHALLENGE', 1);
define('SASL_DIGEST_STATE_DONE',              2);

class sasl_digest
{
	var $m_credentials=array();
	var $m_state=SASL_DIGEST_STATE_START;

	function unq($string)
	{
		return(($string[0]=='"' && $string[strlen($string)-1]=='"') ? substr($string, 1, strlen($string)-2) : $string);
	}

	function H($data)
	{
		return md5($data);
	}

	function KD($secret, $data)
	{
		return $this->H($secret.':'.$data);
	}

	function initialize(&$client)
	{
		return(1);
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_DIGEST_STATE_START)
		{
			$client->m_error='Digest authentication state is not at the start';
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			'user'=>'',
			'password'=>'',
			'uri'=>'',
			'method'=>'',
			'session'=>''
		);
		$defaults=array();
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->m_state=SASL_DIGEST_STATE_RESPOND_CHALLENGE;
		unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
			case SASL_DIGEST_STATE_RESPOND_CHALLENGE:
				$values=explode(',',$response);
				$parameters=array();
				for($v=0; $v<count($values); $v++)
					$parameters[strtok(trim($values[$v]), '=')]=strtok('');

				$message='username="'.$this->m_credentials['user'].'"';
				if(!isset($parameters[$p='realm'])
				&& !isset($parameters[$p='nonce']))
				{
					$client->m_error='Digest authentication parameter '.$p.' is missing from the server response';
					return(SASL_FAIL);
				}
				$message.=', realm='.$parameters['realm'];
				$message.=', nonce='.$parameters['nonce'];
				$message.=', uri="'.$this->m_credentials['uri'].'"';
				if(isset($parameters['algorithm']))
				{
					$algorithm=$this->unq($parameters['algorithm']);
					$message.=', algorithm='.$parameters['algorithm'];
				}
				else
					$algorithm='';

				$realm=$this->unq($parameters['realm']);
				$nonce=$this->unq($parameters['nonce']);
				if(isset($parameters['qop']))
				{
					switch($qop=$this->unq($parameters['qop']))
					{
						case "auth":
							$cnonce=$this->m_credentials['session'];
							break;
						default:
							$client->m_error='Digest authentication quality of protection '.$qop.' is not yet supported';
							return(SASL_FAIL);
					}
				}
				$nc_value='00000001';
				if(isset($parameters['qop'])
				&& !strcmp($algorithm, 'MD5-sess'))
					$A1=$this->H($this->m_credentials['user'].':'. $realm.':'. $this->m_credentials['password']).':'.$nonce.':'.$cnonce;
				else
					$A1=$this->m_credentials['user'].':'. $realm.':'. $this->m_credentials['password'];
				$A2=$this->m_credentials['method'].':'.$this->m_credentials['uri'];
				if(isset($parameters['qop']))
					$response=$this->KD($this->H($A1), $nonce.':'. $nc_value.':'. $cnonce.':'. $qop.':'. $this->H($A2));
				else
					$response=$this->KD($this->H($A1), $nonce.':'. $this->H($A2));
				$message.=', response="'.$response.'"';
				if(isset($parameters['opaque']))
					$message.=', opaque='.$parameters['opaque'];
				if(isset($parameters['qop']))
					$message.=', qop="'.$qop.'"';
				$message.=', nc='.$nc_value;
				if(isset($parameters['qop']))
					$message.=', cnonce="'.$cnonce.'"';
				$client->m_encode_response=0;
				$this->m_state=SASL_DIGEST_STATE_DONE;
				break;
			case SASL_DIGEST_STATE_DONE:
				$client->m_error='Digest authentication was finished without success';
				return(SASL_FAIL);
			default:
				$client->m_error='invalid Digest authentication step state';
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
define("SASL_LOGIN_STATE_START",             0);
define("SASL_LOGIN_STATE_IDENTIFY_USER",     1);
define("SASL_LOGIN_STATE_IDENTIFY_PASSWORD", 2);
define("SASL_LOGIN_STATE_DONE",              3);

class sasl_login
{
	var $m_credentials=array();
	var $m_state=SASL_LOGIN_STATE_START;

	function initialize(&$client)
	{
		return(1);
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_LOGIN_STATE_START)
		{
			$client->m_error="LOGIN authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>""
		);
		$defaults=array(
			"realm"=>""
		);
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->m_state=SASL_LOGIN_STATE_IDENTIFY_USER;
		unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
			case SASL_LOGIN_STATE_IDENTIFY_USER:
				$message=$this->m_credentials["user"].(strlen($this->m_credentials["realm"]) ? "@".$this->m_credentials["realm"] : "");
				$this->m_state=SASL_LOGIN_STATE_IDENTIFY_PASSWORD;
				break;
			case SASL_LOGIN_STATE_IDENTIFY_PASSWORD:
				$message=$this->m_credentials["password"];
				$this->m_state=SASL_LOGIN_STATE_DONE;
				break;
			case SASL_LOGIN_STATE_DONE:
				$client->m_error="LOGIN authentication was finished without success";
				break;
			default:
				$client->m_error="invalid LOGIN authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}


//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------------------------------------------

define("SASL_NTLM_STATE_START",             0);
define("SASL_NTLM_STATE_IDENTIFY_DOMAIN",   1);
define("SASL_NTLM_STATE_RESPOND_CHALLENGE", 2);
define("SASL_NTLM_STATE_DONE",              3);

class sasl_ntlm
{
	var $credentials=array();
	var $state=SASL_NTLM_STATE_START;

	function initialize(&$client)
	{
		if(!function_exists($function="mcrypt_encrypt")
		|| !function_exists($function="mhash"))
		{
			$extensions=array(
				"mcrypt_encrypt"=>"mcrypt",
				"mhash"=>"mhash"
			);
			$client->m_error="the extension ".$extensions[$function]." required by the NTLM SASL client class is not available in this PHP configuration";
			return(0);
		}
		return(1);
	}

	function ASCIIToUnicode($ascii)
	{
		for($unicode="",$a=0;$a<strlen($ascii);$a++)
			$unicode.=substr($ascii,$a,1).chr(0);
		return($unicode);
	}

	function TypeMsg1($domain,$workstation)
	{
		$domain_length=strlen($domain);
		$workstation_length=strlen($workstation);
		$workstation_offset=32;
		$domain_offset=$workstation_offset+$workstation_length;
		return(
			"NTLMSSP\0".
			"\x01\x00\x00\x00".
			"\x07\x32\x00\x00".
			pack("v",$domain_length).
			pack("v",$domain_length).
			pack("V",$domain_offset).
			pack("v",$workstation_length).
			pack("v",$workstation_length).
			pack("V",$workstation_offset).
			$workstation.
			$domain
		);
	}

	function NTLMResponse($challenge,$password)
	{
		$unicode=$this->ASCIIToUnicode($password);
		$md4=mhash(MHASH_MD4,$unicode);
		$padded=$md4.str_repeat(chr(0),21-strlen($md4));
		$iv_size=mcrypt_get_iv_size(MCRYPT_DES,MCRYPT_MODE_ECB);
		$iv=mcrypt_create_iv($iv_size,MCRYPT_RAND);
		for($response="",$third=0;$third<21;$third+=7)
		{
			for($packed="",$p=$third;$p<$third+7;$p++)
				$packed.=str_pad(decbin(ord(substr($padded,$p,1))),8,"0",STR_PAD_LEFT);
			for($key="",$p=0;$p<strlen($packed);$p+=7)
			{
				$s=substr($packed,$p,7);
				$b=$s.((substr_count($s,"1") % 2) ? "0" : "1");
				$key.=chr(bindec($b));
			}
			$ciphertext=mcrypt_encrypt(MCRYPT_DES,$key,$challenge,MCRYPT_MODE_ECB,$iv);
			$response.=$ciphertext;
		}
		return $response;
	}

	function TypeMsg3($ntlm_response,$user,$domain,$workstation)
	{
		$domain_unicode=$this->ASCIIToUnicode($domain);
		$domain_length=strlen($domain_unicode);
		$domain_offset=64;
		$user_unicode=$this->ASCIIToUnicode($user);
		$user_length=strlen($user_unicode);
		$user_offset=$domain_offset+$domain_length;
		$workstation_unicode=$this->ASCIIToUnicode($workstation);
		$workstation_length=strlen($workstation_unicode);
		$workstation_offset=$user_offset+$user_length;
		$lm="";
		$lm_length=strlen($lm);
		$lm_offset=$workstation_offset+$workstation_length;
		$ntlm=$ntlm_response;
		$ntlm_length=strlen($ntlm);
		$ntlm_offset=$lm_offset+$lm_length;
		$session="";
		$session_length=strlen($session);
		$session_offset=$ntlm_offset+$ntlm_length;
		return(
			"NTLMSSP\0".
			"\x03\x00\x00\x00".
			pack("v",$lm_length).
			pack("v",$lm_length).
			pack("V",$lm_offset).
			pack("v",$ntlm_length).
			pack("v",$ntlm_length).
			pack("V",$ntlm_offset).
			pack("v",$domain_length).
			pack("v",$domain_length).
			pack("V",$domain_offset).
			pack("v",$user_length).
			pack("v",$user_length).
			pack("V",$user_offset).
			pack("v",$workstation_length).
			pack("v",$workstation_length).
			pack("V",$workstation_offset).
			pack("v",$session_length).
			pack("v",$session_length).
			pack("V",$session_offset).
			"\x01\x02\x00\x00".
			$domain_unicode.
			$user_unicode.
			$workstation_unicode.
			$lm.
			$ntlm
		);
	}

	function start(&$client, &$message, &$interactions)
	{
		if($this->m_state!=SASL_NTLM_STATE_START)
		{
			$client->m_error="NTLM authentication state is not at the start";
			return(SASL_FAIL);
		}
		$this->m_credentials=array(
			"user"=>"",
			"password"=>"",
			"realm"=>"",
			"workstation"=>""
		);
		$defaults=array();
		$status=$client->getCredentials($this->m_credentials,$defaults,$interactions);
		if($status==SASL_CONTINUE)
			$this->m_state=SASL_NTLM_STATE_IDENTIFY_DOMAIN;
		unset($message);
		return($status);
	}

	function step(&$client, $response, &$message, &$interactions)
	{
		switch($this->m_state)
		{
			case SASL_NTLM_STATE_IDENTIFY_DOMAIN:
				$message=$this->TypeMsg1($this->m_credentials["realm"],$this->m_credentials["workstation"]);
				$this->m_state=SASL_NTLM_STATE_RESPOND_CHALLENGE;
				break;
			case SASL_NTLM_STATE_RESPOND_CHALLENGE:
				$ntlm_response=$this->NTLMResponse(substr($response,24,8),$this->m_credentials["password"]);
				$message=$this->TypeMsg3($ntlm_response,$this->m_credentials["user"],$this->m_credentials["realm"],$this->m_credentials["workstation"]);
				$this->m_state=SASL_NTLM_STATE_DONE;
				break;
			case SASL_NTLM_STATE_DONE:
				$client->m_error="NTLM authentication was finished without success";
				return(SASL_FAIL);
			default:
				$client->m_error="invalid NTLM authentication step state";
				return(SASL_FAIL);
		}
		return(SASL_CONTINUE);
	}
}

############################################################
}
############################################################
?>
Return current item: crVCL PHP Framework