Overview

Packages

  • Mail

Classes

  • Horde_Mail
  • Horde_Mail_Exception
  • Horde_Mail_Rfc822
  • Horde_Mail_Rfc822_Address
  • Horde_Mail_Rfc822_Group
  • Horde_Mail_Rfc822_Object
  • Horde_Mail_Transport
  • Horde_Mail_Transport_Mail
  • Horde_Mail_Transport_Mock
  • Horde_Mail_Transport_Null
  • Horde_Mail_Transport_Sendmail
  • Horde_Mail_Transport_Smtp
  • Horde_Mail_Transport_Smtpmx
  • Overview
  • Package
  • Class
  • Tree
  1: <?PHP
  2: /**
  3:  * SMTP MX implementation.
  4:  * Requires the Net_SMTP class.
  5:  *
  6:  * LICENSE:
  7:  *
  8:  * Copyright (c) 2010, Gerd Schaufelberger
  9:  * All rights reserved.
 10:  *
 11:  * Redistribution and use in source and binary forms, with or without
 12:  * modification, are permitted provided that the following conditions
 13:  * are met:
 14:  *
 15:  * o Redistributions of source code must retain the above copyright
 16:  *   notice, this list of conditions and the following disclaimer.
 17:  * o Redistributions in binary form must reproduce the above copyright
 18:  *   notice, this list of conditions and the following disclaimer in the
 19:  *   documentation and/or other materials provided with the distribution.
 20:  * o The names of the authors may not be used to endorse or promote
 21:  *   products derived from this software without specific prior written
 22:  *   permission.
 23:  *
 24:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 25:  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 26:  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 27:  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 28:  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 29:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 30:  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 31:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 32:  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 33:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 34:  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 35:  *
 36:  * @category   Horde
 37:  * @package    Mail
 38:  * @author     gERD Schaufelberger <gerd@php-tools.net>
 39:  * @copyright  2010 gERD Schaufelberger
 40:  * @license    http://www.horde.org/licenses/bsd New BSD License
 41:  */
 42: 
 43: /**
 44:  * SMTP MX implementation.
 45:  *
 46:  * @author   gERD Schaufelberger <gerd@php-tools.net>
 47:  * @category Horde
 48:  * @package  Mail
 49:  */
 50: class Horde_Mail_Transport_Smtpmx extends Horde_Mail_Transport
 51: {
 52:     /**
 53:      * SMTP connection object.
 54:      *
 55:      * @var Net_SMTP
 56:      */
 57:     protected $_smtp = null;
 58: 
 59:     /**
 60:      * Net_DNS2_Resolver object.
 61:      *
 62:      * @var Net_DNS2_Resolver
 63:      */
 64:     protected $_resolver;
 65: 
 66:     /**
 67:      * Internal error codes.
 68:      * Translate internal error identifier to human readable messages.
 69:      *
 70:      * @var array
 71:      */
 72:     protected $_errorCode = array(
 73:         'not_connected' => array(
 74:             'code' => 1,
 75:             'msg' => 'Could not connect to any mail server ({HOST}) at port {PORT} to send mail to {RCPT}.'
 76:         ),
 77:         'failed_vrfy_rcpt' => array(
 78:             'code' => 2,
 79:             'msg' => 'Recipient "{RCPT}" could not be veryfied.'
 80:         ),
 81:         'failed_set_from' => array(
 82:             'code' => 3,
 83:             'msg' => 'Failed to set sender: {FROM}.'
 84:         ),
 85:         'failed_set_rcpt' => array(
 86:             'code' => 4,
 87:             'msg' => 'Failed to set recipient: {RCPT}.'
 88:         ),
 89:         'failed_send_data' => array(
 90:             'code' => 5,
 91:             'msg' => 'Failed to send mail to: {RCPT}.'
 92:         ),
 93:         'no_from' => array(
 94:             'code' => 5,
 95:             'msg' => 'No from address has be provided.'
 96:         ),
 97:         'send_data' => array(
 98:             'code' => 7,
 99:             'msg' => 'Failed to create Net_SMTP object.'
100:         ),
101:         'no_mx' => array(
102:             'code' => 8,
103:             'msg' => 'No MX-record for {RCPT} found.'
104:         ),
105:         'no_resolver' => array(
106:             'code' => 9,
107:             'msg' => 'Could not start resolver! Install PEAR:Net_DNS2 or switch off "netdns"'
108:         ),
109:         'failed_rset' => array(
110:             'code' => 10,
111:             'msg' => 'RSET command failed, SMTP-connection corrupt.'
112:         )
113:     );
114: 
115:     /**
116:      * Constructor.
117:      *
118:      * @param array $params  Additional options:
119:      *   - debug: (boolean) Activate SMTP debug mode?
120:      *            DEFAULT: false
121:      *   - mailname: (string) The name of the local mail system (a valid
122:      *               hostname which matches the reverse lookup)
123:      *               DEFAULT: Auto-determined
124:      *   - netdns: (boolean) Use PEAR:Net_DNS2 (true) or the PHP builtin
125:      *             getmxrr().
126:      *             DEFAULT: true
127:      *   - port: (integer) Port.
128:      *           DEFAULT: Auto-determined
129:      *   - test: (boolean) Activate test mode?
130:      *           DEFAULT: false
131:      *   - timeout: (integer) The SMTP connection timeout (in seconds).
132:      *              DEFAULT: 10
133:      *   - verp: (boolean) Whether to use VERP.
134:      *           If not a boolean, the string value will be used as the VERP
135:      *           separators.
136:      *           DEFAULT: false
137:      *   - vrfy: (boolean) Whether to use VRFY.
138:      *           DEFAULT: false
139:      */
140:     public function __construct(array $params = array())
141:     {
142:         /* Try to find a valid mailname. */
143:         if (!isset($params['mailname']) && function_exists('posix_uname')) {
144:             $uname = posix_uname();
145:             $params['mailname'] = $uname['nodename'];
146:         }
147: 
148:         if (!isset($params['port'])) {
149:             $params['port'] = getservbyname('smtp', 'tcp');
150:         }
151: 
152:         $this->_params = array_merge(array(
153:             'debug' => false,
154:             'mailname' => 'localhost',
155:             'netdns' => true,
156:             'port' => 25,
157:             'test' => false,
158:             'timeout' => 10,
159:             'verp' => false,
160:             'vrfy' => false
161:         ), $params);
162: 
163:         /* SMTP requires CRLF line endings. */
164:         $this->sep = "\r\n";
165:     }
166: 
167:     /**
168:      * Destructor implementation to ensure that we disconnect from any
169:      * potentially-alive persistent SMTP connections.
170:      */
171:     public function __destruct()
172:     {
173:         if (is_object($this->_smtp)) {
174:             $this->_smtp->disconnect();
175:             $this->_smtp = null;
176:         }
177:     }
178: 
179:     /**
180:      * Send a message.
181:      *
182:      * @param mixed $recipients  Either a comma-seperated list of recipients
183:      *                           (RFC822 compliant), or an array of
184:      *                           recipients, each RFC822 valid. This may
185:      *                           contain recipients not specified in the
186:      *                           headers, for Bcc:, resending messages, etc.
187:      * @param array $headers     The headers to send with the mail, in an
188:      *                           associative array, where the array key is the
189:      *                           header name (ie, 'Subject'), and the array
190:      *                           value is the header value (ie, 'test'). The
191:      *                           header produced from those values would be
192:      *                           'Subject: test'.
193:      *                           If the '_raw' key exists, the value of this
194:      *                           key will be used as the exact text for
195:      *                           sending the message.
196:      * @param mixed $body        The full text of the message body, including
197:      *                           any Mime parts, etc. Either a string or a
198:      *                           stream resource.
199:      *
200:      * @throws Horde_Mail_Exception
201:      */
202:     public function send($recipients, array $headers, $body)
203:     {
204:         $headers = $this->_sanitizeHeaders($headers);
205: 
206:         // Prepare headers
207:         list($from, $textHeaders) = $this->prepareHeaders($headers);
208: 
209:         // Use 'Return-Path' if possible
210:         foreach (array_keys($headers) as $hdr) {
211:             if (strcasecmp($hdr, 'Return-Path') === 0) {
212:                 $from = $headers['Return-Path'];
213:                 break;
214:             }
215:         }
216: 
217:         if (!strlen($from)) {
218:             $this->_error('no_from');
219:         }
220: 
221:         // Prepare recipients
222:         foreach ($this->parseRecipients($recipients) as $rcpt) {
223:             list($user, $host) = explode('@', $rcpt);
224: 
225:             $mx = $this->_getMx($host);
226:             if (!$mx) {
227:                 $this->_error('no_mx', array('rcpt' => $rcpt));
228:             }
229: 
230:             $connected = false;
231:             foreach ($mx as $mserver => $mpriority) {
232:                 $this->_smtp = new Net_SMTP($mserver, $this->_params['port'], $this->_params['mailname']);
233: 
234:                 // configure the SMTP connection.
235:                 if ($this->_params['debug']) {
236:                     $this->_smtp->setDebug(true);
237:                 }
238: 
239:                 // attempt to connect to the configured SMTP server.
240:                 $res = $this->_smtp->connect($this->_params['timeout']);
241:                 if ($res instanceof PEAR_Error) {
242:                     $this->_smtp = null;
243:                     continue;
244:                 }
245: 
246:                 // connection established
247:                 if ($res) {
248:                     $connected = true;
249:                     break;
250:                 }
251:             }
252: 
253:             if (!$connected) {
254:                 $this->_error('not_connected', array(
255:                     'host' => implode(', ', array_keys($mx)),
256:                     'port' => $this->_params['port'],
257:                     'rcpt' => $rcpt
258:                 ));
259:             }
260: 
261:             // Verify recipient
262:             if ($this->_params['vrfy']) {
263:                 $res = $this->_smtp->vrfy($rcpt);
264:                 if ($res instanceof PEAR_Error) {
265:                     $this->_error('failed_vrfy_rcpt', array('rcpt' => $rcpt));
266:                 }
267:             }
268: 
269:             // mail from:
270:             $args['verp'] = $this->_params['verp'];
271:             $res = $this->_smtp->mailFrom($from, $args);
272:             if ($res instanceof PEAR_Error) {
273:                 $this->_error('failed_set_from', array('from' => $from));
274:             }
275: 
276:             // rcpt to:
277:             $res = $this->_smtp->rcptTo($rcpt);
278:             if ($res instanceof PEAR_Error) {
279:                 $this->_error('failed_set_rcpt', array('rcpt' => $rcpt));
280:             }
281: 
282:             // Don't send anything in test mode
283:             if ($this->_params['test']) {
284:                 $res = $this->_smtp->rset();
285:                 if ($res instanceof PEAR_Error) {
286:                     $this->_error('failed_rset');
287:                 }
288: 
289:                 $this->_smtp->disconnect();
290:                 $this->_smtp = null;
291:                 return;
292:             }
293: 
294:             // Send data. Net_SMTP does necessary EOL conversions.
295:             $res = $this->_smtp->data($body, $textHeaders);
296:             if ($res instanceof PEAR_Error) {
297:                 $this->_error('failed_send_data', array('rcpt' => $rcpt));
298:             }
299: 
300:             $this->_smtp->disconnect();
301:             $this->_smtp = null;
302:         }
303:     }
304: 
305:     /**
306:      * Recieve MX records for a host.
307:      *
308:      * @param string $host  Mail host.
309:      *
310:      * @return mixed  Sorted MX list or false on error.
311:      */
312:     protected function _getMx($host)
313:     {
314:         $mx = array();
315: 
316:         if ($this->params['netdns']) {
317:             $this->_loadNetDns();
318: 
319:             try {
320:                 $response = $this->_resolver->query($host, 'MX');
321:                 if (!$response) {
322:                     return false;
323:                 }
324:             } catch (Exception $e) {
325:                 throw new Horde_Mail_Exception($e);
326:             }
327: 
328:             foreach ($response->answer as $rr) {
329:                 if ($rr->type == 'MX') {
330:                     $mx[$rr->exchange] = $rr->preference;
331:                 }
332:             }
333:         } else {
334:             $mxHost = $mxWeight = array();
335: 
336:             if (!getmxrr($host, $mxHost, $mxWeight)) {
337:                 return false;
338:             }
339: 
340:             for ($i = 0; $i < count($mxHost); ++$i) {
341:                 $mx[$mxHost[$i]] = $mxWeight[$i];
342:             }
343:         }
344: 
345:         asort($mx);
346: 
347:         return $mx;
348:     }
349: 
350:     /**
351:      * Initialize Net_DNS2_Resolver.
352:      */
353:     protected function _loadNetDns()
354:     {
355:         if (!$this->_resolver) {
356:             if (!class_exists('Net_DNS2_Resolver')) {
357:                 $this->_error('no_resolver');
358:             }
359:             $this->_resolver = new Net_DNS2_Resolver();
360:         }
361:     }
362: 
363:     /**
364:      * Format error message.
365:      *
366:      * @param string $id   Maps error ids to codes and message.
367:      * @param array $info  Optional information in associative array.
368:      *
369:      * @throws Horde_Mail_Exception
370:      */
371:     protected function _error($id, $info = array())
372:     {
373:         $msg = $this->_errorCode[$id]['msg'];
374: 
375:         // include info to messages
376:         if (!empty($info)) {
377:             $replace = $search = array();
378: 
379:             foreach ($info as $key => $value) {
380:                 $search[] = '{' . strtoupper($key) . '}';
381:                 $replace[] = $value;
382:             }
383: 
384:             $msg = str_replace($search, $replace, $msg);
385:         }
386: 
387:         throw new Horde_Mail_Exception($msg, $this->_errorCode[$id]['code']);
388:     }
389: }
390: 
API documentation generated by ApiGen