3c13b9fb |
1 | <?php |
2 | |
35586184 |
3 | /** |
4 | * auth.php |
5 | * |
867fed37 |
6 | * Contains functions used to do authentication. |
7 | * |
8 | * Dependencies: |
9 | * functions/global.php |
10 | * functions/strings.php. |
35586184 |
11 | * |
4b5049de |
12 | * @copyright © 1999-2007 The SquirrelMail Project Team |
4b4abf93 |
13 | * @license http://opensource.org/licenses/gpl-license.php GNU Public License |
31841a9e |
14 | * @version $Id$ |
d6c32258 |
15 | * @package squirrelmail |
35586184 |
16 | */ |
17 | |
3c13b9fb |
18 | |
f7948940 |
19 | /** |
cffbdf90 |
20 | * Detect whether user is logged in |
202bcbcc |
21 | * |
22 | * Function is similar to is_logged_in() function. If user is logged in, function |
f7948940 |
23 | * returns true. If user is not logged in or session is expired, function saves $_POST |
202bcbcc |
24 | * and $PHP_SELF in session and returns false. POST information is saved in |
f7948940 |
25 | * 'session_expired_post' variable, PHP_SELF is saved in 'session_expired_location'. |
26 | * |
202bcbcc |
27 | * Script that uses this function instead of is_logged_in() function, must handle user |
f7948940 |
28 | * level messages. |
29 | * @return boolean |
30 | * @since 1.5.1 |
31 | */ |
32 | function sqauth_is_logged_in() { |
33 | if ( sqsession_is_registered('user_is_logged_in') ) { |
34 | return true; |
cffbdf90 |
35 | } |
c6f28eb1 |
36 | |
cffbdf90 |
37 | // First we store some information in the new session to prevent |
38 | // information-loss. |
39 | sqGetGlobalVar('PHP_SELF', $PHP_SELF, SQ_SERVER); |
f7948940 |
40 | |
cffbdf90 |
41 | $session_expired_post = $_POST; |
42 | $session_expired_location = $PHP_SELF; |
43 | if (!sqsession_is_registered('session_expired_post')) { |
44 | sqsession_register($session_expired_post,'session_expired_post'); |
45 | } |
46 | if (!sqsession_is_registered('session_expired_location')) { |
47 | sqsession_register($session_expired_location,'session_expired_location'); |
48 | } |
49 | session_write_close(); |
50 | |
51 | return false; |
f7948940 |
52 | } |
53 | |
54 | /** |
55 | * Reads and decodes stored user password information |
56 | * |
57 | * Direct access to password information is deprecated. |
58 | * @return string password in plain text |
59 | * @since 1.5.1 |
60 | */ |
61 | function sqauth_read_password() { |
62 | sqgetGlobalVar('key', $key, SQ_COOKIE); |
63 | sqgetGlobalVar('onetimepad', $onetimepad,SQ_SESSION); |
64 | |
65 | return OneTimePadDecrypt($key, $onetimepad); |
66 | } |
67 | |
68 | /** |
69 | * Saves or updates user password information |
202bcbcc |
70 | * |
f7948940 |
71 | * This function is used to update password information that SquirrelMail |
202bcbcc |
72 | * stores during existing web session. It does not modify password stored |
f7948940 |
73 | * in authentication system used by IMAP server. |
74 | * |
202bcbcc |
75 | * Function must be called before any html output started. Direct access |
76 | * to password information is deprecated. Saved password information is |
381ba319 |
77 | * available only to next executed SquirrelMail script. If your script needs |
202bcbcc |
78 | * access to saved password after sqauth_save_password() call, use returned |
381ba319 |
79 | * OTP encrypted key. |
f7948940 |
80 | * @param string $pass password |
202bcbcc |
81 | * @return string password encrypted with OTP. In case script wants to access |
381ba319 |
82 | * password information before reloading page. |
f7948940 |
83 | * @since 1.5.1 |
84 | */ |
85 | function sqauth_save_password($pass) { |
86 | sqgetGlobalVar('base_uri', $base_uri, SQ_SESSION); |
87 | |
88 | $onetimepad = OneTimePadCreate(strlen($pass)); |
89 | sqsession_register($onetimepad,'onetimepad'); |
90 | $key = OneTimePadEncrypt($pass, $onetimepad); |
381ba319 |
91 | sqsetcookie('key', $key, false, $base_uri); |
92 | return $key; |
f7948940 |
93 | } |
94 | |
8b52a4ca |
95 | /** |
96 | * Given the challenge from the server, supply the response using cram-md5 (See |
97 | * RFC 2195 for details) |
98 | * |
99 | * @param string $username User ID |
100 | * @param string $password User password supplied by User |
101 | * @param string $challenge The challenge supplied by the server |
102 | * @return string The response to be sent to the IMAP server |
f7948940 |
103 | * @since 1.4.0 |
8b52a4ca |
104 | */ |
47a29326 |
105 | function cram_md5_response ($username,$password,$challenge) { |
8b52a4ca |
106 | $challenge=base64_decode($challenge); |
107 | $hash=bin2hex(hmac_md5($challenge,$password)); |
108 | $response=base64_encode($username . " " . $hash) . "\r\n"; |
109 | return $response; |
47a29326 |
110 | } |
111 | |
8b52a4ca |
112 | /** |
113 | * Return Digest-MD5 response. |
114 | * Given the challenge from the server, calculate and return the |
115 | * response-string for digest-md5 authentication. (See RFC 2831 for more |
116 | * details) |
117 | * |
118 | * @param string $username User ID |
119 | * @param string $password User password supplied by User |
120 | * @param string $challenge The challenge supplied by the server |
121 | * @param string $service The service name, usually 'imap'; it is used to |
122 | * define the digest-uri. |
123 | * @param string $host The host name, usually the server's FQDN; it is used to |
124 | * define the digest-uri. |
b0434236 |
125 | * @param string $authz Authorization ID (since 1.5.2) |
8b52a4ca |
126 | * @return string The response to be sent to the IMAP server |
f7948940 |
127 | * @since 1.4.0 |
8b52a4ca |
128 | */ |
b0434236 |
129 | function digest_md5_response ($username,$password,$challenge,$service,$host,$authz='') { |
ea321915 |
130 | $result=digest_md5_parse_challenge($challenge); |
62f7daa5 |
131 | |
ea321915 |
132 | // verify server supports qop=auth |
133 | // $qop = explode(",",$result['qop']); |
134 | //if (!in_array("auth",$qop)) { |
47a29326 |
135 | // rfc2831: client MUST fail if no qop methods supported |
ea321915 |
136 | // return false; |
137 | //} |
138 | $cnonce = base64_encode(bin2hex(hmac_md5(microtime()))); |
139 | $ncount = "00000001"; |
140 | |
141 | /* This can be auth (authentication only), auth-int (integrity protection), or |
142 | auth-conf (confidentiality protection). Right now only auth is supported. |
143 | DO NOT CHANGE THIS VALUE */ |
144 | $qop_value = "auth"; |
145 | |
146 | $digest_uri_value = $service . '/' . $host; |
147 | |
148 | // build the $response_value |
149 | //FIXME This will probably break badly if a server sends more than one realm |
150 | $string_a1 = utf8_encode($username).":"; |
151 | $string_a1 .= utf8_encode($result['realm']).":"; |
152 | $string_a1 .= utf8_encode($password); |
153 | $string_a1 = hmac_md5($string_a1); |
154 | $A1 = $string_a1 . ":" . $result['nonce'] . ":" . $cnonce; |
31dd6e3c |
155 | if(!empty($authz)) { |
156 | $A1 .= ":" . utf8_encode($authz); |
157 | } |
ea321915 |
158 | $A1 = bin2hex(hmac_md5($A1)); |
159 | $A2 = "AUTHENTICATE:$digest_uri_value"; |
160 | // If qop is auth-int or auth-conf, A2 gets a little extra |
161 | if ($qop_value != 'auth') { |
162 | $A2 .= ':00000000000000000000000000000000'; |
163 | } |
164 | $A2 = bin2hex(hmac_md5($A2)); |
165 | |
166 | $string_response = $result['nonce'] . ':' . $ncount . ':' . $cnonce . ':' . $qop_value; |
167 | $response_value = bin2hex(hmac_md5($A1.":".$string_response.":".$A2)); |
168 | |
169 | $reply = 'charset=utf-8,username="' . $username . '",realm="' . $result["realm"] . '",'; |
170 | $reply .= 'nonce="' . $result['nonce'] . '",nc=' . $ncount . ',cnonce="' . $cnonce . '",'; |
171 | $reply .= "digest-uri=\"$digest_uri_value\",response=$response_value"; |
172 | $reply .= ',qop=' . $qop_value; |
b0434236 |
173 | if(!empty($authz)) { |
174 | $reply .= ',authzid=' . $authz; |
175 | } |
ea321915 |
176 | $reply = base64_encode($reply); |
177 | return $reply . "\r\n"; |
62f7daa5 |
178 | |
47a29326 |
179 | } |
180 | |
8b52a4ca |
181 | /** |
182 | * Parse Digest-MD5 challenge. |
183 | * This function parses the challenge sent during DIGEST-MD5 authentication and |
184 | * returns an array. See the RFC for details on what's in the challenge string. |
185 | * |
186 | * @param string $challenge Digest-MD5 Challenge |
187 | * @return array Digest-MD5 challenge decoded data |
f7948940 |
188 | * @since 1.4.0 |
8b52a4ca |
189 | */ |
47a29326 |
190 | function digest_md5_parse_challenge($challenge) { |
ea321915 |
191 | $challenge=base64_decode($challenge); |
192 | while (isset($challenge)) { |
193 | if ($challenge{0} == ',') { // First char is a comma, must not be 1st time through loop |
194 | $challenge=substr($challenge,1); |
195 | } |
196 | $key=explode('=',$challenge,2); |
197 | $challenge=$key[1]; |
198 | $key=$key[0]; |
199 | if ($challenge{0} == '"') { |
200 | // We're in a quoted value |
201 | // Drop the first quote, since we don't care about it |
202 | $challenge=substr($challenge,1); |
203 | // Now explode() to the next quote, which is the end of our value |
204 | $val=explode('"',$challenge,2); |
205 | $challenge=$val[1]; // The rest of the challenge, work on it in next iteration of loop |
206 | $value=explode(',',$val[0]); |
207 | // Now, for those quoted values that are only 1 piece.. |
208 | if (sizeof($value) == 1) { |
209 | $value=$value[0]; // Convert to non-array |
210 | } |
211 | } else { |
212 | // We're in a "simple" value - explode to next comma |
213 | $val=explode(',',$challenge,2); |
214 | if (isset($val[1])) { |
215 | $challenge=$val[1]; |
216 | } else { |
217 | unset($challenge); |
218 | } |
219 | $value=$val[0]; |
220 | } |
221 | $parsed["$key"]=$value; |
222 | } // End of while loop |
223 | return $parsed; |
47a29326 |
224 | } |
225 | |
8b52a4ca |
226 | /** |
227 | * Creates a HMAC digest that can be used for auth purposes |
228 | * See RFCs 2104, 2617, 2831 |
229 | * Uses mhash() extension if available |
230 | * |
231 | * @param string $data Data to apply hash function to. |
232 | * @param string $key Optional key, which, if supplied, will be used to |
233 | * calculate data's HMAC. |
234 | * @return string HMAC Digest string |
f7948940 |
235 | * @since 1.4.0 |
8b52a4ca |
236 | */ |
1c6d997a |
237 | function hmac_md5($data, $key='') { |
639c7164 |
238 | if (extension_loaded('mhash')) { |
ea321915 |
239 | if ($key== '') { |
240 | $mhash=mhash(MHASH_MD5,$data); |
241 | } else { |
242 | $mhash=mhash(MHASH_MD5,$data,$key); |
243 | } |
244 | return $mhash; |
639c7164 |
245 | } |
246 | if (!$key) { |
ea321915 |
247 | return pack('H*',md5($data)); |
639c7164 |
248 | } |
249 | $key = str_pad($key,64,chr(0x00)); |
250 | if (strlen($key) > 64) { |
251 | $key = pack("H*",md5($key)); |
252 | } |
253 | $k_ipad = $key ^ str_repeat(chr(0x36), 64) ; |
254 | $k_opad = $key ^ str_repeat(chr(0x5c), 64) ; |
1c6d997a |
255 | /* Heh, let's get recursive. */ |
256 | $hmac=hmac_md5($k_opad . pack("H*",md5($k_ipad . $data)) ); |
639c7164 |
257 | return $hmac; |
258 | } |
259 | |
62f7daa5 |
260 | /** |
9bd3b1e6 |
261 | * Fillin user and password based on SMTP auth settings. |
262 | * |
9bd3b1e6 |
263 | * @param string $user Reference to SMTP username |
264 | * @param string $pass Reference to SMTP password (unencrypted) |
f7948940 |
265 | * @since 1.5.0 |
9bd3b1e6 |
266 | */ |
267 | function get_smtp_user(&$user, &$pass) { |
62f7daa5 |
268 | global $username, $smtp_auth_mech, |
9bd3b1e6 |
269 | $smtp_sitewide_user, $smtp_sitewide_pass; |
270 | |
271 | if ($smtp_auth_mech == 'none') { |
272 | $user = ''; |
273 | $pass = ''; |
db84eecd |
274 | } elseif ( isset($smtp_sitewide_user) && isset($smtp_sitewide_pass) && |
275 | !empty($smtp_sitewide_user)) { |
9bd3b1e6 |
276 | $user = $smtp_sitewide_user; |
277 | $pass = $smtp_sitewide_pass; |
278 | } else { |
9bd3b1e6 |
279 | $user = $username; |
f7948940 |
280 | $pass = sqauth_read_password(); |
9bd3b1e6 |
281 | } |
282 | } |