From 4dd4172421fd16c5a84f4a80f626672a91d343f0 Mon Sep 17 00:00:00 2001 From: alecpl Date: Sat, 23 Oct 2010 17:03:44 +0000 Subject: - Add support for AUTH=DIGEST-MD5 in IMAP (RFC 2831) --- CHANGELOG | 1 + config/main.inc.php.dist | 4 +- program/include/rcube_imap_generic.php | 122 +++++++++++++++++++++++---------- 3 files changed, 87 insertions(+), 40 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 215c2109c..c5bb2a1c5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,6 +46,7 @@ CHANGELOG Roundcube Webmail - Add support for AUTH=PLAIN in IMAP authentication - Re-implemented SMTP proxy authentication support - Add support for IMAP proxy authentication (#1486690) +- Add support for AUTH=DIGEST-MD5 in IMAP (RFC 2831) RELEASE 0.4.2 ------------- diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist index 9579c0d0f..974e8b3b1 100644 --- a/config/main.inc.php.dist +++ b/config/main.inc.php.dist @@ -70,8 +70,8 @@ $rcmail_config['default_host'] = ''; // TCP port used for IMAP connections $rcmail_config['default_port'] = 143; -// IMAP auth type. Can be "auth" (CRAM-MD5), "plain" (PLAIN), "login" (LOGIN) -// or "check" (or empty) to auto detect. Optional, defaults to "check" +// IMAP AUTH type (DIGEST-MD5, CRAM-MD5, LOGIN, PLAIN or empty to use +// best server supported one) $rcmail_config['imap_auth_type'] = null; // If you know your imap's root directory and its folder delimiter, diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index 4211cca44..62a3d8280 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -371,56 +371,99 @@ class rcube_imap_generic } /** - * CRAM-MD5/PLAIN Authentication + * DIGEST-MD5/CRAM-MD5/PLAIN Authentication * * @param string $user * @param string $pass - * @param string $type Authentication type (PLAIN or CRAM-MD5) + * @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5) * * @return resource Connection resourse on success, error code on error */ function authenticate($user, $pass, $type='PLAIN') { - if ($type == 'CRAM-MD5') { - $ipad = ''; - $opad = ''; - - // initialize ipad, opad - for ($i=0; $i<64; $i++) { - $ipad .= chr(0x36); - $opad .= chr(0x5C); - } - - // pad $pass so it's 64 bytes - $padLen = 64 - strlen($pass); - for ($i=0; $i<$padLen; $i++) { - $pass .= chr(0); + if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') { + if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) { + $this->set_error(self::ERROR_BYE, + "The Auth_SASL package is required for DIGEST-MD5 authentication"); + return self::ERROR_BAD; } - $this->putLine($this->next_tag() . " AUTHENTICATE CRAM-MD5"); + $this->putLine($this->next_tag() . " AUTHENTICATE $type"); $line = trim($this->readLine(1024)); if ($line[0] == '+') { - $challenge = substr($line,2); + $challenge = substr($line, 2); } else { - return self::ERROR_BYE; + return $this->parseResult($line); } - // generate hash - $hash = md5($this->_xor($pass, $opad) . pack("H*", md5($this->_xor($pass, $ipad) . base64_decode($encChallenge)))); - $reply = base64_encode($user . ' ' . $hash); + if ($type == 'CRAM-MD5') { + // RFC2195: CRAM-MD5 + $ipad = ''; + $opad = ''; + + // initialize ipad, opad + for ($i=0; $i<64; $i++) { + $ipad .= chr(0x36); + $opad .= chr(0x5C); + } + + // pad $pass so it's 64 bytes + $padLen = 64 - strlen($pass); + for ($i=0; $i<$padLen; $i++) { + $pass .= chr(0); + } + + // generate hash + $hash = md5($this->_xor($pass, $opad) . pack("H*", + md5($this->_xor($pass, $ipad) . base64_decode($challenge)))); + $reply = base64_encode($user . ' ' . $hash); + + // send result + $this->putLine($reply); + } + else { + // RFC2831: DIGEST-MD5 + // proxy authorization + if (!empty($this->prefs['auth_cid'])) { + $authc = $this->prefs['auth_cid']; + $pass = $this->prefs['auth_pw']; + } + else { + $authc = $user; + } + $auth_sasl = Auth_SASL::factory('digestmd5'); + $reply = base64_encode($auth_sasl->getResponse($authc, $pass, + base64_decode($challenge), $this->host, 'imap', $user)); + + // send result + $this->putLine($reply); + $line = $this->readLine(1024); + + if ($line[0] == '+') { + $challenge = substr($line, 2); + } + else { + return $this->parseResult($line); + } + + // check response + $challenge = base64_decode($challenge); + if (strpos($challenge, 'rspauth=') === false) { + $this->set_error(self::ERROR_BAD, + "Unexpected response from server to DIGEST-MD5 response"); + return self::ERROR_BAD; + } + + $this->putLine(''); + } - // send result, get reply and process it - $this->putLine($reply); $line = $this->readLine(1024); $result = $this->parseResult($line); - if ($result != self::ERROR_OK) { - $this->set_error($result, "Unble to authenticate user (CRAM-MD5): $line"); - } } else { // PLAIN - // proxy authentication + // proxy authorization if (!empty($this->prefs['auth_cid'])) { $authc = $this->prefs['auth_cid']; $pass = $this->prefs['auth_pw']; @@ -440,22 +483,22 @@ class rcube_imap_generic $line = trim($this->readLine(1024)); if ($line[0] != '+') { - return self::ERROR_BYE; + return $this->parseResult($line); } // send result, get reply and process it $this->putLine($reply); $line = $this->readLine(1024); $result = $this->parseResult($line); - if ($result != self::ERROR_OK) { - $this->set_error($result, "Unble to authenticate user (AUTH): $line"); - } } } if ($result == self::ERROR_OK) { return $this->fp; } + else { + $this->set_error($result, "Unable to authenticate user ($type): $line"); + } return $result; } @@ -696,8 +739,11 @@ class rcube_imap_generic // check for supported auth methods if ($auth_method == 'CHECK') { + if ($this->getCapability('AUTH=DIGEST-MD5')) { + $auth_methods[] = 'DIGEST-MD5'; + } if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) { - $auth_methods[] = 'AUTH'; + $auth_methods[] = 'CRAM-MD5'; } if ($this->getCapability('AUTH=PLAIN')) { $auth_methods[] = 'PLAIN'; @@ -708,17 +754,17 @@ class rcube_imap_generic } } else { - $auth_methods[] = $auth_method; + // replace AUTH with CRAM-MD5 for backward compat. + $auth_methods[] = $auth_method == 'AUTH' ? 'CRAM-MD5' : $auth_method; } // Authenticate foreach ($auth_methods as $method) { switch ($method) { - case 'AUTH': - $result = $this->authenticate($user, $password, 'CRAM-MD5'); - break; + case 'DIGEST-MD5': + case 'CRAM-MD5': case 'PLAIN': - $result = $this->authenticate($user, $password, 'PLAIN'); + $result = $this->authenticate($user, $password, $method); break; case 'LOGIN': $result = $this->login($user, $password); -- cgit v1.2.3