Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.
   1  <?php
   2  /**
   3   * Copyright (c) 2002-2003 Richard Heyes
   4   * Copyright 2011-2017 Horde LLC (http://www.horde.org/)
   5   *
   6   * This code is based on the original code contained in the PEAR Auth_SASL
   7   * package (v0.5.1):
   8   *   $Id: DigestMD5.php 294702 2010-02-07 16:03:55Z cweiske $
   9   *
  10   * That code is covered by the BSD 3-Clause license, as set forth below:
  11   * +-----------------------------------------------------------------------+
  12   * | Copyright (c) 2002-2003 Richard Heyes                                 |
  13   * | All rights reserved.                                                  |
  14   * |                                                                       |
  15   * | Redistribution and use in source and binary forms, with or without    |
  16   * | modification, are permitted provided that the following conditions    |
  17   * | are met:                                                              |
  18   * |                                                                       |
  19   * | o Redistributions of source code must retain the above copyright      |
  20   * |   notice, this list of conditions and the following disclaimer.       |
  21   * | o Redistributions in binary form must reproduce the above copyright   |
  22   * |   notice, this list of conditions and the following disclaimer in the |
  23   * |   documentation and/or other materials provided with the distribution.|
  24   * | o The names of the authors may not be used to endorse or promote      |
  25   * |   products derived from this software without specific prior written  |
  26   * |   permission.                                                         |
  27   * |                                                                       |
  28   * | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
  29   * | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
  30   * | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
  31   * | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
  32   * | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
  33   * | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
  34   * | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
  35   * | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
  36   * | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
  37   * | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
  38   * | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
  39   * +-----------------------------------------------------------------------+
  40   *
  41   * @category  Horde
  42   * @copyright 2002-2003 Richard Heyes
  43   * @copyright 2011-2017 Horde LLC
  44   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  45   * @package   Imap_Client
  46   */
  47  
  48  /**
  49   * Provides the code needed to authenticate via the DIGEST-MD5 SASL mechanism
  50   * (defined in RFC 2831). This method has been obsoleted by RFC 6331, but
  51   * still is in use on legacy servers.
  52   *
  53   * @author    Richard Heyes <richard@php.net>
  54   * @author    Michael Slusarz <slusarz@horde.org>
  55   * @copyright 2002-2003 Richard Heyes
  56   * @copyright 2011-2017 Horde LLC
  57   * @license   http://www.horde.org/licenses/lgpl21 LGPL 2.1
  58   * @package   Imap_Client
  59   */
  60  class Horde_Imap_Client_Auth_DigestMD5
  61  {
  62      /**
  63       * Digest response components.
  64       *
  65       * @var string
  66       */
  67      protected $_response;
  68  
  69      /**
  70       * Generate the Digest-MD5 response.
  71       *
  72       * @param string $id         Authentication id (username).
  73       * @param string $pass       Password.
  74       * @param string $challenge  The digest challenge sent by the server.
  75       * @param string $hostname   The hostname of the machine connecting to.
  76       * @param string $service    The service name (e.g. 'imap', 'pop3').
  77       *
  78       * @throws Horde_Imap_Client_Exception
  79       */
  80      public function __construct($id, $pass, $challenge, $hostname, $service)
  81      {
  82          $challenge = $this->_parseChallenge($challenge);
  83          $cnonce = $this->_getCnonce();
  84          $digest_uri = sprintf('%s/%s', $service, $hostname);
  85  
  86          /* Get response value. */
  87          $A1 = sprintf('%s:%s:%s', pack('H32', hash('md5', sprintf('%s:%s:%s', $id, $challenge['realm'], $pass))), $challenge['nonce'], $cnonce);
  88          $A2 = 'AUTHENTICATE:' . $digest_uri;
  89          $response_value = hash('md5', sprintf('%s:%s:00000001:%s:auth:%s', hash('md5', $A1), $challenge['nonce'], $cnonce, hash('md5', $A2)));
  90  
  91          $this->_response = array(
  92              'cnonce' => '"' . $cnonce . '"',
  93              'digest-uri' => '"' . $digest_uri . '"',
  94              'maxbuf' => $challenge['maxbuf'],
  95              'nc' => '00000001',
  96              'nonce' => '"' . $challenge['nonce'] . '"',
  97              'qop' => 'auth',
  98              'response' => $response_value,
  99              'username' => '"' . $id . '"'
 100          );
 101  
 102          if (strlen($challenge['realm'])) {
 103              $this->_response['realm'] = '"' . $challenge['realm'] . '"';
 104          }
 105      }
 106  
 107      /**
 108       * Cooerce to string.
 109       *
 110       * @return string  The digest response (not base64 encoded).
 111       */
 112      public function __toString()
 113      {
 114          $out = array();
 115          foreach ($this->_response as $key => $val) {
 116              $out[] = $key . '=' . $val;
 117          }
 118          return implode(',', $out);
 119      }
 120  
 121      /**
 122       * Return specific digest response directive.
 123       *
 124       * @return mixed  Requested directive, or null if it does not exist.
 125       */
 126      public function __get($name)
 127      {
 128          return isset($this->_response[$name])
 129              ? $this->_response[$name]
 130              : null;
 131      }
 132  
 133      /**
 134      * Parses and verifies the digest challenge.
 135      *
 136      * @param string $challenge  The digest challenge
 137      *
 138      * @return array  The parsed challenge as an array with directives as keys.
 139      *
 140      * @throws Horde_Imap_Client_Exception
 141      */
 142      protected function _parseChallenge($challenge)
 143      {
 144          $tokens = array(
 145              'maxbuf' => 65536,
 146              'realm' => ''
 147          );
 148  
 149          preg_match_all('/([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches, PREG_SET_ORDER);
 150  
 151          foreach ($matches as $val) {
 152              $tokens[$val[1]] = trim($val[2], '"');
 153          }
 154  
 155          // Required directives.
 156          if (!isset($tokens['nonce']) || !isset($tokens['algorithm'])) {
 157              throw new Horde_Imap_Client_Exception(
 158                  Horde_Imap_Client_Translation::r("Authentication failure."),
 159                  Horde_Imap_Client_Exception::SERVER_CONNECT
 160              );
 161          }
 162  
 163          return $tokens;
 164      }
 165  
 166      /**
 167       * Creates the client nonce for the response
 168       *
 169       * @return string  The cnonce value.
 170       */
 171      protected function _getCnonce()
 172      {
 173          if ((@is_readable('/dev/urandom') &&
 174               ($fd = @fopen('/dev/urandom', 'r'))) ||
 175              (@is_readable('/dev/random') &&
 176               ($fd = @fopen('/dev/random', 'r')))) {
 177              $str = fread($fd, 32);
 178              fclose($fd);
 179          } else {
 180              $str = '';
 181              for ($i = 0; $i < 32; ++$i) {
 182                  $str .= chr(mt_rand(0, 255));
 183              }
 184          }
 185  
 186          return base64_encode($str);
 187      }
 188  
 189  }