Subversion Repositories Applications.papyrus

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1173 jp_milcent 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.1 2006-12-14 15:04:29 jp_milcent 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
            return sprintf('username="%s",realm="%s"' . $authzid_string  . ',nonce="%s",cnonce="%s",nc="00000001",qop=auth,digest-uri="%s",response=%s,%d', $authcid, $challenge['realm'], $challenge['nonce'], $cnonce, $digest_uri, $response_value, $challenge['maxbuf']);
78
        } else {
79
            return PEAR::raiseError('Invalid digest challenge');
80
        }
81
    }
82
 
83
    /**
84
    * Parses and verifies the digest challenge*
85
    *
86
    * @param  string $challenge The digest challenge
87
    * @return array             The parsed challenge as an assoc
88
    *                           array in the form "directive => value".
89
    * @access private
90
    */
91
    function _parseChallenge($challenge)
92
    {
93
        $tokens = array();
94
        while (preg_match('/^([a-z-]+)=("[^"]+(?<!\\\)"|[^,]+)/i', $challenge, $matches)) {
95
 
96
            // Ignore these as per rfc2831
97
            if ($matches[1] == 'opaque' OR $matches[1] == 'domain') {
98
                $challenge = substr($challenge, strlen($matches[0]) + 1);
99
                continue;
100
            }
101
 
102
            // Allowed multiple "realm" and "auth-param"
103
            if (!empty($tokens[$matches[1]]) AND ($matches[1] == 'realm' OR $matches[1] == 'auth-param')) {
104
                if (is_array($tokens[$matches[1]])) {
105
                    $tokens[$matches[1]][] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
106
                } else {
107
                    $tokens[$matches[1]] = array($tokens[$matches[1]], preg_replace('/^"(.*)"$/', '\\1', $matches[2]));
108
                }
109
 
110
            // Any other multiple instance = failure
111
            } elseif (!empty($tokens[$matches[1]])) {
112
                $tokens = array();
113
                break;
114
 
115
            } else {
116
                $tokens[$matches[1]] = preg_replace('/^"(.*)"$/', '\\1', $matches[2]);
117
            }
118
 
119
            // Remove the just parsed directive from the challenge
120
            $challenge = substr($challenge, strlen($matches[0]) + 1);
121
        }
122
 
123
        /**
124
        * Defaults and required directives
125
        */
126
        // Realm
127
        if (empty($tokens['realm'])) {
128
            $uname = posix_uname();
129
            $tokens['realm'] = $uname['nodename'];
130
        }
131
 
132
        // Maxbuf
133
        if (empty($tokens['maxbuf'])) {
134
            $tokens['maxbuf'] = 65536;
135
        }
136
 
137
        // Required: nonce, algorithm
138
        if (empty($tokens['nonce']) OR empty($tokens['algorithm'])) {
139
            return array();
140
        }
141
 
142
        return $tokens;
143
    }
144
 
145
    /**
146
    * Creates the response= part of the digest response
147
    *
148
    * @param  string $authcid    Authentication id (username)
149
    * @param  string $pass       Password
150
    * @param  string $realm      Realm as provided by the server
151
    * @param  string $nonce      Nonce as provided by the server
152
    * @param  string $cnonce     Client nonce
153
    * @param  string $digest_uri The digest-uri= value part of the response
154
    * @param  string $authzid    Authorization id
155
    * @return string             The response= part of the digest response
156
    * @access private
157
    */
158
    function _getResponseValue($authcid, $pass, $realm, $nonce, $cnonce, $digest_uri, $authzid = '')
159
    {
160
        if ($authzid == '') {
161
            $A1 = sprintf('%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce);
162
        } else {
163
            $A1 = sprintf('%s:%s:%s:%s', pack('H32', md5(sprintf('%s:%s:%s', $authcid, $realm, $pass))), $nonce, $cnonce, $authzid);
164
        }
165
        $A2 = 'AUTHENTICATE:' . $digest_uri;
166
        return md5(sprintf('%s:%s:00000001:%s:auth:%s', md5($A1), $nonce, $cnonce, md5($A2)));
167
    }
168
 
169
    /**
170
    * Creates the client nonce for the response
171
    *
172
    * @return string  The cnonce value
173
    * @access private
174
    */
175
    function _getCnonce()
176
    {
177
        if (file_exists('/dev/urandom')) {
178
            return base64_encode(fread(fopen('/dev/urandom', 'r'), 32));
179
 
180
        } elseif (file_exists('/dev/random')) {
181
            return base64_encode(fread(fopen('/dev/random', 'r'), 32));
182
 
183
        } else {
184
            $str = '';
185
            mt_srand((double)microtime()*10000000);
186
            for ($i=0; $i<32; $i++) {
187
                $str .= chr(mt_rand(0, 255));
188
            }
189
 
190
            return base64_encode($str);
191
        }
192
    }
193
}
194
?>