瀏覽代碼

Add SCRAM Support

Close #1
Andy 5 年之前
父節點
當前提交
f1e95758e1

+ 65 - 0
class/deliver/Deliver_SMTP.class.php

@@ -245,6 +245,71 @@ class Deliver_SMTP extends Deliver {
         );
         if (boolean_hook_function('smtp_authenticate', $smtp_auth_args, 1)) {
             // authentication succeeded
+        } else if ( substr($smtp_auth_mech, 0, 6) == 'scram-' ) {
+            // Doing SCRAM
+            $hAlg = scram_supports(substr($smtp_auth_mech, 6));
+            if ($hAlg === false) {
+                return(0);
+            }
+            fputs($stream, 'AUTH '.strtoupper($smtp_auth_args)."\r\n");
+
+            $tmp = fgets($stream,1024);
+
+            if ($this->errorCheck($tmp,$stream)) {
+                return(0);
+            }
+            // At this point, $tmp should hold "334 "
+
+
+            // No Channel Binding support...
+            $cbf = 'n';
+            // Generate 20 random bytes
+            $cliNonce = scram_nonce();
+            // Build SCRAM request
+            $scram_request = scram_request($user, $cbf, $cliNonce);
+
+            fputs($stream, $scram_request."\r\n");
+
+            // Get SCRAM response
+            $tmp = fgets($stream,1024);
+            if ($this->errorCheck($tmp,$stream)) {
+                return(0);
+            }
+
+            // At this point, $tmp should hold "334 <challenge string>"
+            $chall = substr($tmp,4);
+
+            $serData = scram_parse_challenge($chall, $cliNonce);
+            if ($serData === false) {
+                return(0);
+            }
+
+            $scram_response = scram_response($hAlg, $user, $cbf, $cliNonce, $serData['r'], $pass, $serData['s'], $serData['i']);
+            
+            fputs($stream, $scram_response."\r\n");
+
+            // Get SCRAM validation
+            $tmp = fgets($stream,1024);
+            if ($this->errorCheck($tmp,$stream)) {
+                return(0);
+            }
+
+            // At this point, $tmp should hold "334 <server verification string>"
+            $serVer = substr($tmp,4);
+
+            $valid = scram_verify($hAlg, $user, $cbf, $cliNonce, $serData['r'], $pass, $serData['s'], $serData['i'], $serVer);
+
+            if ($valid === false) {
+                return(0);
+            }
+            // TODO: For compatibility, this may need to either be blank or NOOP. Find out which!!!
+            fputs($stream, "\r\n");
+            // Just to make sure we're really logged in
+            $tmp = fgets($stream,1024);
+            if ($this->errorCheck($tmp,$stream)) {
+                return(0);
+            }
+        // SCRAM code ends here
         } else if (( $smtp_auth_mech == 'cram-md5') or ( $smtp_auth_mech == 'digest-md5' )) {
             // Doing some form of non-plain auth
             if ($smtp_auth_mech == 'cram-md5') {

+ 62 - 8
config/conf.pl

@@ -1492,7 +1492,7 @@ sub command111 {
     return $new_optional_delimiter;
 }
 # IMAP authentication type
-# Possible values: login, plain, cram-md5, digest-md5
+# Possible values: login, plain, cram-md5, digest-md5, scram-[digest-algo]
 # Now offers to detect supported mechs, assuming server & port are set correctly
 
 sub command112a {
@@ -1504,7 +1504,7 @@ sub command112a {
     } else {
         print "If you have already set the hostname and port number, I can try to\n";
         print "detect the mechanisms your IMAP server supports.\n";
-        print "I will try to detect CRAM-MD5 and DIGEST-MD5 support.  I can't test\n";
+        print "I will try to detect SCRAM, CRAM-MD5, and DIGEST-MD5 support. I can't test\n";
         print "for \"login\" or \"plain\" without knowing a username and password.\n";
         print "Auto-detecting is optional - you can safely say \"n\" here.\n";
         print "\nTry to detect supported mechanisms? [y/N]: ";
@@ -1514,6 +1514,30 @@ sub command112a {
           # Yes, let's try to detect.
           print "Trying to detect IMAP capabilities...\n";
           my $host = $imapServerAddress . ':'. $imapPort;
+          print "SCRAM-SHA-1:\t";
+          my $tmp = detect_auth_support('IMAP',$host,'SCRAM-SHA-1');
+          if (defined($tmp)) {
+              if ($tmp eq 'YES') {
+                  print "$WHT SUPPORTED$NRM\n";
+              } else {
+                print "$WHT NOT SUPPORTED$NRM\n";
+              }
+          } else {
+            print $WHT . " ERROR DETECTING$NRM\n";
+          }
+
+          print "SCRAM-SHA-256:\t";
+          my $tmp = detect_auth_support('IMAP',$host,'SCRAM-SHA-256');
+          if (defined($tmp)) {
+              if ($tmp eq 'YES') {
+                  print "$WHT SUPPORTED$NRM\n";
+              } else {
+                print "$WHT NOT SUPPORTED$NRM\n";
+              }
+          } else {
+            print $WHT . " ERROR DETECTING$NRM\n";
+          }
+
           print "CRAM-MD5:\t";
           my $tmp = detect_auth_support('IMAP',$host,'CRAM-MD5');
           if (defined($tmp)) {
@@ -1545,12 +1569,15 @@ sub command112a {
       print $WHT . "plain" . $NRM . " - SASL PLAIN. If you need this, you already know it.\n";
       print $WHT . "cram-md5" . $NRM . " - Historic. No longer considered secure.\n";
       print $WHT . "digest-md5" . $NRM . " - Historic. No longer considered secure.\n";
+      print $WHT . "scram-sha-1" . $NRM . " - Salted and hashed. Security contested.\n";
+      print $WHT . "scram-sha-256" . $NRM . " - Salted and hashed. Safer than sha-1.\n";
       print "\n*** YOUR IMAP SERVER MUST SUPPORT THE MECHANISM YOU CHOOSE HERE ***\n";
       print "If you don't understand or are unsure, you probably want \"login\"\n\n";
-      print "login, plain, cram-md5, or digest-md5 [$WHT$imap_auth_mech$NRM]: $WHT";
+      print "login, plain, cram-md5, digest-md5, scram-* [$WHT$imap_auth_mech$NRM]: $WHT";
       $inval=<STDIN>;
       chomp($inval);
-      if ( ($inval =~ /^cram-md5\b/i) || ($inval =~ /^digest-md5\b/i) || ($inval =~ /^login\b/i) || ($inval =~ /^plain\b/i)) {
+      if ( ($inval =~ /^cram-md5\b/i) || ($inval =~ /^digest-md5\b/i) || ($inval =~ /^scram-.+\b/i) ||
+           ($inval =~ /^login\b/i) || ($inval =~ /^plain\b/i)) {
         return lc($inval);
       } else {
         # user entered garbage or default value so nothing needs to be set
@@ -1560,7 +1587,7 @@ sub command112a {
 
 
 # SMTP authentication type
-# Possible choices: none, login, plain, cram-md5, digest-md5
+# Possible choices: none, login, plain, cram-md5, digest-md5, scram-[digest-algo]
 sub command112b {
     if ($use_smtp_tls ne "0") {
         print "Auto-detection of login methods is unavailable when using TLS or STARTTLS.\n";
@@ -1641,7 +1668,6 @@ sub command112b {
                   print $WHT . "ERROR DETECTING$NRM\n";
             }
 
-
             print "Testing DIGEST-MD5:\t";
             $tmp=detect_auth_support('SMTP',$host,'DIGEST-MD5');
             if (defined($tmp)) {
@@ -1653,6 +1679,32 @@ sub command112b {
               } else {
                   print $WHT . "ERROR DETECTING$NRM\n";
             }
+
+            # Try SCRAM-SHA-1
+            print "Testing SCRAM-SHA-1:\t";
+            $tmp=detect_auth_support('SMTP',$host,'SCRAM-SHA-1');
+            if (defined($tmp)) {
+                if ($tmp eq 'YES') {
+                    print $WHT . "SUPPORTED$NRM\n";
+                } else {
+                    print $WHT . "NOT SUPPORTED$NRM\n";
+                }
+              } else {
+                  print $WHT . "ERROR DETECTING$NRM\n";
+            }
+
+            # Try SCRAM-SHA-256
+            print "Testing SCRAM-SHA-256:\t";
+            $tmp=detect_auth_support('SMTP',$host,'SCRAM-SHA-256');
+            if (defined($tmp)) {
+                if ($tmp eq 'YES') {
+                    print $WHT . "SUPPORTED$NRM\n";
+                } else {
+                    print $WHT . "NOT SUPPORTED$NRM\n";
+                }
+              } else {
+                  print $WHT . "ERROR DETECTING$NRM\n";
+            }
         }
     }
     print "\nWhat authentication mechanism do you want to use for SMTP connections?\n";
@@ -1661,9 +1713,11 @@ sub command112b {
     print $WHT . "plain" . $NRM . " - SASL PLAIN. Plaintext. If you can do better, you probably should.\n";
     print $WHT . "cram-md5" . $NRM . " - Historic. No longer considered secure.\n";
     print $WHT . "digest-md5" . $NRM . " - Historic. No longer considered secure.\n";
+    print $WHT . "scram-sha-1" . $NRM . " - Salted and hashed. Security contested.\n";
+    print $WHT . "scram-sha-256" . $NRM . " - Salted and hashed. Safer than sha-1.\n";
     print $WHT . "\n*** YOUR SMTP SERVER MUST SUPPORT THE MECHANISM YOU CHOOSE HERE ***\n" . $NRM;
     print "If you don't understand or are unsure, you probably want \"none\"\n\n";
-    print "none, login, plain, cram-md5, or digest-md5 [$WHT$smtp_auth_mech$NRM]: $WHT";
+    print "none, login, plain, cram-md5, digest-md5, scram-* [$WHT$smtp_auth_mech$NRM]: $WHT";
     $inval=<STDIN>;
     chomp($inval);
     if ($inval =~ /^none\b/i) {
@@ -1672,7 +1726,7 @@ sub command112b {
         $smtp_sitewide_pass = '';
         # SMTP doesn't necessarily require logins
         return "none";
-    } elsif ( ($inval =~ /^cram-md5\b/i) || ($inval =~ /^digest-md5\b/i) ||
+    } elsif ( ($inval =~ /^cram-md5\b/i) || ($inval =~ /^digest-md5\b/i) || ($inval =~ /^scram-.+\b/i) ||
               ($inval =~ /^login\b/i) || ($inval =~/^plain\b/i)) {
         command_smtp_sitewide_userpass($inval);
         return lc($inval);

+ 8 - 2
config/config_default.php

@@ -263,7 +263,10 @@ $use_smtp_tls = 0;
 /**
  * SMTP authentication mechanism
  *
- * auth_mech can be either 'none', 'login','plain', 'cram-md5', or 'digest-md5'
+ * auth_mech can be either 'none', 'login', 'plain', 'cram-md5', 'digest-md5',
+ * or 'scram-*'. For SCRAM, any algorithm your PHP install supports for both
+ * the hash and hash_hmac functions is supported.
+ * Note that CRAM-MD5 & DIGEST-MD5 are historic and no longer considered safe.
  * @global string $smtp_auth_mech
  */
 $smtp_auth_mech = 'none';
@@ -293,7 +296,10 @@ $smtp_sitewide_pass = '';
 /**
  * IMAP authentication mechanism
  *
- * auth_mech can be either 'login','plain', 'cram-md5', or 'digest-md5'
+ * auth_mech can be either 'login', 'plain', 'cram-md5', 'digest-md5', or
+ * 'scram-*'. For SCRAM, any algorithm your PHP install supports for both the
+ * hash and hash_hmac functions is supported.
+ * Note that CRAM-MD5 & DIGEST-MD5 are historic and no longer considered safe.
  * @global string $imap_auth_mech
  */
 $imap_auth_mech = 'login';

+ 2 - 0
doc/ChangeLog

@@ -431,6 +431,8 @@ Version 1.5.2 - SVN
     (see notes in config/config_local.example.php for more details)
   - Added handling for RCDATA and RAWTEXT elements in HTML sanitizer
     [CVE-2019-12970]
+  - Added SCRAM authentication support (RFC5802) (RFC7677) for IMAP
+    and SMTP
 
 Version 1.5.1 (branched on 2006-02-12)
 --------------------------------------

+ 13 - 1
doc/authentication.txt

@@ -2,6 +2,7 @@
 IMAP AND SMTP AUTHENTICATION WITH SQUIRRELMAIL
 $Id$
 Chris Hilts tassium@squirrelmail.org
+Andrew Sachen webmaster@realityripple.com
 **********************************************
 
 Prior to SquirrelMail 1.4.0, only plaintext logins for IMAP and SMTP were
@@ -12,6 +13,13 @@ SMTP. TLS is able to be enabled on a per-service basis as well.
 Unless the administrator changes the authentication methods, SquirrelMail
 will default to the "classic" plaintext methods, without TLS.
 
+As of 1.5.2, the SCRAM auth mechanism has also been added. This supercedes the
+now deprecated CRAM-MD5 and DIGEST-MD5 with a salted hash, typically with SHA-1
+or SHA-256. While SHA-1 is potentially insecure, HMAC makes things much safer,
+so SCRAM-SHA-1 is still considered functionally secure. If your mail server
+supports SCRAM, please consider using it, especially if it doesn't support TLS
+or you aren't using it. More especially if you're still using MD5.
+
 Note: There is no point in using TLS if your IMAP server is localhost. You need
 root to sniff the loopback interface, and if you don't trust root, or an attacker
 already has root, the game is over.  You've got a lot more to worry about beyond
@@ -20,6 +28,10 @@ having the loopback interface sniffed.
 REQUIREMENTS
 ------------
 
+SCRAM-SHA-1/SCRAM-SHA-256
+* SquirrelMail 1.5.2 or higher
+* PHP 7.0 or higher (random_int() function for nonce generation)
+
 CRAM/DIGEST-MD5
 * SquirrelMail 1.4.0 or higher
 * If you have the mhash extension to PHP, it will automatically
@@ -108,7 +120,7 @@ configuration utility.
 
 These configuration variables will be used to connect to the SMTP server as long
 as the authentication mechanism is something besides 'none', i.e. 'login', 
-'plain', 'cram-md5', or 'digest-md5'.
+'plain', 'cram-md5', 'digest-md5', 'scram-sha-1', or 'scram-sha-256'.
 
 DEBUGGING SSL ERROR MESSAGES
 ----------------------------

+ 3 - 2
doc/index.html

@@ -41,8 +41,9 @@ on our wiki.  Links to all these manuals and wikis can be found on our
    <dt><a href="authentication.txt">Authentication</a></dt>
    <dd>
      SquirrelMail allows you to log in to your IMAP and SMTP servers using
-     plaintext, CRAM-MD5 or DIGEST-MD5, as well as use SSL for extra security.
-     This document describes how to use this new code, and the requirements.
+     plaintext, SCRAM, CRAM-MD5 or DIGEST-MD5, as well as use SSL/TLS for
+     extra security. This document describes how to use this new code,
+     and the requirements.
    </dd>
 
    <dt><a href="presets.txt">Specific IMAP server setups</a></dt>

+ 218 - 0
functions/auth.php

@@ -136,6 +136,224 @@ function sqauth_save_password($pass) {
     return $key;
 }
 
+/**
+ * Determine if an algorithm is supported by hash() and hash_hmac()
+ *
+ * @param string $algo Algorithm to find.
+ *
+ * @return string Functional $algo as used by hash() and hash_hmac()
+ *      or boolean FALSE
+ *
+ * @since 1.5.2
+ */
+
+function scram_supports($algo) {
+ $HASHs = hash_algos();
+ if (check_php_version(7,2)) {
+  $HMACs = hash_hmac_algos();
+  $HASHs = array_values(array_intersect($HASHs, $HMACs));
+ }
+ $fAlgo = strtolower(str_replace('-', '', $algo));
+ if (in_array($fAlgo, $HASHs))
+  return $fAlgo;
+ return false;
+}
+
+/**
+ * Build client nonce for SCRAM (See RFC 5802 for details)
+ *
+ * @return string A set of twenty random printable ASCII characters
+ *
+ * @since 1.5.2
+ */
+function scram_nonce () {
+    // All printable ASCII characters except commas are OK
+    // (For simplicity, we're just going to use letters and numbers, though)
+    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+    $max = strlen($chars) - 1;
+    $nonce = '';
+    for($i = 0; $i < 20; $i++) {
+        $rndChr = random_int(0, $max);
+        $nonce.= $chars[$rndChr];
+    }
+    return $nonce;
+}
+
+/**
+ * Build client request for SCRAM (See RFC 5802 for details)
+ *
+ * @param string $username User ID
+ * @param string $cbf Channel Binding Flag ('n', 'y', or 'p=tls-unique'/'p=tls-server-end-point')
+ * @param string $nonce Client's random nonce data
+ *
+ * @return string The response to be sent to the server (base64 encoded)
+ *
+ * @since 1.5.2
+ */
+function scram_request ($username,$cbf,$nonce) {
+    return base64_encode($cbf.',,n='.$username.',r='.$nonce);
+}
+
+/**
+ * Parse SCRAM challenge.
+ * This function parses the challenge sent during SCRAM authentication and
+ * returns an array. See the RFC for details on what's in the challenge string.
+ *
+ * @param string $challenge SCRAM Challenge
+ * @param string $nonce Client's random nonce data
+ *
+ * @return array SCRAM challenge decoded data
+ *      or boolean FALSE
+ *
+ * @since 1.5.2
+ */
+function scram_parse_challenge ($challenge,$nonce) {
+    $chall = base64_decode($challenge, true);
+    if ($chall === false) {
+        // The challenge must be base64 encoded
+        return false;
+    }
+    // Chall should now be r=NONCE,s=SALT,i=ITER
+    $sReq = explode(',', $chall);
+    $serNonce = '';
+    $serSalt  = '';
+    $serIter  = 0;
+    for($i = 0; $i < count($sReq); $i++) {
+        switch(substr($sReq[$i], 0, 2)) {
+            case 'r=':
+                $serNonce = substr($sReq[$i], 2);
+                break;
+            case 's=':
+                $serSalt = substr($sReq[$i], 2);
+                break;
+            case 'i=':
+                $serIter = substr($sReq[$i], 2);
+                break;
+        }
+    }
+    if (strlen($serNonce) <= strlen($nonce)) {
+        //the server 'r' value must be bigger than the client 'r' value
+        return false;
+    }
+    if (substr($serNonce, 0, strlen($nonce)) !== $nonce) {
+        // The server 'r' value must begin with the client 'r' value
+        return false;
+    }
+    if (is_numeric($serIter)) {
+        $serIter = intval($serIter);
+    } else {
+        // The iteration value must be a number
+        return false;
+    }
+    $serSaltV = base64_decode($serSalt, true);
+    if ($serSaltV === false) {
+        // The salt must be base64-encoded
+        return false;
+    }
+    $parsed = array();
+    $parsed['r'] = $serNonce;
+    $parsed['s'] = $serSaltV;
+    $parsed['i'] = $serIter;
+    return $parsed;
+}
+
+/**
+ * Build SCRAM response to challenge.
+ * This function hashes the heck out of the password and all previous communications
+ * to create a proof value which is then sent to the server as authentication.
+ *
+ * @param string $alg Hash algorithm to use ('sha1' or 'sha256')
+ * @param string $username User ID
+ * @param string $cbf Channel Binding Flag ('n', 'y', or 'p=tls-unique'/'p=tls-server-end-point')
+ * @param string $cli_nonce Client's random nonce data
+ * @param string $ser_nonce Client + Server's random nonce data
+ * @param string $password User password supplied by User
+ * @param string $salt Raw binary salt data, supplied by the server challenge
+ * @param string $iter PBKDF2 iterations, supplied by the server challenge
+ *
+ * @return string The response to be sent to the server (base64 encoded)
+ *
+ * @since 1.5.2
+ */
+function scram_response ($alg,$username,$cbf,$cli_nonce,$ser_nonce,$password,$salt,$iter) {
+    // salt and hash password
+    $salted_pass = hash_pbkdf2($alg, $password, $salt, $iter, 0, true);
+    $cli_hash = hash_hmac($alg, 'Client Key', $salted_pass, true);
+    $cli_key = hash($alg, $cli_hash, true);
+
+    $c = base64_encode($cbf.',,');
+
+    //generate unproofed communications
+    $cli_request = 'n='.$username.',r='.$cli_nonce;
+    $ser_challenge = 'r='.$ser_nonce.',s='.base64_encode($salt).',i='.$iter;
+    $cli_response_unp = 'c='.$c.',r='.$ser_nonce;
+    $comm_unp = $cli_request.','.$ser_challenge.','.$cli_response_unp;
+
+    //hash unproofed communications
+    $cli_sig = hash_hmac($alg, $comm_unp, $cli_key, true);
+    $cli_proof = $cli_hash ^ $cli_sig;
+
+    //generate proofed response
+    $cli_response = $cli_response_unp.',p='.base64_encode($cli_proof);
+
+    return base64_encode($cli_response);
+}
+
+/**
+ * Verify SCRAM server response.
+ * The final step in SCRAM is to make sure the server isn't just faking validation.
+ * This is done by hashing the unproofed communications with a 'Server Key'
+ * version of the hashed password, and comparing it with the server's final SCRAM message.
+ *
+ * @param string $alg Hash algorithm to use ('sha1' or 'sha256')
+ * @param string $username User ID
+ * @param string $cbf Channel Binding Flag ('n', 'y', or 'p=tls-unique'/'p=tls-server-end-point')
+ * @param string $cli_nonce Client's random nonce data
+ * @param string $ser_nonce Client + Server's random nonce data
+ * @param string $password User password supplied by User
+ * @param string $salt Raw binary salt data, supplied by the server challenge
+ * @param string $iter PBKDF2 iterations, supplied by the server challenge
+ * @param string $proof The server's final SCRAM message (base64 encoded)
+ *
+ * @return boolean Success or failure
+ *
+ * @since 1.5.2
+ */
+function scram_verify ($alg,$username,$cbf,$cli_nonce,$ser_nonce,$password,$salt,$iter,$proof) {
+    $proof = base64_decode($proof, true);
+    if ($proof === false) {
+        // The proof must be base64 encoded
+        return false;
+    }
+    if (substr($proof, 0, 2) !== 'v=') {
+        // The proof was not provided correctly
+        return false;
+    }
+    $proof = substr($proof, 2);
+    $proof = base64_decode($proof, true);
+    if ($proof === false) {
+        // The proof v value must be base64 encoded
+        return false;
+    }
+    // salt and hash password
+    $salted_pass = hash_pbkdf2($alg, $password, $salt, $iter, 0, true);
+    $cli_hash = hash_hmac($alg, 'Client Key', $salted_pass, true);
+    $cli_key = hash($alg, $cli_hash, true);
+
+    $c = base64_encode($cbf.',,');
+
+    //generate unproofed communications
+    $cli_request = 'n='.$username.',r='.$cli_nonce;
+    $ser_challenge = 'r='.$ser_nonce.',s='.base64_encode($salt).',i='.$iter;
+    $cli_response_unp = 'c='.$c.',r='.$ser_nonce;
+    $comm_unp = $cli_request.','.$ser_challenge.','.$cli_response_unp;
+
+    //hash for server
+    $ser_hash = hash_hmac($alg, 'Server Key', $salted_pass, true);
+    $ser_proof = hash_hmac($alg, $comm_unp, $ser_hash, true);
+    return $ser_proof === $proof;
+}
+
 /**
  * Given the challenge from the server, supply the response using cram-md5 (See
  * RFC 2195 for details)

+ 72 - 1
functions/imap_general.php

@@ -879,7 +879,78 @@ function sqimap_login ($username, $password, $imap_server_address,
 
     $imap_stream = sqimap_create_stream($imap_server_address,$imap_port,$use_imap_tls,$stream_options);
 
-    if (($imap_auth_mech == 'cram-md5') OR ($imap_auth_mech == 'digest-md5')) {
+    if (substr($imap_auth_mech, 0, 6) == 'scram-') {
+        // Doing SCRAM
+        $hAlg = scram_supports(substr($imap_auth_mech, 6));
+        if ($hAlg === false) {
+            $response="BAD";
+            $message='PHP server does not appear to support the authentication method selected.';
+            $message .= '  Please contact your system administrator.';
+        } else {
+            $tag=sqimap_session_id(false);
+            fputs($imap_stream, $tag.' AUTHENTICATE '.strtoupper($imap_auth_mech)."\r\n");
+            $tmp = sqimap_fgets($imap_stream);
+            if ($tmp[0] !== '+') {
+                $response="BAD";
+                $message='IMAP server does not appear to support the authentication method selected.';
+                $message .= '  Please contact your system administrator.';
+            } else {
+                // No Channel Binding support...
+                $cbf = 'n';
+                // Generate 20 random bytes
+                $cliNonce = scram_nonce();
+                // Build SCRAM request
+                $scram_request = scram_request($username, $cbf, $cliNonce);
+                fputs($imap_stream, $scram_request."\r\n");
+                // Get SCRAM response
+                $tmp = sqimap_fgets($imap_stream);
+                if ($tmp[0] !== '+'){
+                    $tmp = explode(' ', $tmp, 3);
+                    $response=$tmp[1];
+                    $message=$tmp[2];
+                } else {
+                    // At this point, $tmp should hold "+ <challenge string>"
+                    $chall = substr($tmp,2);
+                    $serData = scram_parse_challenge($chall, $cliNonce);
+                    if ($serData === false) {
+                        $response="BAD";
+                        $message='IMAP server challenge could not be parsed.';
+                        $message .= '  Please contact your system administrator.';
+                    } else {
+                        $scram_response = scram_response($hAlg, $username, $cbf, $cliNonce, $serData['r'], $password, $serData['s'], $serData['i']);
+                        
+                        fputs($imap_stream, $scram_response."\r\n");
+
+                        // Get SCRAM validation
+                        $tmp = sqimap_fgets($imap_stream);
+                        if ($tmp[0] !== '+'){
+                            $tmp = explode(' ', $tmp, 3);
+                            $response=$tmp[1];
+                            $message=$tmp[2];
+                        } else {
+                            // At this point, $tmp should hold "+ <server verification string>"
+                            $serVer = substr($tmp,2);
+                            $valid = scram_verify($hAlg, $username, $cbf, $cliNonce, $serData['r'], $password, $serData['s'], $serData['i'], $serVer);
+                            if ($valid === false) {
+                                $response="BAD";
+                                $message='IMAP server challenge response could not be verified.';
+                                $message .= '  Please contact your system administrator.';
+                            } else {
+                                fputs($imap_stream, "\r\n");
+
+                                // Just to make sure we're really logged in
+                                $tmp = sqimap_fgets($imap_stream);
+                                $tmp = explode(' ', $tmp, 3);
+                                $response=$tmp[1];
+                                $message=$tmp[2];
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    // SCRAM code ends here
+    } else if (($imap_auth_mech == 'cram-md5') OR ($imap_auth_mech == 'digest-md5')) {
         // We're using some sort of authentication OTHER than plain or login
         $tag=sqimap_session_id(false);
         if ($imap_auth_mech == 'digest-md5') {

+ 4 - 0
plugins/administrator/defines.php

@@ -138,6 +138,8 @@ $defcfg = array( '$config_version' => array( 'name' => _("Config File Version"),
                  '$imap_auth_mech' => array( 'name' => _("IMAP Authentication Type"),
                                              'type' => SMOPT_TYPE_STRLIST,
                                              'posvals' => array('login' => _("IMAP login"),
+                                                                'scram-sha-1' => 'SCRAM-SHA-1',
+                                                                'scram-sha-256' => 'SCRAM-SHA-256',
                                                                 'cram-md5' => 'CRAM-MD5',
                                                                 'digest-md5' => 'DIGEST-MD5'),
                                              'default' => 'login' ),
@@ -166,6 +168,8 @@ $defcfg = array( '$config_version' => array( 'name' => _("Config File Version"),
                                              'type' => SMOPT_TYPE_STRLIST,
                                              'posvals' => array('none' => _("No SMTP auth"),
                                                                 'login' => _("Login (plain text)"),
+                                                                'scram-sha-1' => 'SCRAM-SHA-1',
+                                                                'scram-sha-256' => 'SCRAM-SHA-256',
                                                                 'cram-md5' => 'CRAM-MD5',
                                                                 'digest-md5' => 'DIGEST-MD5'),
                                              'default' => 'none'),

+ 1 - 1
po/squirrelmail.pot

@@ -243,7 +243,7 @@ msgstr ""
 msgid "The IMAP server is reporting that plain text logins are disabled."
 msgstr ""
 
-msgid "Using CRAM-MD5 or DIGEST-MD5 authentication instead may work."
+msgid "Using SCRAM, CRAM-MD5, or DIGEST-MD5 authentication instead may work."
 msgstr ""
 
 msgid "Also, the use of TLS may allow SquirrelMail to login."

+ 1 - 1
src/configtest.php

@@ -804,7 +804,7 @@ echo $IND . 'Capabilities: <tt>'.sm_encode_html_special_chars($capline)."</tt><b
 
 if($imap_auth_mech == 'login' && stristr($capline, 'LOGINDISABLED') !== FALSE) {
     do_err('Your server doesn\'t allow plaintext logins. '.
-            'Try enabling another authentication mechanism like CRAM-MD5, DIGEST-MD5 or TLS-encryption '.
+            'Try enabling another authentication mechanism like SCRAM, CRAM-MD5, DIGEST-MD5 or TLS-encryption '.
             'in the SquirrelMail configuration.', FALSE);
 }
 

+ 1 - 1
src/login.php

@@ -46,7 +46,7 @@ if($imap_auth_mech == 'login') {
         sqimap_logout($imap);
         if ($logindisabled) {
             $string = _("The IMAP server is reporting that plain text logins are disabled.").'<br />'.
-                _("Using CRAM-MD5 or DIGEST-MD5 authentication instead may work.").'<br />';
+                _("Using SCRAM, CRAM-MD5, or DIGEST-MD5 authentication instead may work.").'<br />';
             if (!$use_imap_tls) {
                 $string .= _("Also, the use of TLS may allow SquirrelMail to login.").'<br />';
             }