*
* This program is released under the GNU Public License. See the enclosed
* file COPYING for license information. If you did not receive this file, see
* http://www.fsf.org/copyleft/gpl.html.
*
* You should have received a copy of the GNU Public License along with this
* package; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*
* See CHANGES for updates since last release
*
* @author Dan Ellis
* @package sieve-php
* @copyright Copyright 2002-2003, Dan Ellis, All Rights Reserved.
* @version 0.1.0
*/
/**
* Constants
*/
define ("F_NO", 0);
define ("F_OK", 1);
define ("F_DATA", 2);
define ("F_HEAD", 3);
define ("EC_NOT_LOGGED_IN", 0);
define ("EC_QUOTA", 10);
define ("EC_NOSCRIPTS", 20);
define ("EC_UNKNOWN", 255);
/**
* SIEVE class - A Class that implements MANAGESIEVE in PHP4|5.
*
* This program provides a handy interface into the Cyrus timsieved server
* under php4. It is tested with Sieve server included in Cyrus 2.0, but it
* has been upgraded (not tested) to work with older Sieve server versions.
*
* All functions will return either true or false and will fill in
* $sieve->error with a defined error code like EC_QUOTA, raw server errors in
* $sieve->error_raw, and successful responses in $sieve->responses.
*
* NOTE: a major change since version (0.0.5) is the inclusion of a standard
* method to retrieve server responses. All functions will return either true
* or false and will fill in $sieve->error with a defined error code like
* EC_QUOTA, raw server errors in $sieve->error_raw, and successful responses
* in $sieve->responses.
*
* Usage is pretty simple. The basics is login, do what you need and logout.
* There are two sample files (which suck) test.php and testsieve.php.
* test.php allows you to create/delete/view scripts and testsieve.php is a
* very basic sieve server test.
*
* Please let us know of any bugs, problems or ideas at sieve-php development
* list: sieve-php-devel@lists.sourceforge.net. A web interface to subscribe
* to this list is available at:
* https://lists.sourceforge.net/mailman/listinfo/sieve-php-devel
*
* @author Dan Ellis
* @example simple_example.php A simple example that shows usage of sieve-php
* class.
* @example vacationset-sieve.php A more elaborate example of vacation script
* handling.
* @version 0.1.0
* @package sieve-php
* @todo Maybe add the NOOP function.
* @todo Have timing mechanism when port problems arise.
* @todo Provide better error diagnostics.
*/
class sieve {
var $host;
var $port;
var $user;
var $pass;
/**
* a comma seperated list of allowed auth types, in order of preference
*/
var $auth_types;
/**
* type of authentication attempted
*/
var $auth_in_use;
var $line;
var $fp;
var $retval;
var $tmpfile;
var $fh;
var $len;
var $script;
var $loggedin;
var $capabilities;
var $error;
var $error_raw;
var $responses;
//maybe we should add an errorlvl that the user will pass to new sieve = sieve(,,,,E_WARN)
//so we can decide how to handle certain errors?!?
/**
* get response
*/
function get_response()
{
if($this->loggedin == false or feof($this->fp)){
$this->error = EC_NOT_LOGGED_IN;
$this->error_raw = "You are not logged in.";
return false;
}
unset($this->response);
unset($this->error);
unset($this->error_raw);
$this->line=fgets($this->fp,1024);
$this->token = preg_split('/ /', $this->line, 2);
if($this->token[0] == "NO"){
/* we need to try and extract the error code from here. There are two possibilites: one, that it will take the form of:
NO ("yyyyy") "zzzzzzz" or, two, NO {yyyyy} "zzzzzzzzzzz" */
$this->x = 0;
list($this->ltoken, $this->mtoken, $this->rtoken) = preg_split('/ /', $this->line." ", 3);
if($this->mtoken[0] == "{"){
while($this->mtoken[$this->x] != "}" or $this->err_len < 1){
$this->err_len = substr($this->mtoken, 1, $this->x);
$this->x++;
}
//print "
Trying to receive $this->err_len bytes for result
";
$this->line = fgets($this->fp,$this->err_len);
$this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
$this->err_recv = strlen($this->line);
while($this->err_recv < $this->err_len){
//print "
Trying to receive ".($this->err_len-$this->err_recv)." bytes for result
";
$this->line = fgets($this->fp, ($this->err_len-$this->err_recv));
$this->error_raw[]=substr($this->line, 0, strlen($this->line) -2); //we want to be nice and strip crlf's
$this->err_recv += strlen($this->line);
} /* end while */
$this->line = fgets($this->fp, 1024); //we need to grab the last crlf, i think. this may be a bug...
$this->error=EC_UNKNOWN;
} /* end if */
elseif($this->mtoken[0] == "("){
switch($this->mtoken){
case "(\"QUOTA\")":
$this->error = EC_QUOTA;
$this->error_raw=$this->rtoken;
break;
default:
$this->error = EC_UNKNOWN;
$this->error_raw=$this->rtoken;
break;
} /* end switch */
} /* end elseif */
else{
$this->error = EC_UNKNOWN;
$this->error_raw = $this->line;
}
return false;
} /* end if */
elseif(substr($this->token[0],0,2) == "OK"){
return true;
} /* end elseif */
elseif($this->token[0][0] == "{"){
/* Unable wild assumption: that the only function that gets here is the get_script(), doesn't really matter though */
/* the first line is the len field {xx}, which we don't care about at this point */
$this->line = fgets($this->fp,1024);
while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
$this->response[]=$this->line;
$this->line = fgets($this->fp, 1024);
}
if(substr($this->line,0,2) == "OK")
return true;
else
return false;
} /* end elseif */
elseif($this->token[0][0] == "\""){
/* I'm going under the _assumption_ that the only function that will get here is the listscripts().
I could very well be mistaken here, if I am, this part needs some rework */
$this->found_script=false;
while(substr($this->line,0,2) != "OK" and substr($this->line,0,2) != "NO"){
$this->found_script=true;
list($this->ltoken, $this->rtoken) = explode(" ", $this->line." ",2);
//hmmm, a bug in php, if there is no space on explode line, a warning is generated...
if(strcmp(rtrim($this->rtoken), "ACTIVE")==0){
$this->response["ACTIVE"] = substr(rtrim($this->ltoken),1,-1);
}
else
$this->response[] = substr(rtrim($this->ltoken),1,-1);
$this->line = fgets($this->fp, 1024);
} /* end while */
return true;
} /* end elseif */
else{
$this->error = EC_UNKNOWN;
$this->error_raw = $this->line;
print 'UNKNOWN ERROR (Please report this line to sieve-php-devel
Mailing List to include in future releases):
'.$this->line.'
';
return false;
} /* end else */
} /* end get_response() */
/**
* Initialization of the SIEVE class.
*
* It will return
* false if it fails, true if all is well. This also loads some arrays up
* with some handy information:
*
* @param $host string hostname to connect to. Usually the IMAP server where
* a SIEVE daemon, such as timsieved, is listening.
*
* @param $port string Numeric port to connect to. SIEVE daemons usually
* listen to port 2000.
*
* @param $user string is a super-user or proxy-user that has ACL rights to
* login on behalf of the $auth.
*
* @param $pass string password to use for authentication
*
* @param $auth string is the authorized user identity for which the SIEVE
* scripts will be managed.
*
* @param $auth_types string a string containing all the allowed
* authentication types allowed in order of preference, seperated by spaces.
* (ex. "PLAIN DIGEST-MD5 CRAM-MD5" The method the library will try first
* is PLAIN.) The default for this value is PLAIN.
*
* Note: $user, if included, is the account name (and $pass will be the
* password) of an administrator account that can act on behalf of the user.
* If you are using Cyrus, you must make sure that the admin account has
* rights to admin the user. This is to allow admins to edit/view users
* scripts without having to know the user's password. Very handy.
*/
function sieve($host, $port, $user, $pass, $auth="", $auth_types='PLAIN') {
$this->host=$host;
$this->port=$port;
$this->user=$user;
$this->pass=$pass;
if(!strcmp($auth, "")) /* If there is no auth user, we deem the user itself to be the auth'd user */
$this->auth = $this->user;
else
$this->auth = $auth;
$this->auth_types=$auth_types; /* Allowed authentication types */
$this->fp=0;
$this->line="";
$this->retval="";
$this->tmpfile="";
$this->fh=0;
$this->len=0;
$this->capabilities="";
$this->loggedin=false;
$this->error= "";
$this->error_raw="";
}
/**
* Tokenize a line of input by quote marks and return them as an array
*
* @param $string string Input line to parse for quotes
* @return array Array of broken by quotes parts of original string
*/
function parse_for_quotes($string) {
$start = -1;
$index = 0;
for($ptr = 0; $ptr < strlen($string); $ptr++){
if($string[$ptr] == '"' and $string[$ptr] != '\\'){
if($start == -1){
$start = $ptr;
} /* end if */
else{
$token[$index++] = substr($string, $start + 1, $ptr - $start - 1);
$found = true;
$start = -1;
} /* end else */
} /* end if */
} /* end for */
if(isset($token))
return $token;
else
return false;
} /* end function */
/**
* Parser for status responses.
*
* This should probably be replaced by a smarter parser.
*
* @param $string string Input that contains status responses.
* @todo remove this function and dependencies
*/
function status($string) {
/* Need to remove this and all dependencies from the class */
switch (substr($string, 0,2)){
case "NO":
return F_NO; //there should be some function to extract the error code from this line
//NO ("quota") "You are oly allowed x number of scripts"
break;
case "OK":
return F_OK;
break;
default:
switch ($string[0]){
case "{":
//do parse here for curly braces - maybe modify
//parse_for_quotes to handle any parse delimiter?
return F_HEAD;
break;
default:
return F_DATA;
break;
}
}
}
/**
* Attemp to log in to the sieve server.
*
* It will return false if it fails, true if all is well. This also loads
* some arrays up with some handy information:
*
* capabilities["implementation"] contains the sieve version information
*
* capabilities["auth"] contains the supported authentication modes by the
* SIEVE server.
*
* capabilities["modules"] contains the built in modules like "reject",
* "redirect", etc.
*
* capabilities["starttls"] , if is set and equal to true, will show that the
* server supports the STARTTLS extension.
*
* capabilities["unknown"] contains miscellaneous/extraneous header info sieve
* may have sent
*
* @return boolean
*/
function sieve_login() {
$this->fp=fsockopen($this->host,$this->port);
if($this->fp == false)
return false;
$this->line=fgets($this->fp,1024);
//Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
//response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
//So, if we see IMLEMENTATION in the first line, then we are done.
if(preg_match('/IMPLEMENTATION/',$this->line))
{
//we're on the Cyrus V2 sieve server
while(sieve::status($this->line) == F_DATA){
$this->item = sieve::parse_for_quotes($this->line);
if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
$this->capabilities["implementation"] = $this->item[1];
elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
if(strcmp($this->item[0], "SIEVE") == 0)
$this->cap_type="modules";
else
$this->cap_type="auth";
$this->modules = preg_split('/ /', $this->item[1]);
if(is_array($this->modules)){
foreach($this->modules as $this->module)
$this->capabilities[$this->cap_type][$this->module]=true;
} /* end if */
elseif(is_string($this->modules))
$this->capabilites[$this->cap_type][$this->modules]=true;
}
elseif(strcmp($this->item[0], "STARTTLS") == 0) {
$this->capabilities['starttls'] = true;
}
else{
$this->capabilities["unknown"][]=$this->line;
}
$this->line=fgets($this->fp,1024);
}// end while
}
else
{
//we're on the older Cyrus V1. server
//this version does not support module reporting. We only have auth types.
$this->cap_type="auth";
//break apart at the "Cyrus timsieve...." "SASL={......}"
$this->item = sieve::parse_for_quotes($this->line);
$this->capabilities["implementation"] = $this->item[0];
//we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
$this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
//then split again at the ", " stuff.
$this->modules = preg_split("/$this->modules/", ", ");
//fill up our $this->modules property
if(is_array($this->modules)){
foreach($this->modules as $this->module)
$this->capabilities[$this->cap_type][$this->module]=true;
} /* end if */
elseif(is_string($this->modules))
$this->capabilites[$this->cap_type][$this->module]=true;
}
if(sieve::status($this->line) == F_NO){ //here we should do some returning of error codes?
$this->error=EC_UNKNOWN;
$this->error_raw = "Server not allowing connections.";
return false;
}
/* decision login to decide what type of authentication to use... */
/* Loop through each allowed authentication type and see if the server allows the type */
foreach(explode(" ", $this->auth_types) as $auth_type)
{
if ($this->capabilities["auth"][$auth_type])
{
/* We found an auth type that is allowed. */
$this->auth_in_use = $auth_type;
}
}
/* call our authentication program */
return sieve::authenticate();
}
/**
* Log out of the sieve server.
*
* @return boolean Always returns true at this point.
*/
function sieve_logout() {
if($this->loggedin==false)
return false;
fputs($this->fp,"LOGOUT\r\n");
fclose($this->fp);
$this->loggedin=false;
return true;
}
/**
* Send the script contained in $script to the server.
*
* It will return any error results it finds (in $sieve->error and
* $sieve->error_raw), and return true if it is successfully sent. The
* function does _not_ automatically make the script the active script.
*
* @param $scriptname string The name of the SIEVE script.
* @param $script The script to be uploaded.
* @return boolean Returns true if script has been successfully uploaded.
*/
function sieve_sendscript($scriptname, $script) {
if($this->loggedin==false)
return false;
$this->script=stripslashes($script);
$len=strlen($this->script);
//fputs($this->fp, "PUTSCRIPT \"$scriptname\" \{$len+}\r\n");
fputs($this->fp, 'PUTSCRIPT "' . $scriptname . '" {' . $len . '+}' . "\r\n");
fputs($this->fp, "$this->script\r\n");
return sieve::get_response();
}
/**
* Check if there is enough space for a script to be uploaded.
*
* This function returns true or false based on whether the sieve server will
* allow your script to be sent and your quota has not been exceeded. This
* function does not currently work due to a believed bug in timsieved. It
* could be my code too.
*
* It appears the timsieved does not honor the NUMBER type. see lex.c in
* timsieved src. don't expect this function to work yet. I might have
* messed something up here, too.
*
* @param $scriptname string The name of the SIEVE script.
* @param $scriptsize integer The size of the SIEVE script.
* @return boolean
* @todo Does not work; bug fix and test.
*/
function sieve_havespace($scriptname, $scriptsize) {
if($this->loggedin==false)
return false;
fputs($this->fp, "HAVESPACE \"$scriptname\" $scriptsize\r\n");
return sieve::get_response();
}
/**
* Set the script active on the sieve server.
*
* @param $scriptname string The name of the SIEVE script.
* @return boolean
*/
function sieve_setactivescript($scriptname) {
if($this->loggedin==false)
return false;
fputs($this->fp, "SETACTIVE \"$scriptname\"\r\n");
return sieve::get_response();
}
/**
* Return the contents of the requested script.
*
* If you want to display the script, you will need to change all CrLf to
* '.'.
*
* @param $scriptname string The name of the SIEVE script.
* @return arr SIEVE script data.
*/
function sieve_getscript($scriptname) {
unset($this->script);
if($this->loggedin==false)
return false;
fputs($this->fp, "GETSCRIPT \"$scriptname\"\r\n");
return sieve::get_response();
}
/**
* Attempt to delete the script requested.
*
* If the script is currently active, the server will not have any active
* script after the deletion.
*
* @param $scriptname string The name of the SIEVE script.
* @return mixed
*/
function sieve_deletescript($scriptname) {
if($this->loggedin==false)
return false;
fputs($this->fp, "DELETESCRIPT \"$scriptname\"\r\n");
return sieve::get_response();
}
/**
* List available scripts on the SIEVE server.
*
* This function returns true or false. $sieve->response will be filled
* with the names of the scripts found. If a script is active, the
* $sieve->response["ACTIVE"] will contain the name of the active script.
*
* @return boolean
*/
function sieve_listscripts() {
fputs($this->fp, "LISTSCRIPTS\r\n");
sieve::get_response(); //should always return true, even if there are no scripts...
if(isset($this->found_script) and $this->found_script)
return true;
else{
$this->error=EC_NOSCRIPTS; //sieve::getresponse has no way of telling wether a script was found...
$this->error_raw="No scripts found for this account.";
return false;
}
}
/**
* Check availability of connection to the SIEVE server.
*
* This function returns true or false based on whether the connection to the
* sieve server is still alive.
*
* @return boolean
*/
function sieve_alive() {
if(!isset($this->fp) or $this->fp==0){
$this->error = EC_NOT_LOGGED_IN;
return false;
}
elseif(feof($this->fp)){
$this->error = EC_NOT_LOGGED_IN;
return false;
}
else
return true;
}
/**
* Perform SASL authentication to SIEVE server.
*
* Attempts to authenticate to SIEVE, using some SASL authentication method
* such as PLAIN or DIGEST-MD5.
*
*/
function authenticate() {
switch ($this->auth_in_use) {
case "PLAIN":
$auth=base64_encode("$this->auth\0$this->user\0$this->pass");
$this->len=strlen($auth);
fputs($this->fp, 'AUTHENTICATE "PLAIN" {' . $this->len . '+}' . "\r\n");
fputs($this->fp, "$auth\r\n");
$this->line=fgets($this->fp,1024);
while(sieve::status($this->line) == F_DATA)
$this->line=fgets($this->fp,1024);
if(sieve::status($this->line) == F_NO)
return false;
$this->loggedin=true;
return true;
break;
case "DIGEST-MD5":
// SASL DIGEST-MD5 support works with timsieved 1.1.0
// follows rfc2831 for generating the $response to $challenge
fputs($this->fp, "AUTHENTICATE \"DIGEST-MD5\"\r\n");
// $clen is length of server challenge, we ignore it.
$clen = fgets($this->fp, 1024);
// read for 2048, rfc2831 max length allowed
$challenge = fgets($this->fp, 2048);
// vars used when building $response_value and $response
$cnonce = base64_encode(bin2hex(hmac_md5(microtime())));
$ncount = "00000001";
$qop_value = "auth";
$digest_uri_value = "sieve/$this->host";
// decode the challenge string
$result = decode_challenge($challenge);
// verify server supports qop=auth
$qop = explode(",",$result['qop']);
if (!in_array($qop_value, $qop)) {
// rfc2831: client MUST fail if no qop methods supported
return false;
}
// build the $response_value
$string_a1 = utf8_encode($this->user).":";
$string_a1 .= utf8_encode($result['realm']).":";
$string_a1 .= utf8_encode($this->pass);
$string_a1 = hmac_md5($string_a1);
$A1 = $string_a1.":".$result['nonce'].":".$cnonce.":".utf8_encode($this->auth);
$A1 = bin2hex(hmac_md5($A1));
$A2 = bin2hex(hmac_md5("AUTHENTICATE:$digest_uri_value"));
$string_response = $result['nonce'].":".$ncount.":".$cnonce.":".$qop_value;
$response_value = bin2hex(hmac_md5($A1.":".$string_response.":".$A2));
// build the challenge $response
$reply = "charset=utf-8,username=\"".$this->user."\",realm=\"".$result['realm']."\",";
$reply .= "nonce=\"".$result['nonce']."\",nc=$ncount,cnonce=\"$cnonce\",";
$reply .= "digest-uri=\"$digest_uri_value\",response=$response_value,";
$reply .= "qop=$qop_value,authzid=\"".utf8_encode($this->auth)."\"";
$response = base64_encode($reply);
fputs($this->fp, "\"$response\"\r\n");
$this->line = fgets($this->fp, 1024);
while(sieve::status($this->line) == F_DATA)
$this->line = fgets($this->fp,1024);
if(sieve::status($this->line) == F_NO)
return false;
$this->loggedin = TRUE;
return TRUE;
break;
case "CRAM-MD5":
// SASL CRAM-MD5 support works with timsieved 1.1.0
// follows rfc2195 for generating the $response to $challenge
// CRAM-MD5 does not support proxy of $auth by $user
// requires php mhash extension
fputs($this->fp, "AUTHENTICATE \"CRAM-MD5\"\r\n");
// $clen is the length of the challenge line the server gives us
$clen = fgets($this->fp, 1024);
// read for 1024, should be long enough?
$challenge = fgets($this->fp, 1024);
// build a response to the challenge
$hash = bin2hex(hmac_md5(base64_decode($challenge), $this->pass));
$response = base64_encode($this->user." ".$hash);
// respond to the challenge string
fputs($this->fp, "\"$response\"\r\n");
$this->line = fgets($this->fp, 1024);
while(sieve::status($this->line) == F_DATA)
$this->line = fgets($this->fp,1024);
if(sieve::status($this->line) == F_NO)
return false;
$this->loggedin = TRUE;
return TRUE;
break;
case "LOGIN":
$login=base64_encode($this->user);
$pass=base64_encode($this->pass);
fputs($this->fp, "AUTHENTICATE \"LOGIN\"\r\n");
fputs($this->fp, "{".strlen($login)."+}\r\n");
fputs($this->fp, "$login\r\n");
fputs($this->fp, "{".strlen($pass)."+}\r\n");
fputs($this->fp, "$pass\r\n");
$this->line=fgets($this->fp,1024);
while(sieve::status($this->line) == F_HEAD ||
sieve::status($this->line) == F_DATA)
$this->line=fgets($this->fp,1024);
if(sieve::status($this->line) == F_NO)
return false;
$this->loggedin=true;
return true;
break;
default:
echo 'default';
return false;
break;
}//end switch
}
/**
* Return an array of available capabilities.
*
* @return array
*/
function sieve_get_capability() {
if($this->loggedin==false)
return false;
fputs($this->fp, "CAPABILITY\r\n");
$this->line=fgets($this->fp,1024);
//Hack for older versions of Sieve Server. They do not respond with the Cyrus v2. standard
//response. They repsond as follows: "Cyrus timsieved v1.0.0" "SASL={PLAIN,........}"
//So, if we see IMLEMENTATION in the first line, then we are done.
if(preg_match('/IMPLEMENTATION/',$this->line))
{
//we're on the Cyrus V2 sieve server
while(sieve::status($this->line) == F_DATA){
$this->item = sieve::parse_for_quotes($this->line);
if(strcmp($this->item[0], "IMPLEMENTATION") == 0)
$this->capabilities["implementation"] = $this->item[1];
elseif(strcmp($this->item[0], "SIEVE") == 0 or strcmp($this->item[0], "SASL") == 0){
if(strcmp($this->item[0], "SIEVE") == 0)
$this->cap_type="modules";
else
$this->cap_type="auth";
$this->modules = preg_split('/ /', $this->item[1]);
if(is_array($this->modules)){
foreach($this->modules as $this->module)
$this->capabilities[$this->cap_type][$this->module]=true;
} /* end if */
elseif(is_string($this->modules))
$this->capabilites[$this->cap_type][$this->modules]=true;
}
else{
$this->capabilities["unknown"][]=$this->line;
}
$this->line=fgets($this->fp,1024);
}// end while
}
else
{
//we're on the older Cyrus V1. server
//this version does not support module reporting. We only have auth types.
$this->cap_type="auth";
//break apart at the "Cyrus timsieve...." "SASL={......}"
$this->item = sieve::parse_for_quotes($this->line);
$this->capabilities["implementation"] = $this->item[0];
//we should have "SASL={..........}" now. Break out the {xx,yyy,zzzz}
$this->modules = substr($this->item[1], strpos($this->item[1], "{"),strlen($this->item[1])-1);
//then split again at the ", " stuff.
$this->modules = preg_split("/$this->modules/", ", ");
//fill up our $this->modules property
if(is_array($this->modules)){
foreach($this->modules as $this->module)
$this->capabilities[$this->cap_type][$this->module]=true;
} /* end if */
elseif(is_string($this->modules))
$this->capabilites[$this->cap_type][$this->module]=true;
}
return $this->modules;
}
}
/**
* The following functions are support functions and might be handy to the
* sieve class.
*/
if(!function_exists('hmac_md5')) {
/**
* Creates a HMAC digest that can be used for auth purposes.
* See RFCs 2104, 2617, 2831
* Uses mhash() extension if available
*
* Squirrelmail has this function in functions/auth.php, and it might have been
* included already. However, it helps remove the dependancy on mhash.so PHP
* extension, for some sites. If mhash.so _is_ available, it is used for its
* speed.
*
* This function is Copyright (c) 1999-2003 The SquirrelMail Project Team
* Licensed under the GNU GPL. For full terms see the file COPYING.
*
* @param string $data Data to apply hash function to.
* @param string $key Optional key, which, if supplied, will be used to
* calculate data's HMAC.
* @return string HMAC Digest string
*/
function hmac_md5($data, $key='') {
// See RFCs 2104, 2617, 2831
// Uses mhash() extension if available
if (extension_loaded('mhash')) {
if ($key== '') {
$mhash=mhash(MHASH_MD5,$data);
} else {
$mhash=mhash(MHASH_MD5,$data,$key);
}
return $mhash;
}
if (!$key) {
return pack('H*',md5($data));
}
$key = str_pad($key,64,chr(0x00));
if (strlen($key) > 64) {
$key = pack("H*",md5($key));
}
$k_ipad = $key ^ str_repeat(chr(0x36), 64) ;
$k_opad = $key ^ str_repeat(chr(0x5c), 64) ;
/* Heh, let's get recursive. */
$hmac=hmac_md5($k_opad . pack("H*",md5($k_ipad . $data)) );
return $hmac;
}
}
/**
* A hack to decode the challenge from timsieved 1.1.0.
*
* This function may not work with other versions and most certainly won't work
* with other DIGEST-MD5 implentations
*
* @param $input string Challenge supplied by timsieved.
*/
function decode_challenge ($input) {
$input = base64_decode($input);
preg_match("/nonce=\"(.*)\"/U",$input, $matches);
$resp['nonce'] = $matches[1];
preg_match("/realm=\"(.*)\"/U",$input, $matches);
$resp['realm'] = $matches[1];
preg_match("/qop=\"(.*)\"/U",$input, $matches);
$resp['qop'] = $matches[1];
return $resp;
}
// vim:ts=4:et:ft=php
?>