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