Source for file auth.php

Documentation is available at auth.php

  1. <?php
  2.  
  3. /**
  4.  * auth.php
  5.  *
  6.  * Contains functions used to do authentication.
  7.  *
  8.  * @copyright 1999-2020 The SquirrelMail Project Team
  9.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  10.  * @version $Id: auth.php 14840 2020-01-07 07:42:38Z pdontthink $
  11.  * @package squirrelmail
  12.  */
  13.  
  14. /** Put in a safety net here, in case a naughty admin didn't run conf.pl when they upgraded */
  15.  
  16. if (isset($smtp_auth_mech)) {
  17.     $smtp_auth_mech 'none';
  18. }
  19.  
  20. if (isset($imap_auth_mech)) {
  21.     $imap_auth_mech 'login';
  22. }
  23.  
  24. if (isset($use_imap_tls)) {
  25.     $use_imap_tls false;
  26. }
  27.  
  28. if (isset($use_smtp_tls)) {
  29.     $use_smtp_tls false;
  30. }
  31.  
  32. /**
  33.  * Check if user has previously logged in to the SquirrelMail session.  If user
  34.  * has not logged in, execution will stop inside this function.
  35.  *
  36.  * This function optionally checks the referrer of this page request.  If the
  37.  * administrator wants to impose a check that the referrer of this page request
  38.  * is another page on the same domain (otherwise, the page request is likely
  39.  * the result of a XSS or phishing attack), then they need to specify the
  40.  * acceptable referrer domain in a variable named $check_referrer in
  41.  * config/config.php (or the configuration tool) for which the value is
  42.  * usually the same as the $domain setting (for example:
  43.  *    $check_referrer = 'example.com';
  44.  * However, in some cases (where proxy servers are in use, etc.), the
  45.  * acceptable referrer might be different.  If $check_referrer is set to
  46.  * "###DOMAIN###", then the current value of $domain is used (useful in
  47.  * situations where $domain might change at runtime (when using the Login
  48.  * Manager plugin to host multiple domains with one SquirrelMail installation,
  49.  * for example)):
  50.  *    $check_referrer = '###DOMAIN###';
  51.  * NOTE HOWEVER, that referrer checks are not foolproof - they can be spoofed
  52.  * by browsers, and some browsers intentionally don't send them, in which
  53.  * case SquirrelMail silently ignores referrer checks.
  54.  *
  55.  * @return void This function returns ONLY if user has previously logged in
  56.  *  successfully (otherwise, execution terminates herein).
  57.  */
  58. function is_logged_in({
  59.  
  60.     // check for user login as well as referrer if needed
  61.     //
  62.     global $check_referrer$domain;
  63.     if ($check_referrer == '###DOMAIN###'$check_referrer $domain;
  64.     if (!empty($check_referrer)) {
  65.         $ssl_check_referrer 'https://' $check_referrer;
  66.         $plain_check_referrer 'http://' $check_referrer;
  67.     }
  68.     if (!sqgetGlobalVar('HTTP_REFERER'$referrerSQ_SERVER)) $referrer '';
  69.     if (sqsession_is_registered('user_is_logged_in'
  70.      && (!$check_referrer || empty($referrer)
  71.       || ($check_referrer && !empty($referrer)
  72.        && (strpos(strtolower($referrer)strtolower($plain_check_referrer)) === 0
  73.         || strpos(strtolower($referrer)strtolower($ssl_check_referrer)) === 0)))) {
  74.         return;
  75.     else {
  76.  
  77.         global $session_expired_post,
  78.                $session_expired_location$squirrelmail_language;
  79.  
  80.         // use $message to indicate what logout text the user
  81.         // will see... if 0, typical "You must be logged in"
  82.         // if 1, information that the user session was saved
  83.         // and will be resumed after (re)login, if 2, there
  84.         // seems to have been a XSS or phishing attack (bad
  85.         // referrer)
  86.         //
  87.         $message 0;
  88.  
  89.         //  First we store some information in the new session to prevent
  90.         //  information-loss.
  91.         $session_expired_post $_POST;
  92.         if (defined('PAGE_NAME')) {
  93.             $session_expired_location PAGE_NAME;
  94.         }
  95.         
  96.         if (!sqsession_is_registered('session_expired_post')) {
  97.             sqsession_register($session_expired_post,'session_expired_post');
  98.         }
  99.         if (!sqsession_is_registered('session_expired_location')) {
  100.             sqsession_register($session_expired_location,'session_expired_location');
  101.             if ($session_expired_location == 'compose')
  102.                 $message 1;
  103.         }
  104.  
  105.         // was bad referrer the reason we were rejected?
  106.         //
  107.         if (sqsession_is_registered('user_is_logged_in'
  108.          && $check_referrer && !empty($referrer))
  109.             $message 2;
  110.       
  111.         session_write_close();
  112.  
  113.         // signout page will deal with users who aren't logged 
  114.         // in on its own; don't show error here
  115.         if (defined('PAGE_NAME'&& PAGE_NAME == 'signout'{
  116.            return;
  117.         }
  118.  
  119.         include_onceSM_PATH 'functions/display_messages.php' );
  120.         set_up_language($squirrelmail_languagetrue);
  121.         if (!$message)
  122.             logout_error_("You must be logged in to access this page.") );
  123.         else if ($message == 1)
  124.             logout_error_("Your session has expired, but will be resumed after logging in again.") );
  125.         else if ($message == 2)
  126.             logout_error_("The current page request appears to have originated from an unrecognized source.") );
  127.         exit;
  128.     }
  129. }
  130.  
  131. /**
  132.  * Given the challenge from the server, supply the response using cram-md5 (See
  133.  * RFC 2195 for details)
  134.  *
  135.  * @param string $username User ID
  136.  * @param string $password User password supplied by User
  137.  * @param string $challenge The challenge supplied by the server
  138.  * @return string The response to be sent to the IMAP server
  139.  *
  140.  */
  141. function cram_md5_response ($username,$password,$challenge{
  142.     $challenge=base64_decode($challenge);
  143.     $hash=bin2hex(hmac_md5($challenge,$password));
  144.     $response=base64_encode($username " " $hash"\r\n";
  145.     return $response;
  146. }
  147.  
  148. /**
  149.  * Return Digest-MD5 response.
  150.  * Given the challenge from the server, calculate and return the
  151.  * response-string for digest-md5 authentication.  (See RFC 2831 for more
  152.  * details)
  153.  *
  154.  * @param string $username User ID
  155.  * @param string $password User password supplied by User
  156.  * @param string $challenge The challenge supplied by the server
  157.  * @param string $service The service name, usually 'imap'; it is used to
  158.  *    define the digest-uri.
  159.  * @param string $host The host name, usually the server's FQDN; it is used to
  160.  *    define the digest-uri.
  161.  * @param string $authz Authorization ID (since 1.4.23)
  162.  * @return string The response to be sent to the IMAP server
  163.  * @since 1.4.0
  164.  */
  165. function digest_md5_response ($username,$password,$challenge,$service,$host,$authz=''{
  166.     $result=digest_md5_parse_challenge($challenge);
  167.     //FIXME we should check that $result contains the expected values that we use below
  168.  
  169.     // verify server supports qop=auth
  170.     // $qop = explode(",",$result['qop']);
  171.     //if (!in_array("auth",$qop)) {
  172.     // rfc2831: client MUST fail if no qop methods supported
  173.     // return false;
  174.     //}
  175.     $cnonce base64_encode(bin2hex(hmac_md5(microtime())));
  176.     $ncount "00000001";
  177.  
  178.     /* This can be auth (authentication only), auth-int (integrity protection), or
  179.        auth-conf (confidentiality protection).  Right now only auth is supported.
  180.        DO NOT CHANGE THIS VALUE */
  181.     $qop_value "auth";
  182.  
  183.     $digest_uri_value $service '/' $host;
  184.  
  185.     // build the $response_value
  186.     //FIXME This will probably break badly if a server sends more than one realm
  187.     $string_a1 utf8_encode($username).":";
  188.     $string_a1 .= utf8_encode($result['realm']).":";
  189.     $string_a1 .= utf8_encode($password);
  190.     $string_a1 hmac_md5($string_a1);
  191.     $A1 $string_a1 ":" $result['nonce'":" $cnonce;
  192.     if(!empty($authz)) {
  193.         $A1 .= ":" utf8_encode($authz);
  194.     }
  195.     $A1 bin2hex(hmac_md5($A1));
  196.     $A2 "AUTHENTICATE:$digest_uri_value";
  197.     // If qop is auth-int or auth-conf, A2 gets a little extra
  198.     if ($qop_value != 'auth'{
  199.         $A2 .= ':00000000000000000000000000000000';
  200.     }
  201.     $A2 bin2hex(hmac_md5($A2));
  202.  
  203.     $string_response $result['nonce'':' $ncount ':' $cnonce ':' $qop_value;
  204.     $response_value bin2hex(hmac_md5($A1.":".$string_response.":".$A2));
  205.  
  206.     $reply 'charset=utf-8,username="' $username '",realm="' $result["realm"'",';
  207.     $reply .= 'nonce="' $result['nonce''",nc=' $ncount ',cnonce="' $cnonce '",';
  208.     $reply .= "digest-uri=\"$digest_uri_value\",response=$response_value";
  209.     $reply .= ',qop=' $qop_value;
  210.     if(!empty($authz)) {
  211.         $reply .= ',authzid=' $authz;
  212.     }
  213.     $reply base64_encode($reply);
  214.     return $reply "\r\n";
  215.  
  216. }
  217.  
  218. /**
  219.  * Parse Digest-MD5 challenge.
  220.  * This function parses the challenge sent during DIGEST-MD5 authentication and
  221.  * returns an array. See the RFC for details on what's in the challenge string.
  222.  *
  223.  * @param string $challenge Digest-MD5 Challenge
  224.  * @return array Digest-MD5 challenge decoded data
  225.  */
  226. function digest_md5_parse_challenge($challenge{
  227.     $challenge=base64_decode($challenge);
  228.     $parsed array();
  229.     while (!empty($challenge)) {
  230.         if ($challenge{0== ','// First char is a comma, must not be 1st time through loop
  231.             $challenge=substr($challenge,1);
  232.         }
  233.         $key=explode('=',$challenge,2);
  234.         $challenge=$key[1];
  235.         $key=$key[0];
  236.         if ($challenge{0== '"'{
  237.             // We're in a quoted value
  238.             // Drop the first quote, since we don't care about it
  239.             $challenge=substr($challenge,1);
  240.             // Now explode() to the next quote, which is the end of our value
  241.             $val=explode('"',$challenge,2);
  242.             $challenge=$val[1]// The rest of the challenge, work on it in next iteration of loop
  243.             $value=explode(',',$val[0]);
  244.             // Now, for those quoted values that are only 1 piece..
  245.             if (sizeof($value== 1{
  246.                 $value=$value[0];  // Convert to non-array
  247.             }
  248.         else {
  249.             // We're in a "simple" value - explode to next comma
  250.             $val=explode(',',$challenge,2);
  251.             if (isset($val[1])) {
  252.                 $challenge=$val[1];
  253.             else {
  254.                 unset($challenge);
  255.             }
  256.             $value=$val[0];
  257.         }
  258.         $parsed["$key"]=$value;
  259.     // End of while loop
  260.     return $parsed;
  261. }
  262.  
  263. /**
  264.  * Creates a HMAC digest that can be used for auth purposes
  265.  * See RFCs 2104, 2617, 2831
  266.  * Uses mhash() extension if available
  267.  *
  268.  * @param string $data Data to apply hash function to.
  269.  * @param string $key Optional key, which, if supplied, will be used to
  270.  *  calculate data's HMAC.
  271.  * @return string HMAC Digest string
  272.  */
  273. function hmac_md5($data$key=''{
  274.     if (extension_loaded('mhash')) {
  275.         if ($key== ''{
  276.             $mhash=mhash(MHASH_MD5,$data);
  277.         else {
  278.             $mhash=mhash(MHASH_MD5,$data,$key);
  279.         }
  280.         return $mhash;
  281.     }
  282.     if (!$key{
  283.         return pack('H*',md5($data));
  284.     }
  285.     $key str_pad($key,64,chr(0x00));
  286.     if (strlen($key64{
  287.         $key pack("H*",md5($key));
  288.     }
  289.     $k_ipad =  $key str_repeat(chr(0x36)64;
  290.     $k_opad =  $key str_repeat(chr(0x5c)64;
  291.     /* Heh, let's get recursive. */
  292.     $hmac=hmac_md5($k_opad pack("H*",md5($k_ipad $data)) );
  293.     return $hmac;
  294. }
  295.  
  296. /**
  297.  * Reads and decodes stored user password information
  298.  *
  299.  * Direct access to password information is deprecated.
  300.  * @return string password in plain text
  301.  * @since 1.4.11
  302.  */
  303. function sqauth_read_password({
  304.     global $is_login_verified_hook;
  305.     if ($is_login_verified_hookglobal $key;
  306.  
  307.     sqgetGlobalVar('key',         $key,       SQ_COOKIE);
  308.     sqgetGlobalVar('onetimepad',  $onetimepad,SQ_SESSION);
  309.  
  310.     return OneTimePadDecrypt($key$onetimepad);
  311. }
  312.  
  313. /**
  314.  * Saves or updates user password information
  315.  *
  316.  * This function is used to update the password information that
  317.  * SquirrelMail stores in the existing PHP session. It does NOT
  318.  * modify the password stored in the authentication system used
  319.  * by the IMAP server.
  320.  *
  321.  * This function must be called before any html output is started.
  322.  * Direct access to password information is deprecated. The saved
  323.  * password information is available only to the SquirrelMail script
  324.  * that is called/executed AFTER the current one. If your script
  325.  * needs access to the saved password after a sqauth_save_password()
  326.  * call, use the returned OTP encrypted key.
  327.  *
  328.  * @param string $pass password
  329.  *
  330.  * @return string Password encrypted with OTP. In case the script
  331.  *                 wants to access the password information before
  332.  *                 the end of its execution.
  333.  *
  334.  * @since 1.4.16
  335.  *
  336.  */
  337. function sqauth_save_password($pass{
  338.     sqgetGlobalVar('base_uri',    $base_uri,   SQ_SESSION);
  339.  
  340.     $onetimepad OneTimePadCreate(strlen($pass));
  341.     sqsession_register($onetimepad,'onetimepad');
  342.     $key OneTimePadEncrypt($pass$onetimepad);
  343.     sqsetcookie('key'$keyfalse$base_uri);
  344.     return $key;
  345. }
  346.  
  347. /**
  348.  * Fillin user and password based on SMTP auth settings.
  349.  *
  350.  * @param string $user Reference to SMTP username
  351.  * @param string $pass Reference to SMTP password (unencrypted)
  352.  * @since 1.4.11
  353.  */
  354. function get_smtp_user(&$user&$pass{
  355.     global $username$smtp_auth_mech,
  356.            $smtp_sitewide_user$smtp_sitewide_pass;
  357.  
  358.     if ($smtp_auth_mech == 'none'{
  359.         $user '';
  360.         $pass '';
  361.     elseif isset($smtp_sitewide_user&& isset($smtp_sitewide_pass&&
  362.                !empty($smtp_sitewide_user)) {
  363.         $user $smtp_sitewide_user;
  364.         $pass $smtp_sitewide_pass;
  365.     else {
  366.         $user $username;
  367.         $pass sqauth_read_password();
  368.     }
  369.  
  370.     // plugin authors note: override $user or $pass by
  371.     // returning an array where the new username is the
  372.     // first array value and the new password is the 
  373.     // second array value e.g., return array($myuser, $mypass);
  374.     //
  375.     // NOTE: there is another hook in class/deliver/Deliver_SMTP.class.php
  376.     // called "smtp_authenticate" that allows a plugin to run its own
  377.     // custom authentication routine - this hook here is thus slightly
  378.     // mis-named but is too old to change.  Be careful that you do not
  379.     // confuse your hook names.
  380.     //
  381.     $ret do_hook_function('smtp_auth'array($user$pass));
  382.     if (!empty($ret[0]))
  383.         $user $ret[0];
  384.     if (!empty($ret[1]))
  385.         $pass $ret[1];
  386. }

Documentation generated on Mon, 13 Jan 2020 04:24:15 +0100 by phpDocumentor 1.4.3