alecpl
2010-10-22 7bf255bfe1e2fb573da7d1b107bc7cb7fef35198
- Add SASL-IR support (RFC 4959)
- Add LOGINDISABLED support (RFC 2595)
- Add support for AUTH=PLAIN authentication to IMAP


3 files modified
203 ■■■■■ changed files
CHANGELOG 15 ●●●●● patch | view | raw | blame | history
config/main.inc.php.dist 4 ●●●● patch | view | raw | blame | history
program/include/rcube_imap_generic.php 184 ●●●●● patch | view | raw | blame | history
CHANGELOG
@@ -21,8 +21,8 @@
- Improve tabs to fixed width and add tabs in identities info (#1486974)
- Add unique index on users.username+users.mail_host
- Make htmleditor option more consistent and add option to use HTML on reply to HTML message (#1485840)
- Use empty envelope sender address for message disposition notifications (RFC2298.3)
- Support SMTP Delivery Status Notifications - RFC3461 (#1486142)
- Use empty envelope sender address for message disposition notifications (RFC 2298.3)
- Support SMTP Delivery Status Notifications - RFC 3461 (#1486142)
- Use css sprite image for messages list
- Add (different) attachment icon for messages of type multipart/report (#1486165)
- Prevent from inserting empty link when composing HTML message (#1486944)
@@ -31,16 +31,19 @@
- Improve displaying of UI messages (#1486977)
- Fix double e-mail filed in identity form (#1487054)
- Display IMAP errors for LIST/THREAD/SEARCH commands (#1486905)
- Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC2088)
- Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC 2088)
- Add separate column for message status icon (#1486665)
- Add ACL extension support into IMAP classes (RFC4314)
- Add ACL extension support into IMAP classes (RFC 4314)
- Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)
- Add METADATA extension support into IMAP classes (RFC5464)
- Add METADATA extension support into IMAP classes (RFC 5464)
- Fix decoding of e-mail address strings in message headers (#1487068)
- Fix handling of attachments when Content-Disposition is not inline nor attachment (#1487051)
- Improve performance of unseen messages counting (#1487058)
- Improve performance of messages counting using ESEARCH extension (RFC4731)
- Add LIST-STATUS support in rcube_imap_generic class (RFC5819)
- Add LIST-STATUS support in rcube_imap_generic class (RFC 5819)
- Add SASL-IR support in IMAP (RFC 4959)
- Add LOGINDISABLED support (RFC 2595)
- Add support for AUTH=PLAIN in IMAP authentication
RELEASE 0.4.2
-------------
config/main.inc.php.dist
@@ -70,8 +70,8 @@
// TCP port used for IMAP connections
$rcmail_config['default_port'] = 143;
// IMAP auth type. Can be "auth" (CRAM-MD5), "plain" (PLAIN) or "check" to auto detect.
// Optional, defaults to "check"
// IMAP auth type. Can be "auth" (CRAM-MD5), "plain" (PLAIN), "login" (LOGIN)
// or "check" (or empty) to auto detect. Optional, defaults to "check"
$rcmail_config['imap_auth_type'] = null;
// If you know your imap's root directory and its folder delimiter,
program/include/rcube_imap_generic.php
@@ -370,44 +370,95 @@
        $this->capability_readed = false;
    }
    function authenticate($user, $pass, $encChallenge)
    /**
     * CRAM-MD5/PLAIN Authentication
     *
     * @param string $user
     * @param string $pass
     * @param string $type Authentication type (PLAIN or CRAM-MD5)
     *
     * @return resource Connection resourse on success, error code on error
     */
    function authenticate($user, $pass, $type='PLAIN')
    {
        $ipad = '';
        $opad = '';
        if ($type == 'CRAM-MD5') {
            $ipad = '';
            $opad = '';
        // initialize ipad, opad
        for ($i=0; $i<64; $i++) {
            $ipad .= chr(0x36);
            $opad .= chr(0x5C);
            // 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);
            }
            $this->putLine($this->next_tag() . " AUTHENTICATE CRAM-MD5");
            $line = trim($this->readLine(1024));
            if ($line[0] == '+') {
                $challenge = substr($line,2);
            }
            else {
                return self::ERROR_BYE;
            }
            // generate hash
            $hash  = md5($this->_xor($pass, $opad) . pack("H*", md5($this->_xor($pass, $ipad) . base64_decode($encChallenge))));
            $reply = base64_encode($user . ' ' . $hash);
            // 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
            $reply = base64_encode($user . chr(0) . $user . chr(0) . $pass);
            // RFC 4959 (SASL-IR): save one round trip
            if ($this->getCapability('SASL-IR')) {
                $result = $this->execute("AUTHENTICATE PLAIN", array($reply), self::COMMAND_NORESPONSE);
            }
            else {
                $this->putLine($this->next_tag() . " AUTHENTICATE PLAIN");
                $line = trim($this->readLine(1024));
                if ($line[0] != '+') {
                    return self::ERROR_BYE;
                }
                // 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");
                }
            }
        }
        // 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($encChallenge))));
        // generate reply
        $reply = base64_encode($user . ' ' . $hash);
        // send result, get reply
        $this->putLine($reply);
        $line = $this->readLine(1024);
        // process result
        $result = $this->parseResult($line);
        if ($result == self::ERROR_OK) {
            return $this->fp;
        }
        $this->error = "Authentication for $user failed (AUTH): $line";
        return $result;
    }
    /**
     * LOGIN Authentication
     *
     * @param string $user
     * @param string $pass
     *
     * @return resource Connection resourse on success, error code on error
     */
    function login($user, $password)
    {
        list($code, $response) = $this->execute('LOGIN', array(
@@ -421,9 +472,6 @@
        if ($code == self::ERROR_OK) {
            return $this->fp;
        }
        @fclose($this->fp);
        $this->fp    = false;
        return $code;
    }
@@ -634,44 +682,48 @@
            }
        }
        $orig_method = $auth_method;
        $auth_methods = array();
        $result       = null;
        // check for supported auth methods
        if ($auth_method == 'CHECK') {
            // check for supported auth methods
            if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) {
                $auth_method = 'AUTH';
                $auth_methods[] = 'AUTH';
            }
            else {
                // default to plain text auth
                $auth_method = 'PLAIN';
            if ($this->getCapability('AUTH=PLAIN')) {
                $auth_methods[] = 'PLAIN';
            }
            // RFC 2595 (LOGINDISABLED) LOGIN disabled when connection is not secure
            if (!$this->getCapability('LOGINDISABLED')) {
                $auth_methods[] = 'LOGIN';
            }
        }
        else {
            $auth_methods[] = $auth_method;
        }
        // Authenticate
        foreach ($auth_methods as $method) {
            switch ($method) {
            case 'AUTH':
                $result = $this->authenticate($user, $password, 'CRAM-MD5');
                break;
            case 'PLAIN':
                $result = $this->authenticate($user, $password, 'PLAIN');
                break;
            case 'LOGIN':
                   $result = $this->login($user, $password);
                break;
            default:
                $this->set_error(self::ERROR_BAD, "Configuration error. Unknown auth method: $method");
            }
            if (is_resource($result)) {
                break;
            }
        }
        if ($auth_method == 'AUTH') {
            // do CRAM-MD5 authentication
            $this->putLine($this->next_tag() . " AUTHENTICATE CRAM-MD5");
            $line = trim($this->readLine(1024));
            if ($line[0] == '+') {
                // got a challenge string, try CRAM-MD5
                $result = $this->authenticate($user, $password, substr($line,2));
                // stop if server sent BYE response
                if ($result == self::ERROR_BYE) {
                    return false;
                }
            }
            if (!is_resource($result) && $orig_method == 'CHECK') {
                $auth_method = 'PLAIN';
            }
        }
        if ($auth_method == 'PLAIN') {
            // do plain text auth
            $result = $this->login($user, $password);
        }
        // Connected and authenticated
        if (is_resource($result)) {
            if ($this->prefs['force_caps']) {
                $this->clearCapability();
@@ -680,9 +732,13 @@
            $this->logged = true;
            return true;
        } else {
            return false;
        }
        }
        // Close connection
        @fclose($this->fp);
        $this->fp = false;
        return false;
    }
    function connected()