commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-new / civicrm / packages / Mail / smtpmx.php
1 <?PHP
2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4 /**
5 * SMTP MX
6 *
7 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
8 *
9 * PHP versions 4 and 5
10 *
11 * LICENSE:
12 *
13 * Copyright (c) 2010, gERD Schaufelberger
14 * All rights reserved.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
19 *
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
27 * permission.
28 *
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.
40 *
41 * @category Mail
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/
48 */
49
50 require_once 'Net/SMTP.php';
51
52 /**
53 * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
54 *
55 *
56 * @access public
57 * @author gERD Schaufelberger <gerd@php-tools.net>
58 * @package Mail
59 * @version $Revision: 294747 $
60 */
61 class Mail_smtpmx extends Mail {
62
63 /**
64 * SMTP connection object.
65 *
66 * @var object
67 * @access private
68 */
69 var $_smtp = null;
70
71 /**
72 * The port the SMTP server is on.
73 * @var integer
74 * @see getservicebyname()
75 */
76 var $port = 25;
77
78 /**
79 * Hostname or domain that will be sent to the remote SMTP server in the
80 * HELO / EHLO message.
81 *
82 * @var string
83 * @see posix_uname()
84 */
85 var $mailname = 'localhost';
86
87 /**
88 * SMTP connection timeout value. NULL indicates no timeout.
89 *
90 * @var integer
91 */
92 var $timeout = 10;
93
94 /**
95 * use either PEAR:Net_DNS or getmxrr
96 *
97 * @var boolean
98 */
99 var $withNetDns = true;
100
101 /**
102 * PEAR:Net_DNS_Resolver
103 *
104 * @var object
105 */
106 var $resolver;
107
108 /**
109 * Whether to use VERP or not. If not a boolean, the string value
110 * will be used as the VERP separators.
111 *
112 * @var mixed boolean or string
113 */
114 var $verp = false;
115
116 /**
117 * Whether to use VRFY or not.
118 *
119 * @var boolean $vrfy
120 */
121 var $vrfy = false;
122
123 /**
124 * Switch to test mode - don't send emails for real
125 *
126 * @var boolean $debug
127 */
128 var $test = false;
129
130 /**
131 * Turn on Net_SMTP debugging?
132 *
133 * @var boolean $peardebug
134 */
135 var $debug = false;
136
137 /**
138 * internal error codes
139 *
140 * translate internal error identifier to PEAR-Error codes and human
141 * readable messages.
142 *
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?
148 */
149 var $errorCode = array(
150 'not_connected' => array(
151 'code' => 1,
152 'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
153 ),
154 'failed_vrfy_rcpt' => array(
155 'code' => 2,
156 'msg' => 'Recipient "{RCPT}" could not be veryfied.'
157 ),
158 'failed_set_from' => array(
159 'code' => 3,
160 'msg' => 'Failed to set sender: {FROM}.'
161 ),
162 'failed_set_rcpt' => array(
163 'code' => 4,
164 'msg' => 'Failed to set recipient: {RCPT}.'
165 ),
166 'failed_send_data' => array(
167 'code' => 5,
168 'msg' => 'Failed to send mail to: {RCPT}.'
169 ),
170 'no_from' => array(
171 'code' => 5,
172 'msg' => 'No from address has be provided.'
173 ),
174 'send_data' => array(
175 'code' => 7,
176 'msg' => 'Failed to create Net_SMTP object.'
177 ),
178 'no_mx' => array(
179 'code' => 8,
180 'msg' => 'No MX-record for {RCPT} found.'
181 ),
182 'no_resolver' => array(
183 'code' => 9,
184 'msg' => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
185 ),
186 'failed_rset' => array(
187 'code' => 10,
188 'msg' => 'RSET command failed, SMTP-connection corrupt.'
189 ),
190 );
191
192 /**
193 * Constructor.
194 *
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
205 *
206 * If a parameter is present in the $params array, it replaces the
207 * default.
208 *
209 * @access public
210 * @param array Hash containing any parameters different from the
211 * defaults.
212 * @see _Mail_smtpmx()
213 */
214 function __construct($params)
215 {
216 if (isset($params['mailname'])) {
217 $this->mailname = $params['mailname'];
218 } else {
219 // try to find a valid mailname
220 if (function_exists('posix_uname')) {
221 $uname = posix_uname();
222 $this->mailname = $uname['nodename'];
223 }
224 }
225
226 // port number
227 if (isset($params['port'])) {
228 $this->_port = $params['port'];
229 } else {
230 $this->_port = getservbyname('smtp', 'tcp');
231 }
232
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'];
238 }
239
240 /**
241 * Constructor wrapper for PHP4
242 *
243 * @access public
244 * @param array Hash containing any parameters different from the defaults
245 * @see __construct()
246 */
247 function Mail_smtpmx($params)
248 {
249 $this->__construct($params);
250 register_shutdown_function(array(&$this, '__destruct'));
251 }
252
253 /**
254 * Destructor implementation to ensure that we disconnect from any
255 * potentially-alive persistent SMTP connections.
256 */
257 function __destruct()
258 {
259 if (is_object($this->_smtp)) {
260 $this->_smtp->disconnect();
261 $this->_smtp = null;
262 }
263 }
264
265 /**
266 * Implements Mail::send() function using SMTP direct delivery
267 *
268 * @access public
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
273 */
274 function send($recipients, $headers, $body)
275 {
276 if (!is_array($headers)) {
277 return PEAR::raiseError('$headers must be an array');
278 }
279
280 $result = $this->_sanitizeHeaders($headers);
281 if (is_a($result, 'PEAR_Error')) {
282 return $result;
283 }
284
285 // Prepare headers
286 $headerElements = $this->prepareHeaders($headers);
287 if (is_a($headerElements, 'PEAR_Error')) {
288 return $headerElements;
289 }
290 list($from, $textHeaders) = $headerElements;
291
292 // use 'Return-Path' if possible
293 if (!empty($headers['Return-Path'])) {
294 $from = $headers['Return-Path'];
295 }
296 if (!isset($from)) {
297 return $this->_raiseError('no_from');
298 }
299
300 // Prepare recipients
301 $recipients = $this->parseRecipients($recipients);
302 if (is_a($recipients, 'PEAR_Error')) {
303 return $recipients;
304 }
305
306 foreach ($recipients as $rcpt) {
307 list($user, $host) = explode('@', $rcpt);
308
309 $mx = $this->_getMx($host);
310 if (is_a($mx, 'PEAR_Error')) {
311 return $mx;
312 }
313
314 if (empty($mx)) {
315 $info = array('rcpt' => $rcpt);
316 return $this->_raiseError('no_mx', $info);
317 }
318
319 $connected = false;
320 foreach ($mx as $mserver => $mpriority) {
321 $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
322
323 // configure the SMTP connection.
324 if ($this->debug) {
325 $this->_smtp->setDebug(true);
326 }
327
328 // attempt to connect to the configured SMTP server.
329 $res = $this->_smtp->connect($this->timeout);
330 if (is_a($res, 'PEAR_Error')) {
331 $this->_smtp = null;
332 continue;
333 }
334
335 // connection established
336 if ($res) {
337 $connected = true;
338 break;
339 }
340 }
341
342 if (!$connected) {
343 $info = array(
344 'host' => implode(', ', array_keys($mx)),
345 'port' => $this->port,
346 'rcpt' => $rcpt,
347 );
348 return $this->_raiseError('not_connected', $info);
349 }
350
351 // Verify recipient
352 if ($this->vrfy) {
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);
357 }
358 }
359
360 // mail from:
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);
366 }
367
368 // rcpt to:
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);
373 }
374
375 // Don't send anything in test mode
376 if ($this->test) {
377 $result = $this->_smtp->rset();
378 $res = $this->_smtp->rset();
379 if (is_a($res, 'PEAR_Error')) {
380 return $this->_raiseError('failed_rset');
381 }
382
383 $this->_smtp->disconnect();
384 $this->_smtp = null;
385 return true;
386 }
387
388 // Send data
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);
393 }
394
395 $this->_smtp->disconnect();
396 $this->_smtp = null;
397 }
398
399 return true;
400 }
401
402 /**
403 * Recieve mx rexords for a spciefied host
404 *
405 * The MX records
406 *
407 * @access private
408 * @param string $host mail host
409 * @return mixed sorted
410 */
411 function _getMx($host)
412 {
413 $mx = array();
414
415 if ($this->withNetDns) {
416 $res = $this->_loadNetDns();
417 if (is_a($res, 'PEAR_Error')) {
418 return $res;
419 }
420
421 $response = $this->resolver->query($host, 'MX');
422 if (!$response) {
423 return false;
424 }
425
426 foreach ($response->answer as $rr) {
427 if ($rr->type == 'MX') {
428 $mx[$rr->exchange] = $rr->preference;
429 }
430 }
431 } else {
432 $mxHost = array();
433 $mxWeight = array();
434
435 if (!getmxrr($host, $mxHost, $mxWeight)) {
436 return false;
437 }
438 for ($i = 0; $i < count($mxHost); ++$i) {
439 $mx[$mxHost[$i]] = $mxWeight[$i];
440 }
441 }
442
443 asort($mx);
444 return $mx;
445 }
446
447 /**
448 * initialize PEAR:Net_DNS_Resolver
449 *
450 * @access private
451 * @return boolean true on success
452 */
453 function _loadNetDns()
454 {
455 if (is_object($this->resolver)) {
456 return true;
457 }
458
459 if (!include_once 'Net/DNS.php') {
460 return $this->_raiseError('no_resolver');
461 }
462
463 $this->resolver = new Net_DNS_Resolver();
464 if ($this->debug) {
465 $this->resolver->test = 1;
466 }
467
468 return true;
469 }
470
471 /**
472 * raise standardized error
473 *
474 * include additional information in error message
475 *
476 * @access private
477 * @param string $id maps error ids to codes and message
478 * @param array $info optional information in associative array
479 * @see _errorCode
480 */
481 function _raiseError($id, $info = array())
482 {
483 $code = $this->errorCode[$id]['code'];
484 $msg = $this->errorCode[$id]['msg'];
485
486 // include info to messages
487 if (!empty($info)) {
488 $search = array();
489 $replace = array();
490
491 foreach ($info as $key => $value) {
492 array_push($search, '{' . strtoupper($key) . '}');
493 array_push($replace, $value);
494 }
495
496 $msg = str_replace($search, $replace, $msg);
497 }
498
499 return PEAR::raiseError($msg, $code);
500 }
501
502 }