Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
}