/**
  * Authenticate
  *
  * Supported methods:
  * <ul>
  *   <li>PLAIN</li>
  *   <li>LOGIN</li>
  *   <li>DIGEST-MD5</li>
  *   <li>CRAM-MD5</li>
  * </ul>
  *
  * @param   string method one of the SIEVE_SASL_* constants
  * @param   string user
  * @param   string pass
  * @param   string auth default NULL
  * @return  bool success
  * @throws  lang.IllegalArgumentException when the specified method is not supported
  */
 public function authenticate($method, $user, $pass, $auth = NULL)
 {
     if (!$this->hasAuthenticationMethod($method)) {
         throw new IllegalArgumentException('Authentication method ' . $method . ' not supported');
     }
     // Check whether we want to impersonate
     if (NULL === $auth) {
         $auth = $user;
     }
     // Send auth request depending on specified authentication method
     switch ($method) {
         case SIEVE_SASL_PLAIN:
             $e = base64_encode($auth . "" . $user . "" . $pass);
             $this->_sendcmd('AUTHENTICATE "PLAIN" {%d+}', strlen($e));
             $this->_sendcmd($e);
             break;
         case SIEVE_SASL_LOGIN:
             $this->_sendcmd('AUTHENTICATE "LOGIN"');
             $ue = base64_encode($user);
             $this->_sendcmd('{%d+}', strlen($ue));
             $this->_sendcmd($ue);
             $pe = base64_encode($pass);
             $this->_sendcmd('{%d+}', strlen($pe));
             $this->_sendcmd($pe);
             break;
         case SIEVE_SASL_DIGEST_MD5:
             $this->_sendcmd('AUTHENTICATE "DIGEST-MD5"');
             // Read server challenge. Example (decoded):
             //
             // realm="example.com",nonce="GMybUaOM4lpMlJbeRwxOLzTalYDwLAxv/sLf8de4DPA=",
             // qop="auth,auth-int,auth-conf",cipher="rc4-40,rc4-56,rc4",charset=utf-8,
             // algorithm=md5-sess
             //
             // See also xp://security.sasl.DigestChallenge
             $len = $this->_sock->readLine(0x400);
             $str = base64_decode($this->_sock->readLine());
             $this->cat && $this->cat->debug('Challenge (length ' . $len . '):', $str);
             $challenge = DigestChallenge::fromString($str);
             $response = $challenge->responseFor(DC_QOP_AUTH, $user, $pass, $auth);
             $this->cat && $this->cat->debug($challenge, $response);
             // Build and send challenge response
             $response->setDigestUri('sieve/' . $this->_sock->host);
             $cmd = $response->getString();
             $this->cat && $this->cat->debug('Sending challenge response', $cmd);
             $this->_sendcmd('"%s"', base64_encode($cmd));
             // Finally, read the response auth
             $len = $this->_sock->readLine();
             $str = base64_decode($this->_sock->readLine());
             $this->cat && $this->cat->debug('Response auth (length ' . $len . '):', $str);
             return TRUE;
         case SIEVE_SASL_CRAM_MD5:
             $this->_sendcmd('AUTHENTICATE "CRAM-MD5"');
             // Read server challenge. Example (decoded):
             //
             // <*****@*****.**>
             //
             // See also rfc://2195
             $len = $this->_sock->readLine(0x400);
             $challenge = base64_decode($this->_sock->readLine());
             $this->cat && $this->cat->debug('Challenge (length ' . $len . '):', $challenge);
             // Build response and send it
             $response = sprintf('%s %s', $user, bin2hex(HMAC_MD5::hash($challenge, $pass)));
             $this->cat && $this->cat->debug('Sending challenge response', $response);
             $this->_sendcmd('"%s"', base64_encode($response));
             break;
         default:
             throw new IllegalArgumentException('Authentication method ' . $method . ' not implemented');
     }
     // Read the response. Examples:
     //
     // - OK
     // - NO ("SASL" "authentication failure") "Authentication error"
     return $this->_response(TRUE);
 }