From 3875eb68139d878fba2f00bdcaae3c34ebb53da7 Mon Sep 17 00:00:00 2001 From: alecpl Date: Thu, 24 Nov 2011 10:54:59 +0000 Subject: - Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#1487937) --- program/lib/Net/SMTP.php | 167 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 122 insertions(+), 45 deletions(-) (limited to 'program/lib/Net') diff --git a/program/lib/Net/SMTP.php b/program/lib/Net/SMTP.php index 0463758b3..4e04f9191 100644 --- a/program/lib/Net/SMTP.php +++ b/program/lib/Net/SMTP.php @@ -62,7 +62,7 @@ class Net_SMTP * @var array * @access public */ - var $auth_methods = array('DIGEST-MD5', 'CRAM-MD5', 'LOGIN', 'PLAIN'); + var $auth_methods = array(); /** * Use SMTP command pipelining (specified in RFC 2920) if the SMTP @@ -187,15 +187,16 @@ class Net_SMTP $this->_socket_options = $socket_options; $this->_timeout = $timeout; - /* Include the Auth_SASL package. If the package is not - * available, we disable the authentication methods that - * depend upon it. */ - if ((@include_once 'Auth/SASL.php') === false) { - $pos = array_search('DIGEST-MD5', $this->auth_methods); - unset($this->auth_methods[$pos]); - $pos = array_search('CRAM-MD5', $this->auth_methods); - unset($this->auth_methods[$pos]); + /* Include the Auth_SASL package. If the package is available, we + * enable the authentication methods that depend upon it. */ + if ((@include_once 'Auth/SASL.php') === true) { + $this->setAuthMethod('CRAM-MD5', array($this, '_authCram_MD5')); + $this->setAuthMethod('DIGEST-MD5', array($this, '_authDigest_MD5')); } + + /* These standard authentication methods are always available. */ + $this->setAuthMethod('LOGIN', array($this, '_authLogin'), false); + $this->setAuthMethod('PLAIN', array($this, '_authPlain'), false); } /** @@ -250,7 +251,8 @@ class Net_SMTP * * @param string $data The string of data to send. * - * @return mixed True on success or a PEAR_Error object on failure. + * @return mixed The number of bytes that were actually written, + * or a PEAR_Error object on failure. * * @access private * @since 1.1.0 @@ -259,13 +261,14 @@ class Net_SMTP { $this->_debug("Send: $data"); - $error = $this->_socket->write($data); - if ($error === false || PEAR::isError($error)) { - $msg = ($error) ? $error->getMessage() : "unknown error"; - return PEAR::raiseError("Failed to write to socket: $msg"); + $result = $this->_socket->write($data); + if (!$result || PEAR::isError($result)) { + $msg = ($result) ? $result->getMessage() : "unknown error"; + return PEAR::raiseError("Failed to write to socket: $msg", + null, PEAR_ERROR_RETURN); } - return true; + return $result; } /** @@ -292,7 +295,8 @@ class Net_SMTP } if (strcspn($command, "\r\n") !== strlen($command)) { - return PEAR::raiseError('Commands cannot contain newlines'); + return PEAR::raiseError('Commands cannot contain newlines', + null, PEAR_ERROR_RETURN); } return $this->_send($command . "\r\n"); @@ -331,10 +335,11 @@ class Net_SMTP while ($line = $this->_socket->readLine()) { $this->_debug("Recv: $line"); - /* If we receive an empty line, the connection has been closed. */ + /* If we receive an empty line, the connection was closed. */ if (empty($line)) { $this->disconnect(); - return PEAR::raiseError('Connection was unexpectedly closed'); + return PEAR::raiseError('Connection was closed', + null, PEAR_ERROR_RETURN); } /* Read the code and store the rest in the arguments array. */ @@ -366,7 +371,32 @@ class Net_SMTP } return PEAR::raiseError('Invalid response code received from server', - $this->_code); + $this->_code, PEAR_ERROR_RETURN); + } + + /** + * Issue an SMTP command and verify its response. + * + * @param string $command The SMTP command string or data. + * @param mixed $valid The set of valid response codes. These + * may be specified as an array of integer + * values or as a single integer value. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @access public + * @since 1.6.0 + */ + function command($command, $valid) + { + if (PEAR::isError($error = $this->_put($command))) { + return $error; + } + if (PEAR::isError($error = $this->_parseResponse($valid))) { + return $error; + } + + return true; } /** @@ -499,7 +529,8 @@ class Net_SMTP return $error; } if (PEAR::isError($this->_parseResponse(250))) { - return PEAR::raiseError('HELO was not accepted: ', $this->_code); + return PEAR::raiseError('HELO was not accepted: ', $this->_code, + PEAR_ERROR_RETURN); } return true; @@ -533,13 +564,14 @@ class Net_SMTP { $available_methods = explode(' ', $this->_esmtp['AUTH']); - foreach ($this->auth_methods as $method) { + foreach ($this->auth_methods as $method => $callback) { if (in_array($method, $available_methods)) { return $method; } } - return PEAR::raiseError('No supported authentication methods'); + return PEAR::raiseError('No supported authentication methods', + null, PEAR_ERROR_RETURN); } /** @@ -599,33 +631,27 @@ class Net_SMTP } } else { $method = strtoupper($method); - if (!in_array($method, $this->auth_methods)) { + if (!array_key_exists($method, $this->auth_methods)) { return PEAR::raiseError("$method is not a supported authentication method"); } } - switch ($method) { - case 'DIGEST-MD5': - $result = $this->_authDigest_MD5($uid, $pwd, $authz); - break; - - case 'CRAM-MD5': - $result = $this->_authCRAM_MD5($uid, $pwd); - break; - - case 'LOGIN': - $result = $this->_authLogin($uid, $pwd); - break; - - case 'PLAIN': - $result = $this->_authPlain($uid, $pwd, $authz); - break; + if (!isset($this->auth_methods[$method])) { + return PEAR::raiseError("$method is not a supported authentication method"); + } - default: - $result = PEAR::raiseError("$method is not a supported authentication method"); - break; + if (!is_callable($this->auth_methods[$method], false)) { + return PEAR::raiseError("$method authentication method cannot be called"); } + if (is_array($this->auth_methods[$method])) { + list($object, $method) = $this->auth_methods[$method]; + $result = $object->{$method}($uid, $pwd, $authz, $this); + } else { + $func = $this->auth_methods[$method]; + $result = $func($uid, $pwd, $authz, $this); + } + /* If an error was encountered, return the PEAR_Error object. */ if (PEAR::isError($result)) { return $result; @@ -634,6 +660,46 @@ class Net_SMTP return true; } + /** + * Add a new authentication method. + * + * @param string The authentication method name (e.g. 'PLAIN') + * @param mixed The authentication callback (given as the name of a + * function or as an (object, method name) array). + * @param bool Should the new method be prepended to the list of + * available methods? This is the default behavior, + * giving the new method the highest priority. + * + * @return mixed True on success or a PEAR_Error object on failure. + * + * @access public + * @since 1.6.0 + */ + function setAuthMethod($name, $callback, $prepend = true) + { + if (!is_string($name)) { + return PEAR::raiseError('Method name is not a string'); + } + + if (!is_string($callback) && !is_array($callback)) { + return PEAR::raiseError('Method callback must be string or array'); + } + + if (is_array($callback)) { + if (!is_object($callback[0]) || !is_string($callback[1])) + return PEAR::raiseError('Bad mMethod callback array'); + } + + if ($prepend) { + $this->auth_methods = array_merge(array($name => $callback), + $this->auth_methods); + } else { + $this->auth_methods[$name] = $callback; + } + + return true; + } + /** * Authenticates the user using the DIGEST-MD5 method. * @@ -691,13 +757,14 @@ class Net_SMTP * * @param string The userid to authenticate as. * @param string The password to authenticate with. + * @param string The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access private * @since 1.1.0 */ - function _authCRAM_MD5($uid, $pwd) + function _authCRAM_MD5($uid, $pwd, $authz = '') { if (PEAR::isError($error = $this->_put('AUTH', 'CRAM-MD5'))) { return $error; @@ -730,13 +797,14 @@ class Net_SMTP * * @param string The userid to authenticate as. * @param string The password to authenticate with. + * @param string The optional authorization proxy identifier. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access private * @since 1.1.0 */ - function _authLogin($uid, $pwd) + function _authLogin($uid, $pwd, $authz = '') { if (PEAR::isError($error = $this->_put('AUTH', 'LOGIN'))) { return $error; @@ -1012,7 +1080,16 @@ class Net_SMTP /* Stream the contents of the file resource out over our socket * connection, line by line. Each line must be run through the * quoting routine. */ - while ($line = fgets($data, 1024)) { + while (strlen($line = fread($data, 8192)) > 0) { + /* If the last character is an newline, we need to grab the + * next character to check to see if it is a period. */ + while (!feof($data)) { + $char = fread($data, 1); + $line .= $char; + if ($char != "\n") { + break; + } + } $this->quotedata($line); if (PEAR::isError($result = $this->_send($line))) { return $result; -- cgit v1.2.3