2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
7 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
11 * LICENSE: This source file is subject to version 3.0 of the PHP license
12 * that is available through the world-wide-web at the following URI:
13 * http://www.php.net/license/3_0.txt. If you did not receive a copy of
14 * the PHP License and are unable to obtain it through the web, please
15 * send a note to license@php.net so we can mail you a copy immediately.
18 * @package Mail_smtpmx
19 * @author gERD Schaufelberger <gerd@php-tools.net>
20 * @copyright 1997-2005 The PHP Group
21 * @license http://www.php.net/license/3_0.txt PHP License 3.0
22 * @version CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $
26 require_once 'Net/SMTP.php';
29 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
33 * @author gERD Schaufelberger <gerd@php-tools.net>
35 * @version $Revision: 1.2 $
37 class Mail_smtpmx extends Mail {
40 * SMTP connection object.
48 * The port the SMTP server is on.
50 * @see getservicebyname()
55 * Hostname or domain that will be sent to the remote SMTP server in the
56 * HELO / EHLO message.
61 var $mailname = 'localhost';
64 * SMTP connection timeout value. NULL indicates no timeout.
71 * use either PEAR:Net_DNS or getmxrr
75 var $withNetDns = true;
78 * PEAR:Net_DNS_Resolver
85 * Whether to use VERP or not. If not a boolean, the string value
86 * will be used as the VERP separators.
88 * @var mixed boolean or string
93 * Whether to use VRFY or not.
100 * Switch to test mode - don't send emails for real
102 * @var boolean $debug
107 * Turn on Net_SMTP debugging?
109 * @var boolean $peardebug
114 * internal error codes
116 * translate internal error identifier to PEAR-Error codes and human
119 * @var boolean $debug
120 * @todo as I need unique error-codes to identify what exactly went wrond
121 * I did not use intergers as it should be. Instead I added a "namespace"
122 * for each code. This avoids conflicts with error codes from different
123 * classes. How can I use unique error codes and stay conform with PEAR?
125 var $errorCode = array(
126 'not_connected' => array(
128 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
130 'failed_vrfy_rcpt' => array(
132 'msg' => 'Recipient "{RCPT}" could not be veryfied.'
134 'failed_set_from' => array(
136 'msg' => 'Failed to set sender: {FROM}.'
138 'failed_set_rcpt' => array(
140 'msg' => 'Failed to set recipient: {RCPT}.'
142 'failed_send_data' => array(
144 'msg' => 'Failed to send mail to: {RCPT}.'
148 'msg' => 'No from address has be provided.'
150 'send_data' => array(
152 'msg' => 'Failed to create Net_SMTP object.'
156 'msg' => 'No MX-record for {RCPT} found.'
158 'no_resolver' => array(
160 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
162 'failed_rset' => array(
164 'msg' => 'RSET command failed, SMTP-connection corrupt.'
171 * Instantiates a new Mail_smtp:: object based on the parameters
172 * passed in. It looks for the following parameters:
173 * mailname The name of the local mail system (a valid hostname which matches the reverse lookup)
174 * port smtp-port - the default comes from getservicebyname() and should work fine
175 * timeout The SMTP connection timeout. Defaults to 30 seconds.
176 * vrfy Whether to use VRFY or not. Defaults to false.
177 * verp Whether to use VERP or not. Defaults to false.
178 * test Activate test mode? Defaults to false.
179 * debug Activate SMTP and Net_DNS debug mode? Defaults to false.
180 * netdns whether to use PEAR:Net_DNS or the PHP build in function getmxrr, default is true
182 * If a parameter is present in the $params array, it replaces the
186 * @param array Hash containing any parameters different from the
188 * @see _Mail_smtpmx()
190 function __construct($params)
192 if (isset($params['mailname'])) {
193 $this->mailname = $params['mailname'];
195 // try to find a valid mailname
196 if (function_exists('posix_uname')) {
197 $uname = posix_uname();
198 $this->mailname = $uname['nodename'];
203 if (isset($params['port'])) {
204 $this->_port = $params['port'];
206 $this->_port = getservbyname('smtp', 'tcp');
209 if (isset($params['timeout'])) $this->timeout = $params['timeout'];
210 if (isset($params['verp'])) $this->verp = $params['verp'];
211 if (isset($params['test'])) $this->test = $params['test'];
212 if (isset($params['peardebug'])) $this->test = $params['peardebug'];
213 if (isset($params['netdns'])) $this->withNetDns = $params['netdns'];
217 * Constructor wrapper for PHP4
220 * @param array Hash containing any parameters different from the defaults
223 function Mail_smtpmx($params)
225 $this->__construct($params);
226 register_shutdown_function(array(&$this, '__destruct'));
230 * Destructor implementation to ensure that we disconnect from any
231 * potentially-alive persistent SMTP connections.
233 function __destruct()
235 if (is_object($this->_smtp)) {
236 $this->_smtp->disconnect();
242 * Implements Mail::send() function using SMTP direct delivery
245 * @param mixed $recipients in RFC822 style or array
246 * @param array $headers The array of headers to send with the mail.
247 * @param string $body The full text of the message body,
248 * @return mixed Returns true on success, or a PEAR_Error
250 function send($recipients, $headers, $body)
252 if (!is_array($headers)) {
253 return PEAR::raiseError('$headers must be an array');
256 $result = $this->_sanitizeHeaders($headers);
257 if (is_a($result, 'PEAR_Error')) {
262 $headerElements = $this->prepareHeaders($headers);
263 if (is_a($headerElements, 'PEAR_Error')) {
264 return $headerElements;
266 list($from, $textHeaders) = $headerElements;
268 // use 'Return-Path' if possible
269 if (!empty($headers['Return-Path'])) {
270 $from = $headers['Return-Path'];
273 return $this->_raiseError('no_from');
276 // Prepare recipients
277 $recipients = $this->parseRecipients($recipients);
278 if (is_a($recipients, 'PEAR_Error')) {
282 foreach ($recipients as $rcpt) {
283 list($user, $host) = explode('@', $rcpt);
285 $mx = $this->_getMx($host);
286 if (is_a($mx, 'PEAR_Error')) {
291 $info = array('rcpt' => $rcpt);
292 return $this->_raiseError('no_mx', $info);
296 foreach ($mx as $mserver => $mpriority) {
297 $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
299 // configure the SMTP connection.
301 $this->_smtp->setDebug(true);
304 // attempt to connect to the configured SMTP server.
305 $res = $this->_smtp->connect($this->timeout);
306 if (is_a($res, 'PEAR_Error')) {
311 // connection established
320 'host' => implode(', ', array_keys($mx)),
321 'port' => $this->port,
324 return $this->_raiseError('not_connected', $info);
329 $res = $this->_smtp->vrfy($rcpt);
330 if (is_a($res, 'PEAR_Error')) {
331 $info = array('rcpt' => $rcpt);
332 return $this->_raiseError('failed_vrfy_rcpt', $info);
337 $args['verp'] = $this->verp;
338 $res = $this->_smtp->mailFrom($from, $args);
339 if (is_a($res, 'PEAR_Error')) {
340 $info = array('from' => $from);
341 return $this->_raiseError('failed_set_from', $info);
345 $res = $this->_smtp->rcptTo($rcpt);
346 if (is_a($res, 'PEAR_Error')) {
347 $info = array('rcpt' => $rcpt);
348 return $this->_raiseError('failed_set_rcpt', $info);
351 // Don't send anything in test mode
353 $result = $this->_smtp->rset();
354 $res = $this->_smtp->rset();
355 if (is_a($res, 'PEAR_Error')) {
356 return $this->_raiseError('failed_rset');
359 $this->_smtp->disconnect();
365 $res = $this->_smtp->data("$textHeaders\r\n$body");
366 if (is_a($res, 'PEAR_Error')) {
367 $info = array('rcpt' => $rcpt);
368 return $this->_raiseError('failed_send_data', $info);
371 $this->_smtp->disconnect();
379 * Recieve mx rexords for a spciefied host
384 * @param string $host mail host
385 * @return mixed sorted
387 function _getMx($host)
391 if ($this->withNetDns) {
392 $res = $this->_loadNetDns();
393 if (is_a($res, 'PEAR_Error')) {
397 $response = $this->resolver->query($host, 'MX');
402 foreach ($response->answer as $rr) {
403 if ($rr->type == 'MX') {
404 $mx[$rr->exchange] = $rr->preference;
411 if (!getmxrr($host, $mxHost, $mxWeight)) {
414 for ($i = 0; $i < count($mxHost); ++$i) {
415 $mx[$mxHost[$i]] = $mxWeight[$i];
424 * initialize PEAR:Net_DNS_Resolver
427 * @return boolean true on success
429 function _loadNetDns()
431 if (is_object($this->resolver)) {
435 if (!include_once 'Net/DNS.php') {
436 return $this->_raiseError('no_resolver');
439 $this->resolver = new Net_DNS_Resolver();
441 $this->resolver->test = 1;
448 * raise standardized error
450 * include additional information in error message
453 * @param string $id maps error ids to codes and message
454 * @param array $info optional information in associative array
457 function _raiseError($id, $info = array())
459 $code = $this->errorCode[$id]['code'];
460 $msg = $this->errorCode[$id]['msg'];
462 // include info to messages
467 foreach ($info as $key => $value) {
468 array_push($search, '{' . strtoupper($key) . '}');
469 array_push($replace, $value);
472 $msg = str_replace($search, $replace, $msg);
475 return PEAR::raiseError($msg, $code);