[PATCH] Net_Sieve, maintainer unavailable.

From: Date: Mon, 29 Sep 2003 13:59:25 +0000
Subject: [PATCH] Net_Sieve, maintainer unavailable.
Groups: php.pear.dev 
Request: Send a blank email to pear-dev+get-22155@lists.php.net to get a copy of this message
Hi, I tried repeatedly to have the following patch considered by the maintainer of the Net_Sieve PEAR module in the past three months without success. You can read the whole story at http://marc.theaimsgroup.com/?l=pear-dev&m=106270419015702&w=2 Since then, there was a follow-up by Martin Jansen, which led to Michael Bretterklieber taking care of my patches to Auth_SASL (thanks Mike and Martin !). I also tried again to contact Damian Sosa at the address listed in the maintainer page on the 12th sepetember, but have received an answer. Alan Knowles suggested in early september that I file a bug report with bugs.php.net. I would done that, but the service had apparently been down for few days back then. I will today, and give pointer to this and previous post I made on the pear-dev mailing list. So I am reposting my patch again. This one differ slightly from the earlier one as I fixed indentation. The modification to Auth_SASL that was required have been commited and a new Auth_SASL released, so this have been taken care of. I would like to reaffirm that, if for any reason, that patch is not commitable as-is, I am willing to put more work into it as I need the features implemented and would like to provide my client with forward-compatibility with future version of PEAR. Thanks for your consideration, -- Etienne Goyer Linux Québec Technologies Inc. http://www.LinuxQuebec.com etienne.goyer@linuxquebec.com

--- Sieve.php.orig Mon Jul 21 09:26:01 2003 +++ Sieve.php Fri Sep 5 11:43:22 2003 @@ -33,6 +33,7 @@ // +-----------------------------------------------------------------------+ require_once('Net/Socket.php'); +require_once('Auth/SASL.php'); /** * TODO @@ -104,28 +105,33 @@ * using the getError() method. * * @access public - * @param string $user Login username - * @param string $pass Login password + * @param string $authcid Authentication id (username) + * @param string $pass Password * @param string $host Hostname of server * @param string $port Port of server * @param string $logintype Type of login to perform + * @param string $authzid Authorization id */ - function Net_Sieve($user, $pass, $host = 'localhost', $port = 2000, $logintype = 'PLAIN') + function Net_Sieve($authcid, $pass, $host = 'localhost', $port = 2000, $logintype = '', $authzid = '') { $this->_state = NET_SIEVE_STATE_DISCONNECTED; - $this->_data['user'] = $user; - $this->_data['pass'] = $pass; - $this->_data['host'] = $host; - $this->_data['port'] = $port; + if (!isset($authzid)) { $authzid = $authcid; } + + $this->_data['authcid'] = $authcid; + $this->_data['pass'] = $pass; + $this->_data['host'] = $host; + $this->_data['port'] = $port; + $this->_data['logintype'] = $logintype; + $this->_data['authzid'] = $authzid; $this->_sock = &new Net_Socket(); if (PEAR::isError($res = $this->_connect($host, $port))) { $this->_error = $res; return; } - - if (PEAR::isError($res = $this->_login($user, $pass, $logintype))) { + + if (PEAR::isError($res = $this->_login($authcid, $pass, $logintype, $authzid))) { $this->_error = $res; } } @@ -250,7 +256,7 @@ return $res; } // Get logon greeting/capability and parse - if(!PEAR::isError($res = $this->_getResponse())) { + if(!PEAR::isError($res = $this->_doCmd("CAPABILITY"))) { $this->_parseCapability($res); $this->_state = NET_SIEVE_STATE_AUTHORISATION; return true; @@ -266,25 +272,64 @@ * Logs into server. * * @access private - * @param string $user Login username - * @param string $pass Login password + * @param string $authcid Authentication id + * @param string $authzid Authorization id (if any) + * @param string $pass Password * @param string $logintype Type of login method to use * @return mixed True on success, PEAR_Error otherwise */ - function _login($user, $pass, $logintype = 'PLAIN') + function _login($authcid, $pass, $logintype = '', $authzid = '') { if (NET_SIEVE_STATE_AUTHORISATION == $this->_state) { - if ($logintype == 'PLAIN' AND in_array('PLAIN', $this->_capability['sasl'])) { - $this->_sendCmd(sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode(chr(0) . $user . chr(0) . $pass))); - - } elseif ($logintype == 'PLAIN' AND in_array('LOGIN', $this->_capability['sasl'])) { - $this->_sendCmd('AUTHENTICATE "LOGIN"'); - $this->_sendCmd(sprintf('"%s"', base64_encode($user))); - $this->_sendCmd(sprintf('"%s"', base64_encode($pass))); + if (($logintype == 'DIGEST-MD5' OR $logintype == '') AND + in_array('DIGEST-MD5', $this->_capability['sasl'])) { + + $this->_sendCmd(sprintf('AUTHENTICATE "DIGEST-MD5"')); + if (PEAR::isError($challenge = $this->_getChallenge())) { + return $challenge; + } + if (PEAR::isError($sasl = &Auth_SASL::factory('DigestMD5'))) { + return $sasl; + } + if (PEAR::isError($response = $sasl->getResponse($authcid, $pass, $challenge, $this->_data['host'], 'sieve', $authzid))) { + return $response; + } + $res = $this->_doCmd('"' . base64_encode($response) . '"'); + + } elseif (($logintype == 'CRAM-MD5' OR ($logintype == '' and $authzid == '')) AND + in_array('CRAM-MD5', $this->_capability['sasl'])) { + + $this->_sendCmd(sprintf('AUTHENTICATE "CRAM-MD5"')); + if (PEAR::isError($challenge = $this->_getChallenge())) { + return $challenge; + } + if (PEAR::isError($sasl = &Auth_SASL::factory('CramMD5'))) { + return $sasl; + } + if (PEAR::isError($response = $sasl->getResponse($authcid, $pass, $challenge))) { + return $response; + } + $res = $this->_doCmd('"' . base64_encode($response) . '"'); + + } elseif (($logintype == 'PLAIN' OR $logintype == '') AND + in_array('PLAIN', $this->_capability['sasl'])) { + + $res = $this->_doCmd(sprintf('AUTHENTICATE "PLAIN" "%s"', base64_encode($authzid . chr(0) . $authcid . chr(0) . $pass))); + + } elseif (($logintype == 'LOGIN' OR ($logintype == '' AND $authzid == '')) AND + in_array('LOGIN', $this->_capability['sasl'])) { + + $this->_sendCmd('AUTHENTICATE "LOGIN" "' . base64_encode($authcid) . '"'); + // Throw away password prompt + $this->_sock->readLine(); + $res = $this->_doCmd('"' . base64_encode($pass) . '"'); + + } else { + return PEAR::raiseError("No SASL mechanism found."); } - if (!PEAR::isError($res = $this->_getResponse())) { + if (!PEAR::isError($res)) { $this->_state = NET_SIEVE_STATE_TRANSACTION; return true; } else { @@ -305,9 +350,9 @@ function _cmdDeleteScript($scriptname) { if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { - $this->_sendCmd(sprintf('DELETESCRIPT "%s"', $scriptname)); + $res = $this->_doCmd(sprintf('DELETESCRIPT "%s"', $scriptname)); - if (PEAR::isError($res = $this->_getResponse())) { + if (PEAR::isError($res)) { return $res; } else { return true; @@ -327,8 +372,8 @@ function _cmdGetScript($scriptname) { if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { - $this->_sendCmd(sprintf('GETSCRIPT "%s"', $scriptname)); - if (PEAR::isError($res = $this->_getResponse())) { + $res = $this->_doCmd(sprintf('GETSCRIPT "%s"', $scriptname)); + if (PEAR::isError($res)) { return $res; } else { return preg_replace('/{[0-9]+}\r\n/', '', $res); @@ -349,9 +394,9 @@ function _cmdSetActive($scriptname) { if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { - $this->_sendCmd(sprintf('SETACTIVE "%s"', $scriptname)); + $res = $this->_doCmd(sprintf('SETACTIVE "%s"', $scriptname)); - if (PEAR::isError($res = $this->_getResponse())) { + if (PEAR::isError($res)) { return $res; } else { $this->_activeScript = $scriptname; @@ -374,8 +419,8 @@ if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { $scripts = array(); $activescript = null; - $this->_sendCmd('LISTSCRIPTS'); - if (PEAR::isError($res = $this->_getResponse())) { + $res = $this->_doCmd('LISTSCRIPTS'); + if (PEAR::isError($res)) { return $res; } else { $res = explode("\r\n", $res); @@ -406,9 +451,8 @@ function _cmdPutScript($scriptname, $scriptdata) { if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { - $this->_sendCmd(sprintf('PUTSCRIPT "%s" {%d+}', $scriptname, strlen($scriptdata))); - $this->_sendCmd($scriptdata); - if (!PEAR::isError($res = $this->_getResponse())) { + $res = $this->_doCmd(sprintf("PUTSCRIPT \"%s\" {%d+}\r\n%s", $scriptname, strlen($scriptdata), $scriptdata)); + if (!PEAR::isError($res)) { return true; } else { return $res; @@ -428,13 +472,9 @@ { if (NET_SIEVE_STATE_DISCONNECTED !== $this->_state) { $this->_sendCmd('LOGOUT'); - if (!PEAR::isError($res = $this->_getResponse())) { - $this->_sock->disconnect(); - $this->_state = NET_SIEVE_STATE_DISCONNECTED; - return true; - } else { - return $res; - } + $this->_sock->disconnect(); + $this->_state = NET_SIEVE_STATE_DISCONNECTED; + return true; } else { return PEAR::raiseError('Not currently connected'); } @@ -449,8 +489,8 @@ function _cmdCapability() { if (NET_SIEVE_STATE_TRANSACTION === $this->_state) { - $this->_sendCmd('CAPABILITY'); - if (!PEAR::isError($res = $this->_getResponse())) { + $res = $this->_doCmd('CAPABILITY'); + if (!PEAR::isError($res)) { $this->_parseCapability($res); return true; } else { @@ -503,16 +543,46 @@ { $this->_sock->writeLine($cmd); } + + /** + * Retrieves the plaintext SASL challenge from the server. + * + * @access private + * @return mixed Reponse string + */ + function _getChallenge() + { + $challenge = ''; + $line = $this->_sock->readLine(); + + if ('no' == strtolower(substr($line, 0, 2)) or + 'bye' == strtolower(substr($line, 0, 3))) { + // SASL error + preg_match('/.*?\s(.*)/', $line, $matches); + return PEAR::raiseError($matches[1]); + } + // Discard {nnn+} in literal + if (preg_match('/^\{\d*\+{0,1}\}\s*$/', $line)) { + $line = $this->_sock->readLine(); + } + + preg_replace('/^\{\d*\+{0,1}\}\s*/', '', $line); + return base64_decode($line); + } + /** - * Retrieves a response from the server and, to a certain degree, - * parses it. + * Send a command and retrieves a response from the server. + * * * @access private + * @param string $cmd The command to send * @return mixed Reponse string if an OK response, PEAR_Error if a NO response */ - function _getResponse() + function _doCmd($cmd) { + $this->_sock->writeLine($cmd); + $response = ''; while (true) { @@ -526,10 +596,26 @@ $line .= str_replace("\r\n", ' ', $this->_sock->read($matches[1])); } return PEAR::raiseError(trim($response . substr($line, 2))); - } - + + } elseif ('bye' == strtolower(substr($line, 0, 3))) { + // Check for referral, then follow it. Otherwise, carp an error. + if (preg_match('/^bye \(referral "(.*?)"\)/i', $line, $matches)) { + // Follow referral + $this->_data['host'] = $matches[1]; + if (PEAR::isError($this->_cmdLogout()) or + PEAR::isError($this->_connect($this->_data['host'], $this->_data['port'])) or + PEAR::isError($this->_login($this->_data['authcid'], $this->_data['pass'], $this->_data['logintype'], $this->_data['authzid']))) { + return PEAR::raiseError("Can't follow referral to " . $this->_data['host']); + } + $this->_sock->writeLine($cmd); + } else { + return PEAR::raiseError(trim($response . substr($line, 3))); + } + } + $response .= $line . "\r\n"; } } + } -?> \ No newline at end of file +?>
« previous php.pear.dev (#22155) next »