/**
  * Creates a DigestChallenge object from a string
  *
  * Example input string:
  * <pre>
  *   realm="example.com",nonce="GMybUaOM4lpMlJbeRwxOLzTalYDwLAxv/sLf8de4DPA=",
  *   qop="auth,auth-int,auth-conf",cipher="rc4-40,rc4-56,rc4",charset=utf-8,
  *   algorithm=md5-sess    
  * </pre>
  *
  * @param   string s
  * @return  security.sasl.DigestChallenge
  * @throws  lang.FormatException
  */
 public static function fromString($s)
 {
     with($challenge = new DigestChallenge());
     $s .= ',';
     while ($p = strpos($s, '=')) {
         $key = substr($s, 0, $p);
         $t = '"' == $s[$p + 1] ? strpos($s, '"', $p + 2) + 1 : strpos($s, ',', $p + 1);
         $value = trim(substr($s, $p + 1, $t - $p - 1), '"');
         switch ($key) {
             case 'realm':
                 $challenge->setRealm($value);
                 break;
             case 'domain':
                 $challenge->setDomain($value);
                 break;
             case 'nonce':
                 $challenge->setNonce($value);
                 break;
             case 'qop':
                 $challenge->setQop(explode(',', strtolower($value)));
                 break;
             case 'cipher':
                 $challenge->setCipher(explode(',', strtolower($value)));
                 break;
             case 'charset':
                 $challenge->setCharset($value);
                 break;
             case 'algorithm':
                 $challenge->setAlgorithm($value);
                 break;
             case 'stale':
                 $challenge->setStale(0 == strcasecmp($value, 'true'));
                 break;
             case 'opaque':
                 $challenge->setOpaque($value);
                 break;
             case 'maxbuf':
                 $challenge->setMaxbuf(intval($value));
                 break;
             default:
                 throw new FormatException('Unrecognized key "' . $key . '"');
         }
         $s = substr($s, $t + 1);
     }
     return $challenge;
 }
 /**
  * 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);
 }