Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
94 jpm 1
<?php
2
// +-----------------------------------------------------------------------+
3
// | Copyright (c) 2002-2003 Richard Heyes                                 |
4
// | All rights reserved.                                                  |
5
// |                                                                       |
6
// | Redistribution and use in source and binary forms, with or without    |
7
// | modification, are permitted provided that the following conditions    |
8
// | are met:                                                              |
9
// |                                                                       |
10
// | o Redistributions of source code must retain the above copyright      |
11
// |   notice, this list of conditions and the following disclaimer.       |
12
// | o Redistributions in binary form must reproduce the above copyright   |
13
// |   notice, this list of conditions and the following disclaimer in the |
14
// |   documentation and/or other materials provided with the distribution.|
15
// | o The names of the authors may not be used to endorse or promote      |
16
// |   products derived from this software without specific prior written  |
17
// |   permission.                                                         |
18
// |                                                                       |
19
// | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   |
20
// | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     |
21
// | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22
// | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT  |
23
// | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24
// | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT      |
25
// | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26
// | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27
// | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT   |
28
// | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29
// | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  |
30
// |                                                                       |
31
// +-----------------------------------------------------------------------+
32
// | Author: Richard Heyes <richard@php.net>                               |
33
// +-----------------------------------------------------------------------+
34
//
35
// $Id: DigestMD5.php,v 1.8 2006/03/22 05:20:11 amistry Exp $
36
 
37
/**
38
* Implmentation of DIGEST-MD5 SASL mechanism
39
*
40
* @author  Richard Heyes <richard@php.net>
41
* @access  public
42
* @version 1.0
43
* @package Auth_SASL
44
*/
45
 
46
require_once('Auth/SASL/Common.php');
47
 
48
class Auth_SASL_DigestMD5 extends Auth_SASL_Common
49
{
50
    /**
51
    * Provides the (main) client response for DIGEST-MD5
52
    * requires a few extra parameters than the other
53
    * mechanisms, which are unavoidable.
54
    *
55
    * @param  string $authcid   Authentication id (username)
56
    * @param  string $pass      Password
57
    * @param  string $challenge The digest challenge sent by the server
58
    * @param  string $hostname  The hostname of the machine you're connecting to
59
    * @param  string $service   The servicename (eg. imap, pop, acap etc)
60
    * @param  string $authzid   Authorization id (username to proxy as)
61
    * @return string            The digest response (NOT base64 encoded)
62
    * @access public
63
    */
64
    function getResponse($authcid, $pass, $challenge, $hostname, $service, $authzid = '')
65
    {
66
        $challenge = $this->_parseChallenge($challenge);
67
        $authzid_string = '';
68
        if ($authzid != '') {
69
            $authzid_string = ',authzid="' . $authzid . '"';
70
        }
71
 
72
        if (!empty($challenge)) {
73
            $cnonce         = $this->_getCnonce();
74
            $digest_uri     = sprintf('%s/%s', $service, $hostname);
75
            $response_value = $this->_getResponseValue($authcid, $pass, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $authzid);
76
 
77
            if ($challenge['realm']) {
78
                return sprintf('username="%s",realm="%s"' . $authzid_string  .
79
',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
80
            } else {
81
                return sprintf('username="%s"' . $authzid_string  . ',nonce="%s",cnonce="%s",nc=00000001,qop=auth,digest-uri="%s",response=%s,maxbuf=%d', $authcid, $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
82
            }
83
        } else {
84
            return PEAR::raiseError('Invalid digest challenge');
85
        }
86
    }
87
 
88
    /**
89
    * Parses and verifies the digest challenge*
90
    *
91
    * @param  string $challenge The digest challenge
92
    * @return array             The parsed challenge as an assoc
93
    *                           array in the form "directive => value".
94
    * @access private
95
    */
96
    function _parseChallenge($challenge)
97
    {
98
        $tokens = array();
99
        while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
100
 
101
            // Ignore these as per rfc2831
102
            if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
103
                $challenge = substr($challenge, strlen($matches[0]) + 1);
104
                continue;
105
            }
106
 
107
            // Allowed multiple "realm" and "auth-param"
108
            if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
109
                if (is_array($tokens[$matches[1]])) {
110
                    $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
111
                } else {
112
                    $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
113
                }
114
 
115
            // Any other multiple instance = failure
116
            } elseif (!empty($tokens[$matches[1]])) {
117
                $tokens = array();
118
                break;
119
 
120
            } else {
121
                $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
122
            }
123
 
124
            // Remove the just parsed directive from the challenge
125
            $challenge = substr($challenge, strlen($matches[0]) + 1);
126
        }
127
 
128
        /**
129
        * Defaults and required directives
130
        */
131
        // Realm
132
        if (empty($tokens['realm'])) {
133
            $tokens['realm'] = "";
134
        }
135
 
136
        // Maxbuf
137
        if (empty($tokens['maxbuf'])) {
138
            $tokens['maxbuf'] = 65536;
139
        }
140
 
141
        // Required: nonce, algorithm
142
        if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
143
            return array();
144
        }
145
 
146
        return $tokens;
147
    }
148
 
149
    /**
150
    * Creates the response= part of the digest response
151
    *
152
    * @param  string $authcid    Authentication id (username)
153
    * @param  string $pass       Password
154
    * @param  string $realm      Realm as provided by the server
155
    * @param  string $nonce      Nonce as provided by the server
156
    * @param  string $cnonce     Client nonce
157
    * @param  string $digest_uri The digest-uri= value part of the response
158
    * @param  string $authzid    Authorization id
159
    * @return string             The response= part of the digest response
160
    * @access private
161
    */
162
    function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
163
    {
164
        if ($authzid == '') {
165
            $A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
166
        } else {
167
            $A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
168
        }
169
        $A2 = 'AUTHENTICATE:' . $digest_uri;
170
        return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
171
    }
172
 
173
    /**
174
    * Creates the client nonce for the response
175
    *
176
    * @return string  The cnonce value
177
    * @access private
178
    */
179
    function _getCnonce()
180
    {
181
        if (file_exists('/dev/urandom') && $fd = @fopen('/dev/urandom', 'r')) {
182
            return base64_encode(fread($fd, 32));
183
 
184
        } elseif (file_exists('/dev/random') && $fd = @fopen('/dev/random', 'r')) {
185
            return base64_encode(fread($fd, 32));
186
 
187
        } else {
188
            $str = '';
189
            mt_srand((double)microtime()*10000000);
190
            for ($i=0; $i<32; $i++) {
191
                $str .= chr(mt_rand(0, 255));
192
            }
193
 
194
            return base64_encode($str);
195
        }
196
    }
197
}
198
?>