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 | * |
22387c8d |
12 | * @copyright 1999-2017 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 |
de219480 |
24 | * and PAGE_NAME in session and returns false. POST information is saved in |
25 | * 'session_expired_post' variable, PAGE_NAME is saved in 'session_expired_location'. |
f7948940 |
26 | * |
7428254a |
27 | * This function optionally checks the referrer of this page request. If the |
28 | * administrator wants to impose a check that the referrer of this page request |
29 | * is another page on the same domain (otherwise, the page request is likely |
30 | * the result of a XSS or phishing attack), then they need to specify the |
31 | * acceptable referrer domain in a variable named $check_referrer in |
32 | * config/config.php (or the configuration tool) for which the value is |
33 | * usually the same as the $domain setting (for example: |
34 | * $check_referrer = 'example.com'; |
35 | * However, in some cases (where proxy servers are in use, etc.), the |
36 | * acceptable referrer might be different. If $check_referrer is set to |
37 | * "###DOMAIN###", then the current value of $domain is used (useful in |
38 | * situations where $domain might change at runtime (when using the Login |
39 | * Manager plugin to host multiple domains with one SquirrelMail installation, |
40 | * for example)): |
41 | * $check_referrer = '###DOMAIN###'; |
42 | * NOTE HOWEVER, that referrer checks are not foolproof - they can be spoofed |
43 | * by browsers, and some browsers intentionally don't send them, in which |
44 | * case SquirrelMail silently ignores referrer checks. |
45 | * |
202bcbcc |
46 | * Script that uses this function instead of is_logged_in() function, must handle user |
f7948940 |
47 | * level messages. |
48 | * @return boolean |
49 | * @since 1.5.1 |
50 | */ |
51 | function sqauth_is_logged_in() { |
7428254a |
52 | |
53 | global $check_referrer, $domain; |
54 | if (!sqgetGlobalVar('HTTP_REFERER', $referrer, SQ_SERVER)) $referrer = ''; |
55 | if ($check_referrer == '###DOMAIN###') $check_referrer = $domain; |
56 | if (!empty($check_referrer)) { |
57 | $ssl_check_referrer = 'https://' . $check_referrer; |
b0f43f13 |
58 | $plain_check_referrer = 'http://' . $check_referrer; |
7428254a |
59 | } |
60 | if (sqsession_is_registered('user_is_logged_in') |
61 | && (!$check_referrer || empty($referrer) |
62 | || ($check_referrer && !empty($referrer) |
b0f43f13 |
63 | && (strpos(strtolower($referrer), strtolower($plain_check_referrer)) === 0 |
7428254a |
64 | || strpos(strtolower($referrer), strtolower($ssl_check_referrer)) === 0)))) { |
f7948940 |
65 | return true; |
cffbdf90 |
66 | } |
c6f28eb1 |
67 | |
f74ec578 |
68 | // First we store some information in the new session to prevent |
69 | // information-loss. |
70 | $session_expired_post = $_POST; |
71 | if (defined('PAGE_NAME')) |
72 | $session_expired_location = PAGE_NAME; |
73 | else |
74 | $session_expired_location = ''; |
75 | |
76 | if (!sqsession_is_registered('session_expired_post')) { |
77 | sqsession_register($session_expired_post,'session_expired_post'); |
78 | } |
79 | if (!sqsession_is_registered('session_expired_location')) { |
80 | sqsession_register($session_expired_location,'session_expired_location'); |
81 | } |
82 | |
83 | session_write_close(); |
84 | |
85 | return false; |
f7948940 |
86 | } |
87 | |
88 | /** |
89 | * Reads and decodes stored user password information |
90 | * |
91 | * Direct access to password information is deprecated. |
92 | * @return string password in plain text |
93 | * @since 1.5.1 |
94 | */ |
95 | function sqauth_read_password() { |
8747058a |
96 | global $currentHookName; |
97 | if ($currentHookName == 'login_verified') global $key; |
98 | |
f7948940 |
99 | sqgetGlobalVar('key', $key, SQ_COOKIE); |
100 | sqgetGlobalVar('onetimepad', $onetimepad,SQ_SESSION); |
101 | |
102 | return OneTimePadDecrypt($key, $onetimepad); |
103 | } |
104 | |
105 | /** |
106 | * Saves or updates user password information |
202bcbcc |
107 | * |
45d5ec97 |
108 | * This function is used to update the password information that |
109 | * SquirrelMail stores in the existing PHP session. It does NOT |
110 | * modify the password stored in the authentication system used |
111 | * by the IMAP server. |
112 | * |
113 | * This function must be called before any html output is started. |
114 | * Direct access to password information is deprecated. The saved |
115 | * password information is available only to the SquirrelMail script |
116 | * that is called/executed AFTER the current one. If your script |
117 | * needs access to the saved password after a sqauth_save_password() |
118 | * call, use the returned OTP encrypted key. |
f7948940 |
119 | * |
f7948940 |
120 | * @param string $pass password |
45d5ec97 |
121 | * |
122 | * @return string Password encrypted with OTP. In case the script |
123 | * wants to access the password information before |
124 | * the end of its execution. |
125 | * |
f7948940 |
126 | * @since 1.5.1 |
45d5ec97 |
127 | * |
f7948940 |
128 | */ |
129 | function sqauth_save_password($pass) { |
130 | sqgetGlobalVar('base_uri', $base_uri, SQ_SESSION); |
131 | |
132 | $onetimepad = OneTimePadCreate(strlen($pass)); |
133 | sqsession_register($onetimepad,'onetimepad'); |
134 | $key = OneTimePadEncrypt($pass, $onetimepad); |
381ba319 |
135 | sqsetcookie('key', $key, false, $base_uri); |
136 | return $key; |
f7948940 |
137 | } |
138 | |
8b52a4ca |
139 | /** |
140 | * Given the challenge from the server, supply the response using cram-md5 (See |
141 | * RFC 2195 for details) |
142 | * |
143 | * @param string $username User ID |
144 | * @param string $password User password supplied by User |
145 | * @param string $challenge The challenge supplied by the server |
146 | * @return string The response to be sent to the IMAP server |
f7948940 |
147 | * @since 1.4.0 |
8b52a4ca |
148 | */ |
47a29326 |
149 | function cram_md5_response ($username,$password,$challenge) { |
8b52a4ca |
150 | $challenge=base64_decode($challenge); |
151 | $hash=bin2hex(hmac_md5($challenge,$password)); |
152 | $response=base64_encode($username . " " . $hash) . "\r\n"; |
153 | return $response; |
47a29326 |
154 | } |
155 | |
8b52a4ca |
156 | /** |
157 | * Return Digest-MD5 response. |
158 | * Given the challenge from the server, calculate and return the |
159 | * response-string for digest-md5 authentication. (See RFC 2831 for more |
160 | * details) |
161 | * |
162 | * @param string $username User ID |
163 | * @param string $password User password supplied by User |
164 | * @param string $challenge The challenge supplied by the server |
165 | * @param string $service The service name, usually 'imap'; it is used to |
166 | * define the digest-uri. |
167 | * @param string $host The host name, usually the server's FQDN; it is used to |
168 | * define the digest-uri. |
b0434236 |
169 | * @param string $authz Authorization ID (since 1.5.2) |
8b52a4ca |
170 | * @return string The response to be sent to the IMAP server |
f7948940 |
171 | * @since 1.4.0 |
8b52a4ca |
172 | */ |
b0434236 |
173 | function digest_md5_response ($username,$password,$challenge,$service,$host,$authz='') { |
ea321915 |
174 | $result=digest_md5_parse_challenge($challenge); |
b8f35815 |
175 | //FIXME we should check that $result contains the expected values that we use below |
62f7daa5 |
176 | |
ea321915 |
177 | // verify server supports qop=auth |
178 | // $qop = explode(",",$result['qop']); |
179 | //if (!in_array("auth",$qop)) { |
47a29326 |
180 | // rfc2831: client MUST fail if no qop methods supported |
ea321915 |
181 | // return false; |
182 | //} |
183 | $cnonce = base64_encode(bin2hex(hmac_md5(microtime()))); |
184 | $ncount = "00000001"; |
185 | |
186 | /* This can be auth (authentication only), auth-int (integrity protection), or |
187 | auth-conf (confidentiality protection). Right now only auth is supported. |
188 | DO NOT CHANGE THIS VALUE */ |
189 | $qop_value = "auth"; |
190 | |
191 | $digest_uri_value = $service . '/' . $host; |
192 | |
193 | // build the $response_value |
194 | //FIXME This will probably break badly if a server sends more than one realm |
195 | $string_a1 = utf8_encode($username).":"; |
196 | $string_a1 .= utf8_encode($result['realm']).":"; |
197 | $string_a1 .= utf8_encode($password); |
198 | $string_a1 = hmac_md5($string_a1); |
199 | $A1 = $string_a1 . ":" . $result['nonce'] . ":" . $cnonce; |
31dd6e3c |
200 | if(!empty($authz)) { |
201 | $A1 .= ":" . utf8_encode($authz); |
202 | } |
ea321915 |
203 | $A1 = bin2hex(hmac_md5($A1)); |
204 | $A2 = "AUTHENTICATE:$digest_uri_value"; |
205 | // If qop is auth-int or auth-conf, A2 gets a little extra |
206 | if ($qop_value != 'auth') { |
207 | $A2 .= ':00000000000000000000000000000000'; |
208 | } |
209 | $A2 = bin2hex(hmac_md5($A2)); |
210 | |
211 | $string_response = $result['nonce'] . ':' . $ncount . ':' . $cnonce . ':' . $qop_value; |
212 | $response_value = bin2hex(hmac_md5($A1.":".$string_response.":".$A2)); |
213 | |
214 | $reply = 'charset=utf-8,username="' . $username . '",realm="' . $result["realm"] . '",'; |
215 | $reply .= 'nonce="' . $result['nonce'] . '",nc=' . $ncount . ',cnonce="' . $cnonce . '",'; |
216 | $reply .= "digest-uri=\"$digest_uri_value\",response=$response_value"; |
217 | $reply .= ',qop=' . $qop_value; |
b0434236 |
218 | if(!empty($authz)) { |
219 | $reply .= ',authzid=' . $authz; |
220 | } |
ea321915 |
221 | $reply = base64_encode($reply); |
222 | return $reply . "\r\n"; |
62f7daa5 |
223 | |
47a29326 |
224 | } |
225 | |
8b52a4ca |
226 | /** |
227 | * Parse Digest-MD5 challenge. |
228 | * This function parses the challenge sent during DIGEST-MD5 authentication and |
229 | * returns an array. See the RFC for details on what's in the challenge string. |
230 | * |
231 | * @param string $challenge Digest-MD5 Challenge |
232 | * @return array Digest-MD5 challenge decoded data |
f7948940 |
233 | * @since 1.4.0 |
8b52a4ca |
234 | */ |
47a29326 |
235 | function digest_md5_parse_challenge($challenge) { |
ea321915 |
236 | $challenge=base64_decode($challenge); |
b8f35815 |
237 | $parsed = array(); |
238 | while (!empty($challenge)) { |
ea321915 |
239 | if ($challenge{0} == ',') { // First char is a comma, must not be 1st time through loop |
240 | $challenge=substr($challenge,1); |
241 | } |
242 | $key=explode('=',$challenge,2); |
243 | $challenge=$key[1]; |
244 | $key=$key[0]; |
245 | if ($challenge{0} == '"') { |
246 | // We're in a quoted value |
247 | // Drop the first quote, since we don't care about it |
248 | $challenge=substr($challenge,1); |
249 | // Now explode() to the next quote, which is the end of our value |
250 | $val=explode('"',$challenge,2); |
251 | $challenge=$val[1]; // The rest of the challenge, work on it in next iteration of loop |
252 | $value=explode(',',$val[0]); |
253 | // Now, for those quoted values that are only 1 piece.. |
254 | if (sizeof($value) == 1) { |
255 | $value=$value[0]; // Convert to non-array |
256 | } |
257 | } else { |
258 | // We're in a "simple" value - explode to next comma |
259 | $val=explode(',',$challenge,2); |
260 | if (isset($val[1])) { |
261 | $challenge=$val[1]; |
262 | } else { |
263 | unset($challenge); |
264 | } |
265 | $value=$val[0]; |
266 | } |
267 | $parsed["$key"]=$value; |
268 | } // End of while loop |
269 | return $parsed; |
47a29326 |
270 | } |
271 | |
8b52a4ca |
272 | /** |
54536ecf |
273 | * Creates a HMAC digest that can be used for authentication purposes |
274 | * See RFCs 2104, 2617, 2831 |
275 | * |
276 | * Uses PHP's Hash extension if available (enabled by default in PHP |
277 | * 5.1.2+ - see http://www.php.net/manual/en/hash.requirements.php |
278 | * or, if installed on earlier PHP versions, the PECL hash module - |
279 | * see http://pecl.php.net/package/hash |
280 | * |
281 | * Otherwise, will attempt to use the Mhash extension - see |
282 | * http://www.php.net/manual/en/mhash.requirements.php |
283 | * |
284 | * Finally, a fall-back custom implementation is used if none of |
285 | * the above are available. |
286 | * |
287 | * @param string $data The data to be encoded/hashed |
288 | * @param string $key The (shared) secret key that will be used |
289 | * to build the keyed hash. This argument is |
290 | * technically optional, but only for internal |
291 | * use (when the custom hash implementation is |
292 | * being used) - external callers should always |
293 | * specify a value for this argument. |
294 | * |
295 | * @return string The HMAC-MD5 digest string |
296 | * @since 1.4.0 |
297 | * |
298 | */ |
1c6d997a |
299 | function hmac_md5($data, $key='') { |
54536ecf |
300 | |
301 | // use PHP's native Hash extension if possible |
302 | // |
303 | if (function_exists('hash_hmac')) |
304 | return pack('H*', hash_hmac('md5', $data, $key)); |
305 | |
306 | |
307 | // otherwise, use (obsolete) mhash extension if available |
308 | // |
639c7164 |
309 | if (extension_loaded('mhash')) { |
54536ecf |
310 | |
311 | if ($key == '') |
312 | $mhash = mhash(MHASH_MD5, $data); |
313 | else |
314 | $mhash = mhash(MHASH_MD5, $data, $key); |
315 | |
ea321915 |
316 | return $mhash; |
639c7164 |
317 | } |
54536ecf |
318 | |
319 | |
320 | // or, our own implementation... |
321 | // |
322 | if (!$key) |
323 | return pack('H*', md5($data)); |
324 | |
325 | $key = str_pad($key, 64, chr(0x00)); |
326 | |
327 | if (strlen($key) > 64) |
328 | $key = pack("H*", md5($key)); |
329 | |
330 | $k_ipad = $key ^ str_repeat(chr(0x36), 64); |
331 | $k_opad = $key ^ str_repeat(chr(0x5c), 64); |
332 | |
333 | $hmac = hmac_md5($k_opad . pack('H*', md5($k_ipad . $data))); |
334 | |
639c7164 |
335 | return $hmac; |
54536ecf |
336 | |
639c7164 |
337 | } |
338 | |
62f7daa5 |
339 | /** |
9bd3b1e6 |
340 | * Fillin user and password based on SMTP auth settings. |
341 | * |
9bd3b1e6 |
342 | * @param string $user Reference to SMTP username |
343 | * @param string $pass Reference to SMTP password (unencrypted) |
0940b016 |
344 | * @since 1.4.11 |
9bd3b1e6 |
345 | */ |
346 | function get_smtp_user(&$user, &$pass) { |
62f7daa5 |
347 | global $username, $smtp_auth_mech, |
9bd3b1e6 |
348 | $smtp_sitewide_user, $smtp_sitewide_pass; |
349 | |
350 | if ($smtp_auth_mech == 'none') { |
351 | $user = ''; |
352 | $pass = ''; |
db84eecd |
353 | } elseif ( isset($smtp_sitewide_user) && isset($smtp_sitewide_pass) && |
354 | !empty($smtp_sitewide_user)) { |
9bd3b1e6 |
355 | $user = $smtp_sitewide_user; |
356 | $pass = $smtp_sitewide_pass; |
357 | } else { |
9bd3b1e6 |
358 | $user = $username; |
f7948940 |
359 | $pass = sqauth_read_password(); |
9bd3b1e6 |
360 | } |
93917f92 |
361 | |
362 | // plugin authors note: override $user or $pass by |
363 | // directly changing the arguments array contents |
364 | // in your plugin e.g., $args[0] = 'new_username'; |
365 | // |
a53b1ba9 |
366 | // NOTE: there is another hook in class/deliver/Deliver_SMTP.class.php |
367 | // called "smtp_authenticate" that allows a plugin to run its own |
368 | // custom authentication routine - this hook here is thus slightly |
369 | // mis-named but is too old to change. Be careful that you do not |
370 | // confuse your hook names. |
371 | // |
502b1f24 |
372 | $temp = array(&$user, &$pass); |
373 | do_hook('smtp_auth', $temp); |
9bd3b1e6 |
374 | } |