190 lines
7.0 KiB
PHP
190 lines
7.0 KiB
PHP
<?php
|
|
/**
|
|
* Copyright (c) 2002-2003 Richard Heyes
|
|
* Copyright 2011-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* This code is based on the original code contained in the PEAR Auth_SASL
|
|
* package (v0.5.1):
|
|
* $Id: DigestMD5.php 294702 2010-02-07 16:03:55Z cweiske $
|
|
*
|
|
* That code is covered by the BSD 3-Clause license, as set forth below:
|
|
* +-----------------------------------------------------------------------+
|
|
* | Copyright (c) 2002-2003 Richard Heyes |
|
|
* | All rights reserved. |
|
|
* | |
|
|
* | Redistribution and use in source and binary forms, with or without |
|
|
* | modification, are permitted provided that the following conditions |
|
|
* | are met: |
|
|
* | |
|
|
* | o Redistributions of source code must retain the above copyright |
|
|
* | notice, this list of conditions and the following disclaimer. |
|
|
* | o Redistributions in binary form must reproduce the above copyright |
|
|
* | notice, this list of conditions and the following disclaimer in the |
|
|
* | documentation and/or other materials provided with the distribution.|
|
|
* | o The names of the authors may not be used to endorse or promote |
|
|
* | products derived from this software without specific prior written |
|
|
* | permission. |
|
|
* | |
|
|
* | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
|
* | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
|
* | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
|
* | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
|
* | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
|
* | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
|
* | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
|
* | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
|
* | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
|
* | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
|
* | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
|
* +-----------------------------------------------------------------------+
|
|
*
|
|
* @category Horde
|
|
* @copyright 2002-2003 Richard Heyes
|
|
* @copyright 2011-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Imap_Client
|
|
*/
|
|
|
|
/**
|
|
* Provides the code needed to authenticate via the DIGEST-MD5 SASL mechanism
|
|
* (defined in RFC 2831). This method has been obsoleted by RFC 6331, but
|
|
* still is in use on legacy servers.
|
|
*
|
|
* @author Richard Heyes <richard@php.net>
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @copyright 2002-2003 Richard Heyes
|
|
* @copyright 2011-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
|
|
* @package Imap_Client
|
|
*/
|
|
class Horde_Imap_Client_Auth_DigestMD5
|
|
{
|
|
/**
|
|
* Digest response components.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $_response;
|
|
|
|
/**
|
|
* Generate the Digest-MD5 response.
|
|
*
|
|
* @param string $id Authentication id (username).
|
|
* @param string $pass Password.
|
|
* @param string $challenge The digest challenge sent by the server.
|
|
* @param string $hostname The hostname of the machine connecting to.
|
|
* @param string $service The service name (e.g. 'imap', 'pop3').
|
|
*
|
|
* @throws Horde_Imap_Client_Exception
|
|
*/
|
|
public function __construct($id, $pass, $challenge, $hostname, $service)
|
|
{
|
|
$challenge = $this->_parseChallenge($challenge);
|
|
$cnonce = $this->_getCnonce();
|
|
$digest_uri = sprintf('%s/%s', $service, $hostname);
|
|
|
|
/* Get response value. */
|
|
$A1 = sprintf('%s:%s:%s', pack('H32', hash('md5', sprintf('%s:%s:%s', $id, $challenge['realm'], $pass))), $challenge['nonce'], $cnonce);
|
|
$A2 = 'AUTHENTICATE:' . $digest_uri;
|
|
$response_value = hash('md5', sprintf('%s:%s:00000001:%s:auth:%s', hash('md5', $A1), $challenge['nonce'], $cnonce, hash('md5', $A2)));
|
|
|
|
$this->_response = array(
|
|
'cnonce' => '"' . $cnonce . '"',
|
|
'digest-uri' => '"' . $digest_uri . '"',
|
|
'maxbuf' => $challenge['maxbuf'],
|
|
'nc' => '00000001',
|
|
'nonce' => '"' . $challenge['nonce'] . '"',
|
|
'qop' => 'auth',
|
|
'response' => $response_value,
|
|
'username' => '"' . $id . '"'
|
|
);
|
|
|
|
if (strlen($challenge['realm'])) {
|
|
$this->_response['realm'] = '"' . $challenge['realm'] . '"';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cooerce to string.
|
|
*
|
|
* @return string The digest response (not base64 encoded).
|
|
*/
|
|
public function __toString()
|
|
{
|
|
$out = array();
|
|
foreach ($this->_response as $key => $val) {
|
|
$out[] = $key . '=' . $val;
|
|
}
|
|
return implode(',', $out);
|
|
}
|
|
|
|
/**
|
|
* Return specific digest response directive.
|
|
*
|
|
* @return mixed Requested directive, or null if it does not exist.
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
return isset($this->_response[$name])
|
|
? $this->_response[$name]
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Parses and verifies the digest challenge.
|
|
*
|
|
* @param string $challenge The digest challenge
|
|
*
|
|
* @return array The parsed challenge as an array with directives as keys.
|
|
*
|
|
* @throws Horde_Imap_Client_Exception
|
|
*/
|
|
protected function _parseChallenge($challenge)
|
|
{
|
|
$tokens = array(
|
|
'maxbuf' => 65536,
|
|
'realm' => ''
|
|
);
|
|
|
|
preg_match_all('/([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches, PREG_SET_ORDER);
|
|
|
|
foreach ($matches as $val) {
|
|
$tokens[$val[1]] = trim($val[2], '"');
|
|
}
|
|
|
|
// Required directives.
|
|
if (!isset($tokens['nonce']) || !isset($tokens['algorithm'])) {
|
|
throw new Horde_Imap_Client_Exception(
|
|
Horde_Imap_Client_Translation::r("Authentication failure."),
|
|
Horde_Imap_Client_Exception::SERVER_CONNECT
|
|
);
|
|
}
|
|
|
|
return $tokens;
|
|
}
|
|
|
|
/**
|
|
* Creates the client nonce for the response
|
|
*
|
|
* @return string The cnonce value.
|
|
*/
|
|
protected function _getCnonce()
|
|
{
|
|
if ((@is_readable('/dev/urandom') &&
|
|
($fd = @fopen('/dev/urandom', 'r'))) ||
|
|
(@is_readable('/dev/random') &&
|
|
($fd = @fopen('/dev/random', 'r')))) {
|
|
$str = fread($fd, 32);
|
|
fclose($fd);
|
|
} else {
|
|
$str = '';
|
|
for ($i = 0; $i < 32; ++$i) {
|
|
$str .= chr(mt_rand(0, 255));
|
|
}
|
|
}
|
|
|
|
return base64_encode($str);
|
|
}
|
|
|
|
}
|