source: branches/version-2_5-dev/data/module/Mail/smtpmx.php @ 20119

Revision 20119, 14.8 KB checked in by nanasess, 12 years ago (diff)

module 以下は svn:keywords を除外

  • Property svn:eol-style set to LF
  • Property svn:mime-type set to text/x-httpd-php; charset=UTF-8
Line 
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
50require_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 */
61class 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}
Note: See TracBrowser for help on using the repository browser.