From: Chuck Hagenbuch Date: Sun, 18 Jul 2010 20:59:36 +0000 (-0400) Subject: Reorganize Horde_Mail a bit to group the transport classes X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=251fdaa7a63528d0de7301c00a94138780bfda03;p=horde.git Reorganize Horde_Mail a bit to group the transport classes --- diff --git a/framework/Alarm/lib/Horde/Alarm/Handler/Mail.php b/framework/Alarm/lib/Horde/Alarm/Handler/Mail.php index d751054a7..3821cc4ee 100644 --- a/framework/Alarm/lib/Horde/Alarm/Handler/Mail.php +++ b/framework/Alarm/lib/Horde/Alarm/Handler/Mail.php @@ -25,9 +25,9 @@ class Horde_Alarm_Handler_Mail extends Horde_Alarm_Handler protected $_identity; /** - * A Horde_Mail_Driver object. + * A Horde_Mail_Transport object. * - * @var Horde_Mail_Driver + * @var Horde_Mail_Transport */ protected $_mail; @@ -45,7 +45,7 @@ class Horde_Alarm_Handler_Mail extends Horde_Alarm_Handler * Required parameter: * - identity: An identity factory that implements * getIdentity(). - * - mail: A Horde_Mail_Driver instance. + * - mail: A Horde_Mail_Transport instance. * - charset: The charset of the messages. */ public function __construct(array $params = null) @@ -58,9 +58,8 @@ class Horde_Alarm_Handler_Mail extends Horde_Alarm_Handler if (!method_exists($params['identity'], 'getIdentity')) { throw new Horde_Alarm_Exception('Parameter \'identity\' does not implement getIdentity().'); } - $r = new ReflectionObject($params['mail']); - if (!($params['mail'] instanceof Horde_Mail_Driver)) { - throw new Horde_Alarm_Exception('Parameter \'mail\' is not a Horde_Mail_Driver object.'); + if (!($params['mail'] instanceof Horde_Mail_Transport)) { + throw new Horde_Alarm_Exception('Parameter \'mail\' is not a Horde_Mail_Transport object.'); } $this->_identity = $params['identity']; $this->_mail = $params['mail']; diff --git a/framework/Core/lib/Horde/Core/Binder/Mail.php b/framework/Core/lib/Horde/Core/Binder/Mail.php index d2602a6a7..94fa3ab46 100644 --- a/framework/Core/lib/Horde/Core/Binder/Mail.php +++ b/framework/Core/lib/Horde/Core/Binder/Mail.php @@ -7,25 +7,25 @@ class Horde_Core_Binder_Mail implements Horde_Injector_Binder { public function create(Horde_Injector $injector) { - $driver = isset($GLOBALS['conf']['mailer']['type']) + $transport = isset($GLOBALS['conf']['mailer']['type']) ? $GLOBALS['conf']['mailer']['type'] : 'null'; $params = isset($GLOBALS['conf']['mailer']['params']) ? $GLOBALS['conf']['mailer']['params'] : array(); - if (($driver == 'smtp') && + if (($transport == 'smtp') && $params['auth'] && empty($params['username'])) { $params['username'] = $GLOBALS['registry']->getAuth(); $params['password'] = $GLOBALS['registry']->getAuthCredential('password'); } - try { - return Horde_Mail::factory($driver, $params); - } catch (Horde_Mime_Exception $e) { - throw new Horde_Exception($e); + $class = 'Horde_Mail_Transport_' . ucfirst($transport); + if (class_exists($class)) { + return new $class($params); } + throw new Horde_Exception('Unable to find class for transport ' . $transport); } public function equals(Horde_Injector_Binder $binder) diff --git a/framework/Mail/lib/Horde/Mail.php b/framework/Mail/lib/Horde/Mail.php index 44d1797de..d3b6532e1 100644 --- a/framework/Mail/lib/Horde/Mail.php +++ b/framework/Mail/lib/Horde/Mail.php @@ -50,23 +50,22 @@ class Horde_Mail { /** - * Returns a Horde_Mail_Driver:: object. + * Returns a Horde_Mail_Transport:: object. * - * @param string $driver The driver to instantiate. - * @param array $params The parameters to pass to the object. + * @param string $transport The transport to instantiate. + * @param array $params The parameters to pass to the transport. * - * @return Horde_Mail_Driver The driver instance. + * @return Horde_Mail_Transport The transport instance. * @throws Horde_Mail_Exception + * @deprecated */ - static public function factory($driver, $params = array()) + static public function factory($transport, $params = array()) { - $class = __CLASS__ . '_' . ucfirst($driver); - + $class = 'Horde_Mail_Transport_' . ucfirst($transport); if (class_exists($class)) { return new $class($params); } - throw new Horde_Mail_Exception('Unable to find class for driver ' . $driver); + throw new Horde_Mail_Exception('Unable to find class for transport ' . $transport); } - } diff --git a/framework/Mail/lib/Horde/Mail/Driver.php b/framework/Mail/lib/Horde/Mail/Driver.php deleted file mode 100644 index fb0f08804..000000000 --- a/framework/Mail/lib/Horde/Mail/Driver.php +++ /dev/null @@ -1,214 +0,0 @@ - - * @author Michael Slusarz - * @copyright 1997-2010 Chuck Hagenbuch - * @copyright 2010 Michael Slusarz - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * Mail driver base class. - * - * @access public - * @version $Revision: 294747 $ - * @package Mail - */ -abstract class Horde_Mail_Driver -{ - /** - * Line terminator used for separating header lines. - * - * @var string - */ - public $sep = "\r\n"; - - /** - * Configuration parameters. - * - * @var array - */ - protected $_params = array(); - - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - abstract public function send($recipients, array $headers, $body); - - /** - * Take an array of mail headers and return a string containing text - * usable in sending a message. - * - * @param array $headers The array of headers to prepare, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array value - * is the header value (ie, 'test'). The header - * produced from those values would be 'Subject: - * test'. - * If the '_raw' key exists, the value of this key - * will be used as the exact text for sending the - * message. - * - * @return mixed Returns false if it encounters a bad address; otherwise - * returns an array containing two elements: Any From: - * address found in the headers, and the plain text version - * of the headers. - * @throws Horde_Mail_Exception - */ - public function prepareHeaders(array $headers) - { - $lines = array(); - $from = null; - - $parser = new Horde_Mail_Rfc822(); - - foreach ($headers as $key => $value) { - if (strcasecmp($key, 'From') === 0) { - $addresses = $parser->parseAddressList($value, array( - 'nest_groups' => false, - )); - $from = $addresses[0]->mailbox . '@' . $addresses[0]->host; - - // Reject envelope From: addresses with spaces. - if (strstr($from, ' ')) { - return false; - } - - $lines[] = $key . ': ' . $value; - } elseif (strcasecmp($key, 'Received') === 0) { - $received = array(); - if (!is_array($value)) { - $value = array($value); - } - - foreach ($value as $line) { - $received[] = $key . ': ' . $line; - } - - // Put Received: headers at the top. Spam detectors often - // flag messages with Received: headers after the Subject: - // as spam. - $lines = array_merge($received, $lines); - } else { - // If $value is an array (i.e., a list of addresses), convert - // it to a comma-delimited string of its elements (addresses). - if (is_array($value)) { - $value = implode(', ', $value); - } - $lines[] = $key . ': ' . $value; - } - } - - return array($from, isset($headers['_raw']) ? $headers['_raw'] : join($this->sep, $lines)); - } - - /** - * Take a set of recipients and parse them, returning an array of bare - * addresses (forward paths) that can be passed to sendmail or an SMTP - * server with the 'RCPT TO:' command. - * - * @param mixed Either a comma-separated list of recipients (RFC822 - * compliant), or an array of recipients, each RFC822 valid. - * - * @return array Forward paths (bare addresses). - * @throws Horde_Mail_Exception - */ - public function parseRecipients($recipients) - { - // if we're passed an array, assume addresses are valid and - // implode them before parsing. - if (is_array($recipients)) { - $recipients = implode(', ', $recipients); - } - - // Parse recipients, leaving out all personal info. This is - // for smtp recipients, etc. All relevant personal information - // should already be in the headers. - $parser = new Horde_Mail_Rfc822(); - $addresses = $parser->parseAddressList($recipients, array( - 'nest_groups' => false - )); - - $recipients = array(); - if (is_array($addresses)) { - foreach ($addresses as $ob) { - $recipients[] = $ob->mailbox . '@' . $ob->host; - } - } - - return $recipients; - } - - /** - * Sanitize an array of mail headers by removing any additional header - * strings present in a legitimate header's value. The goal of this - * filter is to prevent mail injection attacks. - * - * @param array $headers The associative array of headers to sanitize. - * - * @return array The sanitized headers. - */ - protected function _sanitizeHeaders($headers) - { - foreach (array_keys($headers) as $key) { - $headers[$key] = preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]); - } - - return $headers; - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Mail.php b/framework/Mail/lib/Horde/Mail/Mail.php deleted file mode 100644 index 1fdd83c01..000000000 --- a/framework/Mail/lib/Horde/Mail/Mail.php +++ /dev/null @@ -1,149 +0,0 @@ - - * @author Michael Slusarz - * @copyright 2010 Chuck Hagenbuch - * @copyright 2010 Michael Slusarz - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * Internal PHP-mail() interface. - * - * @category Horde - * @package Mail - */ -class Horde_Mail_Mail extends Horde_Mail_Driver -{ - /** - * Constructor. - * - * @param array $params Additional parameters: - *
-     * 'args' - (string) Extra arguments for the mail() function.
-     * 
- */ - public function __construct(array $params = array()) - { - $this->_params = array_merge($this->_params, $params); - - /* Because the mail() function may pass headers as command - * line arguments, we can't guarantee the use of the standard - * "\r\n" separator. Instead, we use the system's native line - * separator. */ - $this->sep = defined('PHP_EOL') - ? PHP_EOL - : (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; - } - - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - $headers = $this->_sanitizeHeaders($headers); - - // If we're passed an array of recipients, implode it. - if (is_array($recipients)) { - $recipients = array_map('trim', implode(',', $recipients)); - } - - $subject = ''; - - foreach (array_keys($headers) as $hdr) { - if (strcasecmp($hdr, 'Subject') === 0) { - // Get the Subject out of the headers array so that we can - // pass it as a separate argument to mail(). - $subject = $headers[$hdr]; - unset($headers[$hdr]); - } elseif (strcasecmp($hdr, 'To') === 0) { - // Remove the To: header. The mail() function will add its - // own To: header based on the contents of $recipients. - unset($headers[$hdr]); - } - } - - // Flatten the headers out. - list(, $text_headers) = $this->prepareHeaders($headers); - - // mail() requires a string for $body. If resource, need to convert - // to a string. - if (is_resource($body)) { - $body_str = ''; - rewind($body); - while (!feof($body)) { - $body_str .= fread($body, 8192); - } - $body = $body_str; - } - - // We only use mail()'s optional fifth parameter if the additional - // parameters have been provided and we're not running in safe mode. - if (empty($this->_params) || ini_get('safe_mode')) { - $result = mail($recipients, $subject, $body, $text_headers); - } else { - $result = mail($recipients, $subject, $body, $text_headers, isset($this->_params['args']) ? $this->_params['args'] : ''); - } - - // If the mail() function returned failure, we need to create an - // Exception and return it instead of the boolean result. - if ($result === false) { - throw new Horde_Mail_Exception('mail() returned failure.'); - } - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Mock.php b/framework/Mail/lib/Horde/Mail/Mock.php deleted file mode 100644 index 6e68c319b..000000000 --- a/framework/Mail/lib/Horde/Mail/Mock.php +++ /dev/null @@ -1,138 +0,0 @@ - - * @copyright 2010 Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * Mock implementation, for testing. - * - * @category Horde - * @package Mail - */ -class Horde_Mail_Mock extends Horde_Mail_Driver -{ - /** - * Array of messages that have been sent with the mock. - * - * @var array - */ - public $sentMessages = array(); - - /** - * Callback before sending mail. - * - * @var callback - */ - protected $_preSendCallback; - - /** - * Callback after sending mai. - * - * @var callback - */ - protected $_postSendCallback; - - /** - * Constructor. - * - * @param array Optional parameters: - *
-     * 'preSendCallback' - (callback) Called before an email would be sent.
-     * 'postSendCallback' - (callback) Called after an email would have been
-     *                      sent.
-     * 
- */ - public function __construct(array $params = array()) - { - if (isset($params['preSendCallback']) && - is_callable($params['preSendCallback'])) { - $this->_preSendCallback = $params['preSendCallback']; - } - - if (isset($params['postSendCallback']) && - is_callable($params['postSendCallback'])) { - $this->_postSendCallback = $params['postSendCallback']; - } - } - - /** - * Send a message. Silently discards all mail. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - if ($this->_preSendCallback) { - call_user_func_array($this->_preSendCallback, array($this, $recipients, $headers, $body)); - } - - $headers = $this->_sanitizeHeaders($headers); - list(, $text_headers) = $this->prepareHeaders($headers); - - $this->sentMessages[] = array( - 'body' => $body, - 'headers' => $headers, - 'header_text' => $text_headers, - 'recipients' => $recipients - ); - - if ($this->_postSendCallback) { - call_user_func_array($this->_postSendCallback, array($this, $recipients, $headers, $body)); - } - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Null.php b/framework/Mail/lib/Horde/Mail/Null.php deleted file mode 100644 index bf03470ea..000000000 --- a/framework/Mail/lib/Horde/Mail/Null.php +++ /dev/null @@ -1,77 +0,0 @@ - - * @copyright 2010 Phil Kernick - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * Null implementation of the mail interface. - * - * @category Horde - * @package Mail - */ -class Horde_Mail_Null extends Horde_Mail_Driver -{ - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Sendmail.php b/framework/Mail/lib/Horde/Mail/Sendmail.php deleted file mode 100644 index 08c01a39b..000000000 --- a/framework/Mail/lib/Horde/Mail/Sendmail.php +++ /dev/null @@ -1,247 +0,0 @@ - - * @author Michael Slusarz - * @copyright 2010 Chuck Hagenbuch - * @copyright 2010 Michael Slusarz - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * Sendmail interface. - * - * @category Horde - * @package Mail - */ -class Horde_Mail_Sendmail extends Horde_Mail_Driver -{ - /** - * Any extra command-line parameters to pass to the sendmail or - * sendmail wrapper binary. - * - * @var string - */ - protected $_sendmailArgs = '-i'; - - /** - * The location of the sendmail or sendmail wrapper binary on the - * filesystem. - * - * @var string - */ - protected $_sendmailPath = '/usr/sbin/sendmail'; - - /** - * Constructor. - * - * @param array $params Additional parameters: - *
-     * 'sendmail_args' - (string) Any extra parameters to pass to the sendmail
-     *                   or sendmail wrapper binary.
-     *                   DEFAULT: -i
-     * 'sendmail_path' - (string) The location of the sendmail binary on the
-     *                   filesystem.
-     *                   DEFAULT: /usr/sbin/sendmail
-     * 
- */ - public function __construct(array $params = array()) - { - if (isset($params['sendmail_args'])) { - $this->_sendmailArgs = $params['sendmail_args']; - } - - if (isset($params['sendmail_path'])) { - $this->_sendmailPath = $params['sendmail_path']; - } - - /* Because we need to pass message headers to the sendmail program on - * the commandline, we can't guarantee the use of the standard "\r\n" - * separator. Instead, we use the system's native line separator. */ - $this->sep = defined('PHP_EOL') - ? PHP_EOL - : (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; - } - - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - $recipients = implode(' ', array_map('escapeshellarg', $this->parseRecipients($recipients))); - - $headers = $this->_sanitizeHeaders($headers); - list($from, $text_headers) = $this->prepareHeaders($headers); - - /* Since few MTAs are going to allow this header to be forged - * unless it's in the MAIL FROM: exchange, we'll use Return-Path - * instead of From: if it's set. */ - foreach (array_keys($headers) as $hdr) { - if (strcasecmp($hdr, 'Return-Path') === 0) { - $from = $headers[$hdr]; - break; - } - } - - if (!strlen($from)) { - throw new Horde_Mail_Exception('No From address given.'); - } elseif ((strpos($from, ' ') !== false) || - (strpos($from, ';') !== false) || - (strpos($from, '&') !== false) || - (strpos($from, '`') !== false)) { - throw new Horde_Mail_Exception('From address specified with dangerous characters.'); - } - - $mail = @popen($this->_sendmailPath . (empty($this->_sendmailArgs) ? '' : ' ' . $this->_sendmailargs) . ' -f' . escapeshellarg($from) . ' -- ' . $recipients, 'w'); - if (!$mail) { - throw new Horde_Mail_Exception('Failed to open sendmail [' . $this->_sendmailPath . '] for execution.'); - } - - // Write the headers following by two newlines: one to end the headers - // section and a second to separate the headers block from the body. - fputs($mail, $text_headers . $this->sep . $this->sep); - - if (is_resource($body)) { - rewind($body); - while (!feof($body)) { - fputs($mail, fread($body, 8192)); - } - } else { - fputs($mail, $body); - } - $result = pclose($mail); - - if (!$result) { - return; - } - - switch ($result) { - case 64: // EX_USAGE - $msg = 'command line usage error'; - break; - - case 65: // EX_DATAERR - $msg = 'data format error'; - break; - - case 66: // EX_NOINPUT - $msg = 'cannot open input'; - break; - - case 67: // EX_NOUSER - $msg = 'addressee unknown'; - break; - - case 68: // EX_NOHOST - $msg = 'host name unknown'; - break; - - case 69: // EX_UNAVAILABLE - $msg = 'service unavailable'; - break; - - case 70: // EX_SOFTWARE - $msg = 'internal software error'; - break; - - case 71: // EX_OSERR - $msg = 'system error'; - break; - - case 72: // EX_OSFILE - $msg = 'critical system file missing'; - break; - - case 73: // EX_CANTCREAT - $msg = 'cannot create output file'; - break; - - case 74: // EX_IOERR - $msg = 'input/output error'; - - case 75: // EX_TEMPFAIL - $msg = 'temporary failure'; - break; - - case 76: // EX_PROTOCOL - $msg = 'remote error in protocol'; - break; - - case 77: // EX_NOPERM - $msg = 'permission denied'; - break; - - case 77: // EX_NOPERM - $msg = 'permission denied'; - break; - - case 78: // EX_CONFIG - $msg = 'configuration error'; - break; - - case 79: // EX_NOTFOUND - $msg = 'entry not found'; - break; - - default: - $msg = 'unknown error'; - break; - } - - throw new Horde_Mail_Exception('sendmail: ' . $msg . ' (' . $result . ')', $result); - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Smtp.php b/framework/Mail/lib/Horde/Mail/Smtp.php deleted file mode 100644 index 58b0d833c..000000000 --- a/framework/Mail/lib/Horde/Mail/Smtp.php +++ /dev/null @@ -1,356 +0,0 @@ - - * @author Chuck Hagenbuch - * @copyright 2010 Chuck Hagenbuch - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * SMTP implementation. - * - * @category Horde - * @package Mail - */ -class Horde_Mail_Smtp extends Horde_Mail_Driver -{ - /* Error: Failed to create a Net_SMTP object */ - const ERROR_CREATE = 10000; - - /* Error: Failed to connect to SMTP server */ - const ERROR_CONNECT = 10001; - - /* Error: SMTP authentication failure */ - const ERROR_AUTH = 10002; - - /* Error: No From: address has been provided */ - const ERROR_FROM = 10003; - - /* Error: Failed to set sender */ - const ERROR_SENDER = 10004; - - /* Error: Failed to add recipient */ - const ERROR_RECIPIENT = 10005; - - /* Error: Failed to send data */ - const ERROR_DATA = 10006; - - /** - * The SMTP greeting. - * - * @var string - */ - public $greeting = null; - - /** - * The SMTP queued response. - * - * @var string - */ - public $queuedAs = null; - - /** - * SMTP connection object. - * - * @var Net_SMTP - */ - protected $_smtp = null; - - /** - * The list of service extension parameters to pass to the Net_SMTP - * mailFrom() command. - * - * @var array - */ - protected $_extparams = array(); - - /** - * Constructor. - * - * @param array $params Additional parameters: - *
-     * 'auth' - (mixed) SMTP authentication.
-     *          This value may be set to true, false or the name of a specific
-     *          authentication method.
-     *          If the value is set to true, the Net_SMTP package will attempt
-     *          to use the best authentication method advertised by the remote
-     *          SMTP server.
-     *          DEFAULT: false.
-     * 'debug' - (boolean) Activate SMTP debug mode?
-     *           DEFAULT: false
-     * 'host' - (string) The server to connect to.
-     *          DEFAULT: localhost
-     * 'localhost' - (string) Hostname or domain that will be sent to the
-     *               remote SMTP server in the HELO / EHLO message.
-     *               DEFAULT: localhost
-     * 'password' - (string) The password to use for SMTP auth.
-     *              DEFAULT: NONE
-     * 'persist' - (boolean) Should the SMTP connection persist?
-     *             DEFAULT: false
-     * 'pipelining' - (boolean) Use SMTP command pipelining.
-     *                Use SMTP command pipelining (specified in RFC 2920) if
-     *                the SMTP server supports it. This speeds up delivery
-     *                over high-latency connections.
-     *                DEFAULT: false (use default value from Net_SMTP)
-     * 'port' - (integer) The port to connect to.
-     *          DEFAULT: 25
-     * 'timeout' - (integer) The SMTP connection timeout.
-     *             DEFAULT: NONE
-     * 'username' - (string) The username to use for SMTP auth.
-     *              DEFAULT: NONE
-     * 
- */ - public function __construct(array $params = array()) - { - $this->_params = array_merge(array( - 'auth' => false, - 'debug' => false, - 'host' => 'localhost', - 'localhost' => 'localhost', - 'password' => '', - 'persist' => false, - 'pipelining' => false, - 'port' => 25, - 'timeout' => null, - 'username' => '' - ), $params); - - /* Destructor implementation to ensure that we disconnect from any - * potentially-alive persistent SMTP connections. */ - register_shutdown_function(array($this, 'disconnect')); - } - - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - /* If we don't already have an SMTP object, create one. */ - $this->getSMTPObject(); - - $headers = $this->_sanitizeHeaders($headers); - - try { - list($from, $textHeaders) = $this->prepareHeaders($headers); - } catch (Horde_Mail_Exception $e) { - $this->_smtp->rset(); - throw $e; - } - - /* Since few MTAs are going to allow this header to be forged unless - * it's in the MAIL FROM: exchange, we'll use Return-Path instead of - * From: if it's set. */ - foreach (array_keys($headers) as $hdr) { - if (strcasecmp($hdr, 'Return-Path') === 0) { - $from = $headers[$hdr]; - break; - } - } - - if (!strlen($from)) { - $this->_smtp->rset(); - throw new Horde_Mail_Exception('No From: address has been provided', self::ERROR_FROM); - } - - $params = ''; - foreach ($this->_extparams as $key => $val) { - $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); - } - - $res = $this->_smtp->mailFrom($from, ltrim($params)); - if ($res instanceof PEAR_Error) { - $this->_smtp->rset(); - $this->_error("Failed to set sender: $from", $res, self::ERROR_SENDER); - } - - try { - $recipients = $this->parseRecipients($recipients); - } catch (Horde_Mail_Exception $e) { - $this->_smtp->rset(); - throw $e; - } - - foreach ($recipients as $recipient) { - $res = $this->_smtp->rcptTo($recipient); - if ($res instanceof PEAR_Error) { - $this->_smtp->rset(); - $this->_error("Failed to add recipient: $recipient", $res, self::ERROR_RECIPIENT); - } - } - - /* Send the message's headers and the body as SMTP data. */ - $res = $this->_smtp->data($body, $textHeaders); - list(,$args) = $this->_smtp->getResponse(); - - if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { - $this->queuedAs = $queued[1]; - } - - /* We need the greeting; from it we can extract the authorative name - * of the mail server we've really connected to. Ideal if we're - * connecting to a round-robin of relay servers and need to track - * which exact one took the email */ - $this->greeting = $this->_smtp->getGreeting(); - - if ($res instanceof PEAR_Error) { - $this->_smtp->rset(); - $this->_error('Failed to send data', $res, self::ERROR_DATA); - } - - /* If persistent connections are disabled, destroy our SMTP object. */ - if ($this->_params['persist']) { - $this->disconnect(); - } - } - - /** - * Connect to the SMTP server by instantiating a Net_SMTP object. - * - * @return Net_SMTP The SMTP object. - * @throws Horde_Mail_Exception - */ - public function getSMTPObject() - { - if ($this->_smtp) { - return $this->_smtp; - } - - $this->_smtp = new Net_SMTP( - $this->_params['host'], - $this->_params['port'], - $this->_params['localhost'] - ); - - /* If we still don't have an SMTP object at this point, fail. */ - if (!($this->_smtp instanceof Net_SMTP)) { - throw new Horde_Mail_Exception('Failed to create a Net_SMTP object', self::ERROR_CREATE); - } - - /* Configure the SMTP connection. */ - if ($this->_params['debug']) { - $this->_smtp->setDebug(true); - } - - /* Attempt to connect to the configured SMTP server. */ - $res = $this->_smtp->connect($this->_params['timeout']); - if ($res instanceof PEAR_Error) { - $this->_error('Failed to connect to ' . $this->_params['host'] . ':' . $this->_params['port'], $res, self::ERROR_CONNECT); - } - - /* Attempt to authenticate if authentication has been enabled. */ - if ($this->_params['auth']) { - $method = is_string($this->_params['auth']) - ? $this->_params['auth'] - : ''; - - $res = $this->_smtp->auth($this->_params['username'], $this->_params['password'], $method); - if ($res instanceof PEAR_Error) { - $this->_smtp->rset(); - $this->_error("$method authentication failure", $res, self::ERROR_AUTH); - } - } - - return $this->_smtp; - } - - /** - * Add parameter associated with a SMTP service extension. - * - * @param string $keyword Extension keyword. - * @param string $value Any value the keyword needs. - */ - public function addServiceExtensionParameter($keyword, $value = null) - { - $this->_extparams[$keyword] = $value; - } - - /** - * Disconnect and destroy the current SMTP connection. - * - * @return boolean True if the SMTP connection no longer exists. - */ - public function disconnect() - { - /* If we have an SMTP object, disconnect and destroy it. */ - if (is_object($this->_smtp) && $this->_smtp->disconnect()) { - $this->_smtp = null; - } - - /* We are disconnected if we no longer have an SMTP object. */ - return ($this->_smtp === null); - } - - /** - * Build a standardized string describing the current SMTP error. - * - * @param string $text Custom string describing the error context. - * @param PEAR_Error $error PEAR_Error object. - * @param integer $e_code Error code. - * - * @throws Horde_Mail_Exception - */ - protected function _error($text, $error, $e_code) - { - /* Split the SMTP response into a code and a response string. */ - list($code, $response) = $this->_smtp->getResponse(); - - /* Build our standardized error string. */ - throw new Horde_Mail_Exception($text . ' [SMTP: ' . $error->getMessage() . " (code: $code, response: $response)]", $e_code); - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Smtpmx.php b/framework/Mail/lib/Horde/Mail/Smtpmx.php deleted file mode 100644 index 45966a9fc..000000000 --- a/framework/Mail/lib/Horde/Mail/Smtpmx.php +++ /dev/null @@ -1,389 +0,0 @@ - - * @copyright 2010 gERD Schaufelberger - * @license http://opensource.org/licenses/bsd-license.php New BSD License - */ - -/** - * SMTP MX implementation. - * - * @author gERD Schaufelberger - * @category Horde - * @package Mail - */ -class Horde_Mail_Smtpmx extends Horde_Mail_Driver -{ - /** - * SMTP connection object. - * - * @var Net_SMTP - */ - protected $_smtp = null; - - /** - * Net_DNS_Resolver object. - * - * @var Net_DNS_Resolver - */ - protected $_resolver; - - /** - * Internal error codes. - * Translate internal error identifier to human readable messages. - * - * @var array - */ - protected $_errorCode = array( - 'not_connected' => array( - 'code' => 1, - 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.' - ), - 'failed_vrfy_rcpt' => array( - 'code' => 2, - 'msg' => 'Recipient "{RCPT}" could not be veryfied.' - ), - 'failed_set_from' => array( - 'code' => 3, - 'msg' => 'Failed to set sender: {FROM}.' - ), - 'failed_set_rcpt' => array( - 'code' => 4, - 'msg' => 'Failed to set recipient: {RCPT}.' - ), - 'failed_send_data' => array( - 'code' => 5, - 'msg' => 'Failed to send mail to: {RCPT}.' - ), - 'no_from' => array( - 'code' => 5, - 'msg' => 'No from address has be provided.' - ), - 'send_data' => array( - 'code' => 7, - 'msg' => 'Failed to create Net_SMTP object.' - ), - 'no_mx' => array( - 'code' => 8, - 'msg' => 'No MX-record for {RCPT} found.' - ), - 'no_resolver' => array( - 'code' => 9, - 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"' - ), - 'failed_rset' => array( - 'code' => 10, - 'msg' => 'RSET command failed, SMTP-connection corrupt.' - ) - ); - - /** - * Constructor. - * - * @param array $params Additional options: - *
-     * 'debug' - (boolean) Activate SMTP and Net_DNS debug mode?
-     *           DEFAULT: false
-     * 'mailname' - (string) The name of the local mail system (a valid
-     *              hostname which matches the reverse lookup)
-     *              DEFAULT: Auto-determined
-     * 'netdns' - (boolean) Use PEAR:Net_DNS (true) or the PHP builtin
-     *            getmxrr().
-     *            DEFAULT: true
-     * 'port' - (integer) Port.
-     *          DEFAULT: Auto-determined
-     * 'test' - (boolean) Activate test mode?
-     *          DEFAULT: false
-     * 'timeout' - (integer) The SMTP connection timeout (in seconds).
-     *             DEFAULT: 10
-     * 'verp' - (boolean) Whether to use VERP.
-     *          If not a boolean, the string value will be used as the VERP
-     *          separators.
-     *          DEFAULT: false
-     * 'vrfy' - (boolean) Whether to use VRFY.
-     *          DEFAULT: false
-     * 
- */ - public function __construct(array $params = array()) - { - /* Try to find a valid mailname. */ - if (!isset($params['mailname']) && function_exists('posix_uname')) { - $uname = posix_uname(); - $params['mailname'] = $uname['nodename']; - } - - if (!isset($params['port'])) { - $params['port'] = getservbyname('smtp', 'tcp'); - } - - $this->_params = array_merge(array( - 'debug' => false, - 'mailname' => 'localhost', - 'netdns' => true, - 'port' => 25, - 'test' => false, - 'timeout' => 10, - 'verp' => false, - 'vrfy' => false - ), $params); - } - - /** - * Destructor implementation to ensure that we disconnect from any - * potentially-alive persistent SMTP connections. - */ - public function __destruct() - { - if (is_object($this->_smtp)) { - $this->_smtp->disconnect(); - $this->_smtp = null; - } - } - - /** - * Send a message. - * - * @param mixed $recipients Either a comma-seperated list of recipients - * (RFC822 compliant), or an array of - * recipients, each RFC822 valid. This may - * contain recipients not specified in the - * headers, for Bcc:, resending messages, etc. - * @param array $headers The headers to send with the mail, in an - * associative array, where the array key is the - * header name (ie, 'Subject'), and the array - * value is the header value (ie, 'test'). The - * header produced from those values would be - * 'Subject: test'. - * If the '_raw' key exists, the value of this - * key will be used as the exact text for - * sending the message. - * @param mixed $body The full text of the message body, including - * any Mime parts, etc. Either a string or a - * stream resource. - * - * @throws Horde_Mail_Exception - */ - public function send($recipients, array $headers, $body) - { - $headers = $this->_sanitizeHeaders($headers); - - // Prepare headers - list($from, $textHeaders) = $this->prepareHeaders($headers); - - // Use 'Return-Path' if possible - foreach (array_keys($headers) as $hdr) { - if (strcasecmp($hdr, 'Return-Path') === 0) { - $from = $headers['Return-Path']; - break; - } - } - - if (!strlen($from)) { - $this->_error('no_from'); - } - - // Prepare recipients - foreach ($this->parseRecipients($recipients) as $rcpt) { - list($user, $host) = explode('@', $rcpt); - - $mx = $this->_getMx($host); - if (!$mx) { - $this->_error('no_mx', array('rcpt' => $rcpt)); - } - - $connected = false; - foreach ($mx as $mserver => $mpriority) { - $this->_smtp = new Net_SMTP($mserver, $this->_params['port'], $this->_params['mailname']); - - // configure the SMTP connection. - if ($this->_params['debug']) { - $this->_smtp->setDebug(true); - } - - // attempt to connect to the configured SMTP server. - $res = $this->_smtp->connect($this->_params['timeout']); - if ($res instanceof PEAR_Error) { - $this->_smtp = null; - continue; - } - - // connection established - if ($res) { - $connected = true; - break; - } - } - - if (!$connected) { - $this->_error('not_connected', array( - 'host' => implode(', ', array_keys($mx)), - 'port' => $this->_params['port'], - 'rcpt' => $rcpt - )); - } - - // Verify recipient - if ($this->_params['vrfy']) { - $res = $this->_smtp->vrfy($rcpt); - if ($res instanceof PEAR_Error) { - $this->_error('failed_vrfy_rcpt', array('rcpt' => $rcpt)); - } - } - - // mail from: - $args['verp'] = $this->_params['verp']; - $res = $this->_smtp->mailFrom($from, $args); - if ($res instanceof PEAR_Error) { - $this->_error('failed_set_from', array('from' => $from)); - } - - // rcpt to: - $res = $this->_smtp->rcptTo($rcpt); - if ($res instanceof PEAR_Error) { - $this->_error('failed_set_rcpt', array('rcpt' => $rcpt)); - } - - // Don't send anything in test mode - if ($this->_params['test']) { - $res = $this->_smtp->rset(); - if ($res instanceof PEAR_Error) { - $this->_error('failed_rset'); - } - - $this->_smtp->disconnect(); - $this->_smtp = null; - return; - } - - // Send data - $res = $this->_smtp->data($body, $textHeaders); - if ($res instanceof PEAR_Error) { - $this->_error('failed_send_data', array('rcpt' => $rcpt)); - } - - $this->_smtp->disconnect(); - $this->_smtp = null; - } - } - - /** - * Recieve MX records for a host. - * - * @param string $host Mail host. - * - * @return mixed Sorted MX list or false on error. - */ - protected function _getMx($host) - { - $mx = array(); - - if ($this->params['netdns']) { - $this->_loadNetDns(); - - $response = $this->_resolver->query($host, 'MX'); - if (!$response) { - return false; - } - - foreach ($response->answer as $rr) { - if ($rr->type == 'MX') { - $mx[$rr->exchange] = $rr->preference; - } - } - } else { - $mxHost = $mxWeight = array(); - - if (!getmxrr($host, $mxHost, $mxWeight)) { - return false; - } - - for ($i = 0; $i < count($mxHost); ++$i) { - $mx[$mxHost[$i]] = $mxWeight[$i]; - } - } - - asort($mx); - - return $mx; - } - - /** - * Initialize Net_DNS_Resolver. - */ - protected function _loadNetDns() - { - if (!$this->_resolver) { - if (!class_exists('Net_DNS_Resolver')) { - $this->_error('no_resolver'); - } - - $this->_resolver = new Net_DNS_Resolver(); - if ($this->_params['debug']) { - $this->_resolver->test = 1; - } - } - } - - /** - * Format error message. - * - * @param string $id Maps error ids to codes and message. - * @param array $info Optional information in associative array. - * - * @throws Horde_Mail_Exception - */ - protected function _error($id, $info = array()) - { - $msg = $this->_errorCode[$id]['msg']; - - // include info to messages - if (!empty($info)) { - $replace = $search = array(); - - foreach ($info as $key => $value) { - $search[] = '{' . strtoupper($key) . '}'; - $replace[] = $value; - } - - $msg = str_replace($search, $replace, $msg); - } - - throw new Horde_Mail_Exception($msg, $this->_errorCode[$id]['code']); - } - -} diff --git a/framework/Mail/lib/Horde/Mail/Transport.php b/framework/Mail/lib/Horde/Mail/Transport.php new file mode 100644 index 000000000..23edadfbf --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport.php @@ -0,0 +1,213 @@ + + * @author Michael Slusarz + * @copyright 1997-2010 Chuck Hagenbuch + * @copyright 2010 Michael Slusarz + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * Mail transport base class. + * + * @access public + * @version $Revision: 294747 $ + * @package Mail + */ +abstract class Horde_Mail_Transport +{ + /** + * Line terminator used for separating header lines. + * + * @var string + */ + public $sep = "\r\n"; + + /** + * Configuration parameters. + * + * @var array + */ + protected $_params = array(); + + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + abstract public function send($recipients, array $headers, $body); + + /** + * Take an array of mail headers and return a string containing text + * usable in sending a message. + * + * @param array $headers The array of headers to prepare, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array value + * is the header value (ie, 'test'). The header + * produced from those values would be 'Subject: + * test'. + * If the '_raw' key exists, the value of this key + * will be used as the exact text for sending the + * message. + * + * @return mixed Returns false if it encounters a bad address; otherwise + * returns an array containing two elements: Any From: + * address found in the headers, and the plain text version + * of the headers. + * @throws Horde_Mail_Exception + */ + public function prepareHeaders(array $headers) + { + $lines = array(); + $from = null; + + $parser = new Horde_Mail_Rfc822(); + + foreach ($headers as $key => $value) { + if (strcasecmp($key, 'From') === 0) { + $addresses = $parser->parseAddressList($value, array( + 'nest_groups' => false, + )); + $from = $addresses[0]->mailbox . '@' . $addresses[0]->host; + + // Reject envelope From: addresses with spaces. + if (strstr($from, ' ')) { + return false; + } + + $lines[] = $key . ': ' . $value; + } elseif (strcasecmp($key, 'Received') === 0) { + $received = array(); + if (!is_array($value)) { + $value = array($value); + } + + foreach ($value as $line) { + $received[] = $key . ': ' . $line; + } + + // Put Received: headers at the top. Spam detectors often + // flag messages with Received: headers after the Subject: + // as spam. + $lines = array_merge($received, $lines); + } else { + // If $value is an array (i.e., a list of addresses), convert + // it to a comma-delimited string of its elements (addresses). + if (is_array($value)) { + $value = implode(', ', $value); + } + $lines[] = $key . ': ' . $value; + } + } + + return array($from, isset($headers['_raw']) ? $headers['_raw'] : join($this->sep, $lines)); + } + + /** + * Take a set of recipients and parse them, returning an array of bare + * addresses (forward paths) that can be passed to sendmail or an SMTP + * server with the 'RCPT TO:' command. + * + * @param mixed Either a comma-separated list of recipients (RFC822 + * compliant), or an array of recipients, each RFC822 valid. + * + * @return array Forward paths (bare addresses). + * @throws Horde_Mail_Exception + */ + public function parseRecipients($recipients) + { + // if we're passed an array, assume addresses are valid and + // implode them before parsing. + if (is_array($recipients)) { + $recipients = implode(', ', $recipients); + } + + // Parse recipients, leaving out all personal info. This is + // for smtp recipients, etc. All relevant personal information + // should already be in the headers. + $parser = new Horde_Mail_Rfc822(); + $addresses = $parser->parseAddressList($recipients, array( + 'nest_groups' => false + )); + + $recipients = array(); + if (is_array($addresses)) { + foreach ($addresses as $ob) { + $recipients[] = $ob->mailbox . '@' . $ob->host; + } + } + + return $recipients; + } + + /** + * Sanitize an array of mail headers by removing any additional header + * strings present in a legitimate header's value. The goal of this + * filter is to prevent mail injection attacks. + * + * @param array $headers The associative array of headers to sanitize. + * + * @return array The sanitized headers. + */ + protected function _sanitizeHeaders($headers) + { + foreach (array_keys($headers) as $key) { + $headers[$key] = preg_replace('=((||0x0A/%0A|0x0D/%0D|\\n|\\r)\S).*=i', null, $headers[$key]); + } + + return $headers; + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Mail.php b/framework/Mail/lib/Horde/Mail/Transport/Mail.php new file mode 100644 index 000000000..a8300d38c --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Mail.php @@ -0,0 +1,148 @@ + + * @author Michael Slusarz + * @copyright 2010 Chuck Hagenbuch + * @copyright 2010 Michael Slusarz + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * Internal PHP-mail() interface. + * + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Mail extends Horde_Mail_Transport +{ + /** + * Constructor. + * + * @param array $params Additional parameters: + *
+     * 'args' - (string) Extra arguments for the mail() function.
+     * 
+ */ + public function __construct(array $params = array()) + { + $this->_params = array_merge($this->_params, $params); + + /* Because the mail() function may pass headers as command + * line arguments, we can't guarantee the use of the standard + * "\r\n" separator. Instead, we use the system's native line + * separator. */ + $this->sep = defined('PHP_EOL') + ? PHP_EOL + : (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; + } + + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + $headers = $this->_sanitizeHeaders($headers); + + // If we're passed an array of recipients, implode it. + if (is_array($recipients)) { + $recipients = array_map('trim', implode(',', $recipients)); + } + + $subject = ''; + + foreach (array_keys($headers) as $hdr) { + if (strcasecmp($hdr, 'Subject') === 0) { + // Get the Subject out of the headers array so that we can + // pass it as a separate argument to mail(). + $subject = $headers[$hdr]; + unset($headers[$hdr]); + } elseif (strcasecmp($hdr, 'To') === 0) { + // Remove the To: header. The mail() function will add its + // own To: header based on the contents of $recipients. + unset($headers[$hdr]); + } + } + + // Flatten the headers out. + list(, $text_headers) = $this->prepareHeaders($headers); + + // mail() requires a string for $body. If resource, need to convert + // to a string. + if (is_resource($body)) { + $body_str = ''; + rewind($body); + while (!feof($body)) { + $body_str .= fread($body, 8192); + } + $body = $body_str; + } + + // We only use mail()'s optional fifth parameter if the additional + // parameters have been provided and we're not running in safe mode. + if (empty($this->_params) || ini_get('safe_mode')) { + $result = mail($recipients, $subject, $body, $text_headers); + } else { + $result = mail($recipients, $subject, $body, $text_headers, isset($this->_params['args']) ? $this->_params['args'] : ''); + } + + // If the mail() function returned failure, we need to create an + // Exception and return it instead of the boolean result. + if ($result === false) { + throw new Horde_Mail_Exception('mail() returned failure.'); + } + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Mock.php b/framework/Mail/lib/Horde/Mail/Transport/Mock.php new file mode 100644 index 000000000..4b1de5d9d --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Mock.php @@ -0,0 +1,137 @@ + + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * Mock implementation, for testing. + * + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Mock extends Horde_Mail_Transport +{ + /** + * Array of messages that have been sent with the mock. + * + * @var array + */ + public $sentMessages = array(); + + /** + * Callback before sending mail. + * + * @var callback + */ + protected $_preSendCallback; + + /** + * Callback after sending mai. + * + * @var callback + */ + protected $_postSendCallback; + + /** + * Constructor. + * + * @param array Optional parameters: + *
+     * 'preSendCallback' - (callback) Called before an email would be sent.
+     * 'postSendCallback' - (callback) Called after an email would have been
+     *                      sent.
+     * 
+ */ + public function __construct(array $params = array()) + { + if (isset($params['preSendCallback']) && + is_callable($params['preSendCallback'])) { + $this->_preSendCallback = $params['preSendCallback']; + } + + if (isset($params['postSendCallback']) && + is_callable($params['postSendCallback'])) { + $this->_postSendCallback = $params['postSendCallback']; + } + } + + /** + * Send a message. Silently discards all mail. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + if ($this->_preSendCallback) { + call_user_func_array($this->_preSendCallback, array($this, $recipients, $headers, $body)); + } + + $headers = $this->_sanitizeHeaders($headers); + list(, $text_headers) = $this->prepareHeaders($headers); + + $this->sentMessages[] = array( + 'body' => $body, + 'headers' => $headers, + 'header_text' => $text_headers, + 'recipients' => $recipients + ); + + if ($this->_postSendCallback) { + call_user_func_array($this->_postSendCallback, array($this, $recipients, $headers, $body)); + } + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Null.php b/framework/Mail/lib/Horde/Mail/Transport/Null.php new file mode 100644 index 000000000..b39746213 --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Null.php @@ -0,0 +1,76 @@ + + * @copyright 2010 Phil Kernick + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * Null implementation of the mail transport interface. + * + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Null extends Horde_Mail_Transport +{ + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Sendmail.php b/framework/Mail/lib/Horde/Mail/Transport/Sendmail.php new file mode 100644 index 000000000..cf7f6246e --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Sendmail.php @@ -0,0 +1,246 @@ + + * @author Michael Slusarz + * @copyright 2010 Chuck Hagenbuch + * @copyright 2010 Michael Slusarz + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * Sendmail interface. + * + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Sendmail extends Horde_Mail_Transport +{ + /** + * Any extra command-line parameters to pass to the sendmail or + * sendmail wrapper binary. + * + * @var string + */ + protected $_sendmailArgs = '-i'; + + /** + * The location of the sendmail or sendmail wrapper binary on the + * filesystem. + * + * @var string + */ + protected $_sendmailPath = '/usr/sbin/sendmail'; + + /** + * Constructor. + * + * @param array $params Additional parameters: + *
+     * 'sendmail_args' - (string) Any extra parameters to pass to the sendmail
+     *                   or sendmail wrapper binary.
+     *                   DEFAULT: -i
+     * 'sendmail_path' - (string) The location of the sendmail binary on the
+     *                   filesystem.
+     *                   DEFAULT: /usr/sbin/sendmail
+     * 
+ */ + public function __construct(array $params = array()) + { + if (isset($params['sendmail_args'])) { + $this->_sendmailArgs = $params['sendmail_args']; + } + + if (isset($params['sendmail_path'])) { + $this->_sendmailPath = $params['sendmail_path']; + } + + /* Because we need to pass message headers to the sendmail program on + * the commandline, we can't guarantee the use of the standard "\r\n" + * separator. Instead, we use the system's native line separator. */ + $this->sep = defined('PHP_EOL') + ? PHP_EOL + : (strpos(PHP_OS, 'WIN') === false) ? "\n" : "\r\n"; + } + + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + $recipients = implode(' ', array_map('escapeshellarg', $this->parseRecipients($recipients))); + + $headers = $this->_sanitizeHeaders($headers); + list($from, $text_headers) = $this->prepareHeaders($headers); + + /* Since few MTAs are going to allow this header to be forged + * unless it's in the MAIL FROM: exchange, we'll use Return-Path + * instead of From: if it's set. */ + foreach (array_keys($headers) as $hdr) { + if (strcasecmp($hdr, 'Return-Path') === 0) { + $from = $headers[$hdr]; + break; + } + } + + if (!strlen($from)) { + throw new Horde_Mail_Exception('No From address given.'); + } elseif ((strpos($from, ' ') !== false) || + (strpos($from, ';') !== false) || + (strpos($from, '&') !== false) || + (strpos($from, '`') !== false)) { + throw new Horde_Mail_Exception('From address specified with dangerous characters.'); + } + + $mail = @popen($this->_sendmailPath . (empty($this->_sendmailArgs) ? '' : ' ' . $this->_sendmailargs) . ' -f' . escapeshellarg($from) . ' -- ' . $recipients, 'w'); + if (!$mail) { + throw new Horde_Mail_Exception('Failed to open sendmail [' . $this->_sendmailPath . '] for execution.'); + } + + // Write the headers following by two newlines: one to end the headers + // section and a second to separate the headers block from the body. + fputs($mail, $text_headers . $this->sep . $this->sep); + + if (is_resource($body)) { + rewind($body); + while (!feof($body)) { + fputs($mail, fread($body, 8192)); + } + } else { + fputs($mail, $body); + } + $result = pclose($mail); + + if (!$result) { + return; + } + + switch ($result) { + case 64: // EX_USAGE + $msg = 'command line usage error'; + break; + + case 65: // EX_DATAERR + $msg = 'data format error'; + break; + + case 66: // EX_NOINPUT + $msg = 'cannot open input'; + break; + + case 67: // EX_NOUSER + $msg = 'addressee unknown'; + break; + + case 68: // EX_NOHOST + $msg = 'host name unknown'; + break; + + case 69: // EX_UNAVAILABLE + $msg = 'service unavailable'; + break; + + case 70: // EX_SOFTWARE + $msg = 'internal software error'; + break; + + case 71: // EX_OSERR + $msg = 'system error'; + break; + + case 72: // EX_OSFILE + $msg = 'critical system file missing'; + break; + + case 73: // EX_CANTCREAT + $msg = 'cannot create output file'; + break; + + case 74: // EX_IOERR + $msg = 'input/output error'; + + case 75: // EX_TEMPFAIL + $msg = 'temporary failure'; + break; + + case 76: // EX_PROTOCOL + $msg = 'remote error in protocol'; + break; + + case 77: // EX_NOPERM + $msg = 'permission denied'; + break; + + case 77: // EX_NOPERM + $msg = 'permission denied'; + break; + + case 78: // EX_CONFIG + $msg = 'configuration error'; + break; + + case 79: // EX_NOTFOUND + $msg = 'entry not found'; + break; + + default: + $msg = 'unknown error'; + break; + } + + throw new Horde_Mail_Exception('sendmail: ' . $msg . ' (' . $result . ')', $result); + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Smtp.php b/framework/Mail/lib/Horde/Mail/Transport/Smtp.php new file mode 100644 index 000000000..a43b2c256 --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Smtp.php @@ -0,0 +1,355 @@ + + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * SMTP implementation. + * + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Smtp extends Horde_Mail_Transport +{ + /* Error: Failed to create a Net_SMTP object */ + const ERROR_CREATE = 10000; + + /* Error: Failed to connect to SMTP server */ + const ERROR_CONNECT = 10001; + + /* Error: SMTP authentication failure */ + const ERROR_AUTH = 10002; + + /* Error: No From: address has been provided */ + const ERROR_FROM = 10003; + + /* Error: Failed to set sender */ + const ERROR_SENDER = 10004; + + /* Error: Failed to add recipient */ + const ERROR_RECIPIENT = 10005; + + /* Error: Failed to send data */ + const ERROR_DATA = 10006; + + /** + * The SMTP greeting. + * + * @var string + */ + public $greeting = null; + + /** + * The SMTP queued response. + * + * @var string + */ + public $queuedAs = null; + + /** + * SMTP connection object. + * + * @var Net_SMTP + */ + protected $_smtp = null; + + /** + * The list of service extension parameters to pass to the Net_SMTP + * mailFrom() command. + * + * @var array + */ + protected $_extparams = array(); + + /** + * Constructor. + * + * @param array $params Additional parameters: + *
+     * 'auth' - (mixed) SMTP authentication.
+     *          This value may be set to true, false or the name of a specific
+     *          authentication method.
+     *          If the value is set to true, the Net_SMTP package will attempt
+     *          to use the best authentication method advertised by the remote
+     *          SMTP server.
+     *          DEFAULT: false.
+     * 'debug' - (boolean) Activate SMTP debug mode?
+     *           DEFAULT: false
+     * 'host' - (string) The server to connect to.
+     *          DEFAULT: localhost
+     * 'localhost' - (string) Hostname or domain that will be sent to the
+     *               remote SMTP server in the HELO / EHLO message.
+     *               DEFAULT: localhost
+     * 'password' - (string) The password to use for SMTP auth.
+     *              DEFAULT: NONE
+     * 'persist' - (boolean) Should the SMTP connection persist?
+     *             DEFAULT: false
+     * 'pipelining' - (boolean) Use SMTP command pipelining.
+     *                Use SMTP command pipelining (specified in RFC 2920) if
+     *                the SMTP server supports it. This speeds up delivery
+     *                over high-latency connections.
+     *                DEFAULT: false (use default value from Net_SMTP)
+     * 'port' - (integer) The port to connect to.
+     *          DEFAULT: 25
+     * 'timeout' - (integer) The SMTP connection timeout.
+     *             DEFAULT: NONE
+     * 'username' - (string) The username to use for SMTP auth.
+     *              DEFAULT: NONE
+     * 
+ */ + public function __construct(array $params = array()) + { + $this->_params = array_merge(array( + 'auth' => false, + 'debug' => false, + 'host' => 'localhost', + 'localhost' => 'localhost', + 'password' => '', + 'persist' => false, + 'pipelining' => false, + 'port' => 25, + 'timeout' => null, + 'username' => '' + ), $params); + + /* Destructor implementation to ensure that we disconnect from any + * potentially-alive persistent SMTP connections. */ + register_shutdown_function(array($this, 'disconnect')); + } + + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + /* If we don't already have an SMTP object, create one. */ + $this->getSMTPObject(); + + $headers = $this->_sanitizeHeaders($headers); + + try { + list($from, $textHeaders) = $this->prepareHeaders($headers); + } catch (Horde_Mail_Exception $e) { + $this->_smtp->rset(); + throw $e; + } + + /* Since few MTAs are going to allow this header to be forged unless + * it's in the MAIL FROM: exchange, we'll use Return-Path instead of + * From: if it's set. */ + foreach (array_keys($headers) as $hdr) { + if (strcasecmp($hdr, 'Return-Path') === 0) { + $from = $headers[$hdr]; + break; + } + } + + if (!strlen($from)) { + $this->_smtp->rset(); + throw new Horde_Mail_Exception('No From: address has been provided', self::ERROR_FROM); + } + + $params = ''; + foreach ($this->_extparams as $key => $val) { + $params .= ' ' . $key . (is_null($val) ? '' : '=' . $val); + } + + $res = $this->_smtp->mailFrom($from, ltrim($params)); + if ($res instanceof PEAR_Error) { + $this->_smtp->rset(); + $this->_error("Failed to set sender: $from", $res, self::ERROR_SENDER); + } + + try { + $recipients = $this->parseRecipients($recipients); + } catch (Horde_Mail_Exception $e) { + $this->_smtp->rset(); + throw $e; + } + + foreach ($recipients as $recipient) { + $res = $this->_smtp->rcptTo($recipient); + if ($res instanceof PEAR_Error) { + $this->_smtp->rset(); + $this->_error("Failed to add recipient: $recipient", $res, self::ERROR_RECIPIENT); + } + } + + /* Send the message's headers and the body as SMTP data. */ + $res = $this->_smtp->data($body, $textHeaders); + list(,$args) = $this->_smtp->getResponse(); + + if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { + $this->queuedAs = $queued[1]; + } + + /* We need the greeting; from it we can extract the authorative name + * of the mail server we've really connected to. Ideal if we're + * connecting to a round-robin of relay servers and need to track + * which exact one took the email */ + $this->greeting = $this->_smtp->getGreeting(); + + if ($res instanceof PEAR_Error) { + $this->_smtp->rset(); + $this->_error('Failed to send data', $res, self::ERROR_DATA); + } + + /* If persistent connections are disabled, destroy our SMTP object. */ + if ($this->_params['persist']) { + $this->disconnect(); + } + } + + /** + * Connect to the SMTP server by instantiating a Net_SMTP object. + * + * @return Net_SMTP The SMTP object. + * @throws Horde_Mail_Exception + */ + public function getSMTPObject() + { + if ($this->_smtp) { + return $this->_smtp; + } + + $this->_smtp = new Net_SMTP( + $this->_params['host'], + $this->_params['port'], + $this->_params['localhost'] + ); + + /* If we still don't have an SMTP object at this point, fail. */ + if (!($this->_smtp instanceof Net_SMTP)) { + throw new Horde_Mail_Exception('Failed to create a Net_SMTP object', self::ERROR_CREATE); + } + + /* Configure the SMTP connection. */ + if ($this->_params['debug']) { + $this->_smtp->setDebug(true); + } + + /* Attempt to connect to the configured SMTP server. */ + $res = $this->_smtp->connect($this->_params['timeout']); + if ($res instanceof PEAR_Error) { + $this->_error('Failed to connect to ' . $this->_params['host'] . ':' . $this->_params['port'], $res, self::ERROR_CONNECT); + } + + /* Attempt to authenticate if authentication has been enabled. */ + if ($this->_params['auth']) { + $method = is_string($this->_params['auth']) + ? $this->_params['auth'] + : ''; + + $res = $this->_smtp->auth($this->_params['username'], $this->_params['password'], $method); + if ($res instanceof PEAR_Error) { + $this->_smtp->rset(); + $this->_error("$method authentication failure", $res, self::ERROR_AUTH); + } + } + + return $this->_smtp; + } + + /** + * Add parameter associated with a SMTP service extension. + * + * @param string $keyword Extension keyword. + * @param string $value Any value the keyword needs. + */ + public function addServiceExtensionParameter($keyword, $value = null) + { + $this->_extparams[$keyword] = $value; + } + + /** + * Disconnect and destroy the current SMTP connection. + * + * @return boolean True if the SMTP connection no longer exists. + */ + public function disconnect() + { + /* If we have an SMTP object, disconnect and destroy it. */ + if (is_object($this->_smtp) && $this->_smtp->disconnect()) { + $this->_smtp = null; + } + + /* We are disconnected if we no longer have an SMTP object. */ + return ($this->_smtp === null); + } + + /** + * Build a standardized string describing the current SMTP error. + * + * @param string $text Custom string describing the error context. + * @param PEAR_Error $error PEAR_Error object. + * @param integer $e_code Error code. + * + * @throws Horde_Mail_Exception + */ + protected function _error($text, $error, $e_code) + { + /* Split the SMTP response into a code and a response string. */ + list($code, $response) = $this->_smtp->getResponse(); + + /* Build our standardized error string. */ + throw new Horde_Mail_Exception($text . ' [SMTP: ' . $error->getMessage() . " (code: $code, response: $response)]", $e_code); + } +} diff --git a/framework/Mail/lib/Horde/Mail/Transport/Smtpmx.php b/framework/Mail/lib/Horde/Mail/Transport/Smtpmx.php new file mode 100644 index 000000000..235795c1f --- /dev/null +++ b/framework/Mail/lib/Horde/Mail/Transport/Smtpmx.php @@ -0,0 +1,388 @@ + + * @copyright 2010 gERD Schaufelberger + * @license http://opensource.org/licenses/bsd-license.php New BSD License + */ + +/** + * SMTP MX implementation. + * + * @author gERD Schaufelberger + * @category Horde + * @package Mail + */ +class Horde_Mail_Transport_Smtpmx extends Horde_Mail_Transport +{ + /** + * SMTP connection object. + * + * @var Net_SMTP + */ + protected $_smtp = null; + + /** + * Net_DNS_Resolver object. + * + * @var Net_DNS_Resolver + */ + protected $_resolver; + + /** + * Internal error codes. + * Translate internal error identifier to human readable messages. + * + * @var array + */ + protected $_errorCode = array( + 'not_connected' => array( + 'code' => 1, + 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.' + ), + 'failed_vrfy_rcpt' => array( + 'code' => 2, + 'msg' => 'Recipient "{RCPT}" could not be veryfied.' + ), + 'failed_set_from' => array( + 'code' => 3, + 'msg' => 'Failed to set sender: {FROM}.' + ), + 'failed_set_rcpt' => array( + 'code' => 4, + 'msg' => 'Failed to set recipient: {RCPT}.' + ), + 'failed_send_data' => array( + 'code' => 5, + 'msg' => 'Failed to send mail to: {RCPT}.' + ), + 'no_from' => array( + 'code' => 5, + 'msg' => 'No from address has be provided.' + ), + 'send_data' => array( + 'code' => 7, + 'msg' => 'Failed to create Net_SMTP object.' + ), + 'no_mx' => array( + 'code' => 8, + 'msg' => 'No MX-record for {RCPT} found.' + ), + 'no_resolver' => array( + 'code' => 9, + 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"' + ), + 'failed_rset' => array( + 'code' => 10, + 'msg' => 'RSET command failed, SMTP-connection corrupt.' + ) + ); + + /** + * Constructor. + * + * @param array $params Additional options: + *
+     * 'debug' - (boolean) Activate SMTP and Net_DNS debug mode?
+     *           DEFAULT: false
+     * 'mailname' - (string) The name of the local mail system (a valid
+     *              hostname which matches the reverse lookup)
+     *              DEFAULT: Auto-determined
+     * 'netdns' - (boolean) Use PEAR:Net_DNS (true) or the PHP builtin
+     *            getmxrr().
+     *            DEFAULT: true
+     * 'port' - (integer) Port.
+     *          DEFAULT: Auto-determined
+     * 'test' - (boolean) Activate test mode?
+     *          DEFAULT: false
+     * 'timeout' - (integer) The SMTP connection timeout (in seconds).
+     *             DEFAULT: 10
+     * 'verp' - (boolean) Whether to use VERP.
+     *          If not a boolean, the string value will be used as the VERP
+     *          separators.
+     *          DEFAULT: false
+     * 'vrfy' - (boolean) Whether to use VRFY.
+     *          DEFAULT: false
+     * 
+ */ + public function __construct(array $params = array()) + { + /* Try to find a valid mailname. */ + if (!isset($params['mailname']) && function_exists('posix_uname')) { + $uname = posix_uname(); + $params['mailname'] = $uname['nodename']; + } + + if (!isset($params['port'])) { + $params['port'] = getservbyname('smtp', 'tcp'); + } + + $this->_params = array_merge(array( + 'debug' => false, + 'mailname' => 'localhost', + 'netdns' => true, + 'port' => 25, + 'test' => false, + 'timeout' => 10, + 'verp' => false, + 'vrfy' => false + ), $params); + } + + /** + * Destructor implementation to ensure that we disconnect from any + * potentially-alive persistent SMTP connections. + */ + public function __destruct() + { + if (is_object($this->_smtp)) { + $this->_smtp->disconnect(); + $this->_smtp = null; + } + } + + /** + * Send a message. + * + * @param mixed $recipients Either a comma-seperated list of recipients + * (RFC822 compliant), or an array of + * recipients, each RFC822 valid. This may + * contain recipients not specified in the + * headers, for Bcc:, resending messages, etc. + * @param array $headers The headers to send with the mail, in an + * associative array, where the array key is the + * header name (ie, 'Subject'), and the array + * value is the header value (ie, 'test'). The + * header produced from those values would be + * 'Subject: test'. + * If the '_raw' key exists, the value of this + * key will be used as the exact text for + * sending the message. + * @param mixed $body The full text of the message body, including + * any Mime parts, etc. Either a string or a + * stream resource. + * + * @throws Horde_Mail_Exception + */ + public function send($recipients, array $headers, $body) + { + $headers = $this->_sanitizeHeaders($headers); + + // Prepare headers + list($from, $textHeaders) = $this->prepareHeaders($headers); + + // Use 'Return-Path' if possible + foreach (array_keys($headers) as $hdr) { + if (strcasecmp($hdr, 'Return-Path') === 0) { + $from = $headers['Return-Path']; + break; + } + } + + if (!strlen($from)) { + $this->_error('no_from'); + } + + // Prepare recipients + foreach ($this->parseRecipients($recipients) as $rcpt) { + list($user, $host) = explode('@', $rcpt); + + $mx = $this->_getMx($host); + if (!$mx) { + $this->_error('no_mx', array('rcpt' => $rcpt)); + } + + $connected = false; + foreach ($mx as $mserver => $mpriority) { + $this->_smtp = new Net_SMTP($mserver, $this->_params['port'], $this->_params['mailname']); + + // configure the SMTP connection. + if ($this->_params['debug']) { + $this->_smtp->setDebug(true); + } + + // attempt to connect to the configured SMTP server. + $res = $this->_smtp->connect($this->_params['timeout']); + if ($res instanceof PEAR_Error) { + $this->_smtp = null; + continue; + } + + // connection established + if ($res) { + $connected = true; + break; + } + } + + if (!$connected) { + $this->_error('not_connected', array( + 'host' => implode(', ', array_keys($mx)), + 'port' => $this->_params['port'], + 'rcpt' => $rcpt + )); + } + + // Verify recipient + if ($this->_params['vrfy']) { + $res = $this->_smtp->vrfy($rcpt); + if ($res instanceof PEAR_Error) { + $this->_error('failed_vrfy_rcpt', array('rcpt' => $rcpt)); + } + } + + // mail from: + $args['verp'] = $this->_params['verp']; + $res = $this->_smtp->mailFrom($from, $args); + if ($res instanceof PEAR_Error) { + $this->_error('failed_set_from', array('from' => $from)); + } + + // rcpt to: + $res = $this->_smtp->rcptTo($rcpt); + if ($res instanceof PEAR_Error) { + $this->_error('failed_set_rcpt', array('rcpt' => $rcpt)); + } + + // Don't send anything in test mode + if ($this->_params['test']) { + $res = $this->_smtp->rset(); + if ($res instanceof PEAR_Error) { + $this->_error('failed_rset'); + } + + $this->_smtp->disconnect(); + $this->_smtp = null; + return; + } + + // Send data + $res = $this->_smtp->data($body, $textHeaders); + if ($res instanceof PEAR_Error) { + $this->_error('failed_send_data', array('rcpt' => $rcpt)); + } + + $this->_smtp->disconnect(); + $this->_smtp = null; + } + } + + /** + * Recieve MX records for a host. + * + * @param string $host Mail host. + * + * @return mixed Sorted MX list or false on error. + */ + protected function _getMx($host) + { + $mx = array(); + + if ($this->params['netdns']) { + $this->_loadNetDns(); + + $response = $this->_resolver->query($host, 'MX'); + if (!$response) { + return false; + } + + foreach ($response->answer as $rr) { + if ($rr->type == 'MX') { + $mx[$rr->exchange] = $rr->preference; + } + } + } else { + $mxHost = $mxWeight = array(); + + if (!getmxrr($host, $mxHost, $mxWeight)) { + return false; + } + + for ($i = 0; $i < count($mxHost); ++$i) { + $mx[$mxHost[$i]] = $mxWeight[$i]; + } + } + + asort($mx); + + return $mx; + } + + /** + * Initialize Net_DNS_Resolver. + */ + protected function _loadNetDns() + { + if (!$this->_resolver) { + if (!class_exists('Net_DNS_Resolver')) { + $this->_error('no_resolver'); + } + + $this->_resolver = new Net_DNS_Resolver(); + if ($this->_params['debug']) { + $this->_resolver->test = 1; + } + } + } + + /** + * Format error message. + * + * @param string $id Maps error ids to codes and message. + * @param array $info Optional information in associative array. + * + * @throws Horde_Mail_Exception + */ + protected function _error($id, $info = array()) + { + $msg = $this->_errorCode[$id]['msg']; + + // include info to messages + if (!empty($info)) { + $replace = $search = array(); + + foreach ($info as $key => $value) { + $search[] = '{' . strtoupper($key) . '}'; + $replace[] = $value; + } + + $msg = str_replace($search, $replace, $msg); + } + + throw new Horde_Mail_Exception($msg, $this->_errorCode[$id]['code']); + } +} diff --git a/framework/Mail/package.xml b/framework/Mail/package.xml index 835cd21c8..af2099a88 100644 --- a/framework/Mail/package.xml +++ b/framework/Mail/package.xml @@ -38,15 +38,17 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + + + + + + + - - - - - - + @@ -84,18 +86,16 @@ http://pear.php.net/dtd/package-2.0.xsd"> - + + + + + + - - - - - - + - - diff --git a/framework/Mime/lib/Horde/Mime/Part.php b/framework/Mime/lib/Horde/Mime/Part.php index 3cf296fae..66ad8ea96 100644 --- a/framework/Mime/lib/Horde/Mime/Part.php +++ b/framework/Mime/lib/Horde/Mime/Part.php @@ -1471,17 +1471,13 @@ class Horde_Mime_Part * @param string $email The address list to send to. * @param Horde_Mime_Headers $headers The Horde_Mime_Headers object * holding this message's headers. - * @param Horde_Mail_Driver $mailer A Horde_Mail_Driver object. + * @param Horde_Mail_Transport $mailer A Horde_Mail_Transport object. * * @throws Horde_Mime_Exception * @throws InvalidArgumentException */ - public function send($email, $headers, $mailer) + public function send($email, $headers, Horde_Mail_Transport $mailer) { - if (!($mailer instanceof Horde_Mail_Driver)) { - throw new InvalidArgumentException('Invalid Horde_Mail_Driver object passed to send().'); - } - $old_basepart = $this->_basepart; $this->_basepart = true; diff --git a/imp/lib/Injector/Binder/Mail.php b/imp/lib/Injector/Binder/Mail.php index 92c8a83e3..265b38b6e 100644 --- a/imp/lib/Injector/Binder/Mail.php +++ b/imp/lib/Injector/Binder/Mail.php @@ -41,7 +41,12 @@ class IMP_Injector_Binder_Mail implements Horde_Injector_Binder $params['password'] = $imap_ob->getParam('password'); } - return Horde_Mail::factory($GLOBALS['conf']['mailer']['type'], $params); + $transport = $GLOBALS['conf']['mailer']['type']; + $class = 'Horde_Mail_Transport_' . ucfirst($transport); + if (class_exists($class)) { + return new $class($params); + } + throw new Horde_Exception('Unable to find class for transport ' . $transport); } /** @@ -50,5 +55,4 @@ class IMP_Injector_Binder_Mail implements Horde_Injector_Binder { return false; } - }