]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - extlib/Mail/smtpmx.php
i18n/L10n review, extension credits added.
[quix0rs-gnu-social.git] / extlib / 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: 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.
16  *
17  * @category   Mail
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 $
23  * @see        Mail
24  */
25
26 require_once 'Net/SMTP.php';
27
28 /**
29  * SMTP MX implementation of the PEAR Mail interface. Requires the Net_SMTP class.
30  *
31  *
32  * @access public
33  * @author  gERD Schaufelberger <gerd@php-tools.net>
34  * @package Mail
35  * @version $Revision: 1.2 $
36  */
37 class Mail_smtpmx extends Mail {
38
39     /**
40      * SMTP connection object.
41      *
42      * @var object
43      * @access private
44      */
45     var $_smtp = null;
46
47     /**
48      * The port the SMTP server is on.
49      * @var integer
50      * @see getservicebyname()
51      */
52     var $port = 25;
53
54     /**
55      * Hostname or domain that will be sent to the remote SMTP server in the
56      * HELO / EHLO message.
57      *
58      * @var string
59      * @see posix_uname()
60      */
61     var $mailname = 'localhost';
62
63     /**
64      * SMTP connection timeout value.  NULL indicates no timeout.
65      *
66      * @var integer
67      */
68     var $timeout = 10;
69
70     /**
71      * use either PEAR:Net_DNS or getmxrr
72      *
73      * @var boolean
74      */
75     var $withNetDns = true;
76
77     /**
78      * PEAR:Net_DNS_Resolver
79      *
80      * @var object
81      */
82     var $resolver;
83
84     /**
85      * Whether to use VERP or not. If not a boolean, the string value
86      * will be used as the VERP separators.
87      *
88      * @var mixed boolean or string
89      */
90     var $verp = false;
91
92     /**
93      * Whether to use VRFY or not.
94      *
95      * @var boolean $vrfy
96      */
97     var $vrfy = false;
98
99     /**
100      * Switch to test mode - don't send emails for real
101      *
102      * @var boolean $debug
103      */
104     var $test = false;
105
106     /**
107      * Turn on Net_SMTP debugging?
108      *
109      * @var boolean $peardebug
110      */
111     var $debug = false;
112
113     /**
114      * internal error codes
115      *
116      * translate internal error identifier to PEAR-Error codes and human
117      * readable messages.
118      *
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?
124      */
125     var $errorCode = array(
126         'not_connected' => array(
127             'code'  => 1,
128             'msg'   => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
129         ),
130         'failed_vrfy_rcpt' => array(
131             'code'  => 2,
132             'msg'   => 'Recipient "{RCPT}" could not be veryfied.'
133         ),
134         'failed_set_from' => array(
135             'code'  => 3,
136             'msg'   => 'Failed to set sender: {FROM}.'
137         ),
138         'failed_set_rcpt' => array(
139             'code'  => 4,
140             'msg'   => 'Failed to set recipient: {RCPT}.'
141         ),
142         'failed_send_data' => array(
143             'code'  => 5,
144             'msg'   => 'Failed to send mail to: {RCPT}.'
145         ),
146         'no_from' => array(
147             'code'  => 5,
148             'msg'   => 'No from address has be provided.'
149         ),
150         'send_data' => array(
151             'code'  => 7,
152             'msg'   => 'Failed to create Net_SMTP object.'
153         ),
154         'no_mx' => array(
155             'code'  => 8,
156             'msg'   => 'No MX-record for {RCPT} found.'
157         ),
158         'no_resolver' => array(
159             'code'  => 9,
160             'msg'   => 'Could not start resolver! Install PEAR:Net_DNS or switch off "netdns"'
161         ),
162         'failed_rset' => array(
163             'code'  => 10,
164             'msg'   => 'RSET command failed, SMTP-connection corrupt.'
165         ),
166     );
167
168     /**
169      * Constructor.
170      *
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
181      *
182      * If a parameter is present in the $params array, it replaces the
183      * default.
184      *
185      * @access public
186      * @param array Hash containing any parameters different from the
187      *              defaults.
188      * @see _Mail_smtpmx()
189      */
190     function __construct($params)
191     {
192         if (isset($params['mailname'])) {
193             $this->mailname = $params['mailname'];
194         } else {
195             // try to find a valid mailname
196             if (function_exists('posix_uname')) {
197                 $uname = posix_uname();
198                 $this->mailname = $uname['nodename'];
199             }
200         }
201
202         // port number
203         if (isset($params['port'])) {
204             $this->_port = $params['port'];
205         } else {
206             $this->_port = getservbyname('smtp', 'tcp');
207         }
208
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'];
214     }
215
216     /**
217      * Constructor wrapper for PHP4
218      *
219      * @access public
220      * @param array Hash containing any parameters different from the defaults
221      * @see __construct()
222      */
223     function Mail_smtpmx($params)
224     {
225         $this->__construct($params);
226         register_shutdown_function(array(&$this, '__destruct'));
227     }
228
229     /**
230      * Destructor implementation to ensure that we disconnect from any
231      * potentially-alive persistent SMTP connections.
232      */
233     function __destruct()
234     {
235         if (is_object($this->_smtp)) {
236             $this->_smtp->disconnect();
237             $this->_smtp = null;
238         }
239     }
240
241     /**
242      * Implements Mail::send() function using SMTP direct delivery
243      *
244      * @access public
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
249      */
250     function send($recipients, $headers, $body)
251     {
252         if (!is_array($headers)) {
253             return PEAR::raiseError('$headers must be an array');
254         }
255
256         $result = $this->_sanitizeHeaders($headers);
257         if (is_a($result, 'PEAR_Error')) {
258             return $result;
259         }
260
261         // Prepare headers
262         $headerElements = $this->prepareHeaders($headers);
263         if (is_a($headerElements, 'PEAR_Error')) {
264             return $headerElements;
265         }
266         list($from, $textHeaders) = $headerElements;
267
268         // use 'Return-Path' if possible
269         if (!empty($headers['Return-Path'])) {
270             $from = $headers['Return-Path'];
271         }
272         if (!isset($from)) {
273             return $this->_raiseError('no_from');
274         }
275
276         // Prepare recipients
277         $recipients = $this->parseRecipients($recipients);
278         if (is_a($recipients, 'PEAR_Error')) {
279             return $recipients;
280         }
281
282         foreach ($recipients as $rcpt) {
283             list($user, $host) = explode('@', $rcpt);
284
285             $mx = $this->_getMx($host);
286             if (is_a($mx, 'PEAR_Error')) {
287                 return $mx;
288             }
289
290             if (empty($mx)) {
291                 $info = array('rcpt' => $rcpt);
292                 return $this->_raiseError('no_mx', $info);
293             }
294
295             $connected = false;
296             foreach ($mx as $mserver => $mpriority) {
297                 $this->_smtp = new Net_SMTP($mserver, $this->port, $this->mailname);
298
299                 // configure the SMTP connection.
300                 if ($this->debug) {
301                     $this->_smtp->setDebug(true);
302                 }
303
304                 // attempt to connect to the configured SMTP server.
305                 $res = $this->_smtp->connect($this->timeout);
306                 if (is_a($res, 'PEAR_Error')) {
307                     $this->_smtp = null;
308                     continue;
309                 }
310
311                 // connection established
312                 if ($res) {
313                     $connected = true;
314                     break;
315                 }
316             }
317
318             if (!$connected) {
319                 $info = array(
320                     'host' => implode(', ', array_keys($mx)),
321                     'port' => $this->port,
322                     'rcpt' => $rcpt,
323                 );
324                 return $this->_raiseError('not_connected', $info);
325             }
326
327             // Verify recipient
328             if ($this->vrfy) {
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);
333                 }
334             }
335
336             // mail from:
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);
342             }
343
344             // rcpt to:
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);
349             }
350
351             // Don't send anything in test mode
352             if ($this->test) {
353                 $result = $this->_smtp->rset();
354                 $res = $this->_smtp->rset();
355                 if (is_a($res, 'PEAR_Error')) {
356                     return $this->_raiseError('failed_rset');
357                 }
358
359                 $this->_smtp->disconnect();
360                 $this->_smtp = null;
361                 return true;
362             }
363
364             // Send data
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);
369             }
370
371             $this->_smtp->disconnect();
372             $this->_smtp = null;
373         }
374
375         return true;
376     }
377
378     /**
379      * Recieve mx rexords for a spciefied host
380      *
381      * The MX records
382      *
383      * @access private
384      * @param string $host mail host
385      * @return mixed sorted
386      */
387     function _getMx($host)
388     {
389         $mx = array();
390
391         if ($this->withNetDns) {
392             $res = $this->_loadNetDns();
393             if (is_a($res, 'PEAR_Error')) {
394                 return $res;
395             }
396
397             $response = $this->resolver->query($host, 'MX');
398             if (!$response) {
399                 return false;
400             }
401
402             foreach ($response->answer as $rr) {
403                 if ($rr->type == 'MX') {
404                     $mx[$rr->exchange] = $rr->preference;
405                 }
406             }
407         } else {
408             $mxHost = array();
409             $mxWeight = array();
410
411             if (!getmxrr($host, $mxHost, $mxWeight)) {
412                 return false;
413             }
414             for ($i = 0; $i < count($mxHost); ++$i) {
415                 $mx[$mxHost[$i]] = $mxWeight[$i];
416             }
417         }
418
419         asort($mx);
420         return $mx;
421     }
422
423     /**
424      * initialize PEAR:Net_DNS_Resolver
425      *
426      * @access private
427      * @return boolean true on success
428      */
429     function _loadNetDns()
430     {
431         if (is_object($this->resolver)) {
432             return true;
433         }
434
435         if (!include_once 'Net/DNS.php') {
436             return $this->_raiseError('no_resolver');
437         }
438
439         $this->resolver = new Net_DNS_Resolver();
440         if ($this->debug) {
441             $this->resolver->test = 1;
442         }
443
444         return true;
445     }
446
447     /**
448      * raise standardized error
449      *
450      * include additional information in error message
451      *
452      * @access private
453      * @param string $id maps error ids to codes and message
454      * @param array $info optional information in associative array
455      * @see _errorCode
456      */
457     function _raiseError($id, $info = array())
458     {
459         $code = $this->errorCode[$id]['code'];
460         $msg = $this->errorCode[$id]['msg'];
461
462         // include info to messages
463         if (!empty($info)) {
464             $search = array();
465             $replace = array();
466
467             foreach ($info as $key => $value) {
468                 array_push($search, '{' . strtoupper($key) . '}');
469                 array_push($replace, $value);
470             }
471
472             $msg = str_replace($search, $replace, $msg);
473         }
474
475         return PEAR::raiseError($msg, $code);
476     }
477
478 }