2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
7 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
13 * Copyright (c) 2010, gERD Schaufelberger
14 * All rights reserved.
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
20 * o Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
22 * o Redistributions in binary form must reproduce the above copyright
23 * notice, this list of conditions and the following disclaimer in the
24 * documentation and/or other materials provided with the distribution.
25 * o The names of the authors may not be used to endorse or promote
26 * products derived from this software without specific prior written
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
30 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
31 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
32 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
33 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
34 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
35 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
36 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
37 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
38 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
39 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 * @package Mail_smtpmx
43 * @author gERD Schaufelberger <gerd@php-tools.net>
44 * @copyright 2010 gERD Schaufelberger
45 * @license http://opensource.org/licenses/bsd-license.php New BSD License
46 * @version CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $
47 * @link http://pear.php.net/package/Mail/
50 require_once 'Net/SMTP.php';
53 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
57 * @author gERD Schaufelberger <gerd@php-tools.net>
59 * @version $Revision: 294747 $
61 class Mail_smtpmx extends Mail {
64 * SMTP connection object.
72 * The port the SMTP server is on.
74 * @see getservicebyname()
79 * Hostname or domain that will be sent to the remote SMTP server in the
80 * HELO / EHLO message.
85 var $mailname = 'localhost';
88 * SMTP connection timeout value. NULL indicates no timeout.
95 * use either PEAR:Net_DNS or getmxrr
99 var $withNetDns = true;
102 * PEAR:Net_DNS_Resolver
109 * Whether to use VERP or not. If not a boolean, the string value
110 * will be used as the VERP separators.
112 * @var mixed boolean or string
117 * Whether to use VRFY or not.
124 * Switch to test mode - don't send emails for real
126 * @var boolean $debug
131 * Turn on Net_SMTP debugging?
133 * @var boolean $peardebug
138 * internal error codes
140 * translate internal error identifier to PEAR-Error codes and human
143 * @var boolean $debug
144 * @todo as I need unique error-codes to identify what exactly went wrond
145 * I did not use intergers as it should be. Instead I added a "namespace"
146 * for each code. This avoids conflicts with error codes from different
147 * classes. How can I use unique error codes and stay conform with PEAR?
149 var $errorCode = array(
150 'not_connected' => array(
152 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
154 'failed_vrfy_rcpt' => array(
156 'msg' => 'Recipient "{RCPT}" could not be veryfied.'
158 'failed_set_from' => array(
160 'msg' => 'Failed to set sender: {FROM}.'
162 'failed_set_rcpt' => array(
164 'msg' => 'Failed to set recipient: {RCPT}.'
166 'failed_send_data' => array(
168 'msg' => 'Failed to send mail to: {RCPT}.'
172 'msg' => 'No from address has be provided.'
174 'send_data' => array(
176 'msg' => 'Failed to create Net_SMTP object.'
180 'msg' => 'No MX-record for {RCPT} found.'
182 'no_resolver' => array(
184 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
186 'failed_rset' => array(
188 'msg' => 'RSET command failed, SMTP-connection corrupt.'
195 * Instantiates a new Mail_smtp:: object based on the parameters
196 * passed in. It looks for the following parameters:
197 * mailname The name of the local mail system (a valid hostname which matches the reverse lookup)
198 * port smtp-port - the default comes from getservicebyname() and should work fine
199 * timeout The SMTP connection timeout. Defaults to 30 seconds.
200 * vrfy Whether to use VRFY or not. Defaults to false.
201 * verp Whether to use VERP or not. Defaults to false.
202 * test Activate test mode? Defaults to false.
203 * debug Activate SMTP and Net_DNS debug mode? Defaults to false.
204 * netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
206 * If a parameter is present in the $params array, it replaces the
210 * @param array Hash containing any parameters different from the
212 * @see _Mail_smtpmx()
214 function __construct($params)
216 if (isset($params['mailname'])) {
217 $this->mailname = $params['mailname'];
219 // try to find a valid mailname
220 if (function_exists('posix_uname')) {
221 $uname = posix_uname();
222 $this->mailname = $uname['nodename'];
227 if (isset($params['port'])) {
228 $this->_port = $params['port'];
230 $this->_port = getservbyname('smtp', 'tcp');
233 if (isset($params['timeout'])) $this->timeout = $params['timeout'];
234 if (isset($params['verp'])) $this->verp = $params['verp'];
235 if (isset($params['test'])) $this->test = $params['test'];
236 if (isset($params['peardebug'])) $this->test = $params['peardebug'];
237 if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
241 * Constructor wrapper for PHP4
244 * @param array Hash containing any parameters different from the defaults
247 function Mail_smtpmx($params)
249 $this->__construct($params);
250 register_shutdown_function(array(&$this, '__destruct'));
254 * Destructor implementation to ensure that we disconnect from any
255 * potentially-alive persistent SMTP connections.
257 function __destruct()
259 if (is_object($this->_smtp)) {
260 $this->_smtp->disconnect();
266 * Implements Mail::send() function using SMTP direct delivery
269 * @param mixed $recipients in RFC822 style or array
270 * @param array $headers The array of headers to send with the mail.
271 * @param string $body The full text of the message body,
272 * @return mixed Returns true on success, or a PEAR_Error
274 function send($recipients, $headers, $body)
276 if (!is_array($headers)) {
277 return PEAR::raiseError('$headers must be an array');
280 $result = $this->_sanitizeHeaders($headers);
281 if (is_a($result, 'PEAR_Error')) {
286 $headerElements = $this->prepareHeaders($headers);
287 if (is_a($headerElements, 'PEAR_Error')) {
288 return $headerElements;
290 list($from, $textHeaders) = $headerElements;
292 // use 'Return-Path' if possible
293 if (!empty($headers['Return-Path'])) {
294 $from = $headers['Return-Path'];
297 return $this->_raiseError('no_from');
300 // Prepare recipients
301 $recipients = $this->parseRecipients($recipients);
302 if (is_a($recipients, 'PEAR_Error')) {
306 foreach ($recipients as $rcpt) {
307 list($user, $host) = explode('@', $rcpt);
309 $mx = $this->_getMx($host);
310 if (is_a($mx, 'PEAR_Error')) {
315 $info = array('rcpt' => $rcpt);
316 return $this->_raiseError('no_mx', $info);
320 foreach ($mx as $mserver => $mpriority) {
321 $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
323 // configure the SMTP connection.
325 $this->_smtp->setDebug(true);
328 // attempt to connect to the configured SMTP server.
329 $res = $this->_smtp->connect($this->timeout);
330 if (is_a($res, 'PEAR_Error')) {
335 // connection established
344 'host' => implode(', ', array_keys($mx)),
345 'port' => $this->port,
348 return $this->_raiseError('not_connected', $info);
353 $res = $this->_smtp->vrfy($rcpt);
354 if (is_a($res, 'PEAR_Error')) {
355 $info = array('rcpt' => $rcpt);
356 return $this->_raiseError('failed_vrfy_rcpt', $info);
361 $args['verp'] = $this->verp;
362 $res = $this->_smtp->mailFrom($from, $args);
363 if (is_a($res, 'PEAR_Error')) {
364 $info = array('from' => $from);
365 return $this->_raiseError('failed_set_from', $info);
369 $res = $this->_smtp->rcptTo($rcpt);
370 if (is_a($res, 'PEAR_Error')) {
371 $info = array('rcpt' => $rcpt);
372 return $this->_raiseError('failed_set_rcpt', $info);
375 // Don't send anything in test mode
377 $result = $this->_smtp->rset();
378 $res = $this->_smtp->rset();
379 if (is_a($res, 'PEAR_Error')) {
380 return $this->_raiseError('failed_rset');
383 $this->_smtp->disconnect();
389 $res = $this->_smtp->data("$textHeaders\r\n$body");
390 if (is_a($res, 'PEAR_Error')) {
391 $info = array('rcpt' => $rcpt);
392 return $this->_raiseError('failed_send_data', $info);
395 $this->_smtp->disconnect();
403 * Recieve mx rexords for a spciefied host
408 * @param string $host mail host
409 * @return mixed sorted
411 function _getMx($host)
415 if ($this->withNetDns) {
416 $res = $this->_loadNetDns();
417 if (is_a($res, 'PEAR_Error')) {
421 $response = $this->resolver->query($host, 'MX');
426 foreach ($response->answer as $rr) {
427 if ($rr->type == 'MX') {
428 $mx[$rr->exchange] = $rr->preference;
435 if (!getmxrr($host, $mxHost, $mxWeight)) {
438 for ($i = 0; $i < count($mxHost); ++$i) {
439 $mx[$mxHost[$i]] = $mxWeight[$i];
448 * initialize PEAR:Net_DNS_Resolver
451 * @return boolean true on success
453 function _loadNetDns()
455 if (is_object($this->resolver)) {
459 if (!include_once 'Net/DNS.php') {
460 return $this->_raiseError('no_resolver');
463 $this->resolver = new Net_DNS_Resolver();
465 $this->resolver->test = 1;
472 * raise standardized error
474 * include additional information in error message
477 * @param string $id maps error ids to codes and message
478 * @param array $info optional information in associative array
481 function _raiseError($id, $info = array())
483 $code = $this->errorCode[$id]['code'];
484 $msg = $this->errorCode[$id]['msg'];
486 // include info to messages
491 foreach ($info as $key => $value) {
492 array_push($search, '{' . strtoupper($key) . '}');
493 array_push($replace, $value);
496 $msg = str_replace($search, $replace, $msg);
499 return PEAR::raiseError($msg, $code);