[PATCH] Net_Sieve, maintainer unavailable.
| From: | Etienne Goyer | 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 +?>
--- 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 +?>